From acb0917556fc33681c1df5a530cf754193e67705 Mon Sep 17 00:00:00 2001 From: Andree Buschmann Date: Sun, 7 Aug 2011 20:01:04 +0000 Subject: Submit initial patch from FS#12176. Adds support for several new game music formats (AY, GBS, HES, KSS, SGC, VGM and VGZ) and replaces the current NSF and NSFE with a new implementation based on a port of the Game Music Emu library 'GME'. This first submit does not cover the full functionality provided by the author's original patch: Coleco-SGV is not supported, some GME-specific m3u-support has been removed and IRAM is not used yet. Further changes are very likely to follow this submit. Thanks to Mauricio Garrido. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30264 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs/libgme/ay_apu.c | 413 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 apps/codecs/libgme/ay_apu.c (limited to 'apps/codecs/libgme/ay_apu.c') diff --git a/apps/codecs/libgme/ay_apu.c b/apps/codecs/libgme/ay_apu.c new file mode 100644 index 0000000000..a2ec299167 --- /dev/null +++ b/apps/codecs/libgme/ay_apu.c @@ -0,0 +1,413 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "ay_apu.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Emulation inaccuracies: +// * Noise isn't run when not in use +// * Changes to envelope and noise periods are delayed until next reload +// * Super-sonic tone should attenuate output to about 60%, not 50% + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +int const inaudible_freq = 16384; + +int const period_factor = 16; + +static byte const amp_table [16] = +{ +#define ENTRY( n ) (byte) (n * ay_amp_range + 0.5) + // With channels tied together and 1K resistor to ground (as datasheet recommends), + // output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step. + ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625), + ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500), + ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000), + ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000), + + /* + // Measured from an AY-3-8910A chip with date code 8611. + + // Direct voltages without any load (very linear) + ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785), + ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032), + ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043), + ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000), + // With only some load + ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876), + ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388), + ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945), + ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000), + */ +#undef ENTRY +}; + +static byte const modes [8] = +{ +#define MODE( a0,a1, b0,b1, c0,c1 ) \ + (a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5) + MODE( 1,0, 1,0, 1,0 ), + MODE( 1,0, 0,0, 0,0 ), + MODE( 1,0, 0,1, 1,0 ), + MODE( 1,0, 1,1, 1,1 ), + MODE( 0,1, 0,1, 0,1 ), + MODE( 0,1, 1,1, 1,1 ), + MODE( 0,1, 1,0, 0,1 ), + MODE( 0,1, 0,0, 0,0 ), +}; + +void set_output( struct Ay_Apu* this, struct Blip_Buffer* b ) +{ + int i; + for ( i = 0; i < ay_osc_count; ++i ) + Ay_apu_set_output( this, i, b ); +} + +void Ay_apu_init( struct Ay_Apu* this ) +{ + Synth_init( &this->synth_ ); + + // build full table of the upper 8 envelope waveforms + int m; + for ( m = 8; m--; ) + { + byte* out = this->env_modes [m]; + int x, y, flags = modes [m]; + for ( x = 3; --x >= 0; ) + { + int amp = flags & 1; + int end = flags >> 1 & 1; + int step = end - amp; + amp *= 15; + for ( y = 16; --y >= 0; ) + { + *out++ = amp_table [amp]; + amp += step; + } + flags >>= 2; + } + } + + set_output( this, NULL ); + Ay_apu_volume( this, 1.0 ); + Ay_apu_reset( this ); +} + +void Ay_apu_reset( struct Ay_Apu* this ) +{ + this->addr_ = 0; + this->last_time = 0; + this->noise_delay = 0; + this->noise_lfsr = 1; + + struct osc_t* osc; + for ( osc = &this->oscs [ay_osc_count]; osc != this->oscs; ) + { + osc--; + osc->period = period_factor; + osc->delay = 0; + osc->last_amp = 0; + osc->phase = 0; + } + + int i; + for ( i = sizeof this->regs; --i >= 0; ) + this->regs [i] = 0; + this->regs [7] = 0xFF; + write_data_( this, 13, 0 ); +} + +int Ay_apu_read( struct Ay_Apu* this ) +{ + static byte const masks [ay_reg_count] = { + 0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F, + 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00 + }; + return this->regs [this->addr_] & masks [this->addr_]; +} + +void write_data_( struct Ay_Apu* this, int addr, int data ) +{ + assert( (unsigned) addr < ay_reg_count ); + + /* if ( (unsigned) addr >= 14 ) + dprintf( "Wrote to I/O port %02X\n", (int) addr ); */ + + // envelope mode + if ( addr == 13 ) + { + if ( !(data & 8) ) // convert modes 0-7 to proper equivalents + data = (data & 4) ? 15 : 9; + this->env_wave = this->env_modes [data - 7]; + this->env_pos = -48; + this->env_delay = 0; // will get set to envelope period in run_until() + } + this->regs [addr] = data; + + // handle period changes accurately + int i = addr >> 1; + if ( i < ay_osc_count ) + { + blip_time_t period = (this->regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) + + this->regs [i * 2] * period_factor; + if ( !period ) + period = period_factor; + + // adjust time of next timer expiration based on change in period + struct osc_t* osc = &this->oscs [i]; + if ( (osc->delay += period - osc->period) < 0 ) + osc->delay = 0; + osc->period = period; + } + + // TODO: same as above for envelope timer, and it also has a divide by two after it +} + +int const noise_off = 0x08; +int const tone_off = 0x01; + +void run_until( struct Ay_Apu* this, blip_time_t final_end_time ) +{ + require( final_end_time >= this->last_time ); + + // noise period and initial values + blip_time_t const noise_period_factor = period_factor * 2; // verified + blip_time_t noise_period = (this->regs [6] & 0x1F) * noise_period_factor; + if ( !noise_period ) + noise_period = noise_period_factor; + blip_time_t const old_noise_delay = this->noise_delay; + unsigned const old_noise_lfsr = this->noise_lfsr; + + // envelope period + blip_time_t const env_period_factor = period_factor * 2; // verified + blip_time_t env_period = (this->regs [12] * 0x100 + this->regs [11]) * env_period_factor; + if ( !env_period ) + env_period = env_period_factor; // same as period 1 on my AY chip + if ( !this->env_delay ) + this->env_delay = env_period; + + // run each osc separately + int index; + for ( index = 0; index < ay_osc_count; index++ ) + { + struct osc_t* const osc = &this->oscs [index]; + int osc_mode = this->regs [7] >> index; + + // output + struct Blip_Buffer* const osc_output = osc->output; + if ( !osc_output ) + continue; + Blip_set_modified( osc_output ); + + // period + int half_vol = 0; + blip_time_t inaudible_period = (unsigned) (Blip_clock_rate( osc_output ) + + inaudible_freq) / (unsigned) (inaudible_freq * 2); + if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) + { + half_vol = 1; // Actually around 60%, but 50% is close enough + osc_mode |= tone_off; + } + + // envelope + blip_time_t start_time = this->last_time; + blip_time_t end_time = final_end_time; + int const vol_mode = this->regs [0x08 + index]; + int volume = amp_table [vol_mode & 0x0F] >> half_vol; + int osc_env_pos = this->env_pos; + if ( vol_mode & 0x10 ) + { + volume = this->env_wave [osc_env_pos] >> half_vol; + // use envelope only if it's a repeating wave or a ramp that hasn't finished + if ( !(this->regs [13] & 1) || osc_env_pos < -32 ) + { + end_time = start_time + this->env_delay; + if ( end_time >= final_end_time ) + end_time = final_end_time; + + //if ( !(regs [12] | regs [11]) ) + // dprintf( "Used envelope period 0\n" ); + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + + // tone time + blip_time_t const period = osc->period; + blip_time_t time = start_time + osc->delay; + if ( osc_mode & tone_off ) // maintain tone's phase when off + { + int count = (final_end_time - time + period - 1) / period; + time += count * period; + osc->phase ^= count & 1; + } + + // noise time + blip_time_t ntime = final_end_time; + unsigned noise_lfsr = 1; + if ( !(osc_mode & noise_off) ) + { + ntime = start_time + old_noise_delay; + noise_lfsr = old_noise_lfsr; + //if ( (regs [6] & 0x1F) == 0 ) + // dprintf( "Used noise period 0\n" ); + } + + // The following efficiently handles several cases (least demanding first): + // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC + // * Just tone or just noise, envelope disabled + // * Envelope controlling tone and/or noise + // * Tone and noise disabled, envelope enabled with high frequency + // * Tone and noise together + // * Tone and noise together with envelope + + // this loop only runs one iteration if envelope is disabled. If envelope + // is being used as a waveform (tone and noise disabled), this loop will + // still be reasonably efficient since the bulk of it will be skipped. + while ( 1 ) + { + // current amplitude + int amp = 0; + if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) + amp = volume; + { + int delta = amp - osc->last_amp; + if ( delta ) + { + osc->last_amp = amp; + Synth_offset( &this->synth_, start_time, delta, osc_output ); + } + } + + // Run wave and noise interleved with each catching up to the other. + // If one or both are disabled, their "current time" will be past end time, + // so there will be no significant performance hit. + if ( ntime < end_time || time < end_time ) + { + // Since amplitude was updated above, delta will always be +/- volume, + // so we can avoid using last_amp every time to calculate the delta. + int delta = amp * 2 - volume; + int delta_non_zero = delta != 0; + int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); + do + { + // run noise + blip_time_t end = end_time; + if ( end_time > time ) end = time; + if ( phase & delta_non_zero ) + { + while ( ntime <= end ) // must advance *past* time to avoid hang + { + int changed = noise_lfsr + 1; + noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); + if ( changed & 2 ) + { + delta = -delta; + Synth_offset( &this->synth_, ntime, delta, osc_output ); + } + ntime += noise_period; + } + } + else + { + // 20 or more noise periods on average for some music + int remain = end - ntime; + int count = remain / noise_period; + if ( remain >= 0 ) + ntime += noise_period + count * noise_period; + } + + // run tone + end = end_time; + if ( end_time > ntime ) end = ntime; + if ( noise_lfsr & delta_non_zero ) + { + while ( time < end ) + { + delta = -delta; + Synth_offset( &this->synth_, time, delta, osc_output ); + time += period; + + // alternate (less-efficient) implementation + //phase ^= 1; + } + phase = (unsigned) (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); + check( phase == (delta > 0) ); + } + else + { + // loop usually runs less than once + //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); + + while ( time < end ) + { + time += period; + phase ^= 1; + } + } + } + while ( time < end_time || ntime < end_time ); + + osc->last_amp = (delta + volume) >> 1; + if ( !(osc_mode & tone_off) ) + osc->phase = phase; + } + + if ( end_time >= final_end_time ) + break; // breaks first time when envelope is disabled + + // next envelope step + if ( ++osc_env_pos >= 0 ) + osc_env_pos -= 32; + volume = this->env_wave [osc_env_pos] >> half_vol; + + start_time = end_time; + end_time += env_period; + if ( end_time > final_end_time ) + end_time = final_end_time; + } + osc->delay = time - final_end_time; + + if ( !(osc_mode & noise_off) ) + { + this->noise_delay = ntime - final_end_time; + this->noise_lfsr = noise_lfsr; + } + } + + // TODO: optimized saw wave envelope? + + // maintain envelope phase + blip_time_t remain = final_end_time - this->last_time - this->env_delay; + if ( remain >= 0 ) + { + int count = (remain + env_period) / env_period; + this->env_pos += count; + if ( this->env_pos >= 0 ) + this->env_pos = (this->env_pos & 31) - 32; + remain -= count * env_period; + assert( -remain <= env_period ); + } + this->env_delay = -remain; + assert( this->env_delay > 0 ); + assert( this->env_pos < 0 ); + + this->last_time = final_end_time; +} -- cgit v1.2.3