1 /* $Id: coresound.c,v 1.9 2006/09/15 15:29:50 toad32767 Exp $ */
2 /**
3  ** 2005, 2006 by Marco Trillo
4  ** This file is part of UModPlayer, and is released by
5  ** its autors to the Public Domain.
6  ** In case it's not legally possible, its autors grant
7  ** anyone the right to use, redistribute and modify
8  ** this software for any purpose, without any conditions,
9  ** unless such conditions are required by law.
10  **
11  ** THIS FILE COMES WITHOUT ANY WARRANTY. THE AUTHORS
12  ** SHALL NOT BE LIABLE FOR ANY DAMAGE RESULTING BY THE
13  ** USE OR MISUSE OF THIS SOFTWARE.
14  **/
15 
16 /*
17  * =====================
18  *  SOUND RELATED STUFF
19  * =====================
20  */
21 
22 #include <umodplayer.h>
23 
24 #include <termios.h>
25 #include <sys/time.h>
26 
27 #include <messages.h>
28 #include <text.h>
29 #include <coresound.h>
30 
31 LOCAL void CoreSound_Monitor(void);
32 LOCAL Bool CoreSound_AudioPause(void);
33 LOCAL uint8_t aubuffer[BUFFER_SIZE];
34 LOCAL size_t bytes_played = 0;
35 
36 /*
37  * THE CORESOUND ARCHITECTURE
38  *
39  * The CoreSound system is used to play the loaded module (the `song').
40  * The CoreSound caller must provide it with a special function named `monitor'.
41  * The `monitor' function is used to display information
42  * on screen (or other devices) while the song is being played.
43  *
44  * Different `monitor's are used in different places. In example,
45  * the `notes' system uses a `monitor' which displays the notes on
46  * a row while playing; and the default `monitor' displays the current
47  * position in the song (such as order, row, time, etc.).
48  *
49  * The prototype of a monitor is this:
50  * void Monitor (void);
51  *
52  * The Monitor should consist on a loop which calls regularly the
53  * function `CoreSound_InjectPCM()'. The CoreSound_InjectPCM() function
54  * causes the song position to advance (by calling the appropriate ModPlug
55  * function) and plays some audio samples (defined by the BUFFER_SIZE constant).
56  *
57  * The Monitor should check regularly the `AudioActive' global variable.
58  * This variable will be TRUE when the song is still being played.
59  * When this variable is FALSE, the song has reached its end.
60  *
61  * Before returning, the Monitor should call the `CoreSound_Quit()' function
62  * to close the audio device. The Monitor should do this when the `AudioActive'
63  * variable becomes FALSE. Additionally, the Monitor might provide some other
64  * ways to stop the playing process.
65  * In example, some monitors stop playing when a key is pressed by the user.
66  *
67  * This is an skeleton for a monitor:
68  *
69  * void
70  * MyMonitor ()
71  * {
72  * 	for (;;) {
73  * 		CoreSound_InjectPCM();
74  *
75  * 		if (AudioActive == FALSE) {
76  * 			CoreSound_Quit();
77  * 			return;
78  * 		}
79  * 	}
80  * }
81  *
82  * This monitor will simply play the entire song until the end.
83  *
84  * To call CoreSound, use the `CoreSound_Start()' function
85  * passing (a pointer to) your monitor function as a parameter.
86  * In example:
87  *
88  * CoreSound_Start(MyMonitor);
89  *
90  * The `CoreSound_Start()' function will return when the monitor
91  * function returns.
92  *
93  * If you want to use the default monitor which CoreSound provides,
94  * call `CoreSound_StartMonitor()' instead (without parameters).
95  *
96  * This default monitor is used by the `play' and `playlist' commands.
97  */
98 
99 EXPORT void
CoreSound_InitOptions()100 CoreSound_InitOptions()
101 {
102 	ModPlug_Settings MPSettings;
103 
104 	MPSettings.mFlags = sets.flags;
105 	MPSettings.mChannels = sets.channels;
106 	MPSettings.mBits = CoreSound_BitsPerSample;
107 	MPSettings.mFrequency = sets.samplerate;
108 	MPSettings.mResamplingMode = sets.resampling;
109 	MPSettings.mReverbDepth = sets.reverbDepth;
110 	MPSettings.mReverbDelay = sets.reverbDelay;
111 	MPSettings.mBassAmount = sets.bassAmount;
112 	MPSettings.mBassRange = sets.bassRange;
113 	MPSettings.mSurroundDepth = sets.surroundDepth;
114 	MPSettings.mSurroundDelay = sets.surroundDelay;
115 	MPSettings.mLoopCount = 0;
116 
117 	ModPlug_SetSettings(&MPSettings);
118 	ModPlug_SetMasterVolume(file.mod, sets.vol);
119 }
120 
121 LOCAL Bool
CoreSound_InitAudio()122 CoreSound_InitAudio()
123 {
124 	int drv = -1;
125 	ao_sample_format fmt;
126 
127 	if (AudioDriver)
128 		drv = ao_driver_id(AudioDriver);
129 	else
130 		drv = ao_default_driver_id();
131 
132 	if (drv < 0)
133 		return FALSE;
134 
135 	memset(&fmt, 0, sizeof(fmt));
136 	fmt.rate = sets.samplerate;
137 	fmt.bits = 16;
138 	fmt.byte_format = AO_FMT_NATIVE;
139 	fmt.channels = sets.channels;
140 
141 	AO_Device = ao_open_live(drv, &fmt, 0);
142 	if (!AO_Device) {
143 		return FALSE;
144 	}
145 	CoreSound_BitsPerSample = fmt.bits;
146 	CoreSound_InitOptions();
147 	AudioActive = TRUE;
148 
149 	return TRUE;
150 }
151 
152 EXPORT void
CoreSound_InjectPCM()153 CoreSound_InjectPCM()
154 {
155 	int rlen;
156 
157 	if (!AudioActive)
158 		return;
159 
160 	rlen = ModPlug_Read(file.mod, aubuffer, BUFFER_SIZE);
161 
162 	if (rlen == 0) {
163 		CoreSound_CloseAudio();
164 		return;
165 	}
166 	ao_play(AO_Device, aubuffer, BUFFER_SIZE);
167 	bytes_played += BUFFER_SIZE;
168 }
169 
170 EXPORT Bool
CoreSound_StartMonitor()171 CoreSound_StartMonitor()
172 {
173 	return CoreSound_Start(CoreSound_Monitor);
174 }
175 
176 EXPORT Bool
CoreSound_Start(xproc MonitorFunction)177 CoreSound_Start(xproc MonitorFunction)
178 {
179 	if (file.name == NULL) {
180 		return FALSE;
181 	}
182 	ao_initialize();
183 	if (CoreSound_InitAudio() == FALSE) {
184 		error("%s", MESSAGE_AUDIO_ERROR);
185 		return FALSE;
186 	}
187 	bytes_played = 0;
188 	MonitorFunction();
189 
190 	return TRUE;
191 }
192 
193 EXPORT int
KeyPress()194 KeyPress()
195 {
196 	int keyHit;
197 	fd_set fds;
198 	struct timeval tv;
199 	struct termios term, _term;
200 
201 	tcgetattr(0, &_term);
202 	memcpy(&term, &_term, sizeof(term));
203 	cfmakeraw(&term);
204 	tcsetattr(0, TCSANOW, &term);
205 	FD_ZERO(&fds);
206 	FD_SET(0, &fds);
207 	tv.tv_sec = 0;
208 	tv.tv_usec = 0;
209 	keyHit = select(1, &fds, NULL, NULL, &tv);
210 	tcsetattr(0, TCSANOW, &_term);
211 
212 	return keyHit;
213 }
214 
215 LOCAL void
CoreSound_Monitor()216 CoreSound_Monitor()
217 {
218 	unsigned long seconds = 0;
219 	unsigned int bytesPerSample;
220 
221 	bytesPerSample = CoreSound_BitsPerSample >> 3; /* CoreSound_BitsPerSample / 8 */
222 
223 	puts(MESSAGE_PLAYING);
224 	for (;;) {
225 		/*
226 		 * Prepare and inject the PCM samples
227 		 */
228 		CoreSound_InjectPCM();
229 
230 		/*
231 		 * Check if we have reached end
232 		 */
233 		if (AudioActive == FALSE) {
234 			CoreSound_Quit();
235 			putchar('\n');
236 			return;
237 		}
238 		if (KeyPress()) {
239 			if (CoreSound_AudioPause() == TRUE) {
240 				putchar('\n');
241 				return;	/* if key S pressed -> bye */
242 			}
243 
244 			notice("%s\n", MESSAGE_PLAYING);
245 			CoreSound_InjectPCM();
246 		}
247 
248 		/*
249 		 * Calculate the seconds played
250 		 */
251 		seconds = ((bytes_played / bytesPerSample) / sets.channels) / (sets.samplerate);
252 
253 		notice("{%d:%02d}  ord: %d - pat: %d - row: %d - tempo: %d     \r",
254 		    seconds / 60, seconds % 60,
255 		    ModPlug_GetCurrentOrder(file.mod),
256 		    ModPlug_GetCurrentPattern(file.mod),
257 		    ModPlug_GetCurrentRow(file.mod),
258 		    ModPlug_GetCurrentTempo(file.mod)
259 		    );
260 		fflush(stdout);
261 	}
262 	return;
263 }
264 
265 LOCAL Bool
CoreSound_AudioPause()266 CoreSound_AudioPause()
267 {
268 	char *K;
269 
270 	CoreSound_CloseAudio();
271 
272 	for (;;) {
273 		notice("%s", MESSAGE_PAUSED);
274 		fflush(stdout);
275 		K = ReadString();
276 		if (K == NULL) {
277 			CoreSound_Quit();
278 			return TRUE;
279 		}
280 		if (*K == 'S') {
281 			CoreSound_Quit();
282 			free(K);
283 			return TRUE;
284 		} else if (*K == '\0') {
285 			free(K);
286 
287 			/*
288 			 * If CoreSound_InitAudio() fails, return TRUE to quit.
289 			 */
290 			return ! CoreSound_InitAudio();
291 		} else {
292 			free(K);
293 		}
294 	}
295 
296 	return FALSE;
297 }
298 
299 EXPORT void
CoreSound_CloseAudio()300 CoreSound_CloseAudio()
301 {
302 	ao_close(AO_Device);
303 	AudioActive = FALSE;
304 }
305 
306 EXPORT void
CoreSound_Quit()307 CoreSound_Quit()
308 {
309 	if (AudioActive)
310 		CoreSound_CloseAudio();
311 	ao_shutdown();
312 }
313