423 lines
8.8 KiB
C++
423 lines
8.8 KiB
C++
// T6W28_Snd_Emu
|
|
|
|
#include "defs.h"
|
|
#include "T6W28_Apu.h"
|
|
|
|
/* 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
|
|
{
|
|
|
|
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
|
|
|
|
blip_inline 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)
|
|
{
|
|
synth->offset(time, -last_amp_left, outputs[2]);
|
|
last_amp_left = 0;
|
|
}
|
|
|
|
if (last_amp_right)
|
|
{
|
|
synth->offset(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;
|
|
synth->offset(time, delta_left, outputs[2]);
|
|
}
|
|
|
|
if (delta_right)
|
|
{
|
|
last_amp_right = amp_right;
|
|
synth->offset(time, delta_right, outputs[1]);
|
|
}
|
|
}
|
|
|
|
time += delay;
|
|
if (time < end_time)
|
|
{
|
|
Blip_Buffer *const output_left = this->outputs[2];
|
|
Blip_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;
|
|
|
|
synth->offset_inline(time, delta_left, output_left);
|
|
synth->offset_inline(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};
|
|
|
|
blip_inline void T6W28_Noise::reset()
|
|
{
|
|
period = &noise_periods[0];
|
|
shifter = 0x4000;
|
|
tap = 13;
|
|
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;
|
|
synth.offset(time, delta_left, outputs[2]);
|
|
}
|
|
|
|
if (delta_right)
|
|
{
|
|
last_amp_right = amp_right;
|
|
synth.offset(time, delta_right, outputs[1]);
|
|
}
|
|
}
|
|
|
|
time += delay;
|
|
|
|
if (!volume_left && !volume_right)
|
|
time = end_time;
|
|
|
|
if (time < end_time)
|
|
{
|
|
Blip_Buffer *const output_left = this->outputs[2];
|
|
Blip_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;
|
|
synth.offset_inline(time, delta_left, output_left);
|
|
|
|
delta_right = -delta_right;
|
|
synth.offset_inline(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++)
|
|
{
|
|
squares[i].synth = &square_synth;
|
|
oscs[i] = &squares[i];
|
|
}
|
|
oscs[3] = &noise;
|
|
|
|
volume(1.0);
|
|
reset();
|
|
}
|
|
|
|
T6W28_Apu::~T6W28_Apu()
|
|
{
|
|
}
|
|
|
|
void T6W28_Apu::volume(double vol)
|
|
{
|
|
vol *= 0.85 / (osc_count * 64 * 2);
|
|
square_synth.volume(vol);
|
|
noise.synth.volume(vol);
|
|
}
|
|
|
|
void T6W28_Apu::treble_eq(const blip_eq_t &eq)
|
|
{
|
|
square_synth.treble_eq(eq);
|
|
noise.synth.treble_eq(eq);
|
|
}
|
|
|
|
void T6W28_Apu::osc_output(int index, Blip_Buffer *center, Blip_Buffer *left, Blip_Buffer *right)
|
|
{
|
|
require((unsigned)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(Blip_Buffer *center, Blip_Buffer *left, Blip_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;
|
|
}
|
|
}
|
|
|
|
void T6W28_Apu::save_state(T6W28_ApuState *ret)
|
|
{
|
|
for (int x = 0; x < 4; x++)
|
|
{
|
|
ret->volume_left[x] = oscs[x]->volume_left;
|
|
ret->volume_right[x] = oscs[x]->volume_right;
|
|
}
|
|
for (int x = 0; x < 3; x++)
|
|
{
|
|
ret->sq_period[x] = squares[x].period;
|
|
ret->sq_phase[x] = squares[x].phase;
|
|
}
|
|
ret->noise_shifter = noise.shifter;
|
|
ret->noise_tap = noise.tap;
|
|
ret->noise_period_extra = noise.period_extra;
|
|
|
|
if (noise.period == &noise_periods[0])
|
|
ret->noise_period = 0;
|
|
else if (noise.period == &noise_periods[1])
|
|
ret->noise_period = 1;
|
|
else if (noise.period == &noise_periods[2])
|
|
ret->noise_period = 2;
|
|
else
|
|
ret->noise_period = 3;
|
|
|
|
ret->latch_left = latch_left;
|
|
ret->latch_right = latch_right;
|
|
}
|
|
|
|
void T6W28_Apu::load_state(const T6W28_ApuState *state)
|
|
{
|
|
for (int x = 0; x < 4; x++)
|
|
{
|
|
oscs[x]->volume_left = state->volume_left[x];
|
|
oscs[x]->volume_right = state->volume_right[x];
|
|
}
|
|
for (int x = 0; x < 3; x++)
|
|
{
|
|
squares[x].period = state->sq_period[x] & 0x3FFF;
|
|
squares[x].phase = state->sq_phase[x];
|
|
}
|
|
noise.shifter = state->noise_shifter;
|
|
noise.tap = state->noise_tap;
|
|
noise.period_extra = state->noise_period_extra & 0x3FFF;
|
|
|
|
unsigned select = state->noise_period;
|
|
|
|
if (select < 3)
|
|
noise.period = &noise_periods[select];
|
|
else
|
|
noise.period = &noise.period_extra;
|
|
|
|
latch_left = state->latch_left;
|
|
latch_right = state->latch_right;
|
|
}
|
|
}
|