diff options
Diffstat (limited to 'src/devices/sound/vgm_visualizer.cpp')
-rw-r--r-- | src/devices/sound/vgm_visualizer.cpp | 766 |
1 files changed, 766 insertions, 0 deletions
diff --git a/src/devices/sound/vgm_visualizer.cpp b/src/devices/sound/vgm_visualizer.cpp new file mode 100644 index 00000000000..13d25bcb6ca --- /dev/null +++ b/src/devices/sound/vgm_visualizer.cpp @@ -0,0 +1,766 @@ +// license:BSD-3-Clause +// copyright-holders:Ryan Holtz +/*************************************************************************** + + vgm_visualizer.cpp + + Virtual VGM visualizer device. + + Provides a waterfall view, spectrograph view, and VU view. + +***************************************************************************/ + +#include "emu.h" +#include "vgm_visualizer.h" + +#include "wdlfft/fft.h" + +#include <cmath> + +constexpr int vgmviz_device::SCREEN_HEIGHT; + +constexpr float lerp(float a, float b, float f) +{ + return (b - a) * f + a; +} + + +//************************************************************************** +// GLOBAL VARIABLES +//************************************************************************** + +// device type definition +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 +//************************************************************************** + +//------------------------------------------------- +// vgmviz_device - constructor +//------------------------------------------------- + +vgmviz_device::vgmviz_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : device_t(mconfig, VGMVIZ, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_screen(*this, "screen") + , m_palette(*this, "palette") +{ +} + + +//------------------------------------------------- +// ~vgmviz_device - destructor +//------------------------------------------------- + +vgmviz_device::~vgmviz_device() +{ +} + + +//------------------------------------------------- +// device_start - handle device startup +//------------------------------------------------- + +void vgmviz_device::device_start() +{ + stream_alloc(2, 2, machine().sample_rate()); + WDL_fft_init(); + fill_window(); + m_bitmap.resize(SCREEN_WIDTH, SCREEN_HEIGHT); +} + + +//------------------------------------------------- +// fill_window - fill in the windowing data +//------------------------------------------------- + +void vgmviz_device::fill_window() +{ + float window_pos_delta = (M_PI * 2) / FFT_LENGTH; + float power = 0; + for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++) + { + float window_pos = i * window_pos_delta; + m_window[i] = 0.53836f - cosf(window_pos) * 0.46164f; + power += m_window[i]; + } + power = 0.5f / (power * 2.0f - m_window[FFT_LENGTH / 2]); + for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++) + { + m_window[i] *= power; + } +} + + +//------------------------------------------------- +// fill_window - apply windowing data to the +// mixed signal +//------------------------------------------------- + +void vgmviz_device::apply_window(uint32_t buf_index) +{ + float *audio_l = m_audio_buf[buf_index][0]; + float *audio_r = m_audio_buf[buf_index][1]; + float *buf_l = m_fft_buf[0]; + float *buf_r = m_fft_buf[1]; + float *window = m_window; + for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++) + { + *buf_l++ = *audio_l++ * *window; + *buf_r++ = *audio_r++ * *window; + window++; + } + for (int i = 0; i < (FFT_LENGTH / 2) - 1; i++) + { + window--; + *buf_l++ = *audio_l++ * *window; + *buf_r++ = *audio_r++ * *window; + } +} + + +//------------------------------------------------- +// apply_fft - run the FFT on the windowed data +//------------------------------------------------- + +void vgmviz_device::apply_fft() +{ + WDL_real_fft((WDL_FFT_REAL*)m_fft_buf[0], FFT_LENGTH, 0); + WDL_real_fft((WDL_FFT_REAL*)m_fft_buf[1], FFT_LENGTH, 0); + + for (int i = 1; i < FFT_LENGTH/2; i++) + { + for (int chan = 0; chan < 2; chan++) + { + WDL_FFT_COMPLEX* cmpl = (WDL_FFT_COMPLEX*)m_fft_buf[chan] + i; + cmpl->re = sqrtf(cmpl->re * cmpl->re + cmpl->im * cmpl->im); + } + } +} + + +//------------------------------------------------- +// apply_waterfall - calculate the waterfall-view +// data +//------------------------------------------------- + +void vgmviz_device::apply_waterfall() +{ + const 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 < std::min<int>(total_bars, SCREEN_HEIGHT); bar++) + { + if (bar < 2) + { + continue; + } + int permuted = WDL_fft_permute(FFT_LENGTH / 2, bar); + float val = std::max<float>(bins[0][permuted].re, bins[1][permuted].re); + int level = int(log10f(val * 32768.0f) * 95.0f); + m_waterfall_buf[m_waterfall_length % SCREEN_WIDTH][total_bars - bar] = (level < 0) ? 0 : (level > 255 ? 255 : level); + } + m_waterfall_length++; +} + + +//------------------------------------------------- +// find_levels - find average and peak levels +//------------------------------------------------- + +void vgmviz_device::find_levels() +{ + if (m_audio_frames_available < 2 || m_current_rate == 0) + { + m_curr_levels[0] = 0.0f; + m_curr_levels[1] = 0.0f; + m_curr_peaks[0] = 0.0f; + m_curr_peaks[1] = 0.0f; + return; + } + + m_curr_levels[0] = 0.0f; + m_curr_levels[1] = 0.0f; + + int read_index = m_audio_fill_index; + const int samples_needed = m_current_rate / 60; + int samples_remaining = samples_needed; + int samples_found = 0; + do + { + for (int i = std::min<int>(FFT_LENGTH - 1, m_audio_count[read_index]); i >= 0 && samples_remaining > 0; i--, samples_remaining--) + { + for (int chan = 0; chan < 2; chan++) + { + if (m_audio_buf[read_index][chan][i] > m_curr_levels[chan]) + { + m_curr_levels[chan] += m_audio_buf[read_index][chan][i]; + } + } + samples_found++; + samples_remaining--; + } + read_index = 1 - m_audio_fill_index; + } while (samples_remaining > 0 && read_index != m_audio_fill_index); + + if (samples_found > 0) + { + for (int chan = 0; chan < 2; chan++) + { + if (m_curr_levels[chan] > m_curr_peaks[chan]) + { + m_curr_peaks[chan] = m_curr_levels[chan]; + } + } + } +} + + +//------------------------------------------------- +// device_reset - handle device reset +//------------------------------------------------- + +void vgmviz_device::device_reset() +{ + for (int i = 0; i < 2; i++) + { + memset(m_audio_buf[i][0], 0, sizeof(float) * FFT_LENGTH); + memset(m_audio_buf[i][1], 0, sizeof(float) * FFT_LENGTH); + m_audio_count[i] = 0; + } + memset(m_fft_buf[0], 0, sizeof(float) * FFT_LENGTH); + memset(m_fft_buf[1], 0, sizeof(float) * FFT_LENGTH); + m_current_rate = 0; + m_audio_fill_index = 0; + m_audio_frames_available = 0; + memset(m_curr_levels, 0, sizeof(float) * 2); + memset(m_curr_peaks, 0, sizeof(float) * 2); + + m_waterfall_length = 0; + for (int i = 0; i < SCREEN_WIDTH; i++) + { + memset(m_waterfall_buf[i], 0, sizeof(int) * 256); + } + + m_viz_mode = VIZ_WAVEFORM; + m_clear_pending = true; + m_bitmap.fill(0); + + m_history_length = 0; +} + + +//------------------------------------------------- +// cycle_spectrogram - cycle the visualization +// mode among the valid modes. +//------------------------------------------------- + +void vgmviz_device::cycle_viz_mode() +{ + m_viz_mode = (viz_mode)((int)m_viz_mode + 1); + if (m_viz_mode == VIZ_COUNT) + { + m_viz_mode = VIZ_WAVEFORM; + } + m_bitmap.fill(0); + m_clear_pending = true; +} + + +//------------------------------------------------- +// sound_stream_update - update the outgoing +// audio stream and process as necessary +//------------------------------------------------- + +void vgmviz_device::sound_stream_update(sound_stream &stream) +{ + // Passthrough the audio + stream.copy(0, 0); + stream.copy(1, 1); + + // now consume the inputs + for (int pos = 0; pos < stream.samples(); pos++) + { + for (int i = 0; i < stream.output_count(); i++) + { + // Original code took 16-bit sample / 65536.0 instead of 32768.0, so multiply by 0.5 here but is it necessary? + const float sample = stream.get(i, pos) * 0.5f; + m_audio_buf[m_audio_fill_index][i][m_audio_count[m_audio_fill_index]] = sample + 0.5f; + } + + switch (m_viz_mode) + { + default: + update_waveform(); + break; + case VIZ_WATERFALL: + case VIZ_RAW_SPEC: + case VIZ_BAR_SPEC4: + case VIZ_BAR_SPEC8: + case VIZ_BAR_SPEC16: + case VIZ_PILLAR_SPEC4: + case VIZ_PILLAR_SPEC8: + case VIZ_PILLAR_SPEC16: + case VIZ_TOP_SPEC: + case VIZ_TOP_SPEC4: + case VIZ_TOP_SPEC8: + case VIZ_TOP_SPEC16: + update_fft(); + break; + } + } +} + + +//------------------------------------------------- +// update_waveform - perform a wave-style update +//------------------------------------------------- + +void vgmviz_device::update_waveform() +{ + 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() +{ + 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 +//------------------------------------------------- + +void vgmviz_device::init_palette(palette_device &palette) const +{ + for (int i = 0; i < 256; i++) + { + float percent = (float)i / 255.0f; + if (percent < 0.75f) + { + float r = lerp(0.0f, 1.0f, percent / 0.75f); + float g = 1.0f; + float b = 0.0f; + palette.set_pen_color(i, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255))); + } + else + { + float r = lerp(1.0f, 1.0f, (percent - 0.75f) / 0.25f); + float g = lerp(1.0f, 0.0f, (percent - 0.75f) / 0.25f); + float b = 0.0f; + palette.set_pen_color(i, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255))); + } + } + + for (int i = 0; i < FFT_LENGTH / 2; i++) + { + double h = ((double)i / (FFT_LENGTH / 2)) * 360.0; + double s = 1.0; + double v = 1.0; + + double c = s * v; + double x = c * (1 - fabs(fmod(h / 60.0, 2.0) - 1.0)); + double m = v - c; + double rs = 0.0; + double gs = 0.0; + double bs = 0.0; + + if (h >= 0.0 && h < 60.0) + { + rs = c; + gs = x; + bs = 0.0; + } + else if (h >= 60.0 && h < 120.0) + { + rs = x; + gs = c; + bs = 0.0; + } + else if (h >= 120.0 && h < 180.0) + { + rs = 0.0; + gs = c; + bs = x; + } + else if (h >= 180.0 && h < 240.0) + { + rs = 0.0; + gs = x; + bs = c; + } + else if (h >= 240.0 && h < 300.0) + { + rs = x; + gs = 0.0; + bs = c; + } + else if (h < 360.0) + { + rs = c; + gs = 0.0; + bs = x; + } + + palette.set_pen_color(i + 256, rgb_t((uint8_t)((rs + m) * 255), (uint8_t)((gs + m) * 255), (uint8_t)((bs + m) * 255))); + } + + for (int y = 0; y < 256; y++) + { + float percent = (float)y / 255.0f; + if (percent < 0.75f) + { + float r = 0.0f; + float g = 0.0f; + float b = lerp(0.0f, 1.0f, percent / 0.5f); + palette.set_pen_color(y + 256 + FFT_LENGTH / 2, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255))); + } + else + { + float r = lerp(0.0f, 1.0f, (percent - 0.5f) / 0.5f); + float g = lerp(0.0f, 1.0f, (percent - 0.5f) / 0.5f); + float b = 1.0f; + palette.set_pen_color(y + 256 + FFT_LENGTH / 2, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255))); + } + } + + palette.set_pen_color(512 + FFT_LENGTH / 2, rgb_t(0, 0, 0)); +} + + +//------------------------------------------------- +// device_add_mconfig - handle device setup +//------------------------------------------------- + +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(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); +} + + +//------------------------------------------------- +// screen_update - update vu meters +//------------------------------------------------- + +template void vgmviz_device::draw_spectrogram<1, vgmviz_device::SPEC_VIZ_BAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<4, vgmviz_device::SPEC_VIZ_BAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<8, vgmviz_device::SPEC_VIZ_BAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<16, vgmviz_device::SPEC_VIZ_BAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<4, vgmviz_device::SPEC_VIZ_TOP>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<8, vgmviz_device::SPEC_VIZ_TOP>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<16, vgmviz_device::SPEC_VIZ_TOP>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<3, vgmviz_device::SPEC_VIZ_PILLAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<6, vgmviz_device::SPEC_VIZ_PILLAR>(bitmap_rgb32 &bitmap); +template void vgmviz_device::draw_spectrogram<12, vgmviz_device::SPEC_VIZ_PILLAR>(bitmap_rgb32 &bitmap); + +uint32_t vgmviz_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + switch (m_viz_mode) + { + default: + bitmap.fill(0, cliprect); + draw_waveform(bitmap); + break; + case VIZ_WATERFALL: + bitmap.fill(0, cliprect); + draw_waterfall(bitmap); + break; + case VIZ_RAW_SPEC: + draw_spectrogram<1, SPEC_VIZ_BAR>(bitmap); + break; + case VIZ_TOP_SPEC: + draw_spectrogram<1, SPEC_VIZ_TOP>(bitmap); + break; + case VIZ_BAR_SPEC4: + draw_spectrogram<4, SPEC_VIZ_BAR>(bitmap); + break; + case VIZ_PILLAR_SPEC4: + draw_spectrogram<3, SPEC_VIZ_PILLAR>(bitmap); + break; + case VIZ_TOP_SPEC4: + draw_spectrogram<4, SPEC_VIZ_TOP>(bitmap); + break; + case VIZ_BAR_SPEC8: + draw_spectrogram<8, SPEC_VIZ_BAR>(bitmap); + break; + case VIZ_PILLAR_SPEC8: + draw_spectrogram<6, SPEC_VIZ_PILLAR>(bitmap); + break; + case VIZ_TOP_SPEC8: + draw_spectrogram<8, SPEC_VIZ_TOP>(bitmap); + break; + case VIZ_BAR_SPEC16: + draw_spectrogram<16, SPEC_VIZ_BAR>(bitmap); + break; + case VIZ_PILLAR_SPEC16: + draw_spectrogram<12, SPEC_VIZ_PILLAR>(bitmap); + break; + case VIZ_TOP_SPEC16: + draw_spectrogram<16, SPEC_VIZ_TOP>(bitmap); + break; + } + return 0; +} + +template <int BarSize, int SpecMode> void vgmviz_device::draw_spectrogram(bitmap_rgb32 &bitmap) +{ + const int black_index = 512 + FFT_LENGTH / 2; + + if (m_clear_pending || SpecMode == SPEC_VIZ_BAR) + { + bitmap.fill(0); + m_clear_pending = false; + } + + pen_t const *const pal = m_palette->pens(); + + int width = SCREEN_WIDTH; + switch (SpecMode) + { + case SPEC_VIZ_PILLAR: + for (int y = 2; y < SCREEN_HEIGHT; y++) + { + uint32_t const *const src = &m_bitmap.pix(y); + uint32_t *const dst = &bitmap.pix(y - 2); + for (int x = SCREEN_WIDTH - 1; x >= 1; x--) + { + dst[x] = src[x - 1]; + } + } + width = int(SCREEN_WIDTH * 0.75f); + break; + case SPEC_VIZ_TOP: + for (int y = 1; y < SCREEN_HEIGHT; y++) + { + std::copy_n(&m_bitmap.pix(y), SCREEN_WIDTH, &bitmap.pix(y - 1)); + } + break; + default: + break; + } + + const 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 = 2; bar < total_bars && bar < width; bar += BarSize) + { + float max_val = 0.0f; + for (int sub_bar = 0; sub_bar < BarSize && (bar + sub_bar) < total_bars; sub_bar++) + { + int permuted = WDL_fft_permute(FFT_LENGTH/2, bar + sub_bar); + const float max_channel = std::max<float>(bins[0][permuted].re, bins[1][permuted].re); + max_val = std::max<float>(max_channel, max_val); + } + + float raw_level = logf(max_val * 32768.0f); + + int level = 0; + int pal_index = 0; + switch (SpecMode) + { + case SPEC_VIZ_BAR: + level = int(raw_level * 95.0f); + if (level <= 767) + { + pal_index = 255 - level / 3; + } + for (int y = 0; y < SCREEN_HEIGHT; y++) + { + const int bar_y = SCREEN_HEIGHT - y; + uint32_t *const line = &bitmap.pix(y); + for (int x = 0; x < BarSize; x++) + { + line[(bar - 2) + x] = bar_y <= level ? pal[256 + pal_index] : pal[black_index]; + } + } + break; + case SPEC_VIZ_PILLAR: + level = int(raw_level * 59.0f); + if (level < 383) + { + pal_index = 255 - (int)(level * 0.75f); + } + + for (int y = 0; y < SCREEN_HEIGHT; y++) + { + const int bar_y = SCREEN_HEIGHT - y; + uint32_t *const line = &bitmap.pix(y); + line[0] = 0; + if (bar_y > level) + { + continue; + } + const uint32_t entry = pal[256 + pal_index]; + for (int x = (bar_y < level) ? 0 : 1; x < (BarSize - 1); x++) + { + if (bar_y < (level - 1)) + { + const uint8_t r = uint8_t(((entry >> 16) & 0xff) * 0.75f); + const uint8_t g = uint8_t(((entry >> 8) & 0xff) * 0.75f); + const uint8_t b = uint8_t(((entry >> 0) & 0xff) * 0.75f); + line[(bar - 2) + x] = 0xff000000 | (r << 16) | (g << 8) | b; + } + else + { + line[(bar - 2) + x] = pal[256 + pal_index]; + } + } + const uint8_t r = uint8_t(((entry >> 16) & 0xff) * 0.5f); + const uint8_t g = uint8_t(((entry >> 8) & 0xff) * 0.5f); + const uint8_t b = uint8_t(((entry >> 0) & 0xff) * 0.5f); + line[(bar - 2) + (BarSize - 1)] = 0xff000000 | (r << 16) | (g << 8) | b; + if (bar_y < level) + { + line[(bar - 2) + (BarSize - 2)] = 0xff000000 | (r << 16) | (g << 8) | b; + } + } + for (int x = 0; x < SCREEN_WIDTH; x++) + { + bitmap.pix(SCREEN_HEIGHT - 1, x) = 0; + bitmap.pix(SCREEN_HEIGHT - 2, x) = 0; + } + for (int y = 0; y < SCREEN_HEIGHT; y++) + std::copy_n(&bitmap.pix(y), SCREEN_WIDTH, &m_bitmap.pix(y)); + break; + case SPEC_VIZ_TOP: + level = int(raw_level * 63.0f); + if (level < 255) + { + pal_index = 255 - level; + } + + for (int x = 0; x < BarSize; x++) + { + bitmap.pix(SCREEN_HEIGHT - 1, (bar - 2) + x) = level > 0 ? pal[256 + pal_index] : pal[black_index]; + } + + for (int y = 0; y < SCREEN_HEIGHT; y++) + std::copy_n(&bitmap.pix(y), SCREEN_WIDTH, &m_bitmap.pix(y)); + break; + } + } +} + +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.pix(y); + for (int x = 0; x < SCREEN_WIDTH; x++) + { + if (m_waterfall_length < SCREEN_WIDTH) + { + 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 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]; + } + } + } +} + +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.pix(CHANNEL_CENTER, x) = MED_GRAY; + bitmap.pix(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.pix(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.pix(y, x) = RIGHT_COLOR; + y += dy_r; + } while(y != endy_r); + + bitmap.pix(CHANNEL_HEIGHT, x) = WHITE; + bitmap.pix(CHANNEL_HEIGHT + 1, x) = WHITE; + } +} |