mirror of
https://github.com/libretro/fmsx-libretro.git
synced 2026-11-07 10:36:03 -10:00
422 lines
14 KiB
C
422 lines
14 KiB
C
/** EMULib Emulation Library *********************************/
|
|
/** **/
|
|
/** Sound.c **/
|
|
/** **/
|
|
/** This file file implements core part of the sound API. **/
|
|
/** See Sound.h for declarations. **/
|
|
/** **/
|
|
/** Copyright (C) Marat Fayzullin 1996-2021 **/
|
|
/** You are not allowed to distribute this software **/
|
|
/** commercially. Please, notify me, if you make any **/
|
|
/** changes to this file. **/
|
|
/*************************************************************/
|
|
#include "Sound.h"
|
|
#include "MSX.h"
|
|
|
|
#include "../NukeYKT/WrapNukeYKT.h"
|
|
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <direct.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
typedef unsigned char byte;
|
|
typedef unsigned short word;
|
|
|
|
static struct
|
|
{
|
|
int Type; /* Channel type (SND_*) */
|
|
int Freq; /* Channel frequency (Hz) */
|
|
int Volume; /* Channel volume (0..255) */
|
|
|
|
const signed char *Data; /* Wave data (-128..127 each) */
|
|
int Length; /* Wave length in Data */
|
|
int Rate; /* Wave playback rate (or 0Hz) */
|
|
int Pos; /* Wave current position in Data */
|
|
|
|
int Count; /* Phase counter */
|
|
} WaveCH[SND_CHANNELS] =
|
|
{
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 },
|
|
{ SND_MELODIC,0,0,0,0,0,0,0 }
|
|
};
|
|
|
|
/** RenderAudio() Variables *******************************************/
|
|
static int SndRate = 0; /* Sound rate (0=Off) */
|
|
static int NoiseGen = 0x10000; /* Noise generator seed */
|
|
static int NoiseOut = 16; /* NoiseGen bit used for output */
|
|
static int NoiseXor = 14; /* NoiseGen bit used for XORing */
|
|
int MasterSwitch = 0xFFFF; /* Switches to turn channels on/off */
|
|
int MasterVolume = 192; /* Master volume */
|
|
|
|
extern YM2413_NukeYKT OPLL_NukeYKT;
|
|
extern YM2413 OPLL;
|
|
|
|
/** Sound() **************************************************/
|
|
/** Generate sound of given frequency (Hz) and volume **/
|
|
/** (0..255) via given channel. Setting Freq=0 or Volume=0 **/
|
|
/** turns sound off. **/
|
|
/*************************************************************/
|
|
void Sound(int Channel,int Freq,int Volume)
|
|
{
|
|
/* All parameters have to be valid */
|
|
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
|
|
Freq = Freq<0? 0:Freq;
|
|
Volume = Volume<0? 0:Volume>255? 255:Volume;
|
|
|
|
/* Modify channel parameters */
|
|
WaveCH[Channel].Volume = Volume;
|
|
WaveCH[Channel].Freq = Freq;
|
|
|
|
/* When disabling sound, reset waveform */
|
|
if(!Freq||!Volume)
|
|
{
|
|
WaveCH[Channel].Pos = 0;
|
|
WaveCH[Channel].Count = 0;
|
|
}
|
|
}
|
|
|
|
/** SetSound() ***********************************************/
|
|
/** Set sound type at a given channel. **/
|
|
/*************************************************************/
|
|
void SetSound(int Channel,int Type)
|
|
{
|
|
/* Channel has to be valid */
|
|
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
|
|
|
|
/* Set wave channel type */
|
|
WaveCH[Channel].Type = Type;
|
|
}
|
|
|
|
/** SetChannels() ********************************************/
|
|
/** Set master volume (0..255) and switch channels on/off. **/
|
|
/** Each channel N has corresponding bit 2^N in Switch. Set **/
|
|
/** or reset this bit to turn the channel on or off. **/
|
|
/*************************************************************/
|
|
void SetChannels(int Volume,int Switch)
|
|
{
|
|
/* Volume has to be valid */
|
|
Volume = Volume<0? 0:Volume>255? 255:Volume;
|
|
|
|
/* Modify wave master settings */
|
|
MasterVolume = Volume;
|
|
MasterSwitch = Switch&((1<<SND_CHANNELS)-1);
|
|
}
|
|
|
|
/** SetNoise() ***********************************************/
|
|
/** Initialize random noise generator to the given Seed and **/
|
|
/** then take random output from OUTBit and XOR it with **/
|
|
/** XORBit. **/
|
|
/*************************************************************/
|
|
void SetNoise(int Seed,int OUTBit,int XORBit)
|
|
{
|
|
NoiseGen = Seed;
|
|
NoiseOut = OUTBit;
|
|
NoiseXor = XORBit;
|
|
}
|
|
|
|
/** SetWave() ************************************************/
|
|
/** Set waveform for a given channel. The channel will be **/
|
|
/** marked with sound type SND_WAVE. Set Rate=0 if you want **/
|
|
/** waveform to be an instrument or set it to the waveform **/
|
|
/** own playback rate. **/
|
|
/*************************************************************/
|
|
void SetWave(int Channel,const signed char *Data,int Length,int Rate)
|
|
{
|
|
unsigned int J;
|
|
|
|
/* Channel and waveform length have to be valid */
|
|
if((Channel<0)||(Channel>=SND_CHANNELS)||(Length<=0)) return;
|
|
|
|
/* Set wave channel parameters */
|
|
WaveCH[Channel].Type = SND_WAVE;
|
|
WaveCH[Channel].Length = Length;
|
|
WaveCH[Channel].Rate = Rate;
|
|
WaveCH[Channel].Pos = Length? WaveCH[Channel].Pos%Length:0;
|
|
WaveCH[Channel].Count = 0;
|
|
WaveCH[Channel].Data = Data;
|
|
}
|
|
|
|
/** InitSound() **********************************************/
|
|
/** Initialize RenderSound() with given parameters. **/
|
|
/*************************************************************/
|
|
unsigned int InitSound(unsigned int Rate)
|
|
{
|
|
int I;
|
|
|
|
/* Initialize internal variables (keeping MasterVolume/MasterSwitch!) */
|
|
SndRate = 0;
|
|
|
|
/* Reset sound parameters */
|
|
for(I=0;I<SND_CHANNELS;I++)
|
|
{
|
|
/* NOTICE: Preserving Type value! */
|
|
WaveCH[I].Count = 0;
|
|
WaveCH[I].Volume = 0;
|
|
WaveCH[I].Freq = 0;
|
|
}
|
|
|
|
/* Rate=0 means silence */
|
|
if(!Rate) { SndRate=0;return(0); }
|
|
|
|
/* Done */
|
|
SetChannels(MasterVolume,MasterSwitch);
|
|
return(SndRate=Rate);
|
|
}
|
|
|
|
/** RenderAudio() ********************************************/
|
|
/** Render given number of melodic sound samples into an **/
|
|
/** integer buffer for mixing. **/
|
|
/*************************************************************/
|
|
static void RenderAudio(int *Wave,unsigned int Samples)
|
|
{
|
|
int J,K,I,L1,L2,V,A1;
|
|
#ifdef WAVE_INTERPOLATION
|
|
/* Keep GCC happy about variable initialization */
|
|
int A2 = 0;
|
|
int L = 0;
|
|
int N = 0;
|
|
#endif
|
|
|
|
/* Waveform generator */
|
|
for(J=0;J<SND_CHANNELS;J++)
|
|
if(WaveCH[J].Freq&&(V=WaveCH[J].Volume)&&(MasterSwitch&(1<<J)))
|
|
switch(WaveCH[J].Type)
|
|
{
|
|
case SND_WAVE: /* Custom Waveform */
|
|
/* Waveform data must have correct length! */
|
|
if(WaveCH[J].Length<=0) break;
|
|
/* Start counting */
|
|
K = WaveCH[J].Rate>0?
|
|
(SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Rate
|
|
: (SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Length;
|
|
/* Do not allow high frequencies (GBC Frogger) */
|
|
if(K<0x8000) break;
|
|
L1 = WaveCH[J].Pos%WaveCH[J].Length;
|
|
L2 = WaveCH[J].Count;
|
|
A1 = WaveCH[J].Data[L1]*V;
|
|
#if !defined(WAVE_INTERPOLATION)
|
|
/* Add waveform to the buffer */
|
|
for(I=0;I<Samples;I++)
|
|
{
|
|
/* If next step... */
|
|
if(L2>=K)
|
|
{
|
|
L1 = (L1+L2/K)%WaveCH[J].Length;
|
|
A1 = WaveCH[J].Data[L1]*V;
|
|
L2 = L2%K;
|
|
}
|
|
/* Output waveform */
|
|
Wave[I]+=A1;
|
|
/* Next waveform step */
|
|
L2+=0x8000;
|
|
}
|
|
#else /* WAVE_INTERPOLATION */
|
|
/* If expecting interpolation... */
|
|
if(L2<K)
|
|
{
|
|
/* Compute interpolation parameters */
|
|
A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
|
|
L = (L2>>15)+1;
|
|
N = ((K-(L2&0x7FFF))>>15)+1;
|
|
}
|
|
/* Add waveform to the buffer */
|
|
for(I=0;I<Samples;I++)
|
|
if(L2<K)
|
|
{
|
|
/* Interpolate linearly */
|
|
Wave[I]+=A1+L*(A2-A1)/N;
|
|
/* Next waveform step */
|
|
L2+=0x8000;
|
|
/* Next interpolation step */
|
|
L++;
|
|
}
|
|
else
|
|
{
|
|
L1 = (L1+L2/K)%WaveCH[J].Length;
|
|
L2 = (L2%K)+0x8000;
|
|
A1 = WaveCH[J].Data[L1]*V;
|
|
Wave[I]+=A1;
|
|
/* If expecting interpolation... */
|
|
if(L2<K)
|
|
{
|
|
/* Compute interpolation parameters */
|
|
A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
|
|
L = 1;
|
|
N = ((K-L2)>>15)+1;
|
|
}
|
|
}
|
|
#endif /* WAVE_INTERPOLATION */
|
|
/* End counting */
|
|
WaveCH[J].Pos = L1;
|
|
WaveCH[J].Count = L2;
|
|
break;
|
|
|
|
case SND_NOISE: /* White Noise */
|
|
/* For high frequencies, recompute volume */
|
|
if(WaveCH[J].Freq<SndRate)
|
|
K=((unsigned int)WaveCH[J].Freq<<16)/SndRate;
|
|
else
|
|
{
|
|
V = V*SndRate/WaveCH[J].Freq;
|
|
K = 0x10000;
|
|
}
|
|
L1=WaveCH[J].Count;
|
|
for(I=0;I<Samples;I++)
|
|
{
|
|
/* Use NoiseOut bit for output */
|
|
Wave[I]+=((NoiseGen>>NoiseOut)&1? 127:-128)*V;
|
|
L1+=K;
|
|
if(L1&0xFFFF0000)
|
|
{
|
|
/* XOR NoiseOut and NoiseXOR bits and feed them back */
|
|
NoiseGen=
|
|
(((NoiseGen>>NoiseOut)^(NoiseGen>>NoiseXor))&1)
|
|
| ((NoiseGen<<1)&((2<<NoiseOut)-1));
|
|
L1&=0xFFFF;
|
|
}
|
|
}
|
|
WaveCH[J].Count=L1;
|
|
break;
|
|
|
|
case SND_MELODIC: /* Melodic Sound */
|
|
case SND_TRIANGLE: /* Triangular Wave */
|
|
default: /* Default Sound */
|
|
/* Do not allow frequencies that are too high */
|
|
if(WaveCH[J].Freq>=SndRate/2) break;
|
|
K=0x10000*WaveCH[J].Freq/SndRate;
|
|
L1=WaveCH[J].Count;
|
|
#if !defined(SLOW_MELODIC_AUDIO)
|
|
for(I=0;I<Samples;I++,L1+=K)
|
|
Wave[I]+=((L1-K)^(L1+K))&0x8000? 0:(L1&0x8000? 127:-128)*V;
|
|
#else /* SLOW_MELODIC_AUDIO */
|
|
for(I=0;I<Samples;I++,L1+=K)
|
|
{
|
|
L2 = L1+K;
|
|
A1 = L1&0x8000? 127:-128;
|
|
if((L1^L2)&0x8000)
|
|
A1=A1*(0x8000-(L1&0x7FFF)-(L2&0x7FFF))/K;
|
|
Wave[I]+=A1*V;
|
|
}
|
|
#endif /* SLOW_MELODIC_AUDIO */
|
|
WaveCH[J].Count=L1&0xFFFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** PlayAudio() **********************************************/
|
|
/** Normalize and play given number of samples from the mix **/
|
|
/** buffer. Returns the number of samples actually played. **/
|
|
/*************************************************************/
|
|
static unsigned int PlayAudio(int *Wave,unsigned int Samples)
|
|
{
|
|
int16_t Buf[256];
|
|
unsigned int I,K;
|
|
int D;
|
|
/* Check if the buffer contains enough free space */
|
|
unsigned int J = AUDIO_BUFFER_SIZE;
|
|
if(J<Samples) Samples=J;
|
|
|
|
/* Spin until all samples played or WriteAudio() fails */
|
|
for(K=I=J=0;(K<Samples)&&(I==J);K+=I)
|
|
{
|
|
/* Compute number of samples to convert */
|
|
J = sizeof(Buf)/sizeof(int16_t);
|
|
J = Samples-K>J? J:Samples-K;
|
|
|
|
/* Convert samples */
|
|
for(I=0;I<J;++I)
|
|
{
|
|
D = ((*Wave++)*MasterVolume)>>8;
|
|
D = D>32767? 32767:D<-32768? -32768:D;
|
|
Buf[I] = D;
|
|
}
|
|
|
|
/* Play samples */
|
|
I = WriteAudio(Buf,J);
|
|
}
|
|
|
|
/* Return number of samples played */
|
|
return(K);
|
|
}
|
|
|
|
/** RenderAndPlayAudio() *************************************/
|
|
/** Render and play a given number of samples. Returns the **/
|
|
/** number of samples actually played. **/
|
|
/*************************************************************/
|
|
unsigned int RenderAndPlayAudio(unsigned int Samples)
|
|
{
|
|
int Buf[256];
|
|
unsigned int J,I,K,idx;
|
|
float ResampleRate,R,frac;
|
|
|
|
/* Exit if wave sound not initialized */
|
|
if(SndRate<8192) return(0);
|
|
|
|
/* silence fMSX FM-PAC if NukeYKT is active */
|
|
if(OPTION(MSX_NUKEYKT))
|
|
{
|
|
for(K=0;K<YM2413_CHANNELS;K++)
|
|
Sound(K+OPLL.First,0,0);
|
|
OPLL.Changed=0;
|
|
}
|
|
|
|
J = AUDIO_BUFFER_SIZE;
|
|
Samples = Samples<J? Samples:J;
|
|
ResampleRate = (float)OPLL_NukeYKT.sample_write_index/Samples;
|
|
|
|
/* Render and play sound */
|
|
for(I=0,R=0.0;I<Samples;I+=J)
|
|
{
|
|
J = Samples-I;
|
|
J = J<sizeof(Buf)/sizeof(Buf[0])? J:sizeof(Buf)/sizeof(Buf[0]);
|
|
memset(Buf,0,J*sizeof(Buf[0]));
|
|
RenderAudio(Buf,J);
|
|
|
|
/*
|
|
* Merge in YM2413 NukeYKT using simplistic linear resampler.
|
|
* Max input range slightly outside [-1024,+1023], output [-32768,+32767] or 16b signed after amplification by 32.
|
|
* BUT matching it to PSG volume (by hand) requires a further amplification by 4.
|
|
* That's still a factor 4-8 less loud than fMSX's FM-PAC volume.
|
|
*/
|
|
if(OPTION(MSX_NUKEYKT))
|
|
for(K=0;K<J;K++,R+=ResampleRate)
|
|
{
|
|
idx=(unsigned int)R;
|
|
frac=R-idx;
|
|
Buf[K]+=(int)(.5+128.0*(OPLL_NukeYKT.samples[idx]*(1.0-frac) + OPLL_NukeYKT.samples[idx+1]*frac));
|
|
}
|
|
|
|
if(PlayAudio(Buf,J)<J) { I+=J;break; }
|
|
}
|
|
|
|
// move last sample to beginning (for linear resampler) and signal a buffer refill
|
|
if (OPLL_NukeYKT.sample_write_index>0)
|
|
OPLL_NukeYKT.samples[0]=OPLL_NukeYKT.samples[OPLL_NukeYKT.sample_write_index-1];
|
|
OPLL_NukeYKT.sample_write_index=1;
|
|
|
|
/* Return number of samples rendered */
|
|
return I;
|
|
}
|