HatariWii/src/sound.c

1553 lines
51 KiB
C

/*
Hatari - sound.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
This is where we emulate the YM2149. To obtain cycle-accurate timing we store
the current cycle time and this is incremented during each instruction.
When a write occurs in the PSG registers we take the difference in time and
generate this many samples using the previous register data.
Now we begin again from this point. To make sure we always have 1/50th of
samples we update the buffer generation every 1/50th second, just in case no
write took place on the PSG.
NOTE: If the emulator runs slower than 50fps it cannot update the buffers,
but the sound thread still needs some data to play to prevent a 'pop'. The
ONLY feasible solution is to play the same buffer again. I have tried all
kinds of methods to play the sound 'slower', but this produces un-even timing
in the sound and it simply doesn't work. If the emulator cannot keep the
speed, users will have to turn off the sound - that's it.
The new version of the sound core uses/used some code/ideas from the following GPL projects :
- tone and noise steps computations are from StSound 1.2 by Arnaud Carré (Leonard/Oxygene)
(not used since Hatari 1.1.0)
- 5 bits volume table and 16*16*16 combinations of all volume are from Sc68 by Benjamin Gerard
- 4 bits to 5 bits volume interpolation from 16*16*16 to 32*32*32 from YM blep synthesis by Antti Lankila
Special case for per==0 : as measured on a real STF, when tone/noise/env's per==0, we get
the same sound output as when per==1.
*/
/* 2008/05/05 [NP] Fix case where period is 0 for noise, sound or envelope. */
/* In that case, a real ST sounds as if period was in fact 1. */
/* (fix buggy sound replay in ESwat that set volume<0 and trigger */
/* a badly initialised envelope with envper=0). */
/* 2008/07/27 [NP] Better separation between accesses to the YM hardware registers */
/* and the sound rendering routines. Use Sound_WriteReg() to pass */
/* all writes to the sound rendering functions. This allows to */
/* have sound.c independent of psg.c (to ease replacement of */
/* sound.c by another rendering method). */
/* 2008/08/02 [NP] Initial convert of Ym2149Ex.cpp from C++ to C. */
/* Remove unused part of the code (StSound specific). */
/* 2008/08/09 [NP] Complete integration of StSound routines into sound.c */
/* Set EnvPer=3 if EnvPer<3 (ESwat buggy replay). */
/* 2008/08/13 [NP] StSound was generating samples in the range 0-32767, instead */
/* of really signed samples between -32768 and 32767, which could */
/* give incorrect results in many case. */
/* 2008/09/06 [NP] Use sc68 volumes table for a more accurate mixing of the voices */
/* All volumes are converted to 5 bits and the table contains */
/* 32*32*32 values. Samples are signed and centered to get the */
/* biggest amplitude possible. */
/* Faster mixing routines for tone+volume+envelope (don't use */
/* StSound's version anymore, it gave problem with some GCC). */
/* 2008/09/17 [NP] Add ym_normalise_5bit_table to normalise the 32*32*32 table and */
/* to optionally center 16 bit signed sample. */
/* Possibility to mix volumes using a table measured on ST or a */
/* linear mean of the 3 channels' volume. */
/* Default mixing set to YM_LINEAR_MIXING. */
/* 2008/10/14 [NP] Full support for 5 bits volumes : envelopes are generated with */
/* 32 volumes per pattern as on a real YM-2149. Fixed volumes */
/* on 4 bits are converted to their 5 bits equivalent. This should */
/* give the maximum accuracy possible when computing volumes. */
/* New version of Ym2149_EnvStepCompute to handle 5 bits volumes. */
/* Function YM2149_EnvBuild to compute the 96 volumes that define */
/* a single envelope (32 initial volumes, then 64 repeated values).*/
/* 2008/10/26 [NP] Correctly save/restore all necessary variables in */
/* Sound_MemorySnapShot_Capture. */
/* 2008/11/23 [NP] Clean source, remove old sound core. */
/* 2011/11/03 [DS] Stereo DC filtering which accounts for DMA sound. */
const char Sound_fileid[] = "Hatari sound.c : " __DATE__ " " __TIME__;
#include <SDL_types.h>
#include "main.h"
#include "audio.h"
#include "cycles.h"
#include "configuration.h"
#include "dmaSnd.h"
#include "crossbar.h"
#include "file.h"
#include "cycInt.h"
#include "log.h"
#include "memorySnapShot.h"
#include "psg.h"
#include "sound.h"
#include "screen.h"
#include "video.h"
#include "wavFormat.h"
#include "ymFormat.h"
#include "avi_record.h"
#include "clocks_timings.h"
/*--------------------------------------------------------------*/
/* Definition of the possible envelopes shapes (using 5 bits) */
/*--------------------------------------------------------------*/
#define ENV_GODOWN 0 /* 31 -> 0 */
#define ENV_GOUP 1 /* 0 -> 31 */
#define ENV_DOWN 2 /* 0 -> 0 */
#define ENV_UP 3 /* 31 -> 31 */
/* To generate an envelope, we first use block 0, then we repeat blocks 1 and 2 */
static const int YmEnvDef[ 16 ][ 3 ] = {
{ ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 0 \___ */
{ ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 1 \___ */
{ ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 2 \___ */
{ ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 3 \___ */
{ ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 4 /___ */
{ ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 5 /___ */
{ ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 6 /___ */
{ ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 7 /___ */
{ ENV_GODOWN, ENV_GODOWN, ENV_GODOWN } , /* 8 \\\\ */
{ ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 9 \___ */
{ ENV_GODOWN, ENV_GOUP, ENV_GODOWN } , /* A \/\/ */
{ ENV_GODOWN, ENV_UP, ENV_UP } , /* B \--- */
{ ENV_GOUP, ENV_GOUP, ENV_GOUP } , /* C //// */
{ ENV_GOUP, ENV_UP, ENV_UP } , /* D /--- */
{ ENV_GOUP, ENV_GODOWN, ENV_GOUP } , /* E /\/\ */
{ ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* F /___ */
};
/* Buffer to store the 16 envelopes built from YmEnvDef */
static ymu16 YmEnvWaves[ 16 ][ 32 * 3 ]; /* 16 envelopes with 3 blocks of 32 volumes */
/*--------------------------------------------------------------*/
/* Definition of the volumes tables (using 5 bits) and of the */
/* mixing parameters for the 3 voices. */
/*--------------------------------------------------------------*/
/* Table of unsigned 5 bit D/A output level for 1 channel as measured on a real ST (expanded from 4 bits to 5 bits) */
/* Vol 0 should be 310 when measuread as a voltage, but we set it to 0 in order to have a volume=0 matching */
/* the 0 level of a 16 bits unsigned sample (no sound output) */
static const ymu16 ymout1c5bit[ 32 ] =
{
0 /*310*/, 369, 438, 521, 619, 735, 874, 1039,
1234, 1467, 1744, 2072, 2463, 2927, 3479, 4135,
4914, 5841, 6942, 8250, 9806,11654,13851,16462,
19565,23253,27636,32845,39037,46395,55141,65535
};
/* Convert a constant 4 bits volume to the internal 5 bits value : */
/* volume5=volume4*2+1, except for volumes 0 and 1 which remain 0 and 1, */
/* in order to map [0,15] into [0,31] (O must remain 0, and 15 must give 31) */
static const ymu16 YmVolume4to5[ 16 ] = { 0,1,5,7,9,11,13,15,17,19,21,23,25,27,29,31 };
/* Table of unsigned 4 bit D/A output level for 3 channels as measured on a real ST */
static ymu16 volumetable_original[16][16][16] =
#include "ym2149_fixed_vol.h"
/* Corresponding table interpolated to 5 bit D/A output level (16 bits unsigned) */
static ymu16 ymout5_u16[32][32][32];
/* Same table, after conversion to signed results (same pointer, with different type) */
static yms16 *ymout5 = (yms16 *)ymout5_u16;
/*--------------------------------------------------------------*/
/* Other constants / macros */
/*--------------------------------------------------------------*/
/* Number of generated samples per frame (eg. 44Khz=882) */
#define SAMPLES_PER_FRAME (nAudioFrequency/nScreenRefreshRate)
/* Current sound replay freq (usually 44100 Hz) */
#define YM_REPLAY_FREQ nAudioFrequency
/* YM-2149 clock on all Atari models is 2 MHz */
#define YM_ATARI_CLOCK (MachineClocks.YM_Freq)
/* Merge/read the 3 volumes in a single integer (5 bits per volume) */
#define YM_MERGE_VOICE(C,B,A) ( (C)<<10 | (B)<<5 | A )
#define YM_MASK_1VOICE 0x1f
#define YM_MASK_A 0x1f
#define YM_MASK_B (0x1f<<5)
#define YM_MASK_C (0x1f<<10)
/* Constants for YM2149_Normalise_5bit_Table */
#define YM_OUTPUT_LEVEL 0x7fff /* amplitude of the final signal (0..65535 if centered, 0..32767 if not) */
#define YM_OUTPUT_CENTERED false
/*--------------------------------------------------------------*/
/* Variables for the YM2149 emulator (need to be saved and */
/* restored in memory snapshots) */
/*--------------------------------------------------------------*/
static ymu32 stepA , stepB , stepC;
static ymu32 posA , posB , posC;
static ymu32 mixerTA , mixerTB , mixerTC;
static ymu32 mixerNA , mixerNB , mixerNC;
static ymu32 noiseStep;
static ymu32 noisePos;
static ymu32 currentNoise;
static ymu32 RndRack; /* current random seed */
static ymu32 envStep;
static ymu32 envPos;
static int envShape;
static ymu16 EnvMask3Voices = 0; /* mask is 0x1f for voices having an active envelope */
static ymu16 Vol3Voices = 0; /* volume 0-0x1f for voices having a constant volume */
/* volume is set to 0 if voice has an envelope in EnvMask3Voices */
/* Global variables that can be changed/read from other parts of Hatari */
Uint8 SoundRegs[ 14 ];
int YmVolumeMixing = YM_TABLE_MIXING;
bool UseLowPassFilter = false;
bool bEnvelopeFreqFlag; /* Cleared each frame for YM saving */
Sint16 MixBuffer[MIXBUFFER_SIZE][2];
int nGeneratedSamples; /* Generated samples since audio buffer update */
static int ActiveSndBufIdx; /* Current working index into above mix buffer */
static int ActiveSndBufIdxAvi; /* Current working index to save an AVI audio frame */
static yms64 SamplesPerFrame_unrounded = 0; /* Number of samples for the current VBL, with simulated fractional part */
static int SamplesPerFrame; /* Number of samples to generate for the current VBL */
static int CurrentSamplesNb = 0; /* Number of samples already generated for the current VBL */
bool Sound_BufferIndexNeedReset = false;
/*--------------------------------------------------------------*/
/* Local functions prototypes */
/*--------------------------------------------------------------*/
static ymsample LowPassFilter (ymsample x0);
static ymsample PWMaliasFilter (ymsample x0);
static void interpolate_volumetable (ymu16 volumetable[32][32][32]);
static void YM2149_BuildModelVolumeTable(ymu16 volumetable[32][32][32]);
static void YM2149_BuildLinearVolumeTable(ymu16 volumetable[32][32][32]);
static void YM2149_Normalise_5bit_Table(ymu16 *in_5bit , yms16 *out_5bit, unsigned int Level, bool DoCenter);
static void YM2149_EnvBuild (void);
static void Ym2149_BuildVolumeTable (void);
static void Ym2149_Init (void);
static void Ym2149_Reset (void);
static ymu32 YM2149_RndCompute (void);
static ymu32 Ym2149_ToneStepCompute (ymu8 rHigh , ymu8 rLow);
static ymu32 Ym2149_NoiseStepCompute (ymu8 rNoise);
static ymu32 Ym2149_EnvStepCompute (ymu8 rHigh , ymu8 rLow);
static ymsample YM2149_NextSample (void);
static int Sound_SetSamplesPassed(bool FillFrame);
static void Sound_GenerateSamples(int SamplesToGenerate);
/*--------------------------------------------------------------*/
/* DC Adjuster */
/*--------------------------------------------------------------*/
/**
* 6dB/octave first order HPF fc = (1.0-0.998)*44100/(2.0*pi)
* Z pole = 0.99804 --> FS = 44100 Hz : fc=13.7 Hz (11 Hz meas)
* a = (int32_t)(32768.0*(1.0 - pole)) : a = 64 !!!
* Input range: -32768 to 32767 Maximum step: +65536 or -65472
*/
ymsample Subsonic_IIR_HPF_Left(ymsample x0)
{
static yms32 x1 = 0, y1 = 0, y0 = 0;
y1 += ((x0 - x1)<<15) - (y0<<6); /* 64*y0 */
y0 = y1>>15;
x1 = x0;
return y0;
}
ymsample Subsonic_IIR_HPF_Right(ymsample x0)
{
static yms32 x1 = 0, y1 = 0, y0 = 0;
y1 += ((x0 - x1)<<15) - (y0<<6); /* 64*y0 */
y0 = y1>>15;
x1 = x0;
return y0;
}
/*--------------------------------------------------------------*/
/* Low Pass Filter routines. */
/*--------------------------------------------------------------*/
/**
* Get coefficients for different Fs (C10 is in ST only):
* Wc = 2*M_PI*4895.1;
* Fs = 44100;
* warp = Wc/tanf((Wc/2)/Fs);
* b = Wc/(warp+Wc);
* a = (Wc-warp)/(warp+Wc);
*
* #define B_z (yms32)( 0.2667*(1<<15))
* #define A_z (yms32)(-0.4667*(1<<15))
*
* y0 = (B_z*(x0 + x1) - A_z*y0) >> 15;
* x1 = x0;
*
* The Lowpass Filter formed by C10 = 0.1 uF
* and
* R8=1k // 1k*(65119-46602)/65119 // R9=10k // R10=5.1k //
* (R12=470)*(100=Q1_HFE) = 206.865 ohms when YM2149 is High
* and
* R8=1k // R9=10k // R10=5.1k // (R12=470)*(100=Q1_HFE)
* = 759.1 ohms when YM2149 is Low
* High corner is 1/(2*pi*(0.1*10e-6)*206.865) fc = 7693.7 Hz
* Low corner is 1/(2*pi*(0.1*10e-6)*795.1) fc = 2096.6 Hz
* Notes:
* - using STF reference designators R8 R9 R10 C10 (from dec 1986 schematics)
* - using corresponding numbers from psgstrep and psgquart
* - 65119 is the largest value in Paulo's psgstrep table
* - 46602 is the largest value in Paulo's psgquart table
* - this low pass filter uses the highest cutoff frequency
* on the STf (a slightly lower frequency is reasonable).
*
* A first order lowpass filter with a high cutoff frequency
* is used when the YM2149 pulls high, and a lowpass filter
* with a low cutoff frequency is used when R8 pulls low.
*/
static ymsample LowPassFilter(ymsample x0)
{
static yms32 y0 = 0, x1 = 0;
if (x0 >= y0)
/* YM Pull up: fc = 7586.1 Hz (44.1 KHz), fc = 8257.0 Hz (48 KHz) */
y0 = (3*(x0 + x1) + (y0<<1)) >> 3;
else
/* R8 Pull down: fc = 1992.0 Hz (44.1 KHz), fc = 2168.0 Hz (48 KHz) */
y0 = ((x0 + x1) + (6*y0)) >> 3;
x1 = x0;
return y0;
}
/**
* This piecewise selective filter works by filtering the falling
* edge of a sampled pulse-wave differently from the rising edge.
*
* Piecewise selective filtering is effective because harmonics on
* one part of a wave partially define harmonics on other portions.
*
* Piecewise selective filtering can efficiently reduce aliasing
* with minimal harmonic removal.
*
* I disclose this information into the public domain so that it
* cannot be patented. May 23 2012 David Savinkoff.
*/
static ymsample PWMaliasFilter(ymsample x0)
{
static yms32 y0 = 0, x1 = 0;
if (x0 >= y0)
/* YM Pull up */
y0 = x0;
else
/* R8 Pull down */
y0 = (3*(x0 + x1) + (y0<<1)) >> 3;
x1 = x0;
return y0;
}
/*--------------------------------------------------------------*/
/* Build the volume conversion table used to simulate the */
/* behaviour of DAC used with the YM2149 in the atari ST. */
/* The final 32*32*32 table is built using a 16*16*16 table */
/* of all possible fixed volume combinations on a ST. */
/*--------------------------------------------------------------*/
static void interpolate_volumetable(ymu16 volumetable[32][32][32])
{
int i, j, k;
for (i = 1; i < 32; i += 2) { /* Copy 16 Panels to make a Block */
for (j = 1; j < 32; j += 2) { /* Copy 16 Rows to make a Panel */
for (k = 1; k < 32; k += 2) { /* Copy 16 Elements to make a Row */
volumetable[i][j][k] = volumetable_original[(i-1)/2][(j-1)/2][(k-1)/2];
}
volumetable[i][j][0] = volumetable[i][j][1]; /* Move 0th Element */
volumetable[i][j][1] = volumetable[i][j][3]; /* Move 1st Element */
/* Interpolate 3rd Element */
volumetable[i][j][3] = (ymu16)(0.5 + sqrt((double)volumetable[i][j][1] * volumetable[i][j][5]));
for (k = 2; k < 32; k += 2) /* Interpolate Even Elements */
volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][j][k-1] * volumetable[i][j][k+1]));
}
for (k = 0; k < 32; k++) {
volumetable[i][0][k] = volumetable[i][1][k]; /* Move 0th Row */
volumetable[i][1][k] = volumetable[i][3][k]; /* Move 1st Row */
/* Interpolate 3rd Row */
volumetable[i][3][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][1][k] * volumetable[i][5][k]));
}
for (j = 2; j < 32; j += 2) /* Interpolate Even Rows */
for (k = 0; k < 32; k++)
volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][j-1][k] * volumetable[i][j+1][k]));
}
for (j = 0; j < 32; j++)
for (k = 0; k < 32; k++) {
volumetable[0][j][k] = volumetable[1][j][k]; /* Move 0th Panel */
volumetable[1][j][k] = volumetable[3][j][k]; /* Move 1st Panel */
/* Interpolate 3rd Panel */
volumetable[3][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[1][j][k] * volumetable[5][j][k]));
}
for (i = 2; i < 32; i += 2) /* Interpolate Even Panels */
for (j = 0; j < 32; j++) /* Interpolate Even Panels */
for (k = 0; k < 32; k++)
volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i-1][j][k] * volumetable[i+1][j][k]));
}
/*-----------------------------------------------------------------------*/
/**
* Build a linear version of the conversion table.
* We use the mean of the 3 volumes converted to 16 bit values
* (each value of ymout1c5bit is in [0,65535])
*/
static void YM2149_BuildLinearVolumeTable(ymu16 volumetable[32][32][32])
{
int i, j, k;
for (i = 0; i < 32; i++)
for (j = 0; j < 32; j++)
for (k = 0; k < 32; k++)
volumetable[i][j][k] = (ymu16)( ((ymu32)ymout1c5bit[i] + ymout1c5bit[j] + ymout1c5bit[k]) / 3);
}
/*-----------------------------------------------------------------------*/
/**
* Build a circuit analysed version of the conversion table.
* David Savinkoff designed this algorithm by analysing data
* measured by Paulo Simoes and Benjamin Gerard.
* The numbers are arrived at by assuming a current steering
* resistor ladder network and using the voltage divider rule.
*
* If one looks at the ST schematic of the YM2149, one sees
* three sound pins tied together and attached to a 1000 ohm
* resistor (1k) that has the other end grounded.
* The 1k resistor is also in parallel with a 0.1 microfarad
* capacitor (on the Atari ST, not STE or others). The voltage
* developed across the 1K resistor is the output voltage which
* I call Vout.
*
* The output of the YM2149 is modelled well as pullup resistors.
* Thus, the three sound pins are seen as three parallel
* computer-controlled, adjustable pull-up resistors.
* To emulate the output of the YM2149, one must determine the
* resistance values of the YM2149 relative to the 1k resistor,
* which is done by the 'math model'.
*
* The AC + DC math model is:
*
* (MaxVol*WARP) / (1.0 + 1.0/(conductance_[i]+conductance_[j]+conductance_[k]))
* or
* (MaxVol*WARP) / (1.0 + 1.0/( 1/Ra +1/Rb +1/Rc )) , Ra = channel A resistance
*
* Note that the first 1.0 in the formula represents the
* normalized 1k resistor (1.0 * 1000 ohms = 1k).
*
* The YM2149 DC component model represents the output voltage
* filtered of high frequency AC component, but DC component
* remains.
* The YM2149 DC component mode treats the voltage exactly as if
* it were low pass filtered. This is more than what is required
* to make 'quartet mode sound'. Simplicity leads to Generality!
*
* The DC component model model is:
*
* (MaxVol*WARP) / (2.0 + 1.0/( 1/Ra + 1/Rb + 1/Rc))
* or
* (MaxVol*WARP*0.5) / (1.0 + 0.5/( 1/Ra + 1/Rb + 1/Rc))
*
* Note that the 1.0 represents the normalized 1k resistor.
* 0.5 represents 50% duty cycle for the parallel resistors
* being summed (this effectively doubles the pull-up resistance).
*/
static void YM2149_BuildModelVolumeTable(ymu16 volumetable[32][32][32])
{
#define MaxVol 65535.0 /* Normal Mode Maximum value in table */
#define FOURTH2 1.19 /* Fourth root of two from YM2149 */
#define WARP 1.666666666666666667 /* measured as 1.65932 from 46602 */
double conductance;
double conductance_[32];
int i, j, k;
/**
* YM2149 and R8=1k follows (2^-1/4)^(n-31) better when 2 voices are
* summed (A+B or B+C or C+A) rather than individually (A or B or C):
* conductance = 2.0/3.0/(1.0-1.0/WARP)-2.0/3.0;
* When taken into consideration with three voices.
*
* Note that the YM2149 does not use laser trimmed resistances, thus
* has offsets that are added and/or multiplied with (2^-1/4)^(n-31).
*/
conductance = 2.0/3.0/(1.0-1.0/WARP)-2.0/3.0; /* conductance = 1.0 */
/**
* Because the YM current output (voltage source with series resistances)
* is connected to a grounded resistor to develop the output voltage
* (instead of a current to voltage converter), the output transfer
* function is not linear. Thus:
* 2.0*conductance_[n] = 1.0/(1.0-1.0/FOURTH2/(1.0/conductance + 1.0))-1.0;
*/
for (i = 31; i >= 1; i--)
{
conductance_[i] = conductance/2.0;
conductance = 1.0/(1.0-1.0/FOURTH2/(1.0/conductance + 1.0))-1.0;
}
conductance_[0] = 1.0e-8; /* Avoid divide by zero */
/**
* YM2149 AC + DC components model:
* (Note that Maxvol is 65119 in Simoes' table, 65535 in Gerard's)
*
* Sum the conductances as a function of a voltage divider:
* Vout=Vin*Rout/(Rout+Rin)
*/
for (i = 0; i < 32; i++)
for (j = 0; j < 32; j++)
for (k = 0; k < 32; k++)
{
volumetable[i][j][k] = (ymu16)(0.5+(MaxVol*WARP)/(1.0 +
1.0/(conductance_[i]+conductance_[j]+conductance_[k])));
}
/**
* YM2149 DC component model:
* R8=1k (pulldown) + YM//1K (pullup) with YM 50% duty PWM
* (Note that MaxVol is 46602 in Paulo Simoes Quartet mode table)
*
* for (i = 0; i < 32; i++)
* for (j = 0; j < 32; j++)
* for (k = 0; k < 32; k++)
* {
* volumetable[i][j][k] = (ymu16)(0.5+(MaxVol*WARP)/(1.0 +
* 2.0/(conductance_[i]+conductance_[j]+conductance_[k])));
* }
*/
}
/*-----------------------------------------------------------------------*/
/**
* Normalise and optionally center the volume table used to
* convert the 3 volumes to a final signed 16 bit sample.
* This allows to adapt the amplitude/volume of the samples and
* to convert unsigned values to signed values.
* - in_5bit contains 32*32*32 unsigned values in the range
* [0,65535].
* - out_5bit will contain signed values
* Possible values are :
* Level=65535 and DoCenter=TRUE -> [-32768,32767]
* Level=32767 and DoCenter=false -> [0,32767]
* Level=16383 and DoCenter=false -> [0,16383] (to avoid overflow with DMA sound on STe)
*/
static void YM2149_Normalise_5bit_Table(ymu16 *in_5bit , yms16 *out_5bit, unsigned int Level, bool DoCenter)
{
if ( Level )
{
int h;
int Max = in_5bit[0x7fff];
int Center = (Level+1)>>1;
//fprintf ( stderr , "level %d max %d center %d\n" , Level, Max, Center );
/* Change the amplitude of the signal to 'level' : [0,max] -> [0,level] */
/* Then optionally center the signal around Level/2 */
/* This means we go from sthg like [0,65535] to [-32768, 32767] if Level=65535 and DoCenter=TRUE */
for (h=0; h<32*32*32; h++)
{
int tmp = in_5bit[h], res;
res = tmp * Level / Max;
if ( DoCenter )
res -= Center;
out_5bit[h] = res;
//fprintf ( stderr , "h %d in %d out %d\n" , h , tmp , res );
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Precompute all 16 possible envelopes.
* Each envelope is made of 3 blocks of 32 volumes.
*/
static void YM2149_EnvBuild ( void )
{
int env;
int block;
int vol=0 , inc=0;
int i;
for ( env=0 ; env<16 ; env++ ) /* 16 possible envelopes */
for ( block=0 ; block<3 ; block++ ) /* 3 blocks to define an envelope */
{
switch ( YmEnvDef[ env ][ block ] )
{
case ENV_GODOWN : vol=31 ; inc=-1 ; break;
case ENV_GOUP : vol=0 ; inc=1 ; break;
case ENV_DOWN : vol=0 ; inc=0 ; break;
case ENV_UP : vol=31 ; inc=0 ; break;
}
for ( i=0 ; i<32 ; i++ ) /* 32 volumes per block */
{
YmEnvWaves[ env ][ block*32 + i ] = YM_MERGE_VOICE ( vol , vol , vol );
vol += inc;
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Depending on the YM mixing method, build the table used to convert
* the 3 YM volumes into a single sample.
*/
static void Ym2149_BuildVolumeTable(void)
{
/* Depending on the volume mixing method, we use a table based on real measures */
/* or a table based on a linear volume mixing. */
if ( YmVolumeMixing == YM_MODEL_MIXING )
YM2149_BuildModelVolumeTable(ymout5_u16); /* create 32*32*32 circuit analysed model of the volume table */
else if ( YmVolumeMixing == YM_TABLE_MIXING )
interpolate_volumetable(ymout5_u16); /* expand the 16*16*16 values in volumetable_original to 32*32*32 */
else
YM2149_BuildLinearVolumeTable(ymout5_u16); /* combine the 32 possible volumes */
/* Normalise/center the values (convert from u16 to s16) */
/* On STE/TT, we use YM_OUTPUT_LEVEL>>1 to avoid overflow with DMA sound */
if ( (ConfigureParams.System.nMachineType == MACHINE_STE) || (ConfigureParams.System.nMachineType == MACHINE_MEGA_STE)
|| (ConfigureParams.System.nMachineType == MACHINE_TT) )
YM2149_Normalise_5bit_Table ( ymout5_u16[0][0] , ymout5 , (YM_OUTPUT_LEVEL>>1) , YM_OUTPUT_CENTERED );
else
YM2149_Normalise_5bit_Table ( ymout5_u16[0][0] , ymout5 , YM_OUTPUT_LEVEL , YM_OUTPUT_CENTERED );
}
/*-----------------------------------------------------------------------*/
/**
* Init some internal tables for faster results (env, volume)
* and reset the internal states.
*/
static void Ym2149_Init(void)
{
/* Build the 16 envelope shapes */
YM2149_EnvBuild();
/* Build the volume conversion table */
Ym2149_BuildVolumeTable();
/* Reset YM2149 internal states */
Ym2149_Reset();
}
/*-----------------------------------------------------------------------*/
/**
* Reset all ym registers as well as the internal variables
*/
static void Ym2149_Reset(void)
{
int i;
for ( i=0 ; i<14 ; i++ )
Sound_WriteReg ( i , 0 );
Sound_WriteReg ( 7 , 0xff );
posA = 0;
posB = 0;
posC = 0;
currentNoise = 0xffff;
RndRack = 1;
envShape = 0;
envPos = 0;
}
/*-----------------------------------------------------------------------*/
/**
* Returns a pseudo random value, used to generate white noise.
* As measured by David Savinkoff, the YM2149 uses a 17 stage LSFR with
* 2 taps (17,14)
*/
static ymu32 YM2149_RndCompute(void)
{
/* 17 stage, 2 taps (17, 14) LFSR */
if (RndRack & 1)
{
RndRack = RndRack>>1 ^ 0x12000; /* bits 17 and 14 are ones */
return 0xffff;
}
else
{ RndRack >>= 1;
return 0;
}
}
/*-----------------------------------------------------------------------*/
/**
* Compute tone's step based on the input period.
* Although for tone we should have the same result when per==0 and per==1,
* this gives some very sharp and unpleasant sounds in the emulation.
* To get a better sound, we consider all per<=5 to give step=0, which will
* produce a constant output at value '1'. This should be handled with some
* proper filters to remove high frequencies as on a real ST (where per<=9
* gives nearly no audible sound).
* A common replay freq of 44.1 kHz will also not be high enough to correctly
* render possible tone's freq of 125 or 62.5 kHz (when per==1 or per==2)
*/
#define NEWSTEP
#ifndef NEWSTEP
static ymu32 Ym2149_ToneStepCompute(ymu8 rHigh , ymu8 rLow)
{
int per;
yms64 step;
per = rHigh&15;
per = (per<<8)+rLow;
if (per <= (int)(YM_ATARI_CLOCK/(YM_REPLAY_FREQ*7)) )
return 0;
step = YM_ATARI_CLOCK;
step <<= (15+16-3);
step /= (per * YM_REPLAY_FREQ);
return step;
}
#else
static ymu32 Ym2149_ToneStepCompute(ymu8 rHigh , ymu8 rLow)
{
int per;
yms64 step;
per = rHigh&15;
per = (per<<8)+rLow;
#if 0 /* need some high freq filters for this to work correctly */
if ( per == 0 )
per = 1; /* result for Per=0 is the same as for Per=1 */
#else
if (per <= (int)(YM_ATARI_CLOCK/(YM_REPLAY_FREQ*7)) )
return 0; /* discard frequencies higher than 80% of nyquist rate. */
#endif
step = YM_ATARI_CLOCK;
step <<= 24;
step /= (per * 8 * YM_REPLAY_FREQ); /* 0x5ab9 < step < 0x5ab3f46 at 44.1 kHz */
return step;
}
#endif
/*-----------------------------------------------------------------------*/
/**
* Compute noise's step based on the input period.
* On a real STF, we get the same result when per==0 and per==1.
* A common replay freq of 44.1 kHz will not be high enough to correctly
* render possible noise's freq of 125 or 62.5 kHz (when per==1 or per==2).
* With a random wave such as noise, this means that with a replay freq
* of 44.1 kHz, per==1 and per==2 (as well as per==3) will sound the same :
* per==1 step=0x2d59fa3 freq=125 kHz
* per==2 step=0x16acfd1 freq=62.5 kHz
* per==3 step=0x0f1dfe1 freq=41.7 kHz
*/
#ifndef NEWSTEP
static ymu32 Ym2149_NoiseStepCompute(ymu8 rNoise)
{
int per;
yms64 step;
per = (rNoise&0x1f);
if (per<3)
return 0;
step = YM_ATARI_CLOCK;
step <<= (16-1-3);
step /= (per * YM_REPLAY_FREQ);
return step;
}
#else
static ymu32 Ym2149_NoiseStepCompute(ymu8 rNoise)
{
int per;
yms64 step;
per = (rNoise&0x1f);
if ( per == 0 )
per = 1; /* result for Per=0 is the same as for Per=1 */
step = YM_ATARI_CLOCK;
step <<= 24;
step /= (per * 16 * YM_REPLAY_FREQ); /* 0x17683f < step < 0x2d59fa3 at 44.1 kHz */
return step;
}
#endif
/*-----------------------------------------------------------------------*/
/**
* Compute envelope's step. The envelope is made of different patterns
* of 32 volumes. In each pattern, the volume is changed at frequency
* Fe = MasterClock / ( 8 * EnvPer ).
* In our case, we use a lower replay freq ; between 2 consecutive calls
* to envelope's generation, the internal counter will advance 'step'
* units, where step = MasterClock / ( 8 * EnvPer * YM_REPLAY_FREQ )
* As 'step' requires floating point to be stored, we use left shifting
* to multiply 'step' by a fixed amount. All operations are made with
* shifted values ; to get the final value, we must right shift the
* result. We use '<<24', which gives 8 bits for the integer part, and
* the equivalent of 24 bits for the fractional part.
* Since we're using large numbers, we temporarily use 64 bits integer
* to avoid overflow and keep largest precision possible.
* On a real STF, we get the same result when per==0 and per==1.
*/
static ymu32 Ym2149_EnvStepCompute(ymu8 rHigh , ymu8 rLow)
{
yms64 per;
yms64 step;
per = rHigh;
per = (per<<8)+rLow;
step = YM_ATARI_CLOCK;
step <<= 24;
if ( per == 0 )
per = 1; /* result for Per=0 is the same as for Per=1 */
step /= (8 * per * YM_REPLAY_FREQ); /* 0x5ab < step < 0x5ab3f46 at 44.1 kHz */
return step;
}
/*-----------------------------------------------------------------------*/
/**
* Main function : compute the value of the next sample.
* Mixes all 3 voices with tone+noise+env and apply low pass
* filter if needed.
* All operations are done with integer math, using <<24 to simulate
* floating point precision : upper 8 bits are the integer part, lower 24
* are the fractional part.
* Tone is a square wave with 2 states 0 or 1. If integer part of posX is
* even (bit24=0) we consider output is 0, else (bit24=1) we consider
* output is 1. This gives the value of bt for one voice after extending it
* to all 0 bits or all 1 bits using a '-'
*/
#ifndef NEWSTEP
static ymsample YM2149_NextSample(void)
{
ymsample sample;
int bt;
ymu32 bn;
ymu16 Env3Voices;
ymu16 Tone3Voices;
/* Noise value : 0 or 0xffff */
if ( noisePos&0xffff0000 )
{
currentNoise = YM2149_RndCompute();
noisePos &= 0xffff;
}
bn = currentNoise; /* 0 or 0xffff */
/* Get the 5 bits volume corresponding to the current envelope's position */
Env3Voices = YmEnvWaves[ envShape ][ envPos>>24 ]; /* integer part of envPos is in bits 24-31 */
Env3Voices &= EnvMask3Voices; /* only keep volumes for voices using envelope */
//fprintf ( stderr , "env %x %x %x\n" , Env3Voices , envStep , envPos );
/* Tone3Voices will contain the output state of each voice : 0 or 0x1f */
bt = ((((yms32)posA)>>31) | mixerTA) & (bn | mixerNA); /* 0 or 0xffff */
Tone3Voices = bt & YM_MASK_1VOICE; /* 0 or 0x1f */
bt = ((((yms32)posB)>>31) | mixerTB) & (bn | mixerNB);
Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 5;
bt = ((((yms32)posC)>>31) | mixerTC) & (bn | mixerNC);
Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 10;
/* Combine fixed volumes and envelope volumes and keep the resulting */
/* volumes depending on the output state of each voice (0 or 0x1f) */
Tone3Voices &= ( Env3Voices | Vol3Voices );
/* When a step period is 0, the represented frequency was filtered from the */
/* ouput of the YM2149. Thus, use the transient DC component of the sample. */
/* Note that the "-1" table offset is a "good fit" for the DC component. */
if (stepA == 0 && (Tone3Voices & YM_MASK_A) > 1)
Tone3Voices -= 1; /* Voice A AC component removed; Transient DC component remains */
if (stepB == 0 && (Tone3Voices & YM_MASK_B) > 1<<5)
Tone3Voices -= 1<<5; /* Voice B AC component removed; Transient DC component remains */
if (stepC == 0 && (Tone3Voices & YM_MASK_C) > 1<<10)
Tone3Voices -= 1<<10; /* Voice C AC component removed; Transient DC component remains */
/* D/A conversion of the 3 volumes into a sample using a precomputed conversion table */
sample = ymout5[ Tone3Voices ]; /* 16 bits signed value */
/* Increment positions */
posA += stepA;
posB += stepB;
posC += stepC;
noisePos += noiseStep;
envPos += envStep;
if ( envPos >= (3*32) << 24 ) /* blocks 0, 1 and 2 were used (envPos 0 to 95) */
envPos -= (2*32) << 24; /* replay/loop blocks 1 and 2 (envPos 32 to 95) */
/* Apply low pass filter ? */
if ( UseLowPassFilter )
return LowPassFilter(sample);
else
return PWMaliasFilter(sample);
}
#else
static ymsample YM2149_NextSample(void)
{
ymsample sample;
ymu32 bt;
ymu32 bn;
ymu16 Env3Voices; /* 0x00CCBBAA */
ymu16 Tone3Voices; /* 0x00CCBBAA */
/* Noise value : 0 or 0xffff */
if ( noisePos&0xff000000 ) /* integer part > 0 */
{
currentNoise = YM2149_RndCompute();
noisePos &= 0xffffff; /* keep fractional part of noisePos */
}
bn = currentNoise; /* 0 or 0xffff */
/* Get the 5 bits volume corresponding to the current envelope's position */
Env3Voices = YmEnvWaves[ envShape ][ envPos>>24 ]; /* integer part of envPos is in bits 24-31 */
Env3Voices &= EnvMask3Voices; /* only keep volumes for voices using envelope */
//fprintf ( stderr , "env %x %x %x\n" , Env3Voices , envStep , envPos );
/* Tone3Voices will contain the output state of each voice : 0 or 0x1f */
bt = -( (posA>>24) & 1); /* 0 if bit24=0 or 0xffffffff if bit24=1 */
bt = (bt | mixerTA) & (bn | mixerNA); /* 0 or 0xffff */
Tone3Voices = bt & YM_MASK_1VOICE; /* 0 or 0x1f */
bt = -( (posB>>24) & 1);
bt = (bt | mixerTB) & (bn | mixerNB);
Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 5;
bt = -( (posC>>24) & 1);
bt = (bt | mixerTC) & (bn | mixerNC);
Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 10;
/* Combine fixed volumes and envelope volumes and keep the resulting */
/* volumes depending on the output state of each voice (0 or 0x1f) */
Tone3Voices &= ( Env3Voices | Vol3Voices );
/* D/A conversion of the 3 volumes into a sample using a precomputed conversion table */
if (stepA == 0 && (Tone3Voices & YM_MASK_A) > 1)
Tone3Voices -= 1; /* Voice A AC component removed; Transient DC component remains */
if (stepB == 0 && (Tone3Voices & YM_MASK_B) > 1<<5)
Tone3Voices -= 1<<5; /* Voice B AC component removed; Transient DC component remains */
if (stepC == 0 && (Tone3Voices & YM_MASK_C) > 1<<10)
Tone3Voices -= 1<<10; /* Voice C AC component removed; Transient DC component remains */
sample = ymout5[ Tone3Voices ]; /* 16 bits signed value */
/* Increment positions */
posA += stepA;
posB += stepB;
posC += stepC;
noisePos += noiseStep;
envPos += envStep;
if ( envPos >= (3*32) << 24 ) /* blocks 0, 1 and 2 were used (envPos 0 to 95) */
envPos -= (2*32) << 24; /* replay/loop blocks 1 and 2 (envPos 32 to 95) */
/* Apply low pass filter ? */
if ( UseLowPassFilter )
return LowPassFilter(sample);
else
return PWMaliasFilter(sample);
}
#endif
/*-----------------------------------------------------------------------*/
/**
* Update internal variables (steps, volume masks, ...) each
* time an YM register is changed.
*/
#ifndef NEWSTEP
#define BIT_SHIFT 31
#else
#define BIT_SHIFT 24
#endif
void Sound_WriteReg( int reg , Uint8 data )
{
switch (reg)
{
case 0:
SoundRegs[0] = data;
stepA = Ym2149_ToneStepCompute ( SoundRegs[1] , SoundRegs[0] );
if (!stepA) posA = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 1:
SoundRegs[1] = data & 0x0f;
stepA = Ym2149_ToneStepCompute ( SoundRegs[1] , SoundRegs[0] );
if (!stepA) posA = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 2:
SoundRegs[2] = data;
stepB = Ym2149_ToneStepCompute ( SoundRegs[3] , SoundRegs[2] );
if (!stepB) posB = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 3:
SoundRegs[3] = data & 0x0f;
stepB = Ym2149_ToneStepCompute ( SoundRegs[3] , SoundRegs[2] );
if (!stepB) posB = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 4:
SoundRegs[4] = data;
stepC = Ym2149_ToneStepCompute ( SoundRegs[5] , SoundRegs[4] );
if (!stepC) posC = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 5:
SoundRegs[5] = data & 0x0f;
stepC = Ym2149_ToneStepCompute ( SoundRegs[5] , SoundRegs[4] );
if (!stepC) posC = 1u<<BIT_SHIFT; // Assume output always 1 if 0 period (for Digi-sample)
break;
case 6:
SoundRegs[6] = data & 0x1f;
noiseStep = Ym2149_NoiseStepCompute ( SoundRegs[6] );
if (!noiseStep)
{
noisePos = 0;
currentNoise = 0xffff;
}
break;
case 7:
SoundRegs[7] = data & 0x3f; /* ignore bits 6 and 7 */
mixerTA = (data&(1<<0)) ? 0xffff : 0;
mixerTB = (data&(1<<1)) ? 0xffff : 0;
mixerTC = (data&(1<<2)) ? 0xffff : 0;
mixerNA = (data&(1<<3)) ? 0xffff : 0;
mixerNB = (data&(1<<4)) ? 0xffff : 0;
mixerNC = (data&(1<<5)) ? 0xffff : 0;
break;
case 8:
SoundRegs[8] = data & 0x1f;
if ( data & 0x10 )
{
EnvMask3Voices |= YM_MASK_A; /* env ON */
Vol3Voices &= ~YM_MASK_A; /* fixed vol OFF */
}
else
{
EnvMask3Voices &= ~YM_MASK_A; /* env OFF */
Vol3Voices &= ~YM_MASK_A; /* clear previous vol */
Vol3Voices |= YmVolume4to5[ SoundRegs[8] ]; /* fixed vol ON */
}
break;
case 9:
SoundRegs[9] = data & 0x1f;
if ( data & 0x10 )
{
EnvMask3Voices |= YM_MASK_B; /* env ON */
Vol3Voices &= ~YM_MASK_B; /* fixed vol OFF */
}
else
{
EnvMask3Voices &= ~YM_MASK_B; /* env OFF */
Vol3Voices &= ~YM_MASK_B; /* clear previous vol */
Vol3Voices |= ( YmVolume4to5[ SoundRegs[9] ] ) << 5; /* fixed vol ON */
}
break;
case 10:
SoundRegs[10] = data & 0x1f;
if ( data & 0x10 )
{
EnvMask3Voices |= YM_MASK_C; /* env ON */
Vol3Voices &= ~YM_MASK_C; /* fixed vol OFF */
}
else
{
EnvMask3Voices &= ~YM_MASK_C; /* env OFF */
Vol3Voices &= ~YM_MASK_C; /* clear previous vol */
Vol3Voices |= ( YmVolume4to5[ SoundRegs[10] ] ) << 10; /* fixed vol ON */
}
break;
case 11:
SoundRegs[11] = data;
envStep = Ym2149_EnvStepCompute ( SoundRegs[12] , SoundRegs[11] );
break;
case 12:
SoundRegs[12] = data;
envStep = Ym2149_EnvStepCompute ( SoundRegs[12] , SoundRegs[11] );
break;
case 13:
SoundRegs[13] = data & 0xf;
envPos = 0; /* when writing to EnvShape, we must reset the EnvPos */
envShape = SoundRegs[13];
bEnvelopeFreqFlag = true; /* used for YmFormat saving */
break;
}
}
/*-----------------------------------------------------------------------*/
/**
* Init random generator, sound tables and envelopes
* (called only once when Hatari starts)
*/
void Sound_Init(void)
{
/* Build volume/env tables, ... */
Ym2149_Init();
Sound_Reset();
}
/*-----------------------------------------------------------------------*/
/**
* Reset the sound emulation (called from Reset_ST() in reset.c)
*/
void Sound_Reset(void)
{
/* Lock audio system before accessing variables which are used by the
* callback function, too! */
Audio_Lock();
/* Clear sound mixing buffer: */
memset(MixBuffer, 0, sizeof(MixBuffer));
/* Clear cycle counts, buffer index and register '13' flags */
Cycles_SetCounter(CYCLES_COUNTER_SOUND, 0);
bEnvelopeFreqFlag = false;
CompleteSndBufIdx = 0;
/* We do not start with 0 here to fake some initial samples: */
nGeneratedSamples = SoundBufferSize + SAMPLES_PER_FRAME;
ActiveSndBufIdx = nGeneratedSamples % MIXBUFFER_SIZE;
SamplesPerFrame = SAMPLES_PER_FRAME;
CurrentSamplesNb = 0;
ActiveSndBufIdxAvi = ActiveSndBufIdx;
//fprintf ( stderr , "Sound_Reset SoundBufferSize %d SAMPLES_PER_FRAME %d nGeneratedSamples %d , ActiveSndBufIdx %d\n" ,
// SoundBufferSize , SAMPLES_PER_FRAME, nGeneratedSamples , ActiveSndBufIdx );
Ym2149_Reset();
Audio_Unlock();
}
/*-----------------------------------------------------------------------*/
/**
* Reset the sound buffer index variables.
* Very important : this function should only be called by setting
* Sound_BufferIndexNeedReset=true ; sound buffer index should be reset
* only after the sound for the whole VBL was updated (CurrentSamplesNb returns to 0)
* else it will alter the value of DMA Frame Count ($ff8909/0b/0d) and
* could cause crashes in some programs.
*/
void Sound_ResetBufferIndex(void)
{
Audio_Lock();
nGeneratedSamples = SoundBufferSize + SAMPLES_PER_FRAME;
ActiveSndBufIdx = (CompleteSndBufIdx + nGeneratedSamples) % MIXBUFFER_SIZE;
SamplesPerFrame = SAMPLES_PER_FRAME;
CurrentSamplesNb = 0;
ActiveSndBufIdxAvi = ActiveSndBufIdx;
//fprintf ( stderr , "Sound_ResetBufferIndex SoundBufferSize %d SAMPLES_PER_FRAME %d nGeneratedSamples %d , ActiveSndBufIdx %d\n" ,
// SoundBufferSize , SAMPLES_PER_FRAME, nGeneratedSamples , ActiveSndBufIdx );
Audio_Unlock();
}
/*-----------------------------------------------------------------------*/
/**
* Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
*/
void Sound_MemorySnapShot_Capture(bool bSave)
{
/* Save/Restore details */
MemorySnapShot_Store(&stepA, sizeof(stepA));
MemorySnapShot_Store(&stepB, sizeof(stepB));
MemorySnapShot_Store(&stepC, sizeof(stepC));
MemorySnapShot_Store(&posA, sizeof(posA));
MemorySnapShot_Store(&posB, sizeof(posB));
MemorySnapShot_Store(&posC, sizeof(posC));
MemorySnapShot_Store(&mixerTA, sizeof(mixerTA));
MemorySnapShot_Store(&mixerTB, sizeof(mixerTB));
MemorySnapShot_Store(&mixerTC, sizeof(mixerTC));
MemorySnapShot_Store(&mixerNA, sizeof(mixerNA));
MemorySnapShot_Store(&mixerNB, sizeof(mixerNB));
MemorySnapShot_Store(&mixerNC, sizeof(mixerNC));
MemorySnapShot_Store(&noiseStep, sizeof(noiseStep));
MemorySnapShot_Store(&noisePos, sizeof(noisePos));
MemorySnapShot_Store(&currentNoise, sizeof(currentNoise));
MemorySnapShot_Store(&RndRack, sizeof(RndRack));
MemorySnapShot_Store(&envStep, sizeof(envStep));
MemorySnapShot_Store(&envPos, sizeof(envPos));
MemorySnapShot_Store(&envShape, sizeof(envShape));
MemorySnapShot_Store(&EnvMask3Voices, sizeof(EnvMask3Voices));
MemorySnapShot_Store(&Vol3Voices, sizeof(Vol3Voices));
MemorySnapShot_Store(SoundRegs, sizeof(SoundRegs));
// MemorySnapShot_Store(&YmVolumeMixing, sizeof(YmVolumeMixing));
// MemorySnapShot_Store(&UseLowPassFilter, sizeof(UseLowPassFilter));
}
/*-----------------------------------------------------------------------*/
/**
* Find how many samples to generate and store in 'nSamplesToGenerate'
* Also update sound cycles counter to store how many we actually did
* so generates set amount each frame.
* If FillFrame is true, this means we reach the end of the VBL and me must
* add as many samples as necessary to get a total of SamplesPerFrame
* for this VBL.
*/
static int Sound_SetSamplesPassed(bool FillFrame)
{
int nSoundCycles;
int SamplesToGenerate; /* How many samples are needed for this time-frame */
nSoundCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);
/* example : 160256 cycles per VBL, 44Khz = 882 samples per VBL at 50 Hz */
/* 882/160256 samples per cpu clock cycle */
/* Total number of samples that we should have at this point of the VBL */
SamplesToGenerate = nSoundCycles * SamplesPerFrame
/ ClocksTimings_GetCyclesPerVBL ( ConfigureParams.System.nMachineType , nScreenRefreshRate );
//if (SamplesToGenerate > SamplesPerFrame )
//fprintf ( stderr , "over run %d %d\n" , SamplesPerFrame , SamplesToGenerate );
if (SamplesToGenerate > SamplesPerFrame)
SamplesToGenerate = SamplesPerFrame;
SamplesToGenerate -= CurrentSamplesNb; /* don't count samples that were already generated up to now */
if ( SamplesToGenerate < 0 )
SamplesToGenerate = 0;
/* If we're called from the VBL interrupt (FillFrame==true), we must ensure we have */
/* an exact total of SamplesPerFrame samples during a full VBL (we take into account */
/* the samples that were already generated during this VBL) */
if ( FillFrame )
{
SamplesToGenerate = SamplesPerFrame - CurrentSamplesNb; /* how many samples are missing to reach SamplesPerFrame */
if ( SamplesToGenerate < 0 )
SamplesToGenerate = 0;
}
/* Check we don't fill the sound's ring buffer before it's played by Audio_Callback() */
/* This should never happen, except if the system suffers major slowdown due to other */
/* processes or if we run in fast forward mode. */
/* In the case of slowdown, we set Sound_BufferIndexNeedReset to "resync" the working */
/* buffer's index ActiveSndBufIdx with the system buffer's index CompleteSndBufIdx. */
/* In the case of fast forward, we do nothing here, Sound_BufferIndexNeedReset will be */
/* set when the user exits fast forward mode. */
if ( ( SamplesToGenerate > MIXBUFFER_SIZE - nGeneratedSamples ) && ( ConfigureParams.System.bFastForward == false )
&& ( ConfigureParams.Sound.bEnableSound == true ) )
{
Log_Printf ( LOG_WARN , "Your system is too slow, some sound samples were not correctly emulated\n" );
Sound_BufferIndexNeedReset = true;
}
//fprintf ( stderr , "vbl %d hbl %d samp_gen %d / %d frac %lx\n" , nVBLs , nHBL , SamplesToGenerate , SamplesPerFrame , (long int)SamplesPerFrame_unrounded );
return SamplesToGenerate;
}
/*-----------------------------------------------------------------------*/
/**
* Generate samples for all channels during this time-frame
*/
static void Sound_GenerateSamples(int SamplesToGenerate)
{
int i;
int idx;
if (SamplesToGenerate <= 0)
return;
if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
{
for (i = 0; i < SamplesToGenerate; i++)
{
idx = (ActiveSndBufIdx + i) % MIXBUFFER_SIZE;
MixBuffer[idx][0] = MixBuffer[idx][1] = Subsonic_IIR_HPF_Left( YM2149_NextSample() );
}
/* If Falcon emulation, crossbar does the job */
Crossbar_GenerateSamples(ActiveSndBufIdx, SamplesToGenerate);
}
else if (ConfigureParams.System.nMachineType != MACHINE_ST)
{
for (i = 0; i < SamplesToGenerate; i++)
{
idx = (ActiveSndBufIdx + i) % MIXBUFFER_SIZE;
MixBuffer[idx][0] = MixBuffer[idx][1] = YM2149_NextSample();
}
/* If Ste or TT emulation, DmaSnd does mixing and filtering */
DmaSnd_GenerateSamples(ActiveSndBufIdx, SamplesToGenerate);
}
else if (ConfigureParams.System.nMachineType == MACHINE_ST)
{
for (i = 0; i < SamplesToGenerate; i++)
{
idx = (ActiveSndBufIdx + i) % MIXBUFFER_SIZE;
MixBuffer[idx][0] = MixBuffer[idx][1] = Subsonic_IIR_HPF_Left( YM2149_NextSample() );
}
}
ActiveSndBufIdx = (ActiveSndBufIdx + SamplesToGenerate) % MIXBUFFER_SIZE;
nGeneratedSamples += SamplesToGenerate;
CurrentSamplesNb += SamplesToGenerate; /* number of samples generated for current VBL */
}
/*-----------------------------------------------------------------------*/
/**
* This is called to built samples up until this clock cycle
* Sound_Update can be called several times during a VBL ; we must ensure
* that we generate exactly SamplesPerFrame samples between 2 calls
* to Sound_Update_VBL.
*/
void Sound_Update(bool FillFrame)
{
int OldSndBufIdx = ActiveSndBufIdx;
int SamplesToGenerate;
/* Make sure that we don't interfere with the audio callback function */
Audio_Lock();
/* Find how many samples to generate */
SamplesToGenerate = Sound_SetSamplesPassed( FillFrame );
/* And generate */
Sound_GenerateSamples( SamplesToGenerate );
/* Allow audio callback function to occur again */
Audio_Unlock();
/* Save to WAV file, if open */
if (bRecordingWav)
WAVFormat_Update(MixBuffer, OldSndBufIdx, SamplesToGenerate);
}
/*-----------------------------------------------------------------------*/
/**
* On the end of each VBL, complete audio buffer to reach SamplesPerFrame samples.
* As Sound_Update(false) could be called several times during the VBL, the audio
* buffer might be already partially filled.
* We must first complete the buffer using the same value of SamplesPerFrame
* by calling Sound_Update(true) ; then we can compute a new value for
* SamplesPerFrame that will be used for the next VBL to come.
*/
void Sound_Update_VBL(void)
{
Sound_Update(true); /* generate as many samples as needed to fill this VBL */
//fprintf ( stderr , "vbl done %d %d\n" , SamplesPerFrame , CurrentSamplesNb );
CurrentSamplesNb = 0; /* VBL is complete, reset counter for next VBL */
/*Compute a fractional equivalent of SamplesPerFrame for the next VBL, to avoid rounding propagation */
SamplesPerFrame_unrounded += (yms64) ClocksTimings_GetSamplesPerVBL ( ConfigureParams.System.nMachineType ,
nScreenRefreshRate , nAudioFrequency );
SamplesPerFrame = SamplesPerFrame_unrounded >> 28; /* use integer part */
SamplesPerFrame_unrounded &= 0x0fffffff; /* keep fractional part in the lower 28 bits */
/* Reset sound buffer if needed (after pause, fast forward, slow system, ...) */
if ( Sound_BufferIndexNeedReset )
{
Sound_ResetBufferIndex ();
Sound_BufferIndexNeedReset = false;
}
/* Record AVI audio frame is necessary */
if ( bRecordingAvi )
{
int Len;
Len = ActiveSndBufIdx - ActiveSndBufIdxAvi; /* number of generated samples for this frame */
if ( Len < 0 )
Len += MIXBUFFER_SIZE; /* end of ring buffer was reached */
Avi_RecordAudioStream ( MixBuffer , ActiveSndBufIdxAvi , Len );
}
ActiveSndBufIdxAvi = ActiveSndBufIdx; /* save new position for next AVI audio frame */
/* Clear write to register '13', used for YM file saving */
bEnvelopeFreqFlag = false;
}
/*-----------------------------------------------------------------------*/
/**
* Start recording sound, as .YM or .WAV output
*/
bool Sound_BeginRecording(char *pszCaptureFileName)
{
bool bRet;
if (!pszCaptureFileName || strlen(pszCaptureFileName) <= 3)
{
Log_Printf(LOG_ERROR, "Illegal sound recording file name!\n");
return false;
}
/* Did specify .YM or .WAV? If neither report error */
if (File_DoesFileExtensionMatch(pszCaptureFileName,".ym"))
bRet = YMFormat_BeginRecording(pszCaptureFileName);
else if (File_DoesFileExtensionMatch(pszCaptureFileName,".wav"))
bRet = WAVFormat_OpenFile(pszCaptureFileName);
else
{
Log_AlertDlg(LOG_ERROR, "Unknown Sound Recording format.\n"
"Please specify a .YM or .WAV output file.");
bRet = false;
}
return bRet;
}
/*-----------------------------------------------------------------------*/
/**
* End sound recording
*/
void Sound_EndRecording(void)
{
/* Stop sound recording and close files */
if (bRecordingYM)
YMFormat_EndRecording();
if (bRecordingWav)
WAVFormat_CloseFile();
}
/*-----------------------------------------------------------------------*/
/**
* Are we recording sound data?
*/
bool Sound_AreWeRecording(void)
{
return (bRecordingYM || bRecordingWav);
}
/*-----------------------------------------------------------------------*/
/**
* Rebuild volume conversion table
*/
void Sound_SetYmVolumeMixing(void)
{
/* Build the volume conversion table */
Ym2149_BuildVolumeTable();
}