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