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