furnace/src/engine/platform/sound/t6w28/T6W28_Apu.cpp
2022-10-12 00:12:56 -05:00

366 lines
8 KiB
C++

// T6W28_Snd_Emu
#include "T6W28_Apu.h"
#include <stdlib.h>
#include <assert.h>
#undef require
#define require( expr ) if (! (expr) ) return;
/* Copyright (C) 2003-2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
// T6W28_Osc
namespace MDFN_IEN_NGP
{
void fakeBufOffset(int time, int delta, Fake_Buffer* buf) {
buf->curValue+=delta;
}
T6W28_Osc::T6W28_Osc()
{
outputs [0] = NULL; // always stays NULL
outputs [1] = NULL;
outputs [2] = NULL;
outputs [3] = NULL;
}
void T6W28_Osc::reset()
{
delay = 0;
last_amp_left = 0;
last_amp_right = 0;
volume_left = 0;
volume_right = 0;
}
// T6W28_Square
void T6W28_Square::reset()
{
period = 0;
phase = 0;
T6W28_Osc::reset();
}
void T6W28_Square::run( sms_time_t time, sms_time_t end_time )
{
if ((!volume_left && !volume_right) || period <= 128 )
{
// ignore 16kHz and higher
if ( last_amp_left )
{
fakeBufOffset( time, -last_amp_left, outputs[2] );
last_amp_left = 0;
}
if ( last_amp_right )
{
fakeBufOffset( time, -last_amp_right, outputs[1] );
last_amp_right = 0;
}
time += delay;
if ( !period )
{
time = end_time;
}
else if ( time < end_time )
{
// keep calculating phase
int count = (end_time - time + period - 1) / period;
phase = (phase + count) & 1;
time += count * period;
}
}
else
{
int amp_left = phase ? volume_left : -volume_left;
int amp_right = phase ? volume_right : -volume_right;
{
int delta_left = amp_left - last_amp_left;
int delta_right = amp_right - last_amp_right;
if ( delta_left )
{
last_amp_left = amp_left;
fakeBufOffset( time, delta_left, outputs[2] );
}
if ( delta_right )
{
last_amp_right = amp_right;
fakeBufOffset( time, delta_right, outputs[1] );
}
}
time += delay;
if ( time < end_time )
{
Fake_Buffer* const output_left = this->outputs[2];
Fake_Buffer* const output_right = this->outputs[1];
int delta_left = amp_left * 2;
int delta_right = amp_right * 2;
do
{
delta_left = -delta_left;
delta_right = -delta_right;
fakeBufOffset( time, delta_left, output_left );
fakeBufOffset( time, delta_right, output_right );
time += period;
phase ^= 1;
}
while ( time < end_time );
this->last_amp_left = phase ? volume_left : -volume_left;
this->last_amp_right = phase ? volume_right : -volume_right;
}
}
delay = time - end_time;
}
// T6W28_Noise
static const int noise_periods [3] = { 0x100, 0x200, 0x400 };
void T6W28_Noise::reset()
{
period = &noise_periods [0];
shifter = 0x4000;
tap = 13;
period_extra = 0;
T6W28_Osc::reset();
}
void T6W28_Noise::run( sms_time_t time, sms_time_t end_time )
{
int amp_left = volume_left;
int amp_right = volume_right;
if ( shifter & 1 )
{
amp_left = -amp_left;
amp_right = -amp_right;
}
{
int delta_left = amp_left - last_amp_left;
int delta_right = amp_right - last_amp_right;
if ( delta_left )
{
last_amp_left = amp_left;
fakeBufOffset( time, delta_left, outputs[2] );
}
if ( delta_right )
{
last_amp_right = amp_right;
fakeBufOffset( time, delta_right, outputs[1] );
}
}
time += delay;
if ( !volume_left && !volume_right )
time = end_time;
if ( time < end_time )
{
Fake_Buffer* const output_left = this->outputs[2];
Fake_Buffer* const output_right = this->outputs[1];
unsigned l_shifter = this->shifter;
int delta_left = amp_left * 2;
int delta_right = amp_right * 2;
int l_period = *this->period * 2;
if ( !l_period )
l_period = 16;
do
{
int changed = (l_shifter + 1) & 2; // set if prev and next bits differ
l_shifter = (((l_shifter << 14) ^ (l_shifter << tap)) & 0x4000) | (l_shifter >> 1);
if ( changed )
{
delta_left = -delta_left;
fakeBufOffset( time, delta_left, output_left );
delta_right = -delta_right;
fakeBufOffset( time, delta_right, output_right );
}
time += l_period;
}
while ( time < end_time );
this->shifter = l_shifter;
this->last_amp_left = delta_left >> 1;
this->last_amp_right = delta_right >> 1;
}
delay = time - end_time;
}
// T6W28_Apu
T6W28_Apu::T6W28_Apu()
{
for ( int i = 0; i < 3; i++ )
{
oscs [i] = &squares [i];
}
oscs [3] = &noise;
reset();
}
T6W28_Apu::~T6W28_Apu()
{
}
void T6W28_Apu::osc_output( int index, Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right )
{
require( (unsigned int) index < osc_count );
require( (center && left && right) || (!center && !left && !right) );
T6W28_Osc& osc = *oscs [index];
osc.outputs [1] = right;
osc.outputs [2] = left;
osc.outputs [3] = center;
}
void T6W28_Apu::output( Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, center, left, right );
}
void T6W28_Apu::reset()
{
last_time = 0;
latch_left = 0;
latch_right = 0;
squares [0].reset();
squares [1].reset();
squares [2].reset();
noise.reset();
}
void T6W28_Apu::run_until( sms_time_t end_time )
{
require( end_time >= last_time ); // end_time must not be before previous time
if ( end_time > last_time )
{
// run oscillators
for ( int i = 0; i < osc_count; ++i )
{
T6W28_Osc& osc = *oscs [i];
if ( osc.outputs[1] )
{
if ( i < 3 )
squares [i].run( last_time, end_time );
else
noise.run( last_time, end_time );
}
}
last_time = end_time;
}
}
bool T6W28_Apu::end_frame( sms_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
assert( last_time >= end_time );
last_time -= end_time;
return(1);
}
static const unsigned char volumes [16] = {
// volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 )
64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0
};
void T6W28_Apu::write_data_left( sms_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
if ( data & 0x80 )
latch_left = data;
int index = (latch_left >> 5) & 3;
if ( latch_left & 0x10 )
{
oscs [index]->volume_left = volumes [data & 15];
}
else if ( index < 3 )
{
T6W28_Square& sq = squares [index];
if ( data & 0x80 )
sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF);
else
sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00);
}
}
void T6W28_Apu::write_data_right( sms_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
if ( data & 0x80 )
latch_right = data;
int index = (latch_right >> 5) & 3;
//printf("%d\n", index);
if ( latch_right & 0x10 )
{
oscs [index]->volume_right = volumes [data & 15];
}
else if ( index == 2 )
{
if ( data & 0x80 )
noise.period_extra = (noise.period_extra & 0xFF00) | (data << 4 & 0x00FF);
else
noise.period_extra = (noise.period_extra & 0x00FF) | (data << 8 & 0x3F00);
}
else if(index == 3)
{
int select = data & 3;
if ( select < 3 )
noise.period = &noise_periods [select];
else
noise.period = &noise.period_extra;
int const tap_disabled = 16;
noise.tap = (data & 0x04) ? 13 : tap_disabled;
noise.shifter = 0x4000;
}
}
}