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