]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
Set eol-style to sane default.
[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 using namespace std;
29
30 // Global variables
31
32
33 // Local variables
34
35 static SDL_AudioSpec desired;
36 static bool soundInitialized = false;
37 static uint8 amplitude = 0x88;                                  // $78 - $88 seems to be plenty loud!
38 //static uint8 lastValue;
39
40 static bool speakerState;
41 static uint8 soundBuffer[4096];
42 static uint32 soundBufferPos;
43 static uint32 sampleBase;
44 static SDL_cond * conditional = NULL;
45 static SDL_mutex * mutex = NULL;
46
47 // Private function prototypes
48
49 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
50
51 //
52 // Initialize the SDL sound system
53 //
54 void SoundInit(void)
55 {
56 // To weed out problems for now...
57 #if 0
58 return;
59 #endif
60
61         desired.freq = 44100;                                           // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
62         desired.format = AUDIO_U8;                                      // This uses the native endian (for portability)...
63         desired.channels = 1;
64 //      desired.samples = 4096;                                         // Let's try a 4K buffer (can always go lower)
65         desired.samples = 2048;                                         // Let's try a 2K buffer (can always go lower)
66         desired.callback = SDLSoundCallback;
67
68         if (SDL_OpenAudio(&desired, NULL) < 0)          // NULL means SDL guarantees what we want
69         {
70                 WriteLog("Sound: Failed to initialize SDL sound.\n");
71 //              exit(1);
72                 return;
73         }
74
75         conditional = SDL_CreateCond();
76         mutex = SDL_CreateMutex();
77         SDL_mutexP(mutex);                                                      // Must lock the mutex for the cond to work properly...
78 //      lastValue = (speakerState ? amplitude : 0xFF - amplitude);
79         soundBufferPos = 0;
80         sampleBase = 0;
81
82         SDL_PauseAudio(false);                                          // Start playback!
83         soundInitialized = true;
84         WriteLog("Sound: Successfully initialized.\n");
85 }
86
87 //
88 // Close down the SDL sound subsystem
89 //
90 void SoundDone(void)
91 {
92         if (soundInitialized)
93         {
94                 SDL_PauseAudio(true);
95                 SDL_CloseAudio();
96                 SDL_DestroyCond(conditional);
97                 SDL_DestroyMutex(mutex);
98                 WriteLog("Sound: Done.\n");
99         }
100 }
101
102 //
103 // Sound card callback handler
104 //
105 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
106 {
107         // The sound buffer should only starve when starting which will cause it to
108         // lag behind the emulation at most by around 1 frame...
109
110         if (soundBufferPos < (uint32)length)            // The sound buffer is starved...
111         {
112 //printf("Sound buffer starved!\n");
113 //fflush(stdout);
114                 for(uint32 i=0; i<soundBufferPos; i++)
115                         buffer[i] = soundBuffer[i];
116                 // Fill buffer with last value
117                 uint8 lastValue = (speakerState ? amplitude : 0xFF - amplitude);
118 //              uint8 lastValue = (speakerState ? amplitude : amplitude ^ 0xFF);
119 //              memset(buffer, lastValue, length);              // Fill buffer with last value
120                 memset(buffer + soundBufferPos, lastValue, length - soundBufferPos);
121                 soundBufferPos = 0;                                             // Reset soundBufferPos to start of buffer...
122                 sampleBase = 0;                                                 // & sampleBase...
123 //Ick. This should never happen!
124 SDL_CondSignal(conditional);                            // Wake up any threads waiting for the buffer to drain...
125                 return;                                                                 // & bail!
126         }
127
128         memcpy(buffer, soundBuffer, length);            // Fill sound buffer with frame buffered sound
129         soundBufferPos -= length;
130         sampleBase -= length;
131
132 //      if (soundBufferPos > 0)
133 //              memcpy(soundBuffer, soundBuffer + length, soundBufferPos);      // Move current buffer down to start
134 //      memcpy(soundBuffer, soundBuffer + length, length);
135         // Move current buffer down to start
136         for(uint32 i=0; i<soundBufferPos; i++)
137                 soundBuffer[i] = soundBuffer[length + i];
138
139 //      lastValue = buffer[length - 1];
140         SDL_CondSignal(conditional);                            // Wake up any threads waiting for the buffer to drain...
141 }
142
143 // Need some interface functions here to take care of flipping the
144 // waveform at the correct time in the sound stream...
145
146 /*
147 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
148
149 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
150
151 So... I guess what we could do is this:
152
153 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
154    to the current time position.
155 -- The sound callback function copies the pertinent area out of the buffer, resets
156    the time position back (or copies data down from what it took out)
157 */
158
159 void ToggleSpeaker(uint32 time)
160 {
161         if (!soundInitialized)
162                 return;
163
164 #if 0
165 if (time > 95085)//(time & 0x80000000)
166 {
167         WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
168 //      fflush(stdout);
169 }
170 #endif
171
172 // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
173 // Need the last frame position in order to calculate correctly...
174
175         SDL_LockAudio();
176         uint8 sample = (speakerState ? amplitude : 0xFF - amplitude);
177 //      uint8 sample = (speakerState ? amplitude : amplitude ^ 0xFF);
178         uint32 currentPos = sampleBase + (uint32)((double)time / 23.2199);
179
180         if (currentPos > 4095)
181         {
182 #if 0
183 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
184 #endif
185 // Still hanging on this spinlock...
186 // That could be because the "time" value is too high and so the buffer will NEVER be
187 // empty enough...
188 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
189 /*
190 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
191 16401 -> 706 samples, 3504 + 706 = 4210
192
193 And it still thrashed the sound even though it didn't run into a spinlock...
194
195 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
196 */
197                 SDL_UnlockAudio();
198                 SDL_CondWait(conditional, mutex);
199
200 //              while (currentPos > 4095)                               // Spin until buffer empties a bit...
201                 currentPos = sampleBase + (uint32)((double)time / 23.2199);
202                 SDL_LockAudio();
203 #if 0
204 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
205 #endif
206         }
207
208         while (soundBufferPos < currentPos)
209                 soundBuffer[soundBufferPos++] = sample;
210
211         speakerState = !speakerState;
212         SDL_UnlockAudio();
213 }
214
215 void HandleSoundAtFrameEdge(void)
216 {
217         if (!soundInitialized)
218                 return;
219
220         SDL_LockAudio();
221         sampleBase += 735;
222         SDL_UnlockAudio();
223 /*      uint8 sample = (speakerState ? amplitude : 0xFF - amplitude);
224
225 //This shouldn't happen (buffer overflow), but it seems like it *is* happening...
226         if (sampleBase >= 4096)
227 //              sampleBase = 4095;
228 //Kludge, for now... Until I can figure out why it's still stomping on the buffer...
229                 sampleBase = 0;
230
231         while (soundBufferPos < sampleBase)
232                 soundBuffer[soundBufferPos++] = sample;//*/
233 }