ti-nesulator/src/apu/SndAlleg.c
2012-01-31 15:15:55 +00:00

435 lines
13 KiB
C
Executable File

/*
* Allegro Sound Driver for EMULib Sound system - The TI-NESulator Project
* SndAlleg.C
*
* Created by Manoel Trapier
* Copyright 2003-2008 986 Corp. All rights reserved.
*
* $LastChangedDate$
* $Author$
* $HeadURL$
* $Revision$
*
*/
#include <Sound.h>
/* Allegro includes */
#ifdef __APPLE__
#define USE_CONSOLE
#include <Allegro/allegro.h>
#else
#define USE_CONSOLE
#include <allegro.h>
#endif
#include <os_dependent.h>
#include <stdlib.h>
#include <stdio.h>
//#include <unistd.h>
//#include <fcntl.h>
#include <pthread.h>
//#include <sys/ioctl.h>
#define AUDIO_CONV(A) (128+(A))
AUDIOSTREAM *stream;
static pthread_t ThreadID;
static int SoundRate = 0;
static int MasterVolume = 64;
static int MasterSwitch = (1<<SND_CHANNELS)-1;
static int LoopFreq = 25;
static int NoiseGen = 1;
static int Suspended = 0;
static int SoundRun = 0;
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 */
} CH[SND_CHANNELS];
static void UnixSetWave(int Channel,const signed char *Data,int Length,int Rate);
static void UnixSetSound(int Channel,int NewType);
static void UnixDrum(int Type,int Force);
static void UnixSetChannels(int Volume,int Switch);
static void UnixSound(int Channel,int NewFreq,int NewVolume);
static int OpenSoundDevice(int Rate,int Verbose);
static void *DSPLoop(void *Arg);
/** StopSound() **********************************************/
/** Temporarily suspend sound. **/
/*************************************************************/
void StopSound(void) { Suspended=1; }
/** ResumeSound() ********************************************/
/** Resume sound after StopSound(). **/
/*************************************************************/
void ResumeSound(void) { Suspended=0; }
/** OpenSoundDevice() ****************************************/
/** Open /dev/dsp with a given level of sound quality. **/
/** Returns 0 if failed or sound quality (Mode). **/
/*************************************************************/
static int OpenSoundDevice(int Rate,int Verbose)
{
voice_start(stream->voice);
if(Verbose) puts("OK");
return(Rate);
}
/** DSPLoop() ************************************************/
/** Main loop of the sound server. **/
/*************************************************************/
static void *DSPLoop(void *Arg)
{
int Wave[SND_BUFSIZE];
unsigned char *Buf;
register int J,I,K,L,M,N,L1,L2,A1,A2,V;
int FreqCount;
N = L = A2 = 0;
for(J=0;J<SND_CHANNELS;J++)
{
CH[J].Type = SND_MELODIC;
CH[J].Count = 0;
CH[J].Volume = 0;
CH[J].Freq = 0;
}
FreqCount=SoundRate/SND_BUFSIZE;
for(;;)
{
Buf = get_audio_stream_buffer(stream);
if (Buf) {
FreqCount-=LoopFreq;
/* If suspending sound... */
if(Suspended)
{
/* Close sound device */
while(Suspended) sleep(1);
/* Reopen sound device */
SoundRate=OpenSoundDevice(SoundRate,0);
}
/* Waveform generator */
for(J=0,M=MasterSwitch;M&&(J<SND_CHANNELS);J++,M>>=1)
if(CH[J].Freq&&(V=CH[J].Volume)&&(M&1))
switch(CH[J].Type)
{
case SND_NOISE: /* White Noise */
/* For high frequencies, recompute volume */
if(CH[J].Freq<=SoundRate) K=0x10000*CH[J].Freq/SoundRate;
else { V=V*SoundRate/CH[J].Freq;K=0x10000; }
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L1+=K;
if(L1&0xFFFF0000)
{
L1&=0xFFFF;
if((NoiseGen<<=1)&0x80000000) NoiseGen^=0x08000001;
}
Wave[I]+=NoiseGen&1? V:-V;
}
CH[J].Count=L1;
break;
case SND_WAVE: /* Custom Waveform */
/* Waveform data must have correct length! */
if(CH[J].Length<=0) break;
/* Start counting */
K = CH[J].Rate>0? (SoundRate<<15)/CH[J].Freq/CH[J].Rate
: (SoundRate<<15)/CH[J].Freq/CH[J].Length;
L1 = CH[J].Pos%CH[J].Length;
L2 = CH[J].Count;
A1 = CH[J].Data[L1]*V;
/* If expecting interpolation... */
if(L2<K)
{
/* Compute interpolation parameters */
A2 = CH[J].Data[(L1+1)%CH[J].Length]*V;
L = (L2>>15)+1;
N = ((K-(L2&0x7FFF))>>15)+1;
}
/* Add waveform to the buffer */
for(I=0;I<SND_BUFSIZE;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)%CH[J].Length;
L2 = (L2%K)+0x8000;
A1 = CH[J].Data[L1]*V;
Wave[I]+=A1;
/* If expecting interpolation... */
if(L2<K)
{
/* Compute interpolation parameters */
A2 = CH[J].Data[(L1+1)%CH[J].Length]*V;
L = 1;
N = ((K-L2)>>15)+1;
}
}
/* End counting */
CH[J].Pos = L1;
CH[J].Count = L2;
break;
case SND_QS_DU0:
/* Do not allow frequencies that are too high */
if(CH[J].Freq>=SoundRate/3) break;
K=0x10000*CH[J].Freq/SoundRate;
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L2=L1+K;
Wave[I]+=L1&0x2000?(L2&0x8000? V:0):(L2&0x8000? 0:-V);
L1=L2;
}
CH[J].Count=L1;
break;
case SND_QS_DU1:
/* Do not allow frequencies that are too high */
if(CH[J].Freq>=SoundRate/3) break;
K=0x10000*CH[J].Freq/SoundRate;
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L2=L1+K;
Wave[I]+=L1&0x4000?(L2&0x8000? V:0):(L2&0x8000? 0:-V);
L1=L2;
}
CH[J].Count=L1;
break;
case SND_QS_DU3:
/* Do not allow frequencies that are too high */
if(CH[J].Freq>=SoundRate/3) break;
K=0x10000*CH[J].Freq/SoundRate;
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L2=L1+K;
Wave[I]+=L1&0xC000?(L2&0x4000? V:0):(L2&0xC000? 0:-V);
L1=L2;
}
CH[J].Count=L1;
break;
case SND_QS_DU2:
case SND_MELODIC: /* Melodic Sound */
default: /* Default Sound */
/* Do not allow frequencies that are too high */
if(CH[J].Freq>=SoundRate/3) break;
K=0x10000*CH[J].Freq/SoundRate;
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L2=L1+K;
Wave[I]+=L1&0x8000? (L2&0x8000? V:0):(L2&0x8000? 0:-V);
L1=L2;
}
CH[J].Count=L1;
break;
case SND_TRIANGLE: /* Default Sound */
/* Do not allow frequencies that are too high */
if(CH[J].Freq>=SoundRate/3) break;
K=0x10000*CH[J].Freq/SoundRate;
L1=CH[J].Count;
V<<=7;
for(I=0;I<SND_BUFSIZE;I++)
{
L2=L1+K;
Wave[I]+= L1&0x8000?V:-V /*(L2&0x8000? V:0):(L2&0x8000? 0:-V)*/;
L1=L2;
}
CH[J].Count=L1;
break;
}
/* Mix and convert waveforms */
for(J=0;J<SND_BUFSIZE;J++)
{
I=(Wave[J]*MasterVolume)>>16;
I=I<-128? -128:I>127? 127:I;
Buf[J]=AUDIO_CONV(I);
Wave[J]=0;
}
free_audio_stream_buffer(stream);
}
}
return(0);
}
/** InitSound() **********************************************/
/** Initialize DSP. Returns Rate on success, 0 otherwise. **/
/** Mode is 0 to skip initialization (will be silent). **/
/*************************************************************/
int InitSound(int Rate,int Verbose)
{
/* If sound was initialized, kill it */
TrashSound();
/* Silence requested */
if(Rate<=0) return(0);
/* Synthesis rate should be at least 8kHz */
if(Rate<8192) Rate=44100;
/* Initialize things */
SoundRate = 0;
ThreadID = 0;
Suspended = 0;
/* Set driver functions */
SndDriver.SetSound = UnixSetSound;
SndDriver.Drum = UnixDrum;
SndDriver.SetChannels = UnixSetChannels;
SndDriver.Sound = UnixSound;
SndDriver.SetWave = UnixSetWave;
if (install_sound(DIGI_AUTODETECT, MIDI_NONE, "") != 0)
{
console_printf(Console_Error, "%s!\n", allegro_error);
return 1;
}
stream = play_audio_stream(SND_BUFSIZE, 8, FALSE, Rate, 255, 128);
if (!stream) {
console_printf(Console_Error, "Error creating audio stream!\n");
return 1;
}
voice_stop(stream->voice);
/* Open sound device */
if(Verbose) puts("Starting sound server:");
if(!(Rate=OpenSoundDevice(Rate,Verbose))) return(0);
/* Create DSPLoop() thread */
if(Verbose) console_printf(Console_Default, " Creating thread...");
if(pthread_create(&ThreadID,0,DSPLoop,0))
{ if(Verbose) puts("FAILED");return(0); }
/* Detach the thread */
pthread_detach(ThreadID);
/* Done */
SoundRun = 1;
if(Verbose) puts("OK");
return(SoundRate=Rate);
}
/** TrashSound() *********************************************/
/** Shut DSP down. **/
/*************************************************************/
void TrashSound(void)
{
if (SoundRun == 1)
{
StopSound();
console_printf(Console_Default, "%s: Kill thread...\n", __func__);
if(ThreadID) pthread_cancel(ThreadID);
}
SoundRun = 0;
SoundRate = 0;
ThreadID = 0;
}
/** UnixSound() **********************************************/
/** Generate sound of given frequency (Hz) and volume **/
/** (0..255) via given channel. **/
/*************************************************************/
void UnixSound(int Channel,int NewFreq,int NewVolume)
{
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
if(!NewVolume||!NewFreq) { NewVolume=0;NewFreq=0; }
CH[Channel].Volume = NewVolume;
CH[Channel].Freq = NewFreq;
}
/** UnixSetChannels() ****************************************/
/** Set master volume (0..255) and turn channels on/off. **/
/** Each bit in Toggle corresponds to a channel (1=on). **/
/*************************************************************/
void UnixSetChannels(int MVolume,int MSwitch)
{
/* Set new MasterSwitch value */
MasterSwitch = MSwitch;
MasterVolume = MVolume;
}
/** UnixSetSound() *******************************************/
/** Set sound type (SND_NOISE/SND_MELODIC) for a given **/
/** channel. **/
/*************************************************************/
void UnixSetSound(int Channel,int NewType)
{
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
CH[Channel].Type = NewType;
}
/** UnixSetWave() ********************************************/
/** 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 UnixSetWave(int Channel,const signed char *Data,int Length,int Rate)
{
if((Channel<0)||(Channel>=SND_CHANNELS)||(Length<=0)) return;
CH[Channel].Type = SND_WAVE;
CH[Channel].Length = Length;
CH[Channel].Rate = Rate;
CH[Channel].Pos = 0;
CH[Channel].Count = 0;
CH[Channel].Data = Data;
}
/** UnixDrum() ***********************************************/
/** Hit a drum of a given type with given force. **/
/*************************************************************/
void UnixDrum(int Type,int Force)
{
/* This function is currently empty */
}