//---------------------------------------------------------------------------- // EDGE Humidity Music Player //---------------------------------------------------------------------------- // // Copyright (c) 2005 The EDGE Team. // // 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. // //---------------------------------------------------------------------------- // // Based on "oggplayer" code (Andrew Baker) // // TODO: the mechanics here are very similar to the oggplayer_c class // (e.g. Play and Stop methods are identical). Maybe factor out // this common behaviour (e.g. base class or mixin). // // FIXME: doesn't handle HARDWARE devices properly (don't need OpenAL). // #include "i_defs.h" #include "errorcodes.h" #include "i_defs_al.h" #include "i_system.h" #include "humdinger.h" #include "mus_2_midi.h" #include "epi/epi.h" #include "epi/errors.h" #include "epi/endianess.h" #include "epi/strings.h" #include "humidity/hum_buffer.h" static bool humding_inited = false; ALuint humdinger_c::music_source = NO_SOURCE; // // humdinger_c Constructor // humdinger_c::humdinger_c(Humidity::output_device_c *dev) : hum_dev(dev), cur_song(NULL), status(NOT_LOADED) { pcm_buf = new char[BUFFER_SIZE]; } // // humdinger_c Deconstructor // humdinger_c::~humdinger_c() { Close(); Humidity::CloseDevice(hum_dev); delete[] pcm_buf; pcm_buf = NULL; } // // humdinger_c::CreateMusicSource() // bool humdinger_c::CreateMusicSource() { if (music_source != NO_SOURCE) DeleteMusicSource(); alGetError(); alGenSources(1, &music_source); if (alGetError() != AL_NO_ERROR) { music_source = NO_SOURCE; return false; } alSource3f(music_source, AL_POSITION, 0.0, 0.0, 0.0); alSource3f(music_source, AL_VELOCITY, 0.0, 0.0, 0.0); alSource3f(music_source, AL_DIRECTION, 0.0, 0.0, 0.0); alSourcef (music_source, AL_ROLLOFF_FACTOR, 0.0 ); alSourcei (music_source, AL_SOURCE_RELATIVE, AL_TRUE ); return true; } // // humdinnger_c::DeleteMusicSource // void humdinger_c::DeleteMusicSource() { alDeleteSources(1, &music_source); music_source = NO_SOURCE; } // // humdinger_c::StreamIntoBuffer() // bool humdinger_c::StreamIntoBuffer(int buffer) { int p_res = hum_dev->PlaySome(pcm_buf, BUFFER_SIZE / 4 /* FIXME */, Humidity::S16); bool eof = (p_res == Humidity::PLAY_DONE); if (p_res == Humidity::PLAY_ERR) { // Construct an error message epi::string_c s; s = "[humdinger_c::StreamIntoBuffer] Failed: "; s += hum_dev->GetError(); throw epi::error_c(ERR_MUSIC, s.GetString()); /* NOT REACHED */ } alBufferData(buffer, format, pcm_buf, BUFFER_SIZE, 22050); int al_err = alGetError(); if (al_err != AL_NO_ERROR) throw epi::error_c(ERR_MUSIC, "[humdinger_c::StreamIntoBuffer] alBufferData() Failed"); return ! eof; } // // humdinger_c::SetVolume() // void humdinger_c::SetVolume(float gain) { if (music_source == NO_SOURCE) return; alSourcef(music_source, AL_GAIN, gain); } // // humdinger_c::Open() // int humdinger_c::Open(byte *data, int length) { if (length == 0 || ! data) return -1; if (status != NOT_LOADED) Close(); if (!CreateMusicSource()) return -1; L_WriteDebug("humdinger_c::Open (%d bytes)\n", length); // check for MUS format bool need_free_midi = false; if (length > 4 && data[0] == 'M' && data[1] == 'U' && data[2] == 'S' && data[3] == 0x1A) { L_WriteDebug("Format is MUS : doing conversion...\n"); byte *midi_data; int midi_len; if (! Mus2Midi::Convert(data, length, &midi_data, &midi_len, Mus2Midi::DOOM_DIVIS, true)) { I_Warning("Unable to convert MUS to MIDI !\n"); return -1; } data = midi_data; length = midi_len; need_free_midi = true; L_WriteDebug("Conversion done: new length is %d\n", length); } // FIXME IN HUMIDITY: cur_song = Humidity::LoadSong(data, length); cur_song = new Humidity::midi_song_c(); if (cur_song->Decode(data, length) != Humidity::midi_song_c::LOAD_OK) { I_Warning("Unable to decode MIDI !\n"); delete cur_song; cur_song = NULL; return -1; } L_WriteDebug("Decoded MIDI successfully\n"); if (need_free_midi) { Mus2Midi::Free(data); data = NULL; } if (! hum_dev->Start(cur_song, true /* looping */)) { I_Warning("Unable to play MIDI: %s\n", hum_dev->GetError()); delete cur_song; cur_song = NULL; return -1; } L_WriteDebug("Started song successfully\n"); // clear any previous AL error alGetError(); format = AL_FORMAT_STEREO16; //!!! FIXME for mono alGenBuffers(NUM_BUFFERS, buffers); int result = alGetError(); if (result != AL_NO_ERROR) throw epi::error_c(ERR_MUSIC, "[humdinger_c::Open] alGenBuffers() Failed"); // Loaded, but not playing status = STOPPED; // L_WriteDebug("humdinger_c::Open DONE\n"); return 2; // dummy value } // // humdinger_c::Close() // void humdinger_c::Close() { L_WriteDebug("humdinger_c::Close\n"); if (status == NOT_LOADED) return; // Stop playback Stop(); // Release Resource alDeleteBuffers(NUM_BUFFERS, buffers); DeleteMusicSource(); if (cur_song) { Humidity::FreeSong(cur_song); cur_song = NULL; } status = NOT_LOADED; // L_WriteDebug("humdinger_c::Close DONE\n"); } // // humdinger_c::Pause() // void humdinger_c::Pause() { if (status != PLAYING) return; alSourcePause(music_source); status = PAUSED; } // // humdinger_c::Resume() // void humdinger_c::Resume() { if (status != PAUSED) return; alSourcePlay(music_source); status = PLAYING; } // // humdinger_c::Play() // void humdinger_c::Play(bool loop, float gain) { if (status != NOT_LOADED && status != STOPPED) return; if (nosound) return; L_WriteDebug("humdinger_c::Play\n"); // clear any previous AL error alGetError(); // Load up initial buffer data int buffer; for (buffer=0; buffer < NUM_BUFFERS; buffer++) { if (!StreamIntoBuffer(buffers[buffer])) throw epi::error_c(ERR_MUSIC, "[humdinger_c::Play] Failed to load into buffers"); } SetVolume(gain); // Update OpenAL alSourceQueueBuffers(music_source, NUM_BUFFERS, buffers); alSourcePlay(music_source); looping = loop; status = PLAYING; // L_WriteDebug("humdinger_c::Play DONE\n"); } // // humdinger_c::Stop() // void humdinger_c::Stop() { ALuint buffer; int queued; if (status != PLAYING && status != PAUSED) return; // Stop playback alSourceStop(music_source); // Remove any queue buffer alGetSourcei(music_source, AL_BUFFERS_QUEUED, &queued); while (queued) { alSourceUnqueueBuffers(music_source, 1, &buffer); queued--; } status = STOPPED; } // // humdinger_c::Ticker() // void humdinger_c::Ticker() { if (status != PLAYING) return; // L_WriteDebug("humdinger_c::Ticker\n"); int result; int processed; bool active = true; // clear any previous AL error alGetError(); alGetSourcei(music_source, AL_BUFFERS_PROCESSED, &processed); while (processed--) { ALuint buffer; alSourceUnqueueBuffers(music_source, 1, &buffer); result = alGetError(); if (result != AL_NO_ERROR) throw epi::error_c(ERR_MUSIC, "[humdinger_c::Ticker] alSourceUnqueueBuffers() Failed"); active = StreamIntoBuffer(buffer); alSourceQueueBuffers(music_source, 1, &buffer); result = alGetError(); if (result != AL_NO_ERROR) throw epi::error_c(ERR_MUSIC, "[humdinger_c::Ticker] alSourceQueueBuffers() Failed"); } // Check for underrun if (active) { int state; alGetSourcei(music_source, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { L_WriteDebug("humdinger_c::Ticker - UNDERRUN\n"); alGetSourcei(music_source, AL_BUFFERS_PROCESSED, &processed); alSourcePlay(music_source); } } else { L_WriteDebug("humdinger_c::Ticker - END OF SONG\n"); // No more data. Seek to beginning and stop if not looping if (! looping) Stop(); } // L_WriteDebug("humdinger_c::Ticker DONE\n"); } //---------------------------------------------------------------------------- // // HumDingerInit // humdinger_c *HumDingerInit(void) { L_WriteDebug("HumDingerInit...\n"); Humidity::Init(); Humidity::output_device_c **dev_list = Humidity::GetDeviceList(); int dev_count = 0; while (dev_list[dev_count] != NULL) dev_count++; if (dev_count == 0) { // FIXME: store for HumDingerGetError I_Warning("Humidity: no devices are available.\n"); Humidity::Exit(); return NULL; } // !!! FIXME: some control over device selection // (e.g. SW or HW, FM or ! FM). // Using zero gives us the highest priority device. Humidity::output_device_c *dev = dev_list[0]; SYS_ASSERT(dev); // !!! FIXME: some control over stereoness, sample rate if (! dev->Open(true, 22050)) { // FIXME: store for HumDingerGetError (OR NOT ??) I_Warning("Error opening humidity device: %s\n", dev->GetError()); Humidity::Exit(); return NULL; } I_Printf("Humdinger: using device '%s'\n", dev->InfoName()); humding_inited = true; return new humdinger_c(dev); } // // HumDingerTerm // void HumDingerTerm(void) { L_WriteDebug("HumDingerTerm...\n"); if (humding_inited) { Humidity::Exit(); } } // // HumDingerGetError // const char *HumDingerGetError(void) { return "Unknown problem"; //!!!! FIXME } //--- editor settings --- // vi:ts=4:sw=4:noexpandtab