569285794b
For the git migration we want a nice clean repository with UNIX line endings. git does not use svn:eol-style, we just need the file contents to be sane. Sorry everybody. I know this messes up blame. Scumbag *NIX developer says migrating to git will make line ending issues go away; commits giant change to svn which changes line endings anyway. :) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30924 a1c6a512-1295-4272-9138-f99709370657
297 lines
6.9 KiB
C
297 lines
6.9 KiB
C
// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/
|
|
|
|
#include "hes_apu_adpcm.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 */
|
|
|
|
|
|
void Adpcm_init( struct Hes_Apu_Adpcm* this )
|
|
{
|
|
this->output = NULL;
|
|
memset( &this->state, 0, sizeof( this->state ) );
|
|
Adpcm_reset( this );
|
|
}
|
|
|
|
void Adpcm_reset( struct Hes_Apu_Adpcm* this )
|
|
{
|
|
this->last_time = 0;
|
|
this->next_timer = 0;
|
|
this->last_amp = 0;
|
|
|
|
memset( &this->state.pcmbuf, 0, sizeof(this->state.pcmbuf) );
|
|
memset( &this->state.port, 0, sizeof(this->state.port) );
|
|
|
|
this->state.ad_sample = 0;
|
|
this->state.ad_ref_index = 0;
|
|
|
|
this->state.addr = 0;
|
|
this->state.freq = 0;
|
|
this->state.writeptr = 0;
|
|
this->state.readptr = 0;
|
|
this->state.playflag = 0;
|
|
this->state.repeatflag = 0;
|
|
this->state.length = 0;
|
|
this->state.volume = 0xFF;
|
|
this->state.fadetimer = 0;
|
|
this->state.fadecount = 0;
|
|
}
|
|
|
|
static short stepsize[49] = {
|
|
16, 17, 19, 21, 23, 25, 28,
|
|
31, 34, 37, 41, 45, 50, 55,
|
|
60, 66, 73, 80, 88, 97, 107,
|
|
118, 130, 143, 157, 173, 190, 209,
|
|
230, 253, 279, 307, 337, 371, 408,
|
|
449, 494, 544, 598, 658, 724, 796,
|
|
876, 963,1060,1166,1282,1411,1552
|
|
};
|
|
|
|
static int Adpcm_decode( struct Hes_Apu_Adpcm* this,int code );
|
|
static int Adpcm_decode( struct Hes_Apu_Adpcm* this,int code )
|
|
{
|
|
struct State* state = &this->state;
|
|
int step = stepsize[state->ad_ref_index];
|
|
int delta;
|
|
int c = code & 7;
|
|
#if 1
|
|
delta = 0;
|
|
if ( c & 4 ) delta += step;
|
|
step >>= 1;
|
|
if ( c & 2 ) delta += step;
|
|
step >>= 1;
|
|
if ( c & 1 ) delta += step;
|
|
step >>= 1;
|
|
delta += step;
|
|
#else
|
|
delta = ( ( c + c + 1 ) * step ) / 8; // maybe faster, but introduces rounding
|
|
#endif
|
|
if ( c != code )
|
|
{
|
|
state->ad_sample -= delta;
|
|
if ( state->ad_sample < -2048 )
|
|
state->ad_sample = -2048;
|
|
}
|
|
else
|
|
{
|
|
state->ad_sample += delta;
|
|
if ( state->ad_sample > 2047 )
|
|
state->ad_sample = 2047;
|
|
}
|
|
|
|
static int const steps [8] = {
|
|
-1, -1, -1, -1, 2, 4, 6, 8
|
|
};
|
|
state->ad_ref_index += steps [c];
|
|
if ( state->ad_ref_index < 0 )
|
|
state->ad_ref_index = 0;
|
|
else if ( state->ad_ref_index > 48 )
|
|
state->ad_ref_index = 48;
|
|
|
|
return state->ad_sample;
|
|
}
|
|
|
|
static void Adpcm_run_until( struct Hes_Apu_Adpcm* this, blip_time_t end_time );
|
|
static void Adpcm_run_until( struct Hes_Apu_Adpcm* this, blip_time_t end_time )
|
|
{
|
|
struct State* state = &this->state;
|
|
int volume = state->volume;
|
|
int fadetimer = state->fadetimer;
|
|
int fadecount = state->fadecount;
|
|
int last_time = this->last_time;
|
|
int next_timer = this->next_timer;
|
|
int last_amp = this->last_amp;
|
|
|
|
struct Blip_Buffer* output = this->output; // cache often-used values
|
|
|
|
while ( state->playflag && last_time < end_time )
|
|
{
|
|
while ( last_time >= next_timer )
|
|
{
|
|
if ( fadetimer )
|
|
{
|
|
if ( fadecount > 0 )
|
|
{
|
|
fadecount--;
|
|
volume = 0xFF * fadecount / fadetimer;
|
|
}
|
|
else if ( fadecount < 0 )
|
|
{
|
|
fadecount++;
|
|
volume = 0xFF - ( 0xFF * fadecount / fadetimer );
|
|
}
|
|
}
|
|
next_timer += 7159; // 7159091/1000;
|
|
}
|
|
int amp;
|
|
if ( state->ad_low_nibble )
|
|
{
|
|
amp = Adpcm_decode( this, state->pcmbuf[ state->playptr ] & 0x0F );
|
|
state->ad_low_nibble = false;
|
|
state->playptr++;
|
|
state->playedsamplecount++;
|
|
if ( state->playedsamplecount == state->playlength )
|
|
{
|
|
state->playflag = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
amp = Adpcm_decode( this, state->pcmbuf[ state->playptr ] >> 4 );
|
|
state->ad_low_nibble = true;
|
|
}
|
|
amp = amp * volume / 0xFF;
|
|
int delta = amp - last_amp;
|
|
if ( output && delta )
|
|
{
|
|
last_amp = amp;
|
|
Synth_offset_inline( &this->synth, last_time, delta, output );
|
|
}
|
|
last_time += state->freq;
|
|
}
|
|
|
|
if ( !state->playflag )
|
|
{
|
|
while ( next_timer <= end_time ) next_timer += 7159; // 7159091/1000
|
|
last_time = end_time;
|
|
}
|
|
|
|
this->last_time = last_time;
|
|
this->next_timer = next_timer;
|
|
this->last_amp = last_amp;
|
|
state->volume = volume;
|
|
state->fadetimer = fadetimer;
|
|
state->fadecount = fadecount;
|
|
}
|
|
|
|
void Adpcm_write_data( struct Hes_Apu_Adpcm* this, blip_time_t time, int addr, int data )
|
|
{
|
|
if ( time > this->last_time ) Adpcm_run_until( this, time );
|
|
struct State* state = &this->state;
|
|
|
|
data &= 0xFF;
|
|
state->port[ addr & 15 ] = data;
|
|
switch ( addr & 15 )
|
|
{
|
|
case 8:
|
|
state->addr &= 0xFF00;
|
|
state->addr |= data;
|
|
break;
|
|
case 9:
|
|
state->addr &= 0xFF;
|
|
state->addr |= data << 8;
|
|
break;
|
|
case 10:
|
|
state->pcmbuf[ state->writeptr++ ] = data;
|
|
state->playlength ++;
|
|
break;
|
|
case 11:
|
|
dprintf("ADPCM DMA 0x%02X", data);
|
|
break;
|
|
case 13:
|
|
if ( data & 0x80 )
|
|
{
|
|
state->addr = 0;
|
|
state->freq = 0;
|
|
state->writeptr = 0;
|
|
state->readptr = 0;
|
|
state->playflag = 0;
|
|
state->repeatflag = 0;
|
|
state->length = 0;
|
|
state->volume = 0xFF;
|
|
}
|
|
if ( ( data & 3 ) == 3 )
|
|
{
|
|
state->writeptr = state->addr;
|
|
}
|
|
if ( data & 8 )
|
|
{
|
|
state->readptr = state->addr ? state->addr - 1 : state->addr;
|
|
}
|
|
if ( data & 0x10 )
|
|
{
|
|
state->length = state->addr;
|
|
}
|
|
state->repeatflag = data & 0x20;
|
|
state->playflag = data & 0x40;
|
|
if ( state->playflag )
|
|
{
|
|
state->playptr = state->readptr;
|
|
state->playlength = state->length + 1;
|
|
state->playedsamplecount = 0;
|
|
state->ad_sample = 0;
|
|
state->ad_low_nibble = false;
|
|
}
|
|
break;
|
|
case 14:
|
|
state->freq = 7159091 / ( 32000 / ( 16 - ( data & 15 ) ) );
|
|
break;
|
|
case 15:
|
|
switch ( data & 15 )
|
|
{
|
|
case 0:
|
|
case 8:
|
|
case 12:
|
|
state->fadetimer = -100;
|
|
state->fadecount = state->fadetimer;
|
|
break;
|
|
case 10:
|
|
state->fadetimer = 5000;
|
|
state->fadecount = state->fadetimer;
|
|
break;
|
|
case 14:
|
|
state->fadetimer = 1500;
|
|
state->fadecount = state->fadetimer;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int Adpcm_read_data( struct Hes_Apu_Adpcm* this, blip_time_t time, int addr )
|
|
{
|
|
if ( time > this->last_time ) Adpcm_run_until( this, time );
|
|
|
|
struct State* state = &this->state;
|
|
switch ( addr & 15 )
|
|
{
|
|
case 10:
|
|
return state->pcmbuf [state->readptr++];
|
|
case 11:
|
|
return state->port [11] & ~1;
|
|
case 12:
|
|
if (!state->playflag)
|
|
{
|
|
state->port [12] |= 1;
|
|
state->port [12] &= ~8;
|
|
}
|
|
else
|
|
{
|
|
state->port [12] &= ~1;
|
|
state->port [12] |= 8;
|
|
}
|
|
return state->port [12];
|
|
case 13:
|
|
return state->port [13];
|
|
}
|
|
|
|
return 0xFF;
|
|
}
|
|
|
|
void Adpcm_end_frame( struct Hes_Apu_Adpcm* this, blip_time_t end_time )
|
|
{
|
|
Adpcm_run_until( this, end_time );
|
|
this->last_time -= end_time;
|
|
this->next_timer -= end_time;
|
|
check( last_time >= 0 );
|
|
if ( this->output )
|
|
Blip_set_modified( this->output );
|
|
}
|