2012-04-29 01:09:06 +00:00
using System ;
using System.Diagnostics ;
2012-08-07 05:48:30 +00:00
using System.Collections.Generic ;
2012-09-17 05:48:24 +00:00
using System.IO ;
2012-04-29 01:09:06 +00:00
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2013-11-14 19:33:13 +00:00
namespace BizHawk.Emulation.Common.Components
2011-01-11 02:55:51 +00:00
{
2012-08-07 05:48:30 +00:00
// ======================================================================
// Yamaha YM2612 Emulation Core
// Primarily sourced from Nemesis's documentation on Sprite's Mind forums:
// http://gendev.spritesmind.net/forum/viewtopic.php?t=386
/ /
// Notes:
// - In order to facilitate asynchronous sound generation, timer commands
// and reads are emulated immediately, while all other commands are
// queued together with a timestamp and processed at the end of the frame.
// - Commands are stretched in time to match the number of samples requested
// for the frame. For accurate, synchronous sound, simply request the correct
// number of samples for each frame.
// - Output is emulated at native output rate and downsampled (badly) to 44100hz.
// ======================================================================
// TODO: Finish testing Envelope generator
2012-08-10 04:46:29 +00:00
// TODO: maybe add guards when changing envelope parameters to immediately change envelope state (ie dont wait for EG cycle?)
2012-08-07 05:48:30 +00:00
// TODO: Detune
// TODO: LFO
// TODO: Switch from Perfect Operator to Accurate Operator.
// TODO: Operator1 Self-Feedback
// TODO: MEM delayed samples
// TODO: CSM mode
// TODO: SSG-EG
2012-08-10 04:46:29 +00:00
// TODO: Seriously, I think we need better resampling code.
// TODO: Experiment with low-pass filters, etc.
2012-08-07 05:48:30 +00:00
public sealed class YM2612 : ISoundProvider
2011-01-11 02:55:51 +00:00
{
2012-08-07 05:48:30 +00:00
public readonly Channel [ ] Channels = { new Channel ( ) , new Channel ( ) , new Channel ( ) , new Channel ( ) , new Channel ( ) , new Channel ( ) } ;
2012-04-29 01:09:06 +00:00
public YM2612 ( )
2011-01-11 02:55:51 +00:00
{
2012-04-29 06:05:15 +00:00
InitTimers ( ) ;
2012-08-07 05:48:30 +00:00
MaxVolume = short . MaxValue ;
2011-01-11 02:55:51 +00:00
}
2012-08-07 05:48:30 +00:00
// ====================================================================================
int frameStartClock ;
int frameEndClock ;
2011-01-11 02:55:51 +00:00
2012-04-29 01:09:06 +00:00
public void BeginFrame ( int clock )
{
frameStartClock = clock ;
2012-04-29 01:40:38 +00:00
while ( commands . Count > 0 )
{
var cmd = commands . Dequeue ( ) ;
WriteCommand ( cmd ) ;
}
2012-04-29 01:09:06 +00:00
}
public void EndFrame ( int clock )
2011-01-11 02:55:51 +00:00
{
2012-04-29 01:09:06 +00:00
frameEndClock = clock ;
2011-01-11 02:55:51 +00:00
}
2012-08-07 05:48:30 +00:00
// ====================================================================================
// YM2612 I/O
// ====================================================================================
public class QueuedCommand
{
public byte Part ;
public byte Register ;
public byte Data ;
public int Clock ;
}
byte PartSelect ;
byte RegisterSelect ;
bool DacEnable ;
byte DacValue ;
Queue < QueuedCommand > commands = new Queue < QueuedCommand > ( ) ;
const int Slot1 = 0 ;
const int Slot2 = 2 ;
const int Slot3 = 1 ;
const int Slot4 = 3 ;
public byte ReadStatus ( int clock )
{
UpdateTimers ( clock ) ;
byte retval = 0 ;
if ( TimerATripped ) retval | = 1 ;
if ( TimerBTripped ) retval | = 2 ;
return retval ;
}
public void Write ( int addr , byte value , int clock )
{
UpdateTimers ( clock ) ;
if ( addr = = 0 )
{
PartSelect = 1 ;
RegisterSelect = value ;
return ;
}
else if ( addr = = 2 )
{
PartSelect = 2 ;
RegisterSelect = value ;
return ;
}
if ( PartSelect = = 1 )
{
if ( RegisterSelect = = 0x24 ) { WriteTimerA_MSB_24 ( value , clock ) ; return ; }
if ( RegisterSelect = = 0x25 ) { WriteTimerA_LSB_25 ( value , clock ) ; return ; }
if ( RegisterSelect = = 0x26 ) { WriteTimerB_26 ( value , clock ) ; return ; }
if ( RegisterSelect = = 0x27 ) { WriteTimerControl_27 ( value , clock ) ; } // we process immediately AND enqueue command for port $27. Allows accurate tracking of CH3 special modes.
}
// If its not timer related just queue the command write
var cmd = new QueuedCommand { Part = PartSelect , Register = RegisterSelect , Data = value , Clock = clock - frameStartClock } ;
commands . Enqueue ( cmd ) ;
}
void WriteCommand ( QueuedCommand cmd )
{
if ( cmd . Part = = 1 )
Part1_WriteRegister ( cmd . Register , cmd . Data ) ;
else
Part2_WriteRegister ( cmd . Register , cmd . Data ) ;
}
static void GetChanOpP1 ( byte value , out int channel , out int oper )
{
value & = 15 ;
switch ( value )
{
2012-08-10 04:46:29 +00:00
case 0 : channel = 0 ; oper = 0 ; return ;
case 4 : channel = 0 ; oper = 2 ; return ;
case 8 : channel = 0 ; oper = 1 ; return ;
2012-08-07 05:48:30 +00:00
case 12 : channel = 0 ; oper = 3 ; return ;
2012-08-10 04:46:29 +00:00
case 1 : channel = 1 ; oper = 0 ; return ;
case 5 : channel = 1 ; oper = 2 ; return ;
case 9 : channel = 1 ; oper = 1 ; return ;
2012-08-07 05:48:30 +00:00
case 13 : channel = 1 ; oper = 3 ; return ;
2012-08-10 04:46:29 +00:00
case 2 : channel = 2 ; oper = 0 ; return ;
case 6 : channel = 2 ; oper = 2 ; return ;
2012-08-07 05:48:30 +00:00
case 10 : channel = 2 ; oper = 1 ; return ;
case 14 : channel = 2 ; oper = 3 ; return ;
default : channel = - 1 ; oper = - 1 ; return ;
}
}
static void GetChanOpP2 ( byte value , out int channel , out int oper )
{
value & = 15 ;
switch ( value )
{
2012-08-10 04:46:29 +00:00
case 0 : channel = 3 ; oper = 0 ; return ;
case 4 : channel = 3 ; oper = 2 ; return ;
case 8 : channel = 3 ; oper = 1 ; return ;
2012-08-07 05:48:30 +00:00
case 12 : channel = 3 ; oper = 3 ; return ;
2012-08-10 04:46:29 +00:00
case 1 : channel = 4 ; oper = 0 ; return ;
case 5 : channel = 4 ; oper = 2 ; return ;
case 9 : channel = 4 ; oper = 1 ; return ;
2012-08-07 05:48:30 +00:00
case 13 : channel = 4 ; oper = 3 ; return ;
2012-08-10 04:46:29 +00:00
case 2 : channel = 5 ; oper = 0 ; return ;
case 6 : channel = 5 ; oper = 2 ; return ;
2012-08-07 05:48:30 +00:00
case 10 : channel = 5 ; oper = 1 ; return ;
case 14 : channel = 5 ; oper = 3 ; return ;
default : channel = - 1 ; oper = - 1 ; return ;
}
}
void Part1_WriteRegister ( byte register , byte value )
{
int chan , oper ;
GetChanOpP1 ( register , out chan , out oper ) ;
switch ( register & 0xF0 )
{
case 0x20 : WriteLowBlock ( register , value ) ; break ;
2012-08-19 04:41:34 +00:00
case 0x30 : Write_MUL_DT ( chan , oper , value ) ; break ;
2012-08-07 05:48:30 +00:00
case 0x40 : Write_TL ( chan , oper , value ) ; break ;
case 0x50 : Write_AR_KS ( chan , oper , value ) ; break ;
case 0x60 : Write_DR_AM ( chan , oper , value ) ; break ;
case 0x70 : Write_SR ( chan , oper , value ) ; break ;
case 0x80 : Write_RR_SL ( chan , oper , value ) ; break ;
case 0x90 : Write_SSGEG ( chan , oper , value ) ; break ;
case 0xA0 :
case 0xB0 : WriteHighBlockP1 ( register , value ) ; break ;
}
}
void Part2_WriteRegister ( byte register , byte value )
{
int chan , oper ;
GetChanOpP2 ( register , out chan , out oper ) ;
switch ( register & 0xF0 )
{
2012-08-19 04:41:34 +00:00
case 0x30 : Write_MUL_DT ( chan , oper , value ) ; break ;
2012-08-07 05:48:30 +00:00
case 0x40 : Write_TL ( chan , oper , value ) ; break ;
case 0x50 : Write_AR_KS ( chan , oper , value ) ; break ;
case 0x60 : Write_DR_AM ( chan , oper , value ) ; break ;
case 0x70 : Write_SR ( chan , oper , value ) ; break ;
case 0x80 : Write_RR_SL ( chan , oper , value ) ; break ;
case 0x90 : Write_SSGEG ( chan , oper , value ) ; break ;
case 0xA0 :
case 0xB0 : WriteHighBlockP2 ( register , value ) ; break ;
}
}
void WriteLowBlock ( byte register , byte value )
{
switch ( register )
{
//case 0x22: Console.WriteLine("LFO Control {0:X2}", value); break;
case 0x24 : break ; // Timer A MSB, handled immediately
case 0x25 : break ; // Timer A LSB, handled immediately
case 0x26 : break ; // Timer B, handled immediately
//case 0x27: Console.WriteLine("$27: Ch3 Mode / Timer Control {0:X2}", value); break; // determines if CH3 has 1 frequency or 4 frequencies.
case 0x28 : KeyOnOff ( value ) ; break ;
case 0x2A : DacValue = value ; break ;
case 0x2B : DacEnable = ( value & 0x80 ) ! = 0 ; break ;
case 0x2C : throw new Exception ( "something wrote to ym2612 port $2C!" ) ; //http://forums.sonicretro.org/index.php?showtopic=28589
}
}
void WriteHighBlockP1 ( byte register , byte value )
{
switch ( register )
{
case 0xA0 : WriteFrequencyLow ( Channels [ 0 ] , value ) ; break ;
case 0xA1 : WriteFrequencyLow ( Channels [ 1 ] , value ) ; break ;
case 0xA2 : WriteFrequencyLow ( Channels [ 2 ] , value ) ; break ;
case 0xA4 : WriteFrequencyHigh ( Channels [ 0 ] , value ) ; break ;
case 0xA5 : WriteFrequencyHigh ( Channels [ 1 ] , value ) ; break ;
case 0xA6 : WriteFrequencyHigh ( Channels [ 2 ] , value ) ; break ;
case 0xB0 : Write_Feedback_Algorithm ( Channels [ 0 ] , value ) ; break ;
case 0xB1 : Write_Feedback_Algorithm ( Channels [ 1 ] , value ) ; break ;
case 0xB2 : Write_Feedback_Algorithm ( Channels [ 2 ] , value ) ; break ;
case 0xB4 : Write_Stereo_LfoSensitivy ( Channels [ 0 ] , value ) ; break ;
case 0xB5 : Write_Stereo_LfoSensitivy ( Channels [ 1 ] , value ) ; break ;
case 0xB6 : Write_Stereo_LfoSensitivy ( Channels [ 2 ] , value ) ; break ;
}
}
void WriteHighBlockP2 ( byte register , byte value )
{
switch ( register )
{
case 0xA0 : WriteFrequencyLow ( Channels [ 3 ] , value ) ; break ;
case 0xA1 : WriteFrequencyLow ( Channels [ 4 ] , value ) ; break ;
case 0xA2 : WriteFrequencyLow ( Channels [ 5 ] , value ) ; break ;
case 0xA4 : WriteFrequencyHigh ( Channels [ 3 ] , value ) ; break ;
case 0xA5 : WriteFrequencyHigh ( Channels [ 4 ] , value ) ; break ;
case 0xA6 : WriteFrequencyHigh ( Channels [ 5 ] , value ) ; break ;
case 0xB0 : Write_Feedback_Algorithm ( Channels [ 3 ] , value ) ; break ;
case 0xB1 : Write_Feedback_Algorithm ( Channels [ 4 ] , value ) ; break ;
case 0xB2 : Write_Feedback_Algorithm ( Channels [ 5 ] , value ) ; break ;
case 0xB4 : Write_Stereo_LfoSensitivy ( Channels [ 3 ] , value ) ; break ;
case 0xB5 : Write_Stereo_LfoSensitivy ( Channels [ 4 ] , value ) ; break ;
case 0xB6 : Write_Stereo_LfoSensitivy ( Channels [ 5 ] , value ) ; break ;
}
}
void KeyOnOff ( byte value )
{
int channel = value & 3 ;
if ( channel = = 3 ) return ; // illegal channel number, abort
if ( ( value & 4 ) ! = 0 ) channel + = 3 ; // select part 2
var chan = Channels [ channel ] ;
//Console.WriteLine("KeyOnOff for channel {0}", channel);
if ( ( value & 0x10 ) ! = 0 ) KeyOn ( chan . Operators [ Slot1 ] ) ; else KeyOff ( chan . Operators [ Slot1 ] ) ;
if ( ( value & 0x20 ) ! = 0 ) KeyOn ( chan . Operators [ Slot2 ] ) ; else KeyOff ( chan . Operators [ Slot2 ] ) ;
if ( ( value & 0x40 ) ! = 0 ) KeyOn ( chan . Operators [ Slot3 ] ) ; else KeyOff ( chan . Operators [ Slot3 ] ) ;
if ( ( value & 0x80 ) ! = 0 ) KeyOn ( chan . Operators [ Slot4 ] ) ; else KeyOff ( chan . Operators [ Slot4 ] ) ;
}
static void WriteFrequencyLow ( Channel channel , byte value )
{
channel . FrequencyNumber & = 0x700 ;
channel . FrequencyNumber | = value ;
// TODO maybe its 4-frequency mode
// TODO is this right, only reflect change when writing LSB?
2012-08-10 04:46:29 +00:00
Operator op ;
op = channel . Operators [ 0 ] ; op . FrequencyNumber = channel . FrequencyNumber ; op . Block = channel . Block ; CalcKeyCode ( op ) ; CalcRks ( op ) ;
op = channel . Operators [ 1 ] ; op . FrequencyNumber = channel . FrequencyNumber ; op . Block = channel . Block ; CalcKeyCode ( op ) ; CalcRks ( op ) ;
op = channel . Operators [ 2 ] ; op . FrequencyNumber = channel . FrequencyNumber ; op . Block = channel . Block ; CalcKeyCode ( op ) ; CalcRks ( op ) ;
op = channel . Operators [ 3 ] ; op . FrequencyNumber = channel . FrequencyNumber ; op . Block = channel . Block ; CalcKeyCode ( op ) ; CalcRks ( op ) ;
2012-08-07 05:48:30 +00:00
}
static void WriteFrequencyHigh ( Channel channel , byte value )
{
channel . FrequencyNumber & = 0x0FF ;
channel . FrequencyNumber | = ( value & 15 ) < < 8 ;
channel . Block = ( value > > 3 ) & 7 ;
}
2012-08-10 04:46:29 +00:00
static void CalcKeyCode ( Operator op )
{
int freq = op . FrequencyNumber ;
bool f11 = ( ( freq > > 10 ) & 1 ) ! = 0 ;
bool f10 = ( ( freq > > 9 ) & 1 ) ! = 0 ;
bool f09 = ( ( freq > > 8 ) & 1 ) ! = 0 ;
bool f08 = ( ( freq > > 7 ) & 1 ) ! = 0 ;
bool n3a = f11 & ( f10 | f09 | f08 ) ;
bool n3b = ! f11 & f10 & f09 & f08 ;
bool n3 = n3a | n3b ;
op . KeyCode = ( op . Block < < 2 ) | ( f11 ? 2 : 0 ) | ( n3 ? 1 : 0 ) ;
}
static void CalcRks ( Operator op )
{
int shiftVal = 3 - op . KS_KeyScale ;
op . Rks = op . KeyCode > > shiftVal ;
}
2012-08-07 05:48:30 +00:00
static void Write_Feedback_Algorithm ( Channel channel , byte value )
{
channel . Algorithm = value & 7 ;
channel . Feedback = ( value > > 3 ) & 7 ;
}
static void Write_Stereo_LfoSensitivy ( Channel channel , byte value )
{
channel . FMS_FrequencyModulationSensitivity = value & 3 ;
channel . AMS_AmplitudeModulationSensitivity = ( value > > 3 ) & 7 ;
channel . RightOutput = ( value & 0x40 ) ! = 0 ;
2012-08-10 04:46:29 +00:00
channel . LeftOutput = ( value & 0x80 ) ! = 0 ;
2012-08-07 05:48:30 +00:00
}
2012-08-19 04:41:34 +00:00
void Write_MUL_DT ( int chan , int op , byte value )
2012-08-07 05:48:30 +00:00
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . MUL_Multiple = value & 15 ;
2012-08-19 04:41:34 +00:00
oper . DT_Detune = ( value > > 4 ) & 7 ;
2012-08-07 05:48:30 +00:00
}
public void Write_TL ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . TL_TotalLevel = value & 127 ;
}
public void Write_AR_KS ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . AR_AttackRate = value & 31 ;
oper . KS_KeyScale = value > > 6 ;
2012-08-10 04:46:29 +00:00
CalcRks ( oper ) ;
2012-08-07 05:48:30 +00:00
}
public void Write_DR_AM ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . DR_DecayRate = value & 31 ;
oper . AM_AmplitudeModulation = ( value & 128 ) ! = 0 ;
}
public void Write_SR ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . SR_SustainRate = value & 31 ;
}
public void Write_RR_SL ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . RR_ReleaseRate = value & 15 ;
oper . SL_SustainLevel = value > > 4 ;
}
public void Write_SSGEG ( int chan , int op , byte value )
{
if ( chan < 0 ) return ;
var oper = Channels [ chan ] . Operators [ op ] ;
oper . SSG_EG = value & 15 ;
}
// ====================================================================================
// Timers
// ====================================================================================
// Assuming this is connected to a Genesis/MegaDrive:
// The master clock on the Genesis is 53,693,175 MCLK / sec (NTSC)
// 53,203,424 MCLK / sec (PAL)
// 7,670,454 68K cycles / sec (7 MCLK divisor) (NTSC)
// 3,579,545 Z80 cycles / sec (15 MCLK divisor) (NTSC)
// YM2612 is fed by 68000 Clock: 7,670,454 ECLK / sec (NTSC)
// 7,600,489 ECLK / sec (PAL)
// YM2612 has /6 divisor on the EXT CLOCK.
// YM2612 takes 24 cycles to generate a sample. 6*24 = 144. This is where the /144 divisor comes from.
// YM2612 native output rate is 7670454 / 144 = 53267 hz (NTSC), 52781 hz (PAL)
// Timer A ticks at the native output rate (53267 times per second for NTSC).
// Timer B ticks down with a /16 divisor. (3329 times per second for NTSC).
// Ergo, Timer A ticks every 67.2 Z80 cycles. Timer B ticks every 1075.2 Z80 cycles.
// TODO: make this not hardcoded to Genesis timing.
const float timerAZ80Factor = 67.2f ;
const float timerBZ80Factor = 1075.2f ;
const int ntscOutputRate = 53267 ;
const int palOutputRate = 52781 ;
const float ntsc44100Factor = 1.20786848f ;
const float pal44100Factor = 1.19684807f ;
int TimerAPeriod , TimerBPeriod ;
bool TimerATripped , TimerBTripped ;
int TimerAResetClock , TimerBResetClock ;
int TimerALastReset , TimerBLastReset ;
byte TimerControl27 ;
2012-08-10 04:46:29 +00:00
bool TimerALoad { get { return ( TimerControl27 & 1 ) ! = 0 ; } }
bool TimerBLoad { get { return ( TimerControl27 & 2 ) ! = 0 ; } }
2012-08-07 05:48:30 +00:00
bool TimerAEnable { get { return ( TimerControl27 & 4 ) ! = 0 ; } }
bool TimerBEnable { get { return ( TimerControl27 & 8 ) ! = 0 ; } }
2012-08-10 04:46:29 +00:00
bool TimerAReset { get { return ( TimerControl27 & 16 ) ! = 0 ; } }
bool TimerBReset { get { return ( TimerControl27 & 32 ) ! = 0 ; } }
2012-08-07 05:48:30 +00:00
void InitTimers ( )
{
TimerAResetClock = 68812 ;
TimerBResetClock = 275200 ;
}
void UpdateTimers ( int clock )
{
int elapsedCyclesSinceLastTimerAReset = clock - TimerALastReset ;
if ( elapsedCyclesSinceLastTimerAReset > TimerAResetClock )
{
if ( TimerAEnable )
TimerATripped = true ;
int numTimesTripped = elapsedCyclesSinceLastTimerAReset / TimerAResetClock ;
TimerALastReset + = ( TimerAResetClock * numTimesTripped ) ;
}
int elapsedCyclesSinceLastTimerBReset = clock - TimerBLastReset ;
if ( elapsedCyclesSinceLastTimerBReset > TimerBResetClock )
{
if ( TimerBEnable )
TimerBTripped = true ;
int numTimesTripped = elapsedCyclesSinceLastTimerBReset / TimerBResetClock ;
TimerBLastReset + = ( TimerBResetClock * numTimesTripped ) ;
}
}
void WriteTimerA_MSB_24 ( byte value , int clock )
{
TimerAPeriod = ( value < < 2 ) | ( TimerAPeriod & 3 ) ;
TimerAResetClock = ( int ) ( ( 1024 - TimerAPeriod ) * timerAZ80Factor ) ;
}
void WriteTimerA_LSB_25 ( byte value , int clock )
{
TimerAPeriod = ( TimerAPeriod & 0x3FC ) | ( value & 3 ) ;
TimerAResetClock = ( int ) ( ( 1024 - TimerAPeriod ) * timerAZ80Factor ) ;
}
2012-04-29 01:09:06 +00:00
2012-08-07 05:48:30 +00:00
void WriteTimerB_26 ( byte value , int clock )
{
TimerBPeriod = value ;
TimerBResetClock = ( int ) ( ( 256 - TimerBPeriod ) * timerBZ80Factor ) ;
}
void WriteTimerControl_27 ( byte value , int clock )
{
bool lagALoad = TimerALoad ;
bool lagBLoad = TimerBLoad ;
TimerControl27 = value ;
if ( ! lagALoad & & TimerALoad )
TimerALastReset = clock ;
if ( ! lagBLoad & & TimerBLoad )
TimerBLastReset = clock ;
if ( TimerAReset ) TimerATripped = false ;
if ( TimerBReset ) TimerBTripped = false ;
}
// ====================================================================================
2012-08-19 04:41:34 +00:00
// Support Tables
2012-08-07 05:48:30 +00:00
// ====================================================================================
#region tables
static readonly byte [ ] egRateCounterShiftValues =
{
11 , 11 , 11 , 11 , // Rates 0-3
10 , 10 , 10 , 10 , // Rates 4-7
9 , 9 , 9 , 9 , // Rates 8-11
8 , 8 , 8 , 8 , // Rates 12-15
7 , 7 , 7 , 7 , // Rates 16-19
6 , 6 , 6 , 6 , // Rates 20-23
5 , 5 , 5 , 5 , // Rates 24-27
4 , 4 , 4 , 4 , // Rates 28-31
3 , 3 , 3 , 3 , // Rates 32-35
2 , 2 , 2 , 2 , // Rates 36-39
1 , 1 , 1 , 1 , // Rates 40-43
0 , 0 , 0 , 0 , // Rates 44-47
0 , 0 , 0 , 0 , // Rates 48-51
0 , 0 , 0 , 0 , // Rates 52-55
0 , 0 , 0 , 0 , // Rates 56-59
0 , 0 , 0 , 0 // Rates 60-63
} ;
static readonly byte [ ] egRateIncrementValues =
{
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // Rate 0
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // Rate 1
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 2
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 3
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 4
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 5
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 6
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 7
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 8
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 9
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 10
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 11
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 12
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 13
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 14
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 15
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 16
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 17
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 18
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 19
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 20
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 21
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 22
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 23
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 24
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 25
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 26
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 27
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 28
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 29
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 30
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 31
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 32
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 33
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 34
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 35
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 36
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 37
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 38
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 39
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 40
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 41
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 42
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 43
0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , // Rate 44
0 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , // Rate 45
0 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , // Rate 46
0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 47
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // Rate 48
1 , 1 , 1 , 2 , 1 , 1 , 1 , 2 , // Rate 49
1 , 2 , 1 , 2 , 1 , 2 , 1 , 2 , // Rate 50
1 , 2 , 2 , 2 , 1 , 2 , 2 , 2 , // Rate 51
2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , // Rate 52
2 , 2 , 2 , 4 , 2 , 2 , 2 , 4 , // Rate 53
2 , 4 , 2 , 4 , 2 , 4 , 2 , 4 , // Rate 54
2 , 4 , 4 , 4 , 2 , 4 , 4 , 4 , // Rate 55
4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , // Rate 56
4 , 4 , 4 , 8 , 4 , 4 , 4 , 8 , // Rate 57
4 , 8 , 4 , 8 , 4 , 8 , 4 , 8 , // Rate 58
4 , 8 , 8 , 8 , 4 , 8 , 8 , 8 , // Rate 59
8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 , // Rate 60
8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 , // Rate 61
8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 , // Rate 62
8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 // Rate 63
} ;
static readonly int [ ] slTable = // translates a 4-bit SL value into a 10-bit attenuation value
{
0x000 , 0x020 , 0x040 , 0x060 , 0x080 , 0x0A0 , 0x0C0 , 0x0E0 ,
0x100 , 0x120 , 0x140 , 0x160 , 0x180 , 0x1A0 , 0x1C0 , 0x3FF
} ;
2012-08-19 04:41:34 +00:00
static readonly int [ ] detuneTable =
{
0 , 0 , 1 , 2 , // Key-Code 0
0 , 0 , 1 , 2 , // Key-Code 1
0 , 0 , 1 , 2 , // Key-Code 2
0 , 0 , 1 , 2 , // Key-Code 3
0 , 1 , 2 , 2 , // Key-Code 4
0 , 1 , 2 , 3 , // Key-Code 5
0 , 1 , 2 , 3 , // Key-Code 6
0 , 1 , 2 , 3 , // Key-Code 7
0 , 1 , 2 , 4 , // Key-Code 8
0 , 1 , 3 , 4 , // Key-Code 9
0 , 1 , 3 , 4 , // Key-Code 10
0 , 1 , 3 , 5 , // Key-Code 11
0 , 2 , 4 , 5 , // Key-Code 12
0 , 2 , 4 , 6 , // Key-Code 13
0 , 2 , 4 , 6 , // Key-Code 14
0 , 2 , 5 , 7 , // Key-Code 15
0 , 2 , 5 , 8 , // Key-Code 16
0 , 3 , 6 , 8 , // Key-Code 17
0 , 3 , 6 , 9 , // Key-Code 18
0 , 3 , 7 , 10 , // Key-Code 19
0 , 4 , 8 , 11 , // Key-Code 20
0 , 4 , 8 , 12 , // Key-Code 21
0 , 4 , 9 , 13 , // Key-Code 22
0 , 5 , 10 , 14 , // Key-Code 23
0 , 5 , 11 , 16 , // Key-Code 24
0 , 6 , 12 , 17 , // Key-Code 25
0 , 6 , 13 , 19 , // Key-Code 26
0 , 7 , 14 , 20 , // Key-Code 27
0 , 8 , 16 , 22 , // Key-Code 28
0 , 8 , 16 , 22 , // Key-Code 29
0 , 8 , 16 , 22 , // Key-Code 30
0 , 8 , 16 , 22 // Key-Code 31
} ;
2012-08-07 05:48:30 +00:00
#endregion
2012-08-19 04:41:34 +00:00
// ====================================================================================
// Envelope Generator
// ====================================================================================
2012-08-07 05:48:30 +00:00
int egDivisorCounter ; // This provides the /3 divisor to run the envelope generator once for every 3 FM sample output ticks.
int egCycleCounter ; // This provides a rolling counter of the envelope generator update ticks. (/3 divisor already applied)
const int MaxAttenuation = 1023 ;
void MaybeRunEnvelopeGenerator ( )
{
if ( egDivisorCounter = = 0 )
{
for ( int c = 0 ; c < 6 ; c + + )
for ( int o = 0 ; o < 4 ; o + + )
EnvelopeGeneratorTick ( Channels [ c ] . Operators [ o ] ) ;
egCycleCounter + + ;
}
egDivisorCounter + + ;
if ( egDivisorCounter = = 3 )
egDivisorCounter = 0 ;
}
void EnvelopeGeneratorTick ( Operator op )
{
// First, let's handle envelope generator phase transitions.
if ( op . EnvelopeState = = EnvelopeState . Off )
return ;
if ( op . EnvelopeState ! = EnvelopeState . Attack & & op . EgAttenuation = = MaxAttenuation )
{
op . EnvelopeState = EnvelopeState . Off ;
return ;
}
if ( op . EnvelopeState = = EnvelopeState . Attack & & op . EgAttenuation = = 0 )
{
op . EnvelopeState = EnvelopeState . Decay ;
if ( op . SL_SustainLevel = = 0 ) // If Sustain Level is 0, we skip Decay and go straight to Sustain phase.
op . EnvelopeState = EnvelopeState . Sustain ;
}
if ( op . EnvelopeState = = EnvelopeState . Decay & & op . EgAttenuation > = op . Normalized10BitSL )
{
op . EnvelopeState = EnvelopeState . Sustain ;
}
// At this point, we've determined what envelope phase we're in. Lets do the update.
// Start by calculating Rate.
int rate = 0 ;
switch ( op . EnvelopeState )
{
2012-08-10 04:46:29 +00:00
case EnvelopeState . Attack : rate = op . AR_AttackRate ; break ;
case EnvelopeState . Decay : rate = op . DR_DecayRate ; break ;
2012-08-07 05:48:30 +00:00
case EnvelopeState . Sustain : rate = op . SR_SustainRate ; break ;
case EnvelopeState . Release : rate = ( op . RR_ReleaseRate < < 1 ) + 1 ; break ;
}
2012-08-10 04:46:29 +00:00
if ( rate ! = 0 ) // rate=0 is 0 no matter the value of Rks.
rate = Math . Min ( ( rate * 2 ) + op . Rks , 63 ) ;
2012-08-07 05:48:30 +00:00
// Now we have rate. figure out shift value and cycle offset
int shiftValue = egRateCounterShiftValues [ rate ] ;
2012-08-10 04:46:29 +00:00
2012-08-07 05:48:30 +00:00
if ( egCycleCounter % ( 1 < < shiftValue ) = = 0 )
{
// Update attenuation value this tick
int updateCycleOffset = ( egCycleCounter > > shiftValue ) & 7 ; // gives the offset within the 8-step cycle
int attenuationAdjustment = egRateIncrementValues [ ( rate * 8 ) + updateCycleOffset ] ;
if ( op . EnvelopeState = = EnvelopeState . Attack )
2012-08-10 04:46:29 +00:00
op . EgAttenuation + = ( ~ op . EgAttenuation * attenuationAdjustment ) > > 4 ;
2012-08-07 05:48:30 +00:00
else // One of the decay phases
op . EgAttenuation + = attenuationAdjustment ;
}
}
static void KeyOn ( Operator op )
{
op . PhaseCounter = 0 ; // Reset Phase Generator
2012-08-10 04:46:29 +00:00
if ( op . AR_AttackRate > = 30 )
2012-08-07 05:48:30 +00:00
{
2012-08-10 04:46:29 +00:00
// AR of 30 or 31 skips attack phase
2012-08-07 05:48:30 +00:00
op . EgAttenuation = 0 ; // Force minimum attenuation
op . EnvelopeState = EnvelopeState . Decay ;
if ( op . SL_SustainLevel = = 0 ) // If Sustain Level is 0, we skip Decay and go straight to Sustain phase.
op . EnvelopeState = EnvelopeState . Sustain ;
2012-08-10 04:46:29 +00:00
} else {
2012-08-07 05:48:30 +00:00
2012-08-10 04:46:29 +00:00
// Regular Key-On
2012-08-07 05:48:30 +00:00
op . EnvelopeState = EnvelopeState . Attack ;
}
}
static void KeyOff ( Operator op )
{
op . EnvelopeState = EnvelopeState . Release ;
}
// ====================================================================================
// Operator Unit
// ====================================================================================
int GetOperatorOutput ( Operator op , int phaseModulationInput14 )
{
if ( op . EgAttenuation = = MaxAttenuation )
return 0 ;
RunPhaseGenerator ( op ) ;
int phase10 = op . PhaseCounter > > 10 ;
// operators return a 14-bit output, but take a 10-bit input. What 4 bits are discarded? not the obvious ones...
// the input is shifted right by one; the least significant bit and the 3 most significant bits are discarded.
int phaseModulationInput10 = ( phaseModulationInput14 > > 1 ) & 0x3FF ;
phase10 + = phaseModulationInput10 ;
phase10 & = 0x3FF ;
2012-08-10 04:46:29 +00:00
return OperatorCalc ( phase10 , op . AdjustedEGOutput ) ;
2012-08-07 05:48:30 +00:00
}
static void RunPhaseGenerator ( Operator op )
{
// Take the Frequency Number & shift based on Block
int phaseIncrement = op . FrequencyNumber ;
switch ( op . Block )
{
case 0 : phaseIncrement > > = 1 ; break ;
case 1 : break ;
default : phaseIncrement < < = op . Block - 1 ; break ;
}
2012-08-19 04:41:34 +00:00
// Apply Detune
int detuneAdjustment = detuneTable [ ( op . KeyCode * 4 ) + ( op . DT_Detune & 3 ) ] ;
if ( ( op . DT_Detune & 4 ) ! = 0 )
detuneAdjustment = - detuneAdjustment ;
phaseIncrement + = detuneAdjustment ;
phaseIncrement & = 0x1FFFF ; // mask to 17-bits, which is the current size of the register at this point in the calculation. This allows proper detune overflow.
2012-08-07 05:48:30 +00:00
// Apply MUL
switch ( op . MUL_Multiple )
{
2012-08-10 04:46:29 +00:00
case 0 : phaseIncrement / = 2 ; break ;
2012-08-07 05:48:30 +00:00
default : phaseIncrement * = op . MUL_Multiple ; break ;
}
op . PhaseCounter + = phaseIncrement ;
op . PhaseCounter & = 0xFFFFF ;
}
static int OperatorCalc ( int phase10 , int attenuation )
{
// calculate sin
double phaseNormalized = ( phase10 / 1023d ) ;
double sinResult = Math . Sin ( phaseNormalized * Math . PI * 2 ) ;
// convert attenuation into linear power representation
const double attenuationIndividualBitWeighting = 48.0 / 1024.0 ;
double attenuationInBels = ( ( ( double ) attenuation * attenuationIndividualBitWeighting ) / 10.0 ) ;
double powerLinear = Math . Pow ( 10.0 , - attenuationInBels ) ;
// attenuate result
double resultNormalized = sinResult * powerLinear ;
// calculate 14-bit operator output
const int maxOperatorOutput = 8191 ;
return ( int ) ( resultNormalized * maxOperatorOutput ) ;
}
// ====================================================================================
// Channel Unit
// ====================================================================================
const int max14bitValue = 0x1FFF ; // maximum signed value
int GetChannelOutput ( Channel channel , int maxVolume )
{
int outc = 0 ;
switch ( channel . Algorithm )
{
case 0 :
{
int out1 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out1 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out1 = GetOperatorOutput ( channel . Operators [ 2 ] , out1 ) ;
outc = GetOperatorOutput ( channel . Operators [ 3 ] , out1 ) ;
break ;
}
case 1 :
{
int out1 , out2 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 1 ] , 0 ) ;
outc = GetOperatorOutput ( channel . Operators [ 2 ] , Limit ( out1 + out2 , - 8191 , 8191 ) ) ; // TODO test whether these Limit calls are actually correct. technically I expect it to be overflowing in a 10-bit space.
outc = GetOperatorOutput ( channel . Operators [ 3 ] , outc ) ;
break ;
}
case 2 :
{
int out1 , out2 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 1 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 2 ] , out2 ) ;
outc = GetOperatorOutput ( channel . Operators [ 3 ] , Limit ( out1 + out2 , - 8191 , 8191 ) ) ;
break ;
}
case 3 :
{
int out1 , out2 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out1 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 2 ] , 0 ) ;
outc = GetOperatorOutput ( channel . Operators [ 3 ] , Limit ( out1 + out2 , - 8191 , 8191 ) ) ;
break ;
}
case 4 :
{
int out1 , out2 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out1 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 2 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 3 ] , out2 ) ;
outc = Limit ( out1 + out2 , - 8191 , 8191 ) ;
break ;
}
case 5 :
{
int out1 , out2 , out3 , out4 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out3 = GetOperatorOutput ( channel . Operators [ 2 ] , out1 ) ;
out4 = GetOperatorOutput ( channel . Operators [ 3 ] , out1 ) ;
outc = Limit ( out2 + out3 + out4 , - 8191 , 8191 ) ;
break ;
}
case 6 :
{
int out1 , out2 , out3 , out4 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out3 = GetOperatorOutput ( channel . Operators [ 2 ] , out1 ) ;
out4 = GetOperatorOutput ( channel . Operators [ 3 ] , out1 ) ;
outc = Limit ( out2 + out3 + out4 , - 8191 , 8191 ) ;
break ;
}
case 7 :
{
int out1 , out2 , out3 , out4 ;
out1 = GetOperatorOutput ( channel . Operators [ 0 ] , 0 ) ;
out2 = GetOperatorOutput ( channel . Operators [ 1 ] , out1 ) ;
out3 = GetOperatorOutput ( channel . Operators [ 2 ] , 0 ) ;
out4 = GetOperatorOutput ( channel . Operators [ 3 ] , 0 ) ;
outc = Limit ( out2 + out3 + out4 , - 8191 , 8191 ) ;
break ;
}
}
return outc * maxVolume / max14bitValue ;
}
static int Limit ( int value , int min , int max )
{
if ( value < min ) return min ;
if ( value > max ) return max ;
return value ;
}
// ====================================================================================
// Support Classes/Structs/Enums
// ====================================================================================
public enum EnvelopeState
{
Attack ,
Decay ,
Sustain ,
Release ,
Off
}
public sealed class Operator
{
// External Settings
public int TL_TotalLevel ; // 7 bits
public int SL_SustainLevel ; // 4 bits
public int AR_AttackRate ; // 5 bits
public int DR_DecayRate ; // 5 bits
public int SR_SustainRate ; // 5 bits
public int RR_ReleaseRate ; // 4 bits
public int KS_KeyScale ; // 2 bits
public int SSG_EG ; // 4 bits
2012-08-19 04:41:34 +00:00
public int DT_Detune ; // 3 bits
2012-08-07 05:48:30 +00:00
public int MUL_Multiple ; // 4 bits
public bool AM_AmplitudeModulation ; // 1 bit
public int FrequencyNumber ; // 11 bits
public int Block ; // 3 bits
2012-08-10 04:46:29 +00:00
public int KeyCode ; // 5 bits (described on pg 25 of YM2608 docs)
public int Rks ; // 5 bits (described on pg 29 of YM2608 docs)
2012-08-07 05:48:30 +00:00
// Internal State
public int PhaseCounter ; // 20 bits, where the 10 most significant bits are output to the operator.
public EnvelopeState EnvelopeState = EnvelopeState . Off ;
private int egAttenuation = MaxAttenuation ; // 10-bit attenuation value output from envelope generator
public int EgAttenuation
{
get { return egAttenuation ; }
set
{
egAttenuation = value ;
2012-08-19 04:41:34 +00:00
if ( egAttenuation < 0 ) egAttenuation = 0 ;
2012-08-07 05:48:30 +00:00
if ( egAttenuation > 1023 ) egAttenuation = 1023 ;
}
}
public int Normalized10BitSL { get { return slTable [ SL_SustainLevel ] ; } }
2012-08-10 04:46:29 +00:00
public int Normalized10BitTL { get { return TL_TotalLevel < < 3 ; } }
public int AdjustedEGOutput { get { return Math . Min ( egAttenuation + Normalized10BitTL , 1023 ) ; } }
2012-08-07 05:48:30 +00:00
}
public sealed class Channel
{
public readonly Operator [ ] Operators = { new Operator ( ) , new Operator ( ) , new Operator ( ) , new Operator ( ) } ;
public int FrequencyNumber ; // 11 bits
public int Block ; // 3 bits
public int Feedback ; // 3 bits
public int Algorithm ; // 3 bits (algorithms 0 - 7)
public bool SpecialMode ; // TODO, there are 2 special modes, a bool is not going to do the trick.
public bool LeftOutput = true ; // These apparently need to be initialized on.
public bool RightOutput = true ; // These apparently need to be initialized on.
public int AMS_AmplitudeModulationSensitivity ; // 3 bits
public int FMS_FrequencyModulationSensitivity ; // 2 bits
}
// ====================================================================================
// ISoundProvider
// ====================================================================================
public void GetSamples ( short [ ] samples )
{
// Generate raw samples at native sampling rate (~53hz)
int numStereoSamples = samples . Length / 2 ;
int nativeStereoSamples = ( int ) ( numStereoSamples * ntsc44100Factor ) * 2 ;
short [ ] nativeSamples = new short [ nativeStereoSamples ] ;
GetSamplesNative ( nativeSamples ) ;
// downsample from native output rate to 44100.
2012-08-19 04:41:34 +00:00
//CrappyNaiveResampler(nativeSamples, samples);
MaybeBetterDownsampler ( nativeSamples , samples ) ;
2012-08-19 17:46:13 +00:00
//LinearDownsampler(nativeSamples, samples);
2012-08-19 04:41:34 +00:00
}
static void CrappyNaiveResampler ( short [ ] input , short [ ] output )
{
// this is not good resampling code.
int numStereoSamples = output . Length / 2 ;
2012-08-07 05:48:30 +00:00
int offset = 0 ;
for ( int i = 0 ; i < numStereoSamples ; i + + )
{
int nativeOffset = ( ( i * ntscOutputRate ) / 44100 ) * 2 ;
2012-08-19 04:41:34 +00:00
output [ offset + + ] + = input [ nativeOffset + + ] ; // left
output [ offset + + ] + = input [ nativeOffset ] ; // right
}
}
static double Fraction ( double value )
{
return value - Math . Floor ( value ) ;
}
2012-08-19 17:46:13 +00:00
static void LinearDownsampler ( short [ ] input , short [ ] output )
{
double samplefactor = input . Length / ( double ) output . Length ;
for ( int i = 0 ; i < output . Length / 2 ; i + + )
{
// exact position on input stream
double inpos = i * samplefactor ;
// selected interpolation points and weights
int pt0 = ( int ) inpos ; // pt1 = pt0 + 1
double wt1 = inpos - pt0 ; // wt0 = 1 - wt1
double wt0 = 1.0 - wt1 ;
output [ i * 2 + 0 ] = ( short ) ( input [ pt0 * 2 + 0 ] * wt0 + input [ pt0 * 2 + 2 ] * wt1 ) ;
output [ i * 2 + 1 ] = ( short ) ( input [ pt0 * 2 + 1 ] * wt0 + input [ pt0 * 2 + 3 ] * wt1 ) ;
}
}
2012-08-19 04:41:34 +00:00
static void MaybeBetterDownsampler ( short [ ] input , short [ ] output )
{
// This is still not a good resampler. But it's better than the other one. Unsure how much difference it makes.
// The difference with this one is that all source samples will be sampled and weighted, none skipped over.
double nativeSamplesPerOutputSample = ( double ) input . Length / ( double ) output . Length ;
int outputStereoSamples = output . Length / 2 ;
int inputStereoSamples = input . Length / 2 ;
int offset = 0 ;
for ( int i = 0 ; i < outputStereoSamples ; i + + )
{
double startSample = nativeSamplesPerOutputSample * i ;
double endSample = nativeSamplesPerOutputSample * ( i + 1 ) ;
int iStartSample = ( int ) Math . Floor ( startSample ) ;
int iEndSample = ( int ) Math . Floor ( endSample ) ;
double leftSample = 0 ;
double rightSample = 0 ;
for ( int j = iStartSample ; j < = iEndSample ; j + + )
{
if ( j = = inputStereoSamples )
break ;
double weight = 1.0 ;
if ( j = = iStartSample )
weight = 1.0 - Fraction ( startSample ) ;
else if ( j = = iEndSample )
weight = Fraction ( endSample ) ;
leftSample + = ( ( double ) input [ ( j * 2 ) + 0 ] * weight ) ;
rightSample + = ( ( double ) input [ ( j * 2 ) + 1 ] * weight ) ;
}
output [ offset + + ] = ( short ) leftSample ;
output [ offset + + ] = ( short ) rightSample ;
2012-08-07 05:48:30 +00:00
}
}
void GetSamplesNative ( short [ ] samples )
2012-04-29 01:09:06 +00:00
{
int elapsedCycles = frameEndClock - frameStartClock ;
int start = 0 ;
while ( commands . Count > 0 )
{
var cmd = commands . Dequeue ( ) ;
int pos = ( ( cmd . Clock * samples . Length ) / elapsedCycles ) & ~ 1 ;
GetSamplesImmediate ( samples , start , pos - start ) ;
start = pos ;
WriteCommand ( cmd ) ;
}
GetSamplesImmediate ( samples , start , samples . Length - start ) ;
}
void GetSamplesImmediate ( short [ ] samples , int pos , int length )
{
int channelVolume = MaxVolume / 6 ;
2012-08-07 05:48:30 +00:00
for ( int i = 0 ; i < length / 2 ; i + + )
2012-04-29 01:09:06 +00:00
{
2012-08-07 05:48:30 +00:00
MaybeRunEnvelopeGenerator ( ) ;
2012-04-29 01:09:06 +00:00
2012-08-10 04:46:29 +00:00
// Generate FM output
2012-08-07 05:48:30 +00:00
for ( int ch = 0 ; ch < 6 ; ch + + )
2012-04-29 01:09:06 +00:00
{
2012-08-07 05:48:30 +00:00
short sample = ( short ) GetChannelOutput ( Channels [ ch ] , channelVolume ) ;
if ( ch < 5 | | DacEnable = = false )
{
if ( Channels [ ch ] . LeftOutput ) samples [ pos ] + = sample ;
if ( Channels [ ch ] . RightOutput ) samples [ pos + 1 ] + = sample ;
}
else
{
short dacValue = ( short ) ( ( ( DacValue - 80 ) * channelVolume ) / 80 ) ;
if ( Channels [ 5 ] . LeftOutput ) samples [ pos ] + = dacValue ;
if ( Channels [ 5 ] . RightOutput ) samples [ pos + 1 ] + = dacValue ;
}
2012-04-29 01:09:06 +00:00
}
pos + = 2 ;
}
}
2012-08-07 05:48:30 +00:00
public void DiscardSamples ( ) { }
public int MaxVolume { get ; set ; }
2012-09-17 05:48:24 +00:00
// ====================================================================================
// Save States
// ====================================================================================
public void SaveStateBinary ( BinaryWriter writer )
{
writer . Write ( TimerAPeriod ) ;
writer . Write ( TimerBPeriod ) ;
writer . Write ( TimerATripped ) ;
writer . Write ( TimerBTripped ) ;
writer . Write ( TimerAResetClock ) ;
writer . Write ( TimerBResetClock ) ;
writer . Write ( TimerALastReset ) ;
writer . Write ( TimerBLastReset ) ;
writer . Write ( TimerControl27 ) ;
writer . Write ( egDivisorCounter ) ;
writer . Write ( egCycleCounter ) ;
writer . Write ( PartSelect ) ;
writer . Write ( RegisterSelect ) ;
writer . Write ( DacEnable ) ;
writer . Write ( DacValue ) ;
for ( int i = 0 ; i < 6 ; i + + )
ChannelSaveStateBinary ( writer , Channels [ i ] ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
TimerAPeriod = reader . ReadInt32 ( ) ;
TimerBPeriod = reader . ReadInt32 ( ) ;
TimerATripped = reader . ReadBoolean ( ) ;
TimerBTripped = reader . ReadBoolean ( ) ;
TimerAResetClock = reader . ReadInt32 ( ) ;
TimerBResetClock = reader . ReadInt32 ( ) ;
TimerALastReset = reader . ReadInt32 ( ) ;
TimerBLastReset = reader . ReadInt32 ( ) ;
TimerControl27 = reader . ReadByte ( ) ;
egDivisorCounter = reader . ReadInt32 ( ) ;
egCycleCounter = reader . ReadInt32 ( ) ;
PartSelect = reader . ReadByte ( ) ;
RegisterSelect = reader . ReadByte ( ) ;
DacEnable = reader . ReadBoolean ( ) ;
DacValue = reader . ReadByte ( ) ;
for ( int i = 0 ; i < 6 ; i + + )
ChannelLoadStateBinary ( reader , Channels [ i ] ) ;
}
void ChannelSaveStateBinary ( BinaryWriter writer , Channel c )
{
// TODO reduce size of state via casting
writer . Write ( c . FrequencyNumber ) ;
writer . Write ( c . Block ) ;
writer . Write ( c . Feedback ) ;
writer . Write ( c . Algorithm ) ;
writer . Write ( c . SpecialMode ) ;
writer . Write ( c . LeftOutput ) ;
writer . Write ( c . RightOutput ) ;
writer . Write ( c . AMS_AmplitudeModulationSensitivity ) ;
writer . Write ( c . FMS_FrequencyModulationSensitivity ) ;
for ( int i = 0 ; i < 4 ; i + + )
OperatorSaveStateBinary ( writer , c . Operators [ i ] ) ;
}
void ChannelLoadStateBinary ( BinaryReader reader , Channel c )
{
c . FrequencyNumber = reader . ReadInt32 ( ) ;
c . Block = reader . ReadInt32 ( ) ;
c . Feedback = reader . ReadInt32 ( ) ;
c . Algorithm = reader . ReadInt32 ( ) ;
c . SpecialMode = reader . ReadBoolean ( ) ;
c . LeftOutput = reader . ReadBoolean ( ) ;
c . RightOutput = reader . ReadBoolean ( ) ;
c . AMS_AmplitudeModulationSensitivity = reader . ReadInt32 ( ) ;
c . FMS_FrequencyModulationSensitivity = reader . ReadInt32 ( ) ;
for ( int i = 0 ; i < 4 ; i + + )
OperatorLoadStateBinary ( reader , c . Operators [ i ] ) ;
}
void OperatorSaveStateBinary ( BinaryWriter writer , Operator op )
{
// TODO, size of states could be shrunken by using casts.
writer . Write ( op . TL_TotalLevel ) ;
writer . Write ( op . SL_SustainLevel ) ;
writer . Write ( op . AR_AttackRate ) ;
writer . Write ( op . DR_DecayRate ) ;
writer . Write ( op . SR_SustainRate ) ;
writer . Write ( op . RR_ReleaseRate ) ;
writer . Write ( op . KS_KeyScale ) ;
writer . Write ( op . SSG_EG ) ;
writer . Write ( op . DT_Detune ) ;
writer . Write ( op . MUL_Multiple ) ;
writer . Write ( op . AM_AmplitudeModulation ) ;
writer . Write ( op . FrequencyNumber ) ;
writer . Write ( op . Block ) ;
writer . Write ( op . KeyCode ) ;
writer . Write ( op . Rks ) ;
writer . Write ( op . PhaseCounter ) ;
writer . Write ( ( byte ) op . EnvelopeState ) ;
writer . Write ( op . EgAttenuation ) ;
}
void OperatorLoadStateBinary ( BinaryReader reader , Operator op )
{
op . TL_TotalLevel = reader . ReadInt32 ( ) ;
op . SL_SustainLevel = reader . ReadInt32 ( ) ;
op . AR_AttackRate = reader . ReadInt32 ( ) ;
op . DR_DecayRate = reader . ReadInt32 ( ) ;
op . SR_SustainRate = reader . ReadInt32 ( ) ;
op . RR_ReleaseRate = reader . ReadInt32 ( ) ;
op . KS_KeyScale = reader . ReadInt32 ( ) ;
op . SSG_EG = reader . ReadInt32 ( ) ;
op . DT_Detune = reader . ReadInt32 ( ) ;
op . MUL_Multiple = reader . ReadInt32 ( ) ;
op . AM_AmplitudeModulation = reader . ReadBoolean ( ) ;
op . FrequencyNumber = reader . ReadInt32 ( ) ;
op . Block = reader . ReadInt32 ( ) ;
op . KeyCode = reader . ReadInt32 ( ) ;
op . Rks = reader . ReadInt32 ( ) ;
op . PhaseCounter = reader . ReadInt32 ( ) ;
op . EnvelopeState = ( EnvelopeState ) Enum . ToObject ( typeof ( EnvelopeState ) , reader . ReadByte ( ) ) ;
op . EgAttenuation = reader . ReadInt32 ( ) ;
}
2011-01-11 02:55:51 +00:00
}
2012-04-29 01:09:06 +00:00
}