]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
d8a8771bc3c225889506adc9772b30dbb27ea0a0
[apple2] / src / sound.cpp
1 //
2 // Sound Interface
3 //
4 // by James L. Hammons
5 // (C) 2005 Underground Software
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  12/02/2005  Fixed a problem with sound callback thread signaling the
12 //                  main thread
13 // JLH  12/03/2005  Fixed sound callback dropping samples when the sample buffer
14 //                  is shorter than the callback sample buffer
15 //
16
17 // STILL TO DO:
18 //
19 // - Figure out why it's losing samples (Bard's Tale) [DONE]
20 //
21
22 #include "sound.h"
23
24 #include <string.h>                                                             // For memset, memcpy
25 #include <SDL.h>
26 #include "log.h"
27
28
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!
34
35 // Global variables
36
37
38 // Local variables
39
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;
48
49 // Private function prototypes
50
51 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
52
53 //
54 // Initialize the SDL sound system
55 //
56 void SoundInit(void)
57 {
58 // To weed out problems for now...
59 #if 0
60 return;
61 #endif
62
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)...
66         desired.channels = 1;
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;
71
72         if (SDL_OpenAudio(&desired, NULL) < 0)          // NULL means SDL guarantees what we want
73         {
74                 WriteLog("Sound: Failed to initialize SDL sound.\n");
75                 return;
76         }
77
78         conditional = SDL_CreateCond();
79         mutex = SDL_CreateMutex();
80         SDL_mutexP(mutex);                                                      // Must lock the mutex for the cond to work properly...
81         soundBufferPos = 0;
82         sampleBase = 0;
83
84         SDL_PauseAudio(false);                                          // Start playback!
85         soundInitialized = true;
86         WriteLog("Sound: Successfully initialized.\n");
87 }
88
89 //
90 // Close down the SDL sound subsystem
91 //
92 void SoundDone(void)
93 {
94         if (soundInitialized)
95         {
96                 SDL_PauseAudio(true);
97                 SDL_CloseAudio();
98                 SDL_DestroyCond(conditional);
99                 SDL_DestroyMutex(mutex);
100                 WriteLog("Sound: Done.\n");
101         }
102 }
103
104 //
105 // Sound card callback handler
106 //
107 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
108 {
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!)
114
115         if (soundBufferPos < (uint32)length)            // The sound buffer is starved...
116         {
117 //printf("Sound buffer starved!\n");
118 //fflush(stdout);
119                 for(uint32 i=0; i<soundBufferPos; i++)
120                         buffer[i] = soundBuffer[i];
121
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...
128                 return;                                                                 // & bail!
129         }
130
131         memcpy(buffer, soundBuffer, length);            // Fill sound buffer with frame buffered sound
132         soundBufferPos -= length;
133         sampleBase -= length;
134
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];
141
142 //      lastValue = buffer[length - 1];
143         SDL_CondSignal(conditional);                            // Wake up any threads waiting for the buffer to drain...
144 }
145
146 // Need some interface functions here to take care of flipping the
147 // waveform at the correct time in the sound stream...
148
149 /*
150 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
151
152 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
153
154 So... I guess what we could do is this:
155
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)
160 */
161
162 void ToggleSpeaker(uint32 time)
163 {
164         if (!soundInitialized)
165                 return;
166
167 #if 0
168 if (time > 95085)//(time & 0x80000000)
169 {
170         WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
171 //      fflush(stdout);
172 }
173 #endif
174
175         // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
176         // Need the last frame position in order to calculate correctly...
177         // (or do we?)
178
179         SDL_LockAudio();
180         uint32 currentPos = sampleBase + (uint32)((double)time / CYCLES_PER_SAMPLE);
181
182         if (currentPos > SOUND_BUFFER_SIZE - 1)
183         {
184 #if 0
185 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
186 #endif
187 // Still hanging on this spinlock...
188 // That could be because the "time" value is too high and so the buffer will NEVER be
189 // empty enough...
190 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
191 /*
192 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
193 16401 -> 706 samples, 3504 + 706 = 4210
194
195 And it still thrashed the sound even though it didn't run into a spinlock...
196
197 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
198 */
199                 SDL_UnlockAudio();
200                 SDL_CondWait(conditional, mutex);
201                 SDL_LockAudio();
202                 currentPos = sampleBase + (uint32)((double)time / 23.2199);
203 #if 0
204 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
205 #endif
206         }
207
208         int8 sample = (speakerState ? AMPLITUDE : -AMPLITUDE);
209
210         while (soundBufferPos < currentPos)
211                 soundBuffer[soundBufferPos++] = (uint8)sample;
212
213         speakerState = !speakerState;
214         SDL_UnlockAudio();
215 }
216
217 void HandleSoundAtFrameEdge(void)
218 {
219         if (!soundInitialized)
220                 return;
221
222         SDL_LockAudio();
223         sampleBase += SAMPLES_PER_FRAME;
224         SDL_UnlockAudio();
225 }
226
227 /*
228 A better way might be as follows:
229
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
234 each sample
235
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)
238
239 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
240
241 */
242
243
244