/* * This file is part of sidplayfp, a console SID player. * * Copyright 2011-2013 Leandro Nini * Copyright 2000-2001 Simon White * * 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. */ #include "player.h" #include #include #include #include #include #include #include using std::cout; using std::cerr; using std::endl; #include #ifdef HAVE_UNISTD_H # include #endif #include "utils.h" #include "keyboard.h" #include "audio/AudioDrv.h" #include "audio/wav/WavFile.h" #include "ini/types.h" #include #include #include // Previous song select timeout (3 secs) #define SID2_PREV_SONG_TIMEOUT 4 #ifdef HAVE_SIDPLAYFP_BUILDERS_RESIDFP_H # include const char ConsolePlayer::RESIDFP_ID[] = "ReSIDfp"; #endif #ifdef HAVE_SIDPLAYFP_BUILDERS_RESID_H # include const char ConsolePlayer::RESID_ID[] = "ReSID"; #endif #ifdef HAVE_SIDPLAYFP_BUILDERS_HARDSID_H # include const char ConsolePlayer::HARDSID_ID[] = "HardSID"; #endif uint8_t* loadRom(const SID_STRING &romPath, const int size) { SID_IFSTREAM is(romPath.c_str(), std::ios::binary); if (is.is_open()) { try { uint8_t *buffer = new uint8_t[size]; is.read((char*)buffer, size); if (!is.fail()) { is.close(); return buffer; } delete [] buffer; } catch (std::bad_alloc const &ba) {} } return 0; } uint8_t* loadRom(const SID_STRING &romPath, const int size, const TCHAR defaultRom[]) { // Try to load given rom if (!romPath.empty()) { uint8_t* buffer = loadRom(romPath, size); if (buffer) return buffer; } // Fallback to default rom path try { SID_STRING dataPath(utils::getDataPath()); dataPath.append(SEPARATOR).append(TEXT("sidplayfp")).append(SEPARATOR).append(defaultRom); #if !defined _WIN32 && defined HAVE_UNISTD_H if (::access(dataPath.c_str(), R_OK) != 0) { dataPath = PKGDATADIR; dataPath.append(defaultRom); } #endif return loadRom(dataPath, size); } catch (utils::error const &e) { return 0; } } ConsolePlayer::ConsolePlayer (const char * const name) : Event("External Timer\n"), m_name(name), m_tune(0), m_state(playerStopped), m_outfile(NULL), m_context(NULL), m_filename(""), m_quietLevel(0), m_verboseLevel(0), m_cpudebug(false) { // Other defaults m_filter.enabled = true; m_driver.device = NULL; m_driver.sid = EMU_RESIDFP; m_timer.start = 0; m_timer.length = 0; // FOREVER m_timer.valid = false; m_track.first = 0; m_track.selected = 0; m_track.loop = false; m_track.single = false; m_speed.current = 1; m_speed.max = 32; // Read default configuration m_iniCfg.read (); m_engCfg = m_engine.config (); { // Load ini settings IniConfig::audio_section audio = m_iniCfg.audio(); IniConfig::emulation_section emulation = m_iniCfg.emulation(); // INI Configuration Settings m_engCfg.forceC64Model = emulation.modelForced; m_engCfg.defaultC64Model = emulation.modelDefault; m_engCfg.defaultSidModel = emulation.sidModel; m_engCfg.forceSidModel = emulation.forceModel; m_engCfg.frequency = audio.frequency; m_engCfg.playback = audio.playback; m_precision = audio.precision; m_filter.enabled = emulation.filter; m_filter.bias = emulation.bias; m_filter.filterCurve6581 = emulation.filterCurve6581; m_filter.filterCurve8580 = emulation.filterCurve8580; if (!emulation.engine.empty()) { if (emulation.engine.compare(TEXT("RESIDFP")) == 0) { m_driver.sid = EMU_RESIDFP; } else if (emulation.engine.compare(TEXT("RESID")) == 0) { m_driver.sid = EMU_RESID; } #ifdef HAVE_SIDPLAYFP_BUILDERS_HARDSID_H else if (emulation.engine.compare(TEXT("HARDSID")) == 0) { m_driver.sid = EMU_HARDSID; m_driver.output = OUT_NULL; } #endif else if (emulation.engine.compare(TEXT("NONE")) == 0) { m_driver.sid = EMU_NONE; } } } createOutput (OUT_NULL, NULL); createSidEmu (EMU_NONE); uint8_t *kernalRom = loadRom((m_iniCfg.sidplay2()).kernalRom, 8192, TEXT("kernal")); uint8_t *basicRom = loadRom((m_iniCfg.sidplay2()).basicRom, 8192, TEXT("basic")); uint8_t *chargenRom = loadRom((m_iniCfg.sidplay2()).chargenRom, 4096, TEXT("chargen")); m_engine.setRoms(kernalRom, basicRom, chargenRom); delete [] kernalRom; delete [] basicRom; delete [] chargenRom; } IAudio* ConsolePlayer::getWavFile(const SidTuneInfo *tuneInfo) { const char *title = m_outfile; // Generate a name for the wav file if (title == NULL) { title = tuneInfo->dataFileName(); const size_t length = strlen(title); size_t i = length; while (i > 0) { if (title[--i] == '.') break; } if (!i) i = length; std::string name(title, i); // Change name based on subtune if (tuneInfo->songs() > 1) { std::ostringstream sstream; sstream << "[" << tuneInfo->currentSong() << "]"; name.append(sstream.str()); } name.append(WavFile::extension()); title = name.c_str(); } return new WavFile(title); } // Create the output object to process sound buffer bool ConsolePlayer::createOutput (OUTPUTS driver, const SidTuneInfo *tuneInfo) { // Remove old audio driver m_driver.null.close (); m_driver.selected = &m_driver.null; if (m_driver.device != NULL) { if (m_driver.device != &m_driver.null) delete m_driver.device; m_driver.device = NULL; } // Create audio driver switch (driver) { case OUT_NULL: m_driver.device = &m_driver.null; break; case OUT_SOUNDCARD: try { m_driver.device = new audioDrv(); } catch (std::bad_alloc const &ba) { m_driver.device = 0; } break; case OUT_WAV: try { m_driver.device = getWavFile(tuneInfo); } catch (std::bad_alloc const &ba) { m_driver.device = 0; } break; default: break; } // Audio driver failed if (!m_driver.device) { m_driver.device = &m_driver.null; displayError (ERR_NOT_ENOUGH_MEMORY); return false; } // Configure with user settings m_driver.cfg.frequency = m_engCfg.frequency; m_driver.cfg.precision = m_precision; m_driver.cfg.channels = 1; // Mono m_driver.cfg.bufSize = 0; // Recalculate if (m_engCfg.playback == SidConfig::STEREO) m_driver.cfg.channels = 2; { // Open the hardware bool err = false; if (!m_driver.device->open (m_driver.cfg)) err = true; // Can't open the same driver twice if (driver != OUT_NULL) { if (!m_driver.null.open (m_driver.cfg)) err = true; } if (err) { displayError(m_driver.device->getErrorString()); return false; } } // See what we got m_engCfg.frequency = m_driver.cfg.frequency; m_precision = m_driver.cfg.precision; switch (m_driver.cfg.channels) { case 1: if (m_engCfg.playback == SidConfig::STEREO) m_engCfg.playback = SidConfig::MONO; break; case 2: if (m_engCfg.playback != SidConfig::STEREO) m_engCfg.playback = SidConfig::STEREO; break; default: cerr << m_name << ": " << "ERROR: " << m_driver.cfg.channels << " audio channels not supported" << endl; return false; } return true; } // Create the sid emulation bool ConsolePlayer::createSidEmu (SIDEMUS emu) { // Remove old driver and emulation if (m_engCfg.sidEmulation) { sidbuilder *builder = m_engCfg.sidEmulation; m_engCfg.sidEmulation = NULL; m_engine.config (m_engCfg); delete builder; } // Now setup the sid emulation switch (emu) { #ifdef HAVE_SIDPLAYFP_BUILDERS_RESIDFP_H case EMU_RESIDFP: { try { ReSIDfpBuilder *rs = new ReSIDfpBuilder( RESIDFP_ID ); m_engCfg.sidEmulation = rs; if (!rs->getStatus()) goto createSidEmu_error; rs->create ((m_engine.info ()).maxsids()); if (!rs->getStatus()) goto createSidEmu_error; if (m_filter.filterCurve6581) rs->filter6581Curve(m_filter.filterCurve6581); if (m_filter.filterCurve8580) rs->filter8580Curve((double)m_filter.filterCurve8580); } catch (std::bad_alloc const &ba) {} break; } #endif // HAVE_SIDPLAYFP_BUILDERS_RESIDFP_H #ifdef HAVE_SIDPLAYFP_BUILDERS_RESID_H case EMU_RESID: { try { ReSIDBuilder *rs = new ReSIDBuilder( RESID_ID ); m_engCfg.sidEmulation = rs; if (!rs->getStatus()) goto createSidEmu_error; rs->create ((m_engine.info ()).maxsids()); if (!rs->getStatus()) goto createSidEmu_error; rs->bias(m_filter.bias); } catch (std::bad_alloc const &ba) {} break; } #endif // HAVE_SIDPLAYFP_BUILDERS_RESID_H #ifdef HAVE_SIDPLAYFP_BUILDERS_HARDSID_H case EMU_HARDSID: { try { HardSIDBuilder *hs = new HardSIDBuilder( HARDSID_ID ); m_engCfg.sidEmulation = hs; if (!hs->getStatus()) goto createSidEmu_error; hs->create ((m_engine.info ()).maxsids()); if (!hs->getStatus()) goto createSidEmu_error; } catch (std::bad_alloc const &ba) {} break; } #endif // HAVE_SIDPLAYFP_BUILDERS_HARDSID_H default: // Emulation Not yet handled // This default case results in the default // emulation break; } if (!m_engCfg.sidEmulation) { if (emu > EMU_DEFAULT) { // No sid emulation? displayError (ERR_NOT_ENOUGH_MEMORY); return false; } } if (m_engCfg.sidEmulation) { /* set up SID filter. HardSID just ignores call with def. */ m_engCfg.sidEmulation->filter(m_filter.enabled); } return true; createSidEmu_error: displayError (m_engCfg.sidEmulation->error ()); delete m_engCfg.sidEmulation; m_engCfg.sidEmulation = NULL; return false; } bool ConsolePlayer::open (void) { if ((m_state & ~playerFast) == playerRestart) { if (m_quietLevel < 2) cerr << endl; if (m_state & playerFast) m_driver.selected->reset (); m_state = playerStopped; } // Select the required song m_track.selected = m_tune.selectSong (m_track.selected); if (m_engine.load (&m_tune) < 0) { displayError (m_engine.error ()); return false; } // Get tune details const SidTuneInfo *tuneInfo = m_tune.getInfo (); if (!m_track.single) m_track.songs = tuneInfo->songs(); if (!createOutput (m_driver.output, tuneInfo)) return false; if (!createSidEmu (m_driver.sid)) return false; // Configure engine with settings if (!m_engine.config (m_engCfg)) { // Config failed displayError (m_engine.error ()); return false; } // Start the player. Do this by fast // forwarding to the start position m_driver.selected = &m_driver.null; m_speed.current = m_speed.max; m_engine.fastForward (100 * m_speed.current); m_engine.mute(0, 0, v1mute); m_engine.mute(0, 1, v2mute); m_engine.mute(0, 2, v3mute); m_engine.mute(1, 0, v4mute); m_engine.mute(1, 1, v5mute); m_engine.mute(1, 2, v6mute); // As yet we don't have a required songlength // so try the songlength database if (!m_timer.valid) { const int_least32_t length = m_database.length (m_tune); if (length > 0) m_timer.length = length; } // Set up the play timer m_context = m_engine.getEventContext(); m_timer.stop = 0; m_timer.stop += m_timer.length; if (m_timer.valid) { // Length relative to start m_timer.stop += m_timer.start; } else { // Check to make start time dosen't exceed end if (m_timer.stop & (m_timer.start >= m_timer.stop)) { displayError ("ERROR: Start time exceeds song length!"); return false; } } m_timer.current = ~0; m_state = playerRunning; // Update display menu(); event(); return true; } void ConsolePlayer::close () { m_engine.stop(); if (m_state == playerExit) { // Natural finish emuflush (); if (m_driver.file) cerr << (char) 7; // Bell } else // Destroy buffers m_driver.selected->reset (); // Shutdown drivers, etc createOutput (OUT_NULL, NULL); createSidEmu (EMU_NONE); m_engine.load (NULL); m_engine.config (m_engCfg); if (m_quietLevel < 2) { // Correctly leave ansi mode and get prompt to // end up in a suitable location if ((m_iniCfg.console ()).ansi) cerr << '\x1b' << "[0m"; #ifndef _WIN32 cerr << endl; #endif } } // Flush any hardware sid fifos so all music is played void ConsolePlayer::emuflush () { switch (m_driver.sid) { #ifdef HAVE_SIDPLAYFP_BUILDERS_HARDSID_H case EMU_HARDSID: ((HardSIDBuilder *)m_engCfg.sidEmulation)->flush (); break; #endif // HAVE_LIBHARDSID_BUILDER default: break; } } // Out play loop to be externally called bool ConsolePlayer::play () { if (m_state == playerRunning) { // Fill buffer short *buffer = m_driver.selected->buffer (); const uint_least32_t length = m_driver.cfg.bufSize; const uint_least32_t ret = m_engine.play (buffer, length); if (ret < length) { if (m_engine.isPlaying ()) { m_state = playerError; return false; } return false; } } switch (m_state) { case playerRunning: m_driver.selected->write (); // Deliberate run on case playerPaused: // Check for a keypress (approx 250ms rate, but really depends // on music buffer sizes). Don't do this for high quiet levels // as chances are we are under remote control. if ((m_quietLevel < 2) && _kbhit ()) decodeKeys (); return true; default: if (m_quietLevel < 2) cerr << endl; m_engine.stop (); #if HAVE_TSID == 1 if (m_tsid) { m_tsid.addTime ((int) m_timer.current, m_track.selected, m_filename); } #elif HAVE_TSID == 2 if (m_tsid) { char md5[SidTune::MD5_LENGTH + 1]; m_tune.createMD5 (md5); int_least32_t length = m_database.length (md5, m_track.selected); // ignore errors if (length < 0) length = 0; m_tsid.addTime (md5, m_filename, (uint) m_timer.current, m_track.selected, (uint) length); } #endif break; } return false; } void ConsolePlayer::stop () { m_state = playerStopped; m_engine.stop (); } // External Timer Event void ConsolePlayer::event (void) { const uint_least32_t seconds = m_engine.time(); if ( !m_quietLevel ) { cerr << "\b\b\b\b\b" << std::setw(2) << std::setfill('0') << ((seconds / 60) % 100) << ':' << std::setw(2) << std::setfill('0') << (seconds % 60) << std::flush; } if (seconds != m_timer.current) { m_timer.current = seconds; if (seconds == m_timer.start) { // Switch audio drivers. m_driver.selected = m_driver.device; memset (m_driver.selected->buffer (), 0, m_driver.cfg.bufSize); m_speed.current = 1; m_engine.fastForward (100); if (m_cpudebug) m_engine.debug (true, NULL); } else if (m_timer.stop && (seconds == m_timer.stop)) { m_state = playerExit; for (;;) { if (m_track.single) return; // Move to next track m_track.selected++; if (m_track.selected > m_track.songs) m_track.selected = 1; if (m_track.selected == m_track.first) return; m_state = playerRestart; break; } if (m_track.loop) m_state = playerRestart; } } // Units in C64 clock cycles m_context->schedule (*this, 900000, EVENT_CLOCK_PHI1); } void ConsolePlayer::displayError (const char *error) { cerr << m_name << ": " << error << endl; } // Keyboard handling void ConsolePlayer::decodeKeys () { do { const int action = keyboard_decode (); if (action == A_INVALID) continue; switch (action) { case A_RIGHT_ARROW: m_state = playerFastRestart; if (!m_track.single) { m_track.selected++; if (m_track.selected > m_track.songs) m_track.selected = 1; } break; case A_LEFT_ARROW: m_state = playerFastRestart; if (!m_track.single) { // Only select previous song if less than timeout // else restart current song if ((m_engine.time()) < SID2_PREV_SONG_TIMEOUT) { m_track.selected--; if (m_track.selected < 1) m_track.selected = m_track.songs; } } break; case A_UP_ARROW: m_speed.current *= 2; if (m_speed.current > m_speed.max) m_speed.current = m_speed.max; m_engine.fastForward (100 * m_speed.current); break; case A_DOWN_ARROW: m_speed.current = 1; m_engine.fastForward (100); break; case A_HOME: m_state = playerFastRestart; m_track.selected = 1; break; case A_END: m_state = playerFastRestart; m_track.selected = m_track.songs; break; case A_PAUSED: if (m_state == playerPaused) { cerr << "\b\b\b\b\b\b\b\b\b"; // Just to make sure PAUSED is removed from screen cerr << " "; cerr << "\b\b\b\b\b\b\b\b\b"; m_state = playerRunning; } else { cerr << " [PAUSED]"; m_state = playerPaused; m_driver.selected->pause (); } break; case A_TOGGLE_VOICE1: v1mute = !v1mute; m_engine.mute(0, 0, v1mute); break; case A_TOGGLE_VOICE2: v2mute = !v2mute; m_engine.mute(0, 1, v2mute); break; case A_TOGGLE_VOICE3: v3mute = !v3mute; m_engine.mute(0, 2, v3mute); break; case A_TOGGLE_VOICE4: v4mute = !v4mute; m_engine.mute(1, 0, v4mute); break; case A_TOGGLE_VOICE5: v5mute = !v5mute; m_engine.mute(1, 1, v5mute); break; case A_TOGGLE_VOICE6: v6mute = !v6mute; m_engine.mute(1, 2, v6mute); break; case A_TOGGLE_FILTER: m_filter.enabled = !m_filter.enabled; m_engCfg.sidEmulation->filter(m_filter.enabled); break; case A_QUIT: m_state = playerFastExit; return; break; } } while (_kbhit ()); }