/* * cd_sdl.c * * Copyright (C) 1996-1997 Id Software, Inc. * Taken from the Twilight project with modifications * to make it work with Hexen II: Hammer of Thyrion. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) #if defined(USE_SDL2) #include #else #include #endif #else #include "SDL.h" #endif #ifndef SDL_INIT_CDROM /* SDL dropped support for cd audio since v1.3.0 */ #pragma message("Warning: SDL CDAudio support disabled") #include "cd_null.c" #else /* SDL_INIT_CDROM */ #include "quakedef.hpp" static bool cdValid = false; static bool playing = false; static bool wasPlaying = false; static bool enabled = true; static bool playLooping = false; static byte remap[100]; static byte playTrack; static double endOfTrack = -1.0, pausetime = -1.0; static SDL_CD *cd_handle; static int cd_dev = -1; static float old_cdvolume; static bool hw_vol_works = true; static void CDAudio_Eject(void) { if (!cd_handle || !enabled) return; #ifdef __linux__ SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ #endif if (SDL_CDEject(cd_handle) < 0) console::info ("Unable to eject CD-ROM: %s\n", SDL_GetError ()); } static int CDAudio_GetAudioDiskInfo(void) { cdValid = false; if (!cd_handle) return -1; if ( ! CD_INDRIVE(SDL_CDStatus(cd_handle)) ) return -1; cdValid = true; return 0; } int CDAudio_Play(byte track, bool looping) { int len_m, len_s, len_f; if (!cd_handle || !enabled) return -1; if (!cdValid) { CDAudio_GetAudioDiskInfo(); if (!cdValid) return -1; } track = remap[track]; if (track < 1 || track > cd_handle->numtracks) { console::info ("CDAudio_Play: Bad track number %d.\n", track); return -1; } if (cd_handle->track[track-1].type == SDL_DATA_TRACK) { console::info ("CDAudio_Play: track %d is not audio\n", track); return -1; } if (playing) { if (playTrack == track) return 0; CDAudio_Stop(); } if (SDL_CDPlay(cd_handle, cd_handle->track[track-1].offset, cd_handle->track[track-1].length) < 0) { console::info ("CDAudio_Play: Unable to play track %d: %s\n", track, SDL_GetError ()); return -1; } playLooping = looping; playTrack = track; playing = true; FRAMES_TO_MSF(cd_handle->track[track-1].length, &len_m, &len_s, &len_f); endOfTrack = realtime + ((double)len_m * 60.0) + (double)len_s + (double)len_f / (double)CD_FPS; /* Add the pregap for the next track. This means that disc-at-once CDs * won't loop smoothly, but they wouldn't anyway so it doesn't really * matter. SDL doesn't give us pregap information anyway, so you'll * just have to live with it. */ endOfTrack += 2.0; pausetime = -1.0; if (bgmvolume.value == 0) /* don't bother advancing */ CDAudio_Pause (); return 0; } void CDAudio_Stop(void) { if (!cd_handle || !enabled) return; if (!playing) return; #ifdef __linux__ /* Don't really stop, but just pause: On some devices, the CDROMSTOP * ioctl causes any followup ioctls to fail for a considerable time. * observed with a TSSTcorp CDW/DVD SH-M522C drive with TS05 and TS08 * firmware versions running under a 2.6.27.25 kernel, and with a * Samsung DVD r/w drive running under 2.6.35.6 kernel. * Therefore, avoid dead stops if playback may be resumed shortly. */ if (SDL_CDPause(cd_handle) < 0) console::info ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); #else if (SDL_CDStop(cd_handle) < 0) console::info ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); #endif wasPlaying = false; playing = false; pausetime = -1.0; endOfTrack = -1.0; } void CDAudio_Pause(void) { if (!cd_handle || !enabled) return; if (!playing) return; if (SDL_CDPause(cd_handle) < 0) console::info ("Unable to pause CD-ROM: %s\n", SDL_GetError()); wasPlaying = playing; playing = false; pausetime = realtime; } void CDAudio_Resume(void) { if (!cd_handle || !enabled) return; if (!cdValid) return; if (!wasPlaying) return; if (SDL_CDResume(cd_handle) < 0) console::info ("Unable to resume CD-ROM: %s\n", SDL_GetError()); playing = true; endOfTrack += realtime - pausetime; pausetime = -1.0; } static int get_first_audiotrk (void) { int i; for (i = 0; i < cd_handle->numtracks; ++i) if (cd_handle->track[i].type != SDL_DATA_TRACK) return ++i; return 1; } static void CD_f (void) { const char *command; int ret, n; if (command::argc() < 2) { console::info("commands:"); console::info("on, off, reset, remap, \n"); console::info("play, stop, loop, pause, resume\n"); console::info("eject, info, next, prev\n"); return; } command = command::argv (1)->c_str(); if (std::is_eq(common::strcasecmp(command, "on"))) { enabled = true; return; } if (std::is_eq(common::strcasecmp(command, "off"))) { if (playing) CDAudio_Stop(); enabled = false; return; } if (std::is_eq(common::strcasecmp(command, "reset"))) { enabled = true; if (playing) CDAudio_Stop(); for (n = 0; n < 100; n++) remap[n] = n; CDAudio_GetAudioDiskInfo(); return; } if (std::is_eq(common::strcasecmp(command, "remap"))) { ret = command::argc() - 2; if (ret <= 0) { for (n = 1; n < 100; n++) if (remap[n] != n) console::info(" %u -> %u\n", n, remap[n]); return; } for (n = 1; n <= ret; n++) remap[n] = atoi(command::argv (n + 1)->c_str()); return; } if (!cdValid) { CDAudio_GetAudioDiskInfo(); if (!cdValid) { console::info("No CD in player.\n"); return; } } if (std::is_eq(common::strcasecmp(command, "play"))) { n = atoi(command::argv (2)->c_str()); if (n == 0) n = 1; CDAudio_Play((byte)n, false); return; } if (std::is_eq(common::strcasecmp(command, "loop"))) { CDAudio_Play((byte)atoi(command::argv (2)->c_str()), true); return; } if (std::is_eq(common::strcasecmp(command, "stop"))) { CDAudio_Stop(); return; } if (std::is_eq(common::strcasecmp(command, "pause"))) { CDAudio_Pause(); return; } if (std::is_eq(common::strcasecmp(command, "resume"))) { CDAudio_Resume(); return; } if (std::is_eq(common::strcasecmp(command, "eject"))) { if (playing) CDAudio_Stop(); CDAudio_Eject(); cdValid = false; return; } if (std::is_eq(common::strcasecmp(command, "info"))) { int current_min, current_sec, current_frame; int length_min, length_sec, length_frame; console::info ("%u tracks\n", cd_handle->numtracks); if (playing) console::info("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack); else if (wasPlaying) console::info("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack); if (playing || wasPlaying) { SDL_CDStatus(cd_handle); FRAMES_TO_MSF(cd_handle->cur_frame, ¤t_min, ¤t_sec, ¤t_frame); FRAMES_TO_MSF(cd_handle->track[playTrack-1].length, &length_min, &length_sec, &length_frame); console::info ("Current position: %d:%02d.%02d (of %d:%02d.%02d)\n", current_min, current_sec, current_frame * 60 / CD_FPS, length_min, length_sec, length_frame * 60 / CD_FPS); } console::info("Volume is %f\n", bgmvolume.value); return; } if (std::is_eq(common::strcasecmp(command, "next"))) { if (playTrack == cd_handle->numtracks) playTrack = get_first_audiotrk() - 1; CDAudio_Play(playTrack + 1, playLooping); return; } if (std::is_eq(common::strcasecmp(command, "prev"))) { if (playTrack == get_first_audiotrk()) playTrack = cd_handle->numtracks + 1; CDAudio_Play(playTrack - 1, playLooping); return; } console::info ("cd: unknown command \"%s\"\n", command); } static bool CD_GetVolume (void *unused) { /* FIXME: write proper code in here when SDL supports cdrom volume control some day. */ return false; } static bool CD_SetVolume (void *unused) { /* FIXME: write proper code in here when SDL supports cdrom volume control some day. */ return false; } static bool CDAudio_SetVolume (float value) { if (!cd_handle || !enabled) return false; old_cdvolume = value; if (value == 0.0f) CDAudio_Pause (); else CDAudio_Resume(); if (!hw_vol_works) { return false; } else { /* FIXME: write proper code in here when SDL supports cdrom volume control some day. */ return CD_SetVolume (NULL); } } void CDAudio_Update(void) { CDstatus curstat; /* static double lastchk;*/ if (!cd_handle || !enabled) return; if (old_cdvolume != bgmvolume.value) CDAudio_SetVolume (bgmvolume.value); /* if (playing && realtime > lastchk)*/ if (playing && realtime > endOfTrack) { /* lastchk = realtime + 2;*/ /* two seconds between chks */ curstat = SDL_CDStatus(cd_handle); if (curstat != CD_PLAYING && curstat != CD_PAUSED) { playing = false; endOfTrack = -1.0; if (playLooping) CDAudio_Play(playTrack, true); } } } static const char *get_cddev_arg (const char *arg) { #if defined(_WIN32) /* arg should be like "D:\", make sure it is so, * but tolerate args like "D" or "D:", as well. */ static char drive[4]; if (!arg || ! *arg) return NULL; if (arg[1] != '\0') { if (arg[1] != ':') return NULL; if (arg[2] != '\0') { if (arg[2] != '\\' && arg[2] != '/') return NULL; if (arg[3] != '\0') return NULL; } } if (*arg >= 'A' && *arg <= 'Z') { drive[0] = *arg; drive[1] = ':'; drive[2] = '\\'; drive[3] = '\0'; return drive; } else if (*arg >= 'a' && *arg <= 'z') { /* make it uppercase for SDL */ drive[0] = *arg - ('a' - 'A'); drive[1] = ':'; drive[2] = '\\'; drive[3] = '\0'; return drive; } return NULL; #else if (!arg || ! *arg) return NULL; return arg; #endif } static void export_cddev_arg (void) { /* Bad ugly hack to workaround SDL's cdrom device detection. * not needed for windows due to the way SDL_cdrom works. */ #if !defined(_WIN32) auto i = common::check_param("-cddev"); if (i.has_value() && i.value() < common::_argv.size() - 1 && common::_argv[i.value()+1][0] != '\0') { static char arg[64]; q_snprintf(arg, sizeof(arg), "SDL_CDROM=%s", common::_argv[i.value()+1].c_str()); putenv(arg); } #endif } int CDAudio_Init(void) { if (common::safe_mode || common::check_param("-nocdaudio").has_value()) return -1; export_cddev_arg(); if (SDL_InitSubSystem(SDL_INIT_CDROM) < 0) { console::info("Couldn't init SDL cdrom: %s\n", SDL_GetError()); return -1; } const int sdl_num_drives = SDL_CDNumDrives(); console::info ("SDL detected %d CD-ROM drive%c\n", sdl_num_drives, sdl_num_drives == 1 ? ' ' : 's'); if (sdl_num_drives < 1) return -1; if (const auto dev = common::check_param("-cddev"); dev.has_value() && dev.value() < common::_argv.size() - 1) { const char *userdev = get_cddev_arg(common::_argv[dev.value()+1].c_str()); if (!userdev) { console::info("Invalid argument to -cddev\n"); return -1; } for (auto i = 0; i < sdl_num_drives; i++) { if (std::is_eq(common::strcasecmp(SDL_CDName(i), userdev))) { cd_dev = i; break; } } if (cd_dev == -1) { console::info("SDL couldn't find cdrom device %s\n", userdev); return -1; } } if (cd_dev == -1) cd_dev = 0; /* default drive */ cd_handle = SDL_CDOpen(cd_dev); if (!cd_handle) { console::info ("CDAudio_Init: Unable to open CD-ROM drive %s (%s)\n", SDL_CDName(cd_dev), SDL_GetError()); return -1; } for (auto i = 0; i < 100; i++) remap[i] = i; enabled = true; old_cdvolume = bgmvolume.value; console::info("CDAudio initialized (SDL, using %s)\n", SDL_CDName(cd_dev)); if (CDAudio_GetAudioDiskInfo()) { console::info("CDAudio_Init: No CD in drive\n"); cdValid = false; } command::add ("cd", CD_f); hw_vol_works = CD_GetVolume (NULL); /* no SDL support at present. */ if (hw_vol_works) hw_vol_works = CDAudio_SetVolume (bgmvolume.value); return 0; } void CDAudio_Shutdown(void) { if (!cd_handle) return; CDAudio_Stop(); if (hw_vol_works) CD_SetVolume (NULL); /* no SDL support at present. */ #ifdef __linux__ SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ #endif SDL_CDClose(cd_handle); cd_handle = NULL; cd_dev = -1; SDL_QuitSubSystem(SDL_INIT_CDROM); } #endif /* SDL_INIT_CDROM */