-vgmplay: Fixed DC offset issue, and added more visualization modes. [Ryan Holtz]

This commit is contained in:
mooglyguy 2020-03-30 14:28:02 +02:00
parent ee75582aa3
commit b4a32cf83a
3 changed files with 219 additions and 67 deletions

View File

@ -17,7 +17,6 @@
#include <cmath>
constexpr float lerp(float a, float b, float f)
{
return (b - a) * f + a;
@ -33,6 +32,16 @@ DEFINE_DEVICE_TYPE(VGMVIZ, vgmviz_device, "vgmviz", "VGM Visualizer")
/*static*/ const bool vgmviz_device::NEEDS_FFT[VIZ_COUNT] =
{
false, // VIZ_WAVEFORM
true, // VIZ_WATERFALL
true, // VIZ_RAWSPEC
true, // VIZ_BARSPEC4
true, // VIZ_BARSPEC8
true // VIZ_BARSPEC16
};
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
@ -147,23 +156,17 @@ void vgmviz_device::apply_fft()
void vgmviz_device::apply_waterfall()
{
int total_bars = FFT_LENGTH / 2;
int bar_step = total_bars / 256;
WDL_FFT_COMPLEX* bins[2] = { (WDL_FFT_COMPLEX*)m_fft_buf[0], (WDL_FFT_COMPLEX*)m_fft_buf[1] };
int bar_index = 0;
for (int bar = 0; bar < 256; bar++, bar_index += bar_step)
for (int bar = 0; bar < std::min<int>(total_bars, SCREEN_HEIGHT); bar++)
{
if (bar_index < 2)
if (bar < 2)
{
continue;
}
float val = 0.0f;
for (int i = 0; i < bar_step; i++)
{
int permuted = WDL_fft_permute(FFT_LENGTH / 2, (bar * bar_step) + i);
val = std::max<WDL_FFT_REAL>(bins[0][permuted].re + bins[1][permuted].re, val);
}
int permuted = WDL_fft_permute(FFT_LENGTH / 2, bar);
float val = bins[0][permuted].re + bins[1][permuted].re;
int level = int(logf(val * 32768.0f) * 31.0f);
m_waterfall_buf[m_waterfall_length % (FFT_LENGTH / 2 + 16)][255 - bar] = (level < 0) ? 0 : (level > 255 ? 255 : level);
m_waterfall_buf[m_waterfall_length % SCREEN_WIDTH][total_bars - bar] = (level < 0) ? 0 : (level > 255 ? 255 : level);
}
m_waterfall_length++;
}
@ -242,29 +245,32 @@ void vgmviz_device::device_reset()
memset(m_curr_peaks, 0, sizeof(float) * 2);
m_waterfall_length = 0;
for (int i = 0; i < 1024; i++)
for (int i = 0; i < SCREEN_WIDTH; i++)
{
memset(m_waterfall_buf[i], 0, sizeof(int) * 256);
}
m_spec_mode = SPEC_RAW;
m_viz_mode = VIZ_WAVEFORM;
m_history_length = 0;
}
//-------------------------------------------------
// cycle_spectrogram - cycle the spectrogram mode
// among the valid spectrogram modes.
// cycle_spectrogram - cycle the visualization
// mode among the valid modes.
//-------------------------------------------------
void vgmviz_device::cycle_spectrogram()
void vgmviz_device::cycle_viz_mode()
{
m_spec_mode = (spec_mode)((int)m_spec_mode + 1);
if (m_spec_mode == SPEC_COUNT)
m_viz_mode = (viz_mode)((int)m_viz_mode + 1);
if (m_viz_mode == VIZ_COUNT)
{
m_spec_mode = SPEC_RAW;
m_viz_mode = VIZ_WAVEFORM;
}
}
//-------------------------------------------------
// sound_stream_update - update the outgoing
// audio stream and process as necessary
@ -291,27 +297,68 @@ void vgmviz_device::sound_stream_update(sound_stream &stream, stream_sample_t **
for (int i = 0; i < m_outputs; i++)
{
m_audio_buf[m_audio_fill_index][i][m_audio_count[m_audio_fill_index]] = (outputs[i][pos] + 32768.0f) / 65336.0f;
const float sample = (float)(int16_t)outputs[i][pos] / 65336.0f;
m_audio_buf[m_audio_fill_index][i][m_audio_count[m_audio_fill_index]] = sample + 0.5f;
}
m_audio_count[m_audio_fill_index]++;
if (m_audio_count[m_audio_fill_index] >= FFT_LENGTH)
switch (m_viz_mode)
{
apply_window(m_audio_fill_index);
apply_fft();
apply_waterfall();
m_audio_fill_index = 1 - m_audio_fill_index;
if (m_audio_frames_available < 2)
{
m_audio_frames_available++;
}
m_audio_count[m_audio_fill_index] = 0;
default:
update_waveform(outputs);
break;
case VIZ_WATERFALL:
case VIZ_RAWSPEC:
case VIZ_BARSPEC4:
case VIZ_BARSPEC8:
case VIZ_BARSPEC16:
update_fft(outputs);
break;
}
}
}
//-------------------------------------------------
// update_waveform - perform a wave-style update
//-------------------------------------------------
void vgmviz_device::update_waveform(stream_sample_t **outputs)
{
m_history_length++;
m_audio_count[m_audio_fill_index]++;
if (m_audio_count[m_audio_fill_index] >= FFT_LENGTH)
{
m_audio_fill_index = 1 - m_audio_fill_index;
if (m_audio_frames_available < 2)
{
m_audio_frames_available++;
}
m_audio_count[m_audio_fill_index] = 0;
}
}
//-------------------------------------------------
// update_fft - keep the FFT up-to-date
//-------------------------------------------------
void vgmviz_device::update_fft(stream_sample_t **outputs)
{
m_audio_count[m_audio_fill_index]++;
if (m_audio_count[m_audio_fill_index] >= FFT_LENGTH)
{
apply_window(m_audio_fill_index);
apply_fft();
apply_waterfall();
m_audio_fill_index = 1 - m_audio_fill_index;
if (m_audio_frames_available < 2)
{
m_audio_frames_available++;
}
m_audio_count[m_audio_fill_index] = 0;
}
}
//-------------------------------------------------
// init_palette - initialize the palette
//-------------------------------------------------
@ -422,8 +469,8 @@ void vgmviz_device::device_add_mconfig(machine_config &config)
SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
m_screen->set_refresh_hz(60);
m_screen->set_vblank_time(ATTOSECONDS_IN_USEC(2500));
m_screen->set_size(FFT_LENGTH / 2 + 16, 768);
m_screen->set_visarea(0, FFT_LENGTH / 2 + 15, 0, 767);
m_screen->set_size(SCREEN_WIDTH, SCREEN_HEIGHT);
m_screen->set_visarea(0, SCREEN_WIDTH-1, 0, SCREEN_HEIGHT-1);
m_screen->set_screen_update(FUNC(vgmviz_device::screen_update));
PALETTE(config, m_palette, FUNC(vgmviz_device::init_palette), 512 + FFT_LENGTH / 2 + 1);
@ -438,11 +485,33 @@ uint32_t vgmviz_device::screen_update(screen_device &screen, bitmap_rgb32 &bitma
{
bitmap.fill(0, cliprect);
switch (m_viz_mode)
{
default:
draw_waveform(bitmap);
break;
case VIZ_WATERFALL:
draw_waterfall(bitmap);
break;
case VIZ_RAWSPEC:
case VIZ_BARSPEC4:
case VIZ_BARSPEC8:
case VIZ_BARSPEC16:
draw_spectrogram(bitmap);
break;
}
return 0;
}
void vgmviz_device::draw_spectrogram(bitmap_rgb32 &bitmap)
{
const pen_t *pal = m_palette->pens();
const int black_idx = (512 + FFT_LENGTH / 2);
/*
find_levels();
const pen_t *pal = m_palette->pens();
int chan_x = 0;
const int black_idx = (512 + FFT_LENGTH / 2);
for (int chan = 0; chan < 2; chan++)
{
int level = int(m_curr_levels[chan] * 255.0f);
@ -460,27 +529,28 @@ uint32_t vgmviz_device::screen_update(screen_device &screen, bitmap_rgb32 &bitma
chan_x += 8;
m_curr_peaks[chan] *= 0.99f;
}
*/
int bar_size = 1;
switch (m_spec_mode)
switch (m_viz_mode)
{
default:
bar_size = 1;
break;
case SPEC_BAR4:
case VIZ_BARSPEC4:
bar_size = 4;
break;
case SPEC_BAR8:
case VIZ_BARSPEC8:
bar_size = 8;
break;
case SPEC_BAR16:
case VIZ_BARSPEC16:
bar_size = 16;
break;
}
int total_bars = FFT_LENGTH / 2;
WDL_FFT_COMPLEX *bins[2] = { (WDL_FFT_COMPLEX *)m_fft_buf[0], (WDL_FFT_COMPLEX *)m_fft_buf[1] };
for (int bar = 0; bar < total_bars; bar += bar_size)
for (int bar = 0; bar < total_bars && bar < SCREEN_WIDTH; bar += bar_size)
{
if (bar < 2)
{
@ -492,37 +562,96 @@ uint32_t vgmviz_device::screen_update(screen_device &screen, bitmap_rgb32 &bitma
int permuted = WDL_fft_permute(FFT_LENGTH/2, bar + sub_bar);
max_val = std::max<float>((bins[0][permuted].re + bins[1][permuted].re) * 0.5f, max_val);
}
int level = int(logf(max_val * 32768.0f) * 63.0f);
for (int y = 0; y < 512; y++)
int level = int(logf(max_val * 32768.0f) * 96.0f);
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
int bar_y = 511 - y;
uint32_t *line = &bitmap.pix32(y + 256);
int bar_y = SCREEN_HEIGHT - y;
uint32_t *line = &bitmap.pix32(y);
bool lit = bar_y <= level;
const int x_limit = bar_size == 1 ? 1 : bar_size - 1;
for (int x = 0; x < x_limit; x++)
{
line[(bar - 2) + x + 16] = pal[lit ? (256 + bar) : black_idx];
line[(bar - 2) + x] = pal[lit ? (256 + bar) : black_idx];
}
}
}
}
const int width = FFT_LENGTH / 2 + 16;
for (int y = 0; y < 256; y++)
void vgmviz_device::draw_waterfall(bitmap_rgb32 &bitmap)
{
const pen_t *pal = m_palette->pens();
float tex_height = ((float)FFT_LENGTH / 2) - 1.0f;
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
const float v0 = (float)y / SCREEN_HEIGHT;
const float v1 = (float)(y + 1) / SCREEN_HEIGHT;
const float v0h = v0 * tex_height;
const float v1h = v1 * tex_height;
const int v0_index = (int)v0h;
const int v1_index = (int)v1h;
const float interp = v0h - (float)v0_index;
uint32_t* line = &bitmap.pix32(y);
for (int x = 0; x < width; x++)
for (int x = 0; x < SCREEN_WIDTH; x++)
{
if (m_waterfall_length < width)
if (m_waterfall_length < SCREEN_WIDTH)
{
const int sample = m_waterfall_buf[x][y];
const float s0 = m_waterfall_buf[x][v0_index];
const float s1 = m_waterfall_buf[x][v1_index];
const int sample = (int)std::round(lerp(s0, s1, interp));
*line++ = pal[256 + FFT_LENGTH / 2 + sample];
}
else
{
const int sample = m_waterfall_buf[((m_waterfall_length - width) + x) % width][y];
const int x_index = ((m_waterfall_length - SCREEN_WIDTH) + x) % SCREEN_WIDTH;
const float s0 = m_waterfall_buf[x_index][v0_index];
const float s1 = m_waterfall_buf[x_index][v1_index];
const int sample = (int)std::round(lerp(s0, s1, interp));
*line++ = pal[256 + FFT_LENGTH / 2 + sample];
}
}
}
return 0;
}
void vgmviz_device::draw_waveform(bitmap_rgb32 &bitmap)
{
static const uint32_t MED_GRAY = 0xff7f7f7f;
static const uint32_t WHITE = 0xffffffff;
static const uint32_t LEFT_COLOR = 0xffbf0000;
static const uint32_t RIGHT_COLOR = 0xff00bf00;
static const int CHANNEL_HEIGHT = (SCREEN_HEIGHT / 2) - 1;
static const int CHANNEL_CENTER = CHANNEL_HEIGHT / 2;
if (m_audio_frames_available == 0)
return;
for (int x = 0; x < SCREEN_WIDTH; x++)
{
bitmap.pix32(CHANNEL_CENTER, x) = MED_GRAY;
bitmap.pix32(CHANNEL_HEIGHT + 1 + CHANNEL_CENTER, x) = MED_GRAY;
const float raw_l = m_audio_buf[1 - m_audio_fill_index][0][((int)m_history_length + 1 + x) % FFT_LENGTH];
const int sample_l = (int)((raw_l - 0.5f) * (CHANNEL_HEIGHT - 1));
const int dy_l = (sample_l == 0) ? 0 : ((sample_l < 0) ? -1 : 1);
const int endy_l = CHANNEL_CENTER;
int y = endy_l - sample_l;
do
{
bitmap.pix32(y, x) = LEFT_COLOR;
y += dy_l;
} while(y != endy_l);
const float raw_r = m_audio_buf[1 - m_audio_fill_index][1][((int)m_history_length + 1 + x) % FFT_LENGTH];
const int sample_r = (int)((raw_r - 0.5f) * (CHANNEL_HEIGHT - 1));
const int dy_r = (sample_r == 0) ? 0 : ((sample_r < 0) ? -1 : 1);
const int endy_r = CHANNEL_HEIGHT + 1 + CHANNEL_CENTER;
y = endy_r - sample_r;
do
{
bitmap.pix32(y, x) = RIGHT_COLOR;
y += dy_r;
} while(y != endy_r);
bitmap.pix32(CHANNEL_HEIGHT, x) = WHITE;
bitmap.pix32(CHANNEL_HEIGHT + 1, x) = WHITE;
}
}

View File

@ -46,20 +46,29 @@ public:
vgmviz_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
virtual ~vgmviz_device();
void cycle_spectrogram();
void cycle_viz_mode();
void toggle_normalize();
protected:
enum spec_mode : int
enum viz_mode : int
{
SPEC_RAW,
SPEC_BAR4,
SPEC_BAR8,
SPEC_BAR16,
VIZ_WAVEFORM,
VIZ_WATERFALL,
VIZ_SPEC_START,
VIZ_RAWSPEC = VIZ_SPEC_START,
VIZ_BARSPEC4,
VIZ_BARSPEC8,
VIZ_BARSPEC16,
VIZ_SPEC_END = VIZ_BARSPEC16,
SPEC_COUNT
VIZ_COUNT
};
static constexpr int FFT_LENGTH = 512;
static constexpr int BUF_LENGTH = FFT_LENGTH;
static constexpr int SCREEN_WIDTH = FFT_LENGTH / 2;
static constexpr int SCREEN_HEIGHT = 768;
static constexpr size_t NORMALIZE_BUF_SIZE = 131072;
// device-level overrides
virtual void device_add_mconfig(machine_config &config) override;
@ -69,9 +78,15 @@ protected:
// device_sound_interface-level overrides
void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
void update_waveform(stream_sample_t **outputs);
void update_fft(stream_sample_t **outputs);
void init_palette(palette_device &palette) const;
uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
void draw_waveform(bitmap_rgb32 &bitmap);
void draw_waterfall(bitmap_rgb32 &bitmap);
void draw_spectrogram(bitmap_rgb32 &bitmap);
void fill_window();
void apply_window(uint32_t buf_index);
@ -82,6 +97,7 @@ protected:
required_device<screen_device> m_screen;
required_device<palette_device> m_palette;
uint32_t m_history_length;
float m_audio_buf[2][2][FFT_LENGTH];
float m_fft_buf[2][FFT_LENGTH];
int m_current_rate;
@ -91,11 +107,16 @@ protected:
bool m_audio_available;
int m_waterfall_length;
int m_waterfall_buf[1024][256];
int m_waterfall_buf[SCREEN_WIDTH * 2][FFT_LENGTH / 2];
float m_curr_levels[2];
float m_curr_peaks[2];
float m_window[FFT_LENGTH];
spec_mode m_spec_mode;
viz_mode m_viz_mode;
static const bool NEEDS_FFT[VIZ_COUNT];
};
#endif // MAME_SOUND_VGMVIZ_H

View File

@ -393,7 +393,7 @@ enum vgmplay_inputs : uint8_t
VGMPLAY_PLAY,
VGMPLAY_RESTART,
VGMPLAY_LOOP,
VGMPLAY_SPEC,
VGMPLAY_VIZ,
};
class vgmplay_state : public driver_device
@ -2758,6 +2758,8 @@ QUICKLOAD_LOAD_MEMBER(vgmplay_state::load_file)
device.set_unscaled_clock(c & ~0xc0000000);
if (device.unscaled_clock() != 0)
dynamic_cast<device_sound_interface *>(&device)->set_output_gain(ALL_OUTPUTS, chip_volume);
else
dynamic_cast<device_sound_interface *>(&device)->set_output_gain(ALL_OUTPUTS, 0);
return (c & 0x80000000) != 0;
});
@ -3155,8 +3157,8 @@ INPUT_CHANGED_MEMBER(vgmplay_state::key_pressed)
case VGMPLAY_LOOP:
m_vgmplay->toggle_loop();
break;
case VGMPLAY_SPEC:
m_mixer->cycle_spectrogram();
case VGMPLAY_VIZ:
m_mixer->cycle_viz_mode();
break;
}
}
@ -3168,7 +3170,7 @@ static INPUT_PORTS_START( vgmplay )
PORT_BIT(0x0004, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CHANGED_MEMBER(DEVICE_SELF, vgmplay_state, key_pressed, VGMPLAY_PLAY) PORT_NAME("Play")
PORT_BIT(0x0008, IP_ACTIVE_HIGH, IPT_BUTTON4) PORT_CHANGED_MEMBER(DEVICE_SELF, vgmplay_state, key_pressed, VGMPLAY_RESTART) PORT_NAME("Restart")
PORT_BIT(0x0010, IP_ACTIVE_HIGH, IPT_BUTTON5) PORT_CHANGED_MEMBER(DEVICE_SELF, vgmplay_state, key_pressed, VGMPLAY_LOOP) PORT_NAME("Loop")
PORT_BIT(0x0020, IP_ACTIVE_HIGH, IPT_BUTTON6) PORT_CHANGED_MEMBER(DEVICE_SELF, vgmplay_state, key_pressed, VGMPLAY_SPEC) PORT_NAME("Spectrogram Mode")
PORT_BIT(0x0020, IP_ACTIVE_HIGH, IPT_BUTTON6) PORT_CHANGED_MEMBER(DEVICE_SELF, vgmplay_state, key_pressed, VGMPLAY_VIZ) PORT_NAME("Visualization Mode")
INPUT_PORTS_END
void vgmplay_state::file_map(address_map &map)