1 /*
2 Copyright (C) 2004 Andreas Kirsch
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19 #include <math.h>
20 #include <SDL.h>
21 
22 #include "quakedef.h"
23 
24 #include "snd_main.h"
25 
26 
27 static unsigned int sdlaudiotime = 0;
28 
29 
30 // Note: SDL calls SDL_LockAudio() right before this function, so no need to lock the audio data here
Buffer_Callback(void * userdata,Uint8 * stream,int len)31 static void Buffer_Callback (void *userdata, Uint8 *stream, int len)
32 {
33 	unsigned int factor, RequestedFrames, MaxFrames, FrameCount;
34 	unsigned int StartOffset, EndOffset;
35 
36 	factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
37 	if ((unsigned int)len % factor != 0)
38 		Sys_Error("SDL sound: invalid buffer length passed to Buffer_Callback (%d bytes)\n", len);
39 
40 	RequestedFrames = (unsigned int)len / factor;
41 
42 	if (SndSys_LockRenderBuffer())
43 	{
44 		if (snd_usethreadedmixing)
45 		{
46 			S_MixToBuffer(stream, RequestedFrames);
47 			if (snd_blocked)
48 				memset(stream, snd_renderbuffer->format.width == 1 ? 0x80 : 0, len);
49 			SndSys_UnlockRenderBuffer();
50 			return;
51 		}
52 
53 		// Transfert up to a chunk of samples from snd_renderbuffer to stream
54 		MaxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
55 		if (MaxFrames > RequestedFrames)
56 			FrameCount = RequestedFrames;
57 		else
58 			FrameCount = MaxFrames;
59 		StartOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
60 		EndOffset = (snd_renderbuffer->startframe + FrameCount) % snd_renderbuffer->maxframes;
61 		if (StartOffset > EndOffset)  // if the buffer wraps
62 		{
63 			unsigned int PartialLength1, PartialLength2;
64 
65 			PartialLength1 = (snd_renderbuffer->maxframes - StartOffset) * factor;
66 			memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], PartialLength1);
67 
68 			PartialLength2 = FrameCount * factor - PartialLength1;
69 			memcpy(&stream[PartialLength1], &snd_renderbuffer->ring[0], PartialLength2);
70 		}
71 		else
72 			memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], FrameCount * factor);
73 
74 		snd_renderbuffer->startframe += FrameCount;
75 
76 		if (FrameCount < RequestedFrames && developer_insane.integer && vid_activewindow)
77 			Con_DPrintf("SDL sound: %u sample frames missing\n", RequestedFrames - FrameCount);
78 
79 		sdlaudiotime += RequestedFrames;
80 
81 		SndSys_UnlockRenderBuffer();
82 	}
83 }
84 
85 
86 /*
87 ====================
88 SndSys_Init
89 
90 Create "snd_renderbuffer" with the proper sound format if the call is successful
91 May return a suggested format if the requested format isn't available
92 ====================
93 */
SndSys_Init(const snd_format_t * requested,snd_format_t * suggested)94 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
95 {
96 	unsigned int buffersize;
97 	SDL_AudioSpec wantspec;
98 	SDL_AudioSpec obtainspec;
99 
100 	snd_threaded = false;
101 
102 	Con_DPrint ("SndSys_Init: using the SDL module\n");
103 
104 	// Init the SDL Audio subsystem
105 	if( SDL_InitSubSystem( SDL_INIT_AUDIO ) ) {
106 		Con_Print( "Initializing the SDL Audio subsystem failed!\n" );
107 		return false;
108 	}
109 
110 	buffersize = (unsigned int)ceil((double)requested->speed / 25.0); // 2048 bytes on 24kHz to 48kHz
111 
112 	// Init the SDL Audio subsystem
113 	wantspec.callback = Buffer_Callback;
114 	wantspec.userdata = NULL;
115 	wantspec.freq = requested->speed;
116 	wantspec.format = ((requested->width == 1) ? AUDIO_U8 : AUDIO_S16SYS);
117 	wantspec.channels = requested->channels;
118 	wantspec.samples = CeilPowerOf2(buffersize);  // needs to be a power of 2 on some platforms.
119 
120 	Con_Printf("Wanted audio Specification:\n"
121 				"\tChannels  : %i\n"
122 				"\tFormat    : 0x%X\n"
123 				"\tFrequency : %i\n"
124 				"\tSamples   : %i\n",
125 				wantspec.channels, wantspec.format, wantspec.freq, wantspec.samples);
126 
127 	if( SDL_OpenAudio( &wantspec, &obtainspec ) )
128 	{
129 		Con_Printf( "Failed to open the audio device! (%s)\n", SDL_GetError() );
130 		return false;
131 	}
132 
133 	Con_Printf("Obtained audio specification:\n"
134 				"\tChannels  : %i\n"
135 				"\tFormat    : 0x%X\n"
136 				"\tFrequency : %i\n"
137 				"\tSamples   : %i\n",
138 				obtainspec.channels, obtainspec.format, obtainspec.freq, obtainspec.samples);
139 
140 	// If we haven't obtained what we wanted
141 	if (wantspec.freq != obtainspec.freq ||
142 		wantspec.format != obtainspec.format ||
143 		wantspec.channels != obtainspec.channels)
144 	{
145 		SDL_CloseAudio();
146 
147 		// Pass the obtained format as a suggested format
148 		if (suggested != NULL)
149 		{
150 			suggested->speed = obtainspec.freq;
151 			// FIXME: check the format more carefully. There are plenty of unsupported cases
152 			suggested->width = ((obtainspec.format == AUDIO_U8) ? 1 : 2);
153 			suggested->channels = obtainspec.channels;
154 		}
155 
156 		return false;
157 	}
158 
159 	snd_threaded = true;
160 
161 	snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
162 	if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
163 		Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD);
164 
165 	sdlaudiotime = 0;
166 	SDL_PauseAudio( false );
167 
168 	return true;
169 }
170 
171 
172 /*
173 ====================
174 SndSys_Shutdown
175 
176 Stop the sound card, delete "snd_renderbuffer" and free its other resources
177 ====================
178 */
SndSys_Shutdown(void)179 void SndSys_Shutdown(void)
180 {
181 	SDL_CloseAudio();
182 
183 	if (snd_renderbuffer != NULL)
184 	{
185 		Mem_Free(snd_renderbuffer->ring);
186 		Mem_Free(snd_renderbuffer);
187 		snd_renderbuffer = NULL;
188 	}
189 }
190 
191 
192 /*
193 ====================
194 SndSys_Submit
195 
196 Submit the contents of "snd_renderbuffer" to the sound card
197 ====================
198 */
SndSys_Submit(void)199 void SndSys_Submit (void)
200 {
201 	// Nothing to do here (this sound module is callback-based)
202 }
203 
204 
205 /*
206 ====================
207 SndSys_GetSoundTime
208 
209 Returns the number of sample frames consumed since the sound started
210 ====================
211 */
SndSys_GetSoundTime(void)212 unsigned int SndSys_GetSoundTime (void)
213 {
214 	return sdlaudiotime;
215 }
216 
217 
218 /*
219 ====================
220 SndSys_LockRenderBuffer
221 
222 Get the exclusive lock on "snd_renderbuffer"
223 ====================
224 */
SndSys_LockRenderBuffer(void)225 qboolean SndSys_LockRenderBuffer (void)
226 {
227 	SDL_LockAudio();
228 	return true;
229 }
230 
231 
232 /*
233 ====================
234 SndSys_UnlockRenderBuffer
235 
236 Release the exclusive lock on "snd_renderbuffer"
237 ====================
238 */
SndSys_UnlockRenderBuffer(void)239 void SndSys_UnlockRenderBuffer (void)
240 {
241 	SDL_UnlockAudio();
242 }
243 
244 /*
245 ====================
246 SndSys_SendKeyEvents
247 
248 Send keyboard events originating from the sound system (e.g. MIDI)
249 ====================
250 */
SndSys_SendKeyEvents(void)251 void SndSys_SendKeyEvents(void)
252 {
253 	// not supported
254 }
255