]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
Added VBL, fixed sound-on-write, added .bin disk support.
[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 // - Figure out why it's playing too fast [DONE]
21 //
22
23 #include "sound.h"
24
25 #include <string.h>                                                             // For memset, memcpy
26 #include <SDL2/SDL.h>
27 #include "log.h"
28
29 // Useful defines
30
31 //#define DEBUG
32 //#define WRITE_OUT_WAVE
33
34 //#define SAMPLE_RATE                   (44100.0)
35 #define SAMPLE_RATE                     (48000.0)
36 #define SAMPLES_PER_FRAME       (SAMPLE_RATE / 60.0)
37 #define CYCLES_PER_SAMPLE       (1024000.0 / SAMPLE_RATE)
38 //#define SOUND_BUFFER_SIZE     (8192)
39 #define SOUND_BUFFER_SIZE       (32768)
40
41 // Global variables
42
43
44 // Local variables
45
46 static SDL_AudioSpec desired, obtained;
47 static SDL_AudioDeviceID device;
48 static bool soundInitialized = false;
49 static bool speakerState = false;
50 static int16_t soundBuffer[SOUND_BUFFER_SIZE];
51 static uint32_t soundBufferPos;
52 static uint64_t lastToggleCycles;
53 static SDL_cond * conditional = NULL;
54 static SDL_mutex * mutex = NULL;
55 static SDL_mutex * mutex2 = NULL;
56 static int16_t sample;
57 static uint8_t ampPtr = 12;                                             // Start with -2047 - +2047
58 static int16_t amplitude[17] = { 0, 1, 2, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047,
59         4095, 8191, 16383, 32767 };
60 #ifdef WRITE_OUT_WAVE
61 static FILE * fp = NULL;
62 #endif
63
64 // Private function prototypes
65
66 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
67
68
69 //
70 // Initialize the SDL sound system
71 //
72 void SoundInit(void)
73 {
74 #if 0
75 // To weed out problems for now...
76 return;
77 #endif
78         SDL_zero(desired);
79         desired.freq = SAMPLE_RATE;                                     // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
80         desired.format = AUDIO_S16SYS;                          // This uses the native endian (for portability)...
81         desired.channels = 1;
82         desired.samples = 512;                                          // Let's try a 1/2K buffer (can always go lower)
83         desired.callback = SDLSoundCallback;
84
85         device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
86
87         if (device == 0)
88         {
89                 WriteLog("Sound: Failed to initialize SDL sound.\n");
90                 return;
91         }
92
93         conditional = SDL_CreateCond();
94         mutex = SDL_CreateMutex();
95         mutex2 = SDL_CreateMutex();// Let's try real signalling...
96         soundBufferPos = 0;
97         lastToggleCycles = 0;
98         sample = desired.silence;       // ? wilwok ? yes
99
100         SDL_PauseAudioDevice(device, 0);                        // Start playback!
101         soundInitialized = true;
102         WriteLog("Sound: Successfully initialized.\n");
103
104 #ifdef WRITE_OUT_WAVE
105         fp = fopen("./apple2.wav", "wb");
106 #endif
107 }
108
109
110 //
111 // Close down the SDL sound subsystem
112 //
113 void SoundDone(void)
114 {
115         if (soundInitialized)
116         {
117                 SDL_PauseAudioDevice(device, 1);
118                 SDL_CloseAudioDevice(device);
119                 SDL_DestroyCond(conditional);
120                 SDL_DestroyMutex(mutex);
121                 SDL_DestroyMutex(mutex2);
122                 WriteLog("Sound: Done.\n");
123
124 #ifdef WRITE_OUT_WAVE
125                 fclose(fp);
126 #endif
127         }
128 }
129
130
131 void SoundPause(void)
132 {
133         if (soundInitialized)
134                 SDL_PauseAudioDevice(device, 1);
135 }
136
137
138 void SoundResume(void)
139 {
140         if (soundInitialized)
141                 SDL_PauseAudioDevice(device, 0);
142 }
143
144
145 //
146 // Sound card callback handler
147 //
148 static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
149 {
150 //WriteLog("SDLSoundCallback(): begin (soundBufferPos=%i)\n", soundBufferPos);
151         // The sound buffer should only starve when starting which will cause it to
152         // lag behind the emulation at most by around 1 frame...
153         // (Actually, this should never happen since we fill the buffer beforehand.)
154         // (But, then again, if the sound hasn't been toggled for a while, then this
155         //  makes perfect sense as the buffer won't have been filled at all!)
156         // (Should NOT starve now, now that we properly handle frame edges...)
157
158         // Let's try using a mutex for shared resource consumption...
159 //Actually, I think Lock/UnlockAudio() does this already...
160 //WriteLog("SDLSoundCallback: soundBufferPos = %i\n", soundBufferPos);
161         SDL_mutexP(mutex2);
162
163         // Recast this as a 16-bit type...
164         int16_t * buffer = (int16_t *)buffer8;
165         uint32_t length = (uint32_t)length8 / 2;
166
167 //WriteLog("SDLSoundCallback(): filling buffer...\n");
168         if (soundBufferPos < length)
169         {
170                 // The sound buffer is starved...
171                 for(uint32_t i=0; i<soundBufferPos; i++)
172                         buffer[i] = soundBuffer[i];
173
174                 // Fill buffer with last value
175                 for(uint32_t i=soundBufferPos; i<length; i++)
176                         buffer[i] = sample;
177
178                 // Reset soundBufferPos to start of buffer...
179                 soundBufferPos = 0;
180         }
181         else
182         {
183                 // Fill sound buffer with frame buffered sound
184                 for(uint32_t i=0; i<length; i++)
185                         buffer[i] = soundBuffer[i];
186
187                 soundBufferPos -= length;
188
189                 // Move current buffer down to start
190                 for(uint32_t i=0; i<soundBufferPos; i++)
191                         soundBuffer[i] = soundBuffer[length + i];
192         }
193
194         // Free the mutex...
195 //WriteLog("SDLSoundCallback(): SDL_mutexV(mutex2)\n");
196         SDL_mutexV(mutex2);
197         // Wake up any threads waiting for the buffer to drain...
198         SDL_CondSignal(conditional);
199 //WriteLog("SDLSoundCallback(): end\n");
200 }
201
202
203 // This is called by the main CPU thread every ~21.333 cycles.
204 void WriteSampleToBuffer(void)
205 {
206 //WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
207         SDL_mutexP(mutex2);
208
209         // This should almost never happen, but, if it does...
210         while (soundBufferPos >= (SOUND_BUFFER_SIZE - 1))
211         {
212 //WriteLog("WriteSampleToBuffer(): Waiting for sound thread. soundBufferPos=%i, SOUNDBUFFERSIZE-1=%i\n", soundBufferPos, SOUND_BUFFER_SIZE-1);
213                 SDL_mutexV(mutex2);     // Release it so sound thread can get it,
214                 SDL_mutexP(mutex);      // Must lock the mutex for the cond to work properly...
215                 SDL_CondWait(conditional, mutex);       // Sleep/wait for the sound thread
216                 SDL_mutexV(mutex);      // Must unlock the mutex for the cond to work properly...
217                 SDL_mutexP(mutex2);     // Re-lock it until we're done with it...
218         }
219
220         soundBuffer[soundBufferPos++] = sample;
221 //WriteLog("WriteSampleToBuffer(): SDL_mutexV(mutex2)\n");
222         SDL_mutexV(mutex2);
223 }
224
225
226 void ToggleSpeaker(void)
227 {
228         if (!soundInitialized)
229                 return;
230
231         speakerState = !speakerState;
232         sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
233 }
234
235
236 void VolumeUp(void)
237 {
238         // Currently set for 16-bit samples
239         if (ampPtr < 16)
240                 ampPtr++;
241 }
242
243
244 void VolumeDown(void)
245 {
246         if (ampPtr > 0)
247                 ampPtr--;
248 }
249
250
251 uint8_t GetVolume(void)
252 {
253         return ampPtr;
254 }
255