5 // (C) 2005 Underground Software
7 // JLH = James L. Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 12/02/2005 Fixed a problem with sound callback thread signaling the
13 // JLH 12/03/2005 Fixed sound callback dropping samples when the sample buffer
14 // is shorter than the callback sample buffer
19 // - Figure out why it's losing samples (Bard's Tale) [DONE]
24 #include <string.h> // For memset, memcpy
29 #define SAMPLE_RATE (44100.0)
30 #define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
31 #define CYCLES_PER_SAMPLE ((1024000.0 / 60.0) / (SAMPLES_PER_FRAME))
32 #define SOUND_BUFFER_SIZE 8192
33 #define AMPLITUDE (32) // -32 - +32 seems to be plenty loud!
40 static SDL_AudioSpec desired;
41 static bool soundInitialized = false;
42 static bool speakerState;
43 static uint8 soundBuffer[SOUND_BUFFER_SIZE];
44 static uint32 soundBufferPos;
45 static uint32 sampleBase;
46 static SDL_cond * conditional = NULL;
47 static SDL_mutex * mutex = NULL;
49 // Private function prototypes
51 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
54 // Initialize the SDL sound system
58 // To weed out problems for now...
63 desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
64 desired.format = AUDIO_S8; // This uses the native endian (for portability)...
65 // desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)...
67 // desired.samples = 4096; // Let's try a 4K buffer (can always go lower)
68 // desired.samples = 2048; // Let's try a 2K buffer (can always go lower)
69 desired.samples = 1024; // Let's try a 1K buffer (can always go lower)
70 desired.callback = SDLSoundCallback;
72 if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want
74 WriteLog("Sound: Failed to initialize SDL sound.\n");
78 conditional = SDL_CreateCond();
79 mutex = SDL_CreateMutex();
80 SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly...
84 SDL_PauseAudio(false); // Start playback!
85 soundInitialized = true;
86 WriteLog("Sound: Successfully initialized.\n");
90 // Close down the SDL sound subsystem
98 SDL_DestroyCond(conditional);
99 SDL_DestroyMutex(mutex);
100 WriteLog("Sound: Done.\n");
105 // Sound card callback handler
107 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
109 // The sound buffer should only starve when starting which will cause it to
110 // lag behind the emulation at most by around 1 frame...
111 // (Actually, this should never happen since we fill the buffer beforehand.)
112 // (But, then again, if the sound hasn't been toggled for a while, then this
113 // makes perfect sense as the buffer won't have been filled at all!)
115 if (soundBufferPos < (uint32)length) // The sound buffer is starved...
117 //printf("Sound buffer starved!\n");
119 for(uint32 i=0; i<soundBufferPos; i++)
120 buffer[i] = soundBuffer[i];
122 // Fill buffer with last value
123 memset(buffer + soundBufferPos, (uint8)(speakerState ? AMPLITUDE : -AMPLITUDE), length - soundBufferPos);
124 soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
125 sampleBase = 0; // & sampleBase...
126 //Ick. This should never happen!
127 SDL_CondSignal(conditional); // Wake up any threads waiting for the buffer to drain...
131 memcpy(buffer, soundBuffer, length); // Fill sound buffer with frame buffered sound
132 soundBufferPos -= length;
133 sampleBase -= length;
135 // if (soundBufferPos > 0)
136 // memcpy(soundBuffer, soundBuffer + length, soundBufferPos); // Move current buffer down to start
137 // memcpy(soundBuffer, soundBuffer + length, length);
138 // Move current buffer down to start
139 for(uint32 i=0; i<soundBufferPos; i++)
140 soundBuffer[i] = soundBuffer[length + i];
142 // lastValue = buffer[length - 1];
143 SDL_CondSignal(conditional); // Wake up any threads waiting for the buffer to drain...
146 // Need some interface functions here to take care of flipping the
147 // waveform at the correct time in the sound stream...
150 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
152 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
154 So... I guess what we could do is this:
156 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
157 to the current time position.
158 -- The sound callback function copies the pertinent area out of the buffer, resets
159 the time position back (or copies data down from what it took out)
162 void ToggleSpeaker(uint32 time)
164 if (!soundInitialized)
168 if (time > 95085)//(time & 0x80000000)
170 WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
175 // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
176 // Need the last frame position in order to calculate correctly...
180 uint32 currentPos = sampleBase + (uint32)((double)time / CYCLES_PER_SAMPLE);
182 if (currentPos > SOUND_BUFFER_SIZE - 1)
185 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
187 // Still hanging on this spinlock...
188 // That could be because the "time" value is too high and so the buffer will NEVER be
190 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
192 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
193 16401 -> 706 samples, 3504 + 706 = 4210
195 And it still thrashed the sound even though it didn't run into a spinlock...
197 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
200 SDL_CondWait(conditional, mutex);
202 currentPos = sampleBase + (uint32)((double)time / 23.2199);
204 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
208 int8 sample = (speakerState ? AMPLITUDE : -AMPLITUDE);
210 while (soundBufferPos < currentPos)
211 soundBuffer[soundBufferPos++] = (uint8)sample;
213 speakerState = !speakerState;
217 void HandleSoundAtFrameEdge(void)
219 if (!soundInitialized)
223 sampleBase += SAMPLES_PER_FRAME;
228 A better way might be as follows:
230 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will fit
231 into the given buffer and keep going. Have the toggle function check to see if the buffer is full,
232 and if it is, way for a signal from the interrupt that there's room for more. Can keep a circular
233 buffer. Also, would need a timestamp buffer on the order of 2096 samples *in theory* could toggle
236 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and all that
237 (though the timestamp could wrap--need to check into that)
239 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.