Fix issue with noise during startup
[xonotic/darkplaces.git] / cd_shared.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
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 */
20 // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All
21 // rights reserved.
22
23 #include "quakedef.h"
24 #include "cdaudio.h"
25 #include "sound.h"
26
27 // used by menu to ghost CD audio slider
28 cvar_t cdaudioinitialized = {CVAR_READONLY,"cdaudioinitialized","0","indicates if CD Audio system is active"};
29
30 #define MAX_PLAYLISTS 10
31 int music_playlist_active = -1;
32 int music_playlist_playing = 0; // 0 = not playing, 1 = playing, -1 = tried and failed
33
34 cvar_t music_playlist_index = {0, "music_playlist_index", "-1", "selects which of the music_playlist_ variables is the active one, -1 disables playlists"};
35 cvar_t music_playlist_list[MAX_PLAYLISTS] =
36 {
37         {0, "music_playlist_list0", "", "list of tracks to play"},
38         {0, "music_playlist_list1", "", "list of tracks to play"},
39         {0, "music_playlist_list2", "", "list of tracks to play"},
40         {0, "music_playlist_list3", "", "list of tracks to play"},
41         {0, "music_playlist_list4", "", "list of tracks to play"},
42         {0, "music_playlist_list5", "", "list of tracks to play"},
43         {0, "music_playlist_list6", "", "list of tracks to play"},
44         {0, "music_playlist_list7", "", "list of tracks to play"},
45         {0, "music_playlist_list8", "", "list of tracks to play"},
46         {0, "music_playlist_list9", "", "list of tracks to play"}
47 };
48 cvar_t music_playlist_current[MAX_PLAYLISTS] =
49 {
50         {0, "music_playlist_current0", "0", "current track index to play in list"},
51         {0, "music_playlist_current1", "0", "current track index to play in list"},
52         {0, "music_playlist_current2", "0", "current track index to play in list"},
53         {0, "music_playlist_current3", "0", "current track index to play in list"},
54         {0, "music_playlist_current4", "0", "current track index to play in list"},
55         {0, "music_playlist_current5", "0", "current track index to play in list"},
56         {0, "music_playlist_current6", "0", "current track index to play in list"},
57         {0, "music_playlist_current7", "0", "current track index to play in list"},
58         {0, "music_playlist_current8", "0", "current track index to play in list"},
59         {0, "music_playlist_current9", "0", "current track index to play in list"},
60 };
61 cvar_t music_playlist_random[MAX_PLAYLISTS] =
62 {
63         {0, "music_playlist_random0", "0", "enables random play order if 1, 0 is sequential play"},
64         {0, "music_playlist_random1", "0", "enables random play order if 1, 0 is sequential play"},
65         {0, "music_playlist_random2", "0", "enables random play order if 1, 0 is sequential play"},
66         {0, "music_playlist_random3", "0", "enables random play order if 1, 0 is sequential play"},
67         {0, "music_playlist_random4", "0", "enables random play order if 1, 0 is sequential play"},
68         {0, "music_playlist_random5", "0", "enables random play order if 1, 0 is sequential play"},
69         {0, "music_playlist_random6", "0", "enables random play order if 1, 0 is sequential play"},
70         {0, "music_playlist_random7", "0", "enables random play order if 1, 0 is sequential play"},
71         {0, "music_playlist_random8", "0", "enables random play order if 1, 0 is sequential play"},
72         {0, "music_playlist_random9", "0", "enables random play order if 1, 0 is sequential play"},
73 };
74 cvar_t music_playlist_sampleposition[MAX_PLAYLISTS] =
75 {
76         {0, "music_playlist_sampleposition0", "-1", "resume position for track, -1 restarts every time"},
77         {0, "music_playlist_sampleposition1", "-1", "resume position for track, -1 restarts every time"},
78         {0, "music_playlist_sampleposition2", "-1", "resume position for track, -1 restarts every time"},
79         {0, "music_playlist_sampleposition3", "-1", "resume position for track, -1 restarts every time"},
80         {0, "music_playlist_sampleposition4", "-1", "resume position for track, -1 restarts every time"},
81         {0, "music_playlist_sampleposition5", "-1", "resume position for track, -1 restarts every time"},
82         {0, "music_playlist_sampleposition6", "-1", "resume position for track, -1 restarts every time"},
83         {0, "music_playlist_sampleposition7", "-1", "resume position for track, -1 restarts every time"},
84         {0, "music_playlist_sampleposition8", "-1", "resume position for track, -1 restarts every time"},
85         {0, "music_playlist_sampleposition9", "-1", "resume position for track, -1 restarts every time"},
86 };
87
88 static qboolean wasPlaying = false;
89 static qboolean initialized = false;
90 static qboolean enabled = false;
91 static float cdvolume;
92 typedef char filename_t[MAX_QPATH];
93 #ifdef MAXTRACKS
94 static filename_t remap[MAXTRACKS];
95 #endif
96 static int faketrack = -1;
97
98 static qboolean cdPlaying = false;
99 static qboolean cdPlayLooping = false;
100 static unsigned char cdPlayTrack;
101
102 static void CDAudio_Eject (void)
103 {
104 }
105
106
107 static void CDAudio_CloseDoor (void)
108 {
109 }
110
111 static int CDAudio_GetAudioDiskInfo (void)
112 {
113         return -1;
114 }
115
116 // Helper for CDAudio_Play, the "cd" command, and the music_playlist system.
117 // Does _not_ act as NOP when a playlist is active, simply because this is used
118 // _by_ playlist code. So beware when calling this.
119 static void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition)
120 {
121         unsigned int track;
122         sfx_t* sfx;
123         char filename[MAX_QPATH];
124
125         Host_StartVideo();
126
127         if (!enabled)
128                 return;
129
130         if(tryreal && strspn(trackname, "0123456789") == strlen(trackname))
131         {
132                 track = (unsigned int) atoi(trackname);
133 #ifdef MAXTRACKS
134                 if(track > 0 && track < MAXTRACKS && *remap[track])
135                         trackname = remap[track];
136 #endif
137         }
138
139         if(tryreal && strspn(trackname, "0123456789") == strlen(trackname))
140         {
141                 track = (unsigned int) atoi(trackname);
142                 if (track < 1)
143                 {
144                         Con_DPrintf("CDAudio: Bad track number %u.\n", track);
145                         return;
146                 }
147         }
148         else
149                 track = 0;
150
151         // div0: I assume this code was intentionally there. Maybe turn it into a cvar?
152         if (cdPlaying && cdPlayTrack == track && faketrack == -1)
153                 return;
154         CDAudio_Stop ();
155
156         // Try playing a fake track (sound file) first
157         if(track >= 1)
158         {
159                                               dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.wav", track);
160                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.ogg", track);
161                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%03u.ogg", track);// added by motorsep
162                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%03u.ogg", track);// added by motorsep
163                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.wav", track);
164                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.ogg", track);
165                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%02u.ogg", track);// added by motorsep
166                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%02u.ogg", track);// added by motorsep
167         }
168         else
169         {
170                                               dpsnprintf(filename, sizeof(filename), "%s", trackname);
171                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.wav", trackname);
172                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.ogg", trackname);
173                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s", trackname);
174                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.wav", trackname);
175                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.ogg", trackname);
176                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s", trackname);
177                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.wav", trackname);
178                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.ogg", trackname);
179                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/%s.ogg", trackname); // added by motorsep
180                 if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/%s.ogg", trackname); // added by motorsep
181         }
182         if (FS_FileExists(filename) && (sfx = S_PrecacheSound (filename, false, false)))
183         {
184                 faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND, 1.0f);
185                 if (faketrack != -1)
186                 {
187                         if(track >= 1)
188                                 Con_DPrintf ("BGM track %u playing...\n", track);
189                         else
190                                 Con_DPrintf ("BGM track %s playing...\n", trackname);
191                 }
192         }
193
194         if (faketrack == -1)
195         {
196                 if(track >= 1)
197                         Con_DPrintf ("Could not load BGM track %u.\n", track);
198                 else
199                         Con_DPrintf ("Could not load BGM track %s.\n", trackname);
200                 return;
201         }
202
203         cdPlayLooping = looping;
204         cdPlayTrack = track;
205         cdPlaying = true;
206
207         if (cdvolume == 0.0 || bgmvolume.value == 0)
208                 CDAudio_Pause ();
209 }
210
211 void CDAudio_Play (int track, qboolean looping)
212 {
213         char buf[20];
214         if (music_playlist_index.integer >= 0)
215                 return;
216         dpsnprintf(buf, sizeof(buf), "%d", (int) track);
217         CDAudio_Play_byName(buf, looping, true, 0);
218 }
219
220 float CDAudio_GetPosition (void)
221 {
222         if(faketrack != -1)
223                 return S_GetChannelPosition(faketrack);
224         return -1;
225 }
226
227 static void CDAudio_StopPlaylistTrack(void);
228
229 void CDAudio_Stop (void)
230 {
231         if (!enabled)
232                 return;
233
234         // save the playlist position
235         CDAudio_StopPlaylistTrack();
236
237         if (faketrack != -1)
238         {
239                 S_StopChannel (faketrack, true, true);
240                 faketrack = -1;
241         }
242
243         wasPlaying = false;
244         cdPlaying = false;
245 }
246
247 void CDAudio_Pause (void)
248 {
249         if (!enabled || !cdPlaying || faketrack == -1)
250                 return;
251
252         S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, true);
253         wasPlaying = cdPlaying;
254         cdPlaying = false;
255 }
256
257
258 void CDAudio_Resume (void)
259 {
260         if (!enabled || cdPlaying || !wasPlaying || faketrack == -1)
261                 return;
262
263         S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, false);
264         cdPlaying = true;
265 }
266
267 static void CD_f (void)
268 {
269         const char *command;
270 #ifdef MAXTRACKS
271         int ret;
272         int n;
273 #endif
274
275         command = Cmd_Argv (1);
276
277         if (strcasecmp(command, "remap") != 0)
278                 Host_StartVideo();
279
280         if (strcasecmp(command, "on") == 0)
281         {
282                 enabled = true;
283                 return;
284         }
285
286         if (strcasecmp(command, "off") == 0)
287         {
288                 CDAudio_Stop();
289                 enabled = false;
290                 return;
291         }
292
293         if (strcasecmp(command, "reset") == 0)
294         {
295                 enabled = true;
296                 CDAudio_Stop();
297 #ifdef MAXTRACKS
298                 for (n = 0; n < MAXTRACKS; n++)
299                         *remap[n] = 0; // empty string, that is, unremapped
300 #endif
301                 CDAudio_GetAudioDiskInfo();
302                 return;
303         }
304
305         if (strcasecmp(command, "rescan") == 0)
306         {
307                 CDAudio_Shutdown();
308                 CDAudio_Startup();
309                 return;
310         }
311
312         if (strcasecmp(command, "remap") == 0)
313         {
314 #ifdef MAXTRACKS
315                 ret = Cmd_Argc() - 2;
316                 if (ret <= 0)
317                 {
318                         for (n = 1; n < MAXTRACKS; n++)
319                                 if (*remap[n])
320                                         Con_Printf("  %u -> %s\n", n, remap[n]);
321                         return;
322                 }
323                 for (n = 1; n <= ret; n++)
324                         strlcpy(remap[n], Cmd_Argv (n+1), sizeof(*remap));
325 #endif
326                 return;
327         }
328
329         if (strcasecmp(command, "close") == 0)
330         {
331                 CDAudio_CloseDoor();
332                 return;
333         }
334
335         if (strcasecmp(command, "play") == 0)
336         {
337                 if (music_playlist_index.integer >= 0)
338                         return;
339                 CDAudio_Play_byName(Cmd_Argv (2), false, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0);
340                 return;
341         }
342
343         if (strcasecmp(command, "loop") == 0)
344         {
345                 if (music_playlist_index.integer >= 0)
346                         return;
347                 CDAudio_Play_byName(Cmd_Argv (2), true, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0);
348                 return;
349         }
350
351         if (strcasecmp(command, "stop") == 0)
352         {
353                 if (music_playlist_index.integer >= 0)
354                         return;
355                 CDAudio_Stop();
356                 return;
357         }
358
359         if (strcasecmp(command, "pause") == 0)
360         {
361                 if (music_playlist_index.integer >= 0)
362                         return;
363                 CDAudio_Pause();
364                 return;
365         }
366
367         if (strcasecmp(command, "resume") == 0)
368         {
369                 if (music_playlist_index.integer >= 0)
370                         return;
371                 CDAudio_Resume();
372                 return;
373         }
374
375         if (strcasecmp(command, "eject") == 0)
376         {
377                 if (faketrack == -1)
378                         CDAudio_Stop();
379                 CDAudio_Eject();
380                 return;
381         }
382
383         if (strcasecmp(command, "info") == 0)
384         {
385                 CDAudio_GetAudioDiskInfo ();
386                 if (cdPlaying)
387                         Con_Printf("Currently %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack);
388                 else if (wasPlaying)
389                         Con_Printf("Paused %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack);
390                 if (cdvolume >= 0)
391                         Con_Printf("Volume is %f\n", cdvolume);
392                 else
393                         Con_Printf("Can't get CD volume\n");
394                 return;
395         }
396
397         Con_Printf("CD commands:\n");
398         Con_Printf("cd on - enables CD audio system\n");
399         Con_Printf("cd off - stops and disables CD audio system\n");
400         Con_Printf("cd reset - resets CD audio system (clears track remapping and re-reads disc information)\n");
401         Con_Printf("cd rescan - rescans disks in drives (to use another disc)\n");
402         Con_Printf("cd remap <remap1> [remap2] [remap3] [...] - chooses emulated CD tracks to play when a map asks for a particular track, this has many uses\n");
403         Con_Printf("cd close - closes CD tray\n");
404         Con_Printf("cd eject - stops playing music and opens CD tray to allow you to change disc\n");
405         Con_Printf("cd play <tracknumber> <startposition> - plays selected track in remapping table\n");
406         Con_Printf("cd loop <tracknumber> <startposition> - plays and repeats selected track in remapping table\n");
407         Con_Printf("cd stop - stops playing current CD track\n");
408         Con_Printf("cd pause - pauses CD playback\n");
409         Con_Printf("cd resume - unpauses CD playback\n");
410         Con_Printf("cd info - prints basic disc information (number of tracks, currently playing track, volume level)\n");
411 }
412
413 static void CDAudio_SetVolume (float newvol)
414 {
415         // If the volume hasn't changed
416         if (newvol == cdvolume)
417                 return;
418
419         // If the CD has been muted
420         if (newvol == 0.0f)
421                 CDAudio_Pause ();
422         else
423         {
424                 // If the CD has been unmuted
425                 if (cdvolume == 0.0f)
426                         CDAudio_Resume ();
427
428                 if (faketrack != -1)
429                         S_SetChannelVolume (faketrack, newvol);
430         }
431
432         cdvolume = newvol;
433 }
434
435 static void CDAudio_StopPlaylistTrack(void)
436 {
437         if (music_playlist_active >= 0 && music_playlist_active < MAX_PLAYLISTS && music_playlist_sampleposition[music_playlist_active].value >= 0)
438         {
439                 // save position for resume
440                 float position = CDAudio_GetPosition();
441                 Cvar_SetValueQuick(&music_playlist_sampleposition[music_playlist_active], position >= 0 ? position : 0);
442         }
443         music_playlist_active = -1;
444         music_playlist_playing = 0; // not playing
445 }
446
447 static void CDAudio_StartPlaylist(qboolean resume)
448 {
449         const char *list;
450         const char *t;
451         int index;
452         int current;
453         int randomplay;
454         int count;
455         int listindex;
456         float position;
457         char trackname[MAX_QPATH];
458         CDAudio_Stop();
459         index = music_playlist_index.integer;
460         if (index >= 0 && index < MAX_PLAYLISTS && bgmvolume.value > 0)
461         {
462                 list = music_playlist_list[index].string;
463                 current = music_playlist_current[index].integer;
464                 randomplay = music_playlist_random[index].integer;
465                 position = music_playlist_sampleposition[index].value;
466                 count = 0;
467                 trackname[0] = 0;
468                 if (list && list[0])
469                 {
470                         for (t = list;;count++)
471                         {
472                                 if (!COM_ParseToken_Console(&t))
473                                         break;
474                                 // if we don't find the desired track, use the first one
475                                 if (count == 0)
476                                         strlcpy(trackname, com_token, sizeof(trackname));
477                         }
478                 }
479                 if (count > 0)
480                 {
481                         // position < 0 means never resume track
482                         if (position < 0)
483                                 position = 0;
484                         // advance to next track in playlist if the last one ended
485                         if (!resume)
486                         {
487                                 position = 0;
488                                 current++;
489                                 if (randomplay)
490                                         current = (int)lhrandom(0, count);
491                         }
492                         // wrap playlist position if needed
493                         if (current >= count)
494                                 current = 0;
495                         // set current
496                         Cvar_SetValueQuick(&music_playlist_current[index], current);
497                         // get the Nth trackname
498                         if (current >= 0 && current < count)
499                         {
500                                 for (listindex = 0, t = list;;listindex++)
501                                 {
502                                         if (!COM_ParseToken_Console(&t))
503                                                 break;
504                                         if (listindex == current)
505                                         {
506                                                 strlcpy(trackname, com_token, sizeof(trackname));
507                                                 break;
508                                         }
509                                 }
510                         }
511                         if (trackname[0])
512                         {
513                                 CDAudio_Play_byName(trackname, false, false, position);
514                                 if (faketrack != -1)
515                                         music_playlist_active = index;
516                         }
517                 }
518         }
519         music_playlist_playing = music_playlist_active >= 0 ? 1 : -1;
520 }
521
522 void CDAudio_Update (void)
523 {
524         static int lastplaylist = -1;
525         if (!enabled)
526                 return;
527
528         CDAudio_SetVolume (bgmvolume.value);
529         if (music_playlist_playing > 0 && CDAudio_GetPosition() < 0)
530         {
531                 // this track ended, start a new track from the beginning
532                 CDAudio_StartPlaylist(false);
533                 lastplaylist = music_playlist_index.integer;
534         }
535         else if (lastplaylist != music_playlist_index.integer
536         || (bgmvolume.value > 0 && !music_playlist_playing && music_playlist_index.integer >= 0))
537         {
538                 // active playlist changed, save position and switch track
539                 CDAudio_StartPlaylist(true);
540                 lastplaylist = music_playlist_index.integer;
541         }
542 }
543
544 int CDAudio_Init (void)
545 {
546         int i;
547
548         if (cls.state == ca_dedicated)
549                 return -1;
550
551 // COMMANDLINEOPTION: Sound: -nocdaudio disables CD audio support
552         if (COM_CheckParm("-nocdaudio"))
553                 return -1;
554
555 #ifdef MAXTRACKS
556         for (i = 0; i < MAXTRACKS; i++)
557                 *remap[i] = 0;
558 #endif
559
560         Cvar_RegisterVariable(&cdaudioinitialized);
561         Cvar_SetValueQuick(&cdaudioinitialized, true);
562         enabled = true;
563
564         Cvar_RegisterVariable(&music_playlist_index);
565         for (i = 0;i < MAX_PLAYLISTS;i++)
566         {
567                 Cvar_RegisterVariable(&music_playlist_list[i]);
568                 Cvar_RegisterVariable(&music_playlist_current[i]);
569                 Cvar_RegisterVariable(&music_playlist_random[i]);
570                 Cvar_RegisterVariable(&music_playlist_sampleposition[i]);
571         }
572
573         Cmd_AddCommand("cd", CD_f, "execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage");
574
575         return 0;
576 }
577
578 int CDAudio_Startup (void)
579 {
580         if (COM_CheckParm("-nocdaudio"))
581                 return -1;
582
583         initialized = true;
584
585         Con_Print("CD Audio Initialized\n");
586
587         return 0;
588 }
589
590 void CDAudio_Shutdown (void)
591 {
592         if (!initialized)
593                 return;
594
595         CDAudio_Stop();
596         initialized = false;
597 }