#include "ofOpenALSoundPlayer.h" #ifdef OF_SOUND_PLAYER_OPENAL #include "ofConstants.h" #include "glm/gtc/constants.hpp" #include "glm/common.hpp" #include "ofLog.h" #include "ofEvents.h" #include #if defined (TARGET_OF_IOS) || defined (TARGET_OSX) #include #include #else #include #include #endif #ifdef OF_USING_MPG123 #include #endif using namespace std; static ALCdevice * alDevice = nullptr; static ALCcontext * alContext = nullptr; vector ofOpenALSoundPlayer::window; float ofOpenALSoundPlayer::windowSum = 0.f; kiss_fftr_cfg ofOpenALSoundPlayer::systemFftCfg=0; vector ofOpenALSoundPlayer::systemWindowedSignal; vector ofOpenALSoundPlayer::systemBins; vector ofOpenALSoundPlayer::systemCx_out; static set & players(){ static set * players = new set; return *players; } void ofOpenALSoundUpdate(){ alcProcessContext(alContext); } // ---------------------------------------------------------------------------- // from http://devmaster.net/posts/2893/openal-lesson-6-advanced-loading-and-error-handles static string getALErrorString(ALenum error) { switch(error) { case AL_NO_ERROR: return "AL_NO_ERROR"; case AL_INVALID_NAME: return "AL_INVALID_NAME"; case AL_INVALID_ENUM: return "AL_INVALID_ENUM"; case AL_INVALID_VALUE: return "AL_INVALID_VALUE"; case AL_INVALID_OPERATION: return "AL_INVALID_OPERATION"; case AL_OUT_OF_MEMORY: return "AL_OUT_OF_MEMORY"; }; return "UNKWOWN_ERROR"; } static string getALCErrorString(ALCenum error) { switch(error) { case ALC_NO_ERROR: return "ALC_NO_ERROR"; case ALC_INVALID_DEVICE: return "ALC_INVALID_DEVICE"; case ALC_INVALID_CONTEXT: return "ALC_INVALID_CONTEXT"; case ALC_INVALID_ENUM: return "ALC_INVALID_ENUM"; case ALC_INVALID_VALUE: return "ALC_INVALID_VALUE"; case ALC_OUT_OF_MEMORY: return "ALC_OUT_OF_MEMORY"; }; return "UNKWOWN_ERROR"; } #ifdef OF_USING_MPG123 static string getMpg123EncodingString(int encoding) { switch(encoding) { case MPG123_ENC_16: return "MPG123_ENC_16"; #if MPG123_API_VERSION>=36 case MPG123_ENC_24: return "MPG123_ENC_24"; #endif case MPG123_ENC_32: return "MPG123_ENC_32"; case MPG123_ENC_8: return "MPG123_ENC_8"; case MPG123_ENC_ALAW_8: return "MPG123_ENC_ALAW_8"; case MPG123_ENC_FLOAT: return "MPG123_ENC_FLOAT"; case MPG123_ENC_FLOAT_32: return "MPG123_ENC_FLOAT_32"; case MPG123_ENC_FLOAT_64: return "MPG123_ENC_FLOAT_64"; case MPG123_ENC_SIGNED: return "MPG123_ENC_SIGNED"; case MPG123_ENC_SIGNED_16: return "MPG123_ENC_SIGNED_16"; #if MPG123_API_VERSION>=36 case MPG123_ENC_SIGNED_24: return "MPG123_ENC_SIGNED_24"; #endif case MPG123_ENC_SIGNED_32: return "MPG123_ENC_SIGNED_32"; case MPG123_ENC_SIGNED_8: return "MPG123_ENC_SIGNED_8"; case MPG123_ENC_ULAW_8: return "MPG123_ENC_ULAW_8"; case MPG123_ENC_UNSIGNED_16: return "MPG123_ENC_UNSIGNED_16"; #if MPG123_API_VERSION>=36 case MPG123_ENC_UNSIGNED_24: return "MPG123_ENC_UNSIGNED_24"; #endif case MPG123_ENC_UNSIGNED_32: return "MPG123_ENC_UNSIGNED_32"; case MPG123_ENC_UNSIGNED_8: return "MPG123_ENC_UNSIGNED_8"; default: return "MPG123_ENC_ANY"; } } #endif #define BUFFER_STREAM_SIZE 4096 // now, the individual sound player: //------------------------------------------------------------ ofOpenALSoundPlayer::ofOpenALSoundPlayer(){ bLoop = false; bLoadedOk = false; pan = 0.0f; // range for oF is -1 to 1, volume = 1.0f; internalFreq = 44100; speed = 1; bPaused = false; isStreaming = false; channels = 0; duration = 0; fftCfg = 0; streamf = 0; #ifdef OF_USING_MPG123 mp3streamf = 0; #endif players().insert(this); } // ---------------------------------------------------------------------------- ofOpenALSoundPlayer::~ofOpenALSoundPlayer(){ unload(); kiss_fftr_free(fftCfg); players().erase(this); if( players().empty() ){ close(); } } //--------------------------------------- // this should only be called once void ofOpenALSoundPlayer::initialize(){ if( !alDevice ){ alDevice = alcOpenDevice( nullptr ); if( !alDevice ){ ofLogError("ofOpenALSoundPlayer") << "initialize(): couldn't open OpenAL default device"; return; }else{ ofLogVerbose("ofOpenALSoundPlayer") << "initialize(): opening "<< alcGetString( alDevice, ALC_DEVICE_SPECIFIER ); } // Create OpenAL context and make it current. If fails, close the OpenAL device that was just opened. alContext = alcCreateContext( alDevice, nullptr ); if( !alContext ){ ALCenum err = alcGetError( alDevice ); ofLogError("ofOpenALSoundPlayer") << "initialize(): couldn't not create OpenAL context : "<< getALCErrorString( err ); close(); return; } if( alcMakeContextCurrent( alContext )==ALC_FALSE ){ ALCenum err = alcGetError( alDevice ); ofLogError("ofOpenALSoundPlayer") << "initialize(): couldn't not make current the create OpenAL context : "<< getALCErrorString( err ); close(); return; }; alListener3f( AL_POSITION, 0,0,0 ); #ifdef OF_USING_MPG123 mpg123_init(); #endif } ofLogVerbose("ofOpenALSoundPlayer") << "initialize(): Done"; } //--------------------------------------- void ofOpenALSoundPlayer::createWindow(int size){ if(int(window.size())!=size){ windowSum = 0; window.resize(size); // hanning window for(int i = 0; i < size; i++){ window[i] = .54 - .46 * cos((glm::two_pi() * i) / (size - 1)); windowSum += window[i]; } } } //--------------------------------------- void ofOpenALSoundPlayer::close(){ // Destroy the OpenAL context (if any) before closing the device if( alDevice ){ if( alContext ){ #ifdef OF_USING_MPG123 mpg123_exit(); #endif alcMakeContextCurrent(nullptr); alcDestroyContext(alContext); alContext = nullptr; } if( alcCloseDevice( alDevice )==ALC_FALSE ){ ofLogNotice("ofOpenALSoundPlayer") << "initialize(): error closing OpenAL device."; } alDevice = nullptr; } } // ---------------------------------------------------------------------------- bool ofOpenALSoundPlayer::sfReadFile(const std::filesystem::path& path, vector & buffer, vector & fftAuxBuffer){ SF_INFO sfInfo; SNDFILE* f = sf_open(path.string().c_str(),SFM_READ,&sfInfo); if(!f){ ofLogError("ofOpenALSoundPlayer") << "sfReadFile(): couldn't read \"" << path << "\""; return false; } buffer.resize(sfInfo.frames*sfInfo.channels); fftAuxBuffer.resize(sfInfo.frames*sfInfo.channels); int subformat = sfInfo.format & SF_FORMAT_SUBMASK ; if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE){ double scale ; sf_command (f, SFC_CALC_SIGNAL_MAX, &scale, sizeof (scale)) ; if (scale < 1e-10) scale = 1.0 ; else scale = 32700.0 / scale ; sf_count_t samples_read = sf_read_float (f, &fftAuxBuffer[0], fftAuxBuffer.size()); if(samples_read<(int)fftAuxBuffer.size()){ ofLogWarning("ofOpenALSoundPlayer") << "sfReadFile(): read " << samples_read << " float samples, expected " << fftAuxBuffer.size() << " for \"" << path << "\""; } for (int i = 0 ; i < int(fftAuxBuffer.size()) ; i++){ fftAuxBuffer[i] *= scale ; buffer[i] = 32565.0 * fftAuxBuffer[i]; } }else{ sf_count_t frames_read = sf_readf_short(f,&buffer[0],sfInfo.frames); if(frames_read & buffer,vector & fftAuxBuffer){ int err = MPG123_OK; mpg123_handle * f = mpg123_new(nullptr,&err); if(mpg123_open(f,path.string().c_str())!=MPG123_OK){ ofLogError("ofOpenALSoundPlayer") << "mpg123ReadFile(): couldn't read \"" << path << "\""; return false; } mpg123_enc_enum encoding; long int rate; mpg123_getformat(f,&rate,&channels,(int*)&encoding); if(encoding!=MPG123_ENC_SIGNED_16){ ofLogError("ofOpenALSoundPlayer") << "mpg123ReadFile(): " << getMpg123EncodingString(encoding) << " encoding for \"" << path << "\"" << " unsupported, expecting MPG123_ENC_SIGNED_16"; return false; } samplerate = rate; size_t done=0; size_t buffer_size = mpg123_outblock( f ); buffer.resize(buffer_size/2); while(mpg123_read(f,(unsigned char*)&buffer[buffer.size()-buffer_size/2],buffer_size,&done)!=MPG123_DONE){ buffer.resize(buffer.size()+buffer_size/2); }; buffer.resize(buffer.size()-(buffer_size/2-done/2)); mpg123_close(f); mpg123_delete(f); fftAuxBuffer.resize(buffer.size()); for(int i=0;i<(int)buffer.size();i++){ fftAuxBuffer[i] = float(buffer[i])/32565.f; } duration = float(buffer.size()/channels) / float(samplerate); return true; } #endif //------------------------------------------------------------ bool ofOpenALSoundPlayer::sfStream(const std::filesystem::path& path,vector & buffer,vector & fftAuxBuffer){ if(!streamf){ SF_INFO sfInfo; streamf = sf_open(path.string().c_str(),SFM_READ,&sfInfo); if(!streamf){ ofLogError("ofOpenALSoundPlayer") << "sfStream(): couldn't read \"" << path << "\""; return false; } stream_subformat = sfInfo.format & SF_FORMAT_SUBMASK ; if (stream_subformat == SF_FORMAT_FLOAT || stream_subformat == SF_FORMAT_DOUBLE){ sf_command (streamf, SFC_CALC_SIGNAL_MAX, &stream_scale, sizeof (stream_scale)) ; if (stream_scale < 1e-10) stream_scale = 1.0 ; else stream_scale = 32700.0 / stream_scale ; } channels = sfInfo.channels; duration = float(sfInfo.frames) / float(sfInfo.samplerate); samplerate = sfInfo.samplerate; stream_samples_read = 0; } int curr_buffer_size = BUFFER_STREAM_SIZE*channels; if(speed>1) curr_buffer_size *= (int)round(speed); buffer.resize(curr_buffer_size); fftAuxBuffer.resize(buffer.size()); if (stream_subformat == SF_FORMAT_FLOAT || stream_subformat == SF_FORMAT_DOUBLE){ sf_count_t samples_read = sf_read_float (streamf, &fftAuxBuffer[0], fftAuxBuffer.size()); stream_samples_read += samples_read; if(samples_read<(int)fftAuxBuffer.size()){ fftAuxBuffer.resize(samples_read); buffer.resize(samples_read); setPosition(0); if(!bLoop) stopThread(); stream_samples_read = 0; stream_end = true; } for (int i = 0 ; i < int(fftAuxBuffer.size()) ; i++){ fftAuxBuffer[i] *= stream_scale ; buffer[i] = 32565.0 * fftAuxBuffer[i]; } }else{ sf_count_t frames_read = sf_readf_short(streamf,&buffer[0],curr_buffer_size/channels); stream_samples_read += frames_read*channels; if(frames_read & buffer,vector & fftAuxBuffer){ if(!mp3streamf){ int err = MPG123_OK; mp3streamf = mpg123_new(nullptr,&err); if(mpg123_open(mp3streamf,path.string().c_str())!=MPG123_OK){ mpg123_close(mp3streamf); mpg123_delete(mp3streamf); ofLogError("ofOpenALSoundPlayer") << "mpg123Stream(): couldn't read \"" << path << "\""; return false; } long int rate; mpg123_getformat(mp3streamf,&rate,&channels,(int*)&stream_encoding); if(stream_encoding!=MPG123_ENC_SIGNED_16){ ofLogError("ofOpenALSoundPlayer") << "mpg123Stream(): " << getMpg123EncodingString(stream_encoding) << " encoding for \"" << path << "\"" << " unsupported, expecting MPG123_ENC_SIGNED_16"; return false; } samplerate = rate; mp3_buffer_size = mpg123_outblock( mp3streamf ); mpg123_seek(mp3streamf,0,SEEK_END); off_t samples = mpg123_tell(mp3streamf); duration = float(samples/channels) / float(samplerate); mpg123_seek(mp3streamf,0,SEEK_SET); } int curr_buffer_size = mp3_buffer_size; if(speed>1) curr_buffer_size *= (int)round(speed); buffer.resize(curr_buffer_size); fftAuxBuffer.resize(buffer.size()); size_t done=0; if(mpg123_read(mp3streamf,(unsigned char*)&buffer[0],curr_buffer_size*2,&done)==MPG123_DONE){ setPosition(0); buffer.resize(done/2); fftAuxBuffer.resize(done/2); if(!bLoop) stopThread(); stream_end = true; } for(int i=0;i<(int)buffer.size();i++){ fftAuxBuffer[i] = float(buffer[i])/32565.f; } return true; } #endif //------------------------------------------------------------ bool ofOpenALSoundPlayer::stream(const std::filesystem::path& fileName, vector & buffer){ #ifdef OF_USING_MPG123 if(ofFilePath::getFileExt(fileName)=="mp3" || ofFilePath::getFileExt(fileName)=="MP3" || mp3streamf){ if(!mpg123Stream(fileName,buffer,fftAuxBuffer)) return false; }else #endif if(!sfStream(fileName,buffer,fftAuxBuffer)) return false; fftBuffers.resize(channels); int numFrames = buffer.size()/channels; for(int i=0;i & buffer){ #ifdef OF_USING_MPG123 if(ofFilePath::getFileExt(fileName)!="mp3" && ofFilePath::getFileExt(fileName)!="MP3"){ if(!sfReadFile(fileName,buffer,fftAuxBuffer)) return false; }else{ if(!mpg123ReadFile(fileName,buffer,fftAuxBuffer)) return false; } #else if(!sfReadFile(fileName,buffer,fftAuxBuffer)) return false; #endif fftBuffers.resize(channels); int numFrames = buffer.size()/channels; for(int i=0;i > multibuffer; multibuffer.resize(channels); sources.resize(channels); alGenSources(channels, &sources[0]); if(isStreaming){ for(int s=0; s<2;s++){ for(int i=0;i > multibuffer; multibuffer.resize(channels); while(isThreadRunning()){ std::unique_lock lock(mutex); for(int i=0; i1){ for(int j=0;j lock(mutex); // Delete sources before buffers. alDeleteSources(sources.size(),&sources[0]); alDeleteBuffers(buffers.size(),&buffers[0]); sources.clear(); buffers.clear(); } // Free resources and close file descriptors. #ifdef OF_USING_MPG123 if(mp3streamf){ mpg123_close(mp3streamf); mpg123_delete(mp3streamf); } mp3streamf = 0; #endif if(streamf){ sf_close(streamf); } streamf = 0; bLoadedOk = false; } //------------------------------------------------------------ bool ofOpenALSoundPlayer::isPlaying() const{ if(sources.empty()) return false; if(isStreaming) return isThreadRunning(); ALint state; bool playing=false; for(int i=0;i<(int)sources.size();i++){ alGetSourcei(sources[i],AL_SOURCE_STATE,&state); playing |= (state == AL_PLAYING); } return playing; } //------------------------------------------------------------ bool ofOpenALSoundPlayer::isPaused() const{ if(sources.empty()) return false; ALint state; bool paused=true; for(int i=0;i<(int)sources.size();i++){ alGetSourcei(sources[i],AL_SOURCE_STATE,&state); paused &= (state == AL_PAUSED); } return paused; } //------------------------------------------------------------ float ofOpenALSoundPlayer::getSpeed() const{ return speed; } //------------------------------------------------------------ float ofOpenALSoundPlayer::getPan() const{ return pan; } //------------------------------------------------------------ float ofOpenALSoundPlayer::getVolume() const{ return volume; } //------------------------------------------------------------ void ofOpenALSoundPlayer::setVolume(float vol){ volume = vol; if(sources.empty()) return; if(channels==1){ alSourcef (sources[sources.size()-1], AL_GAIN, vol); }else{ setPan(pan); } } //------------------------------------------------------------ void ofOpenALSoundPlayer::setPosition(float pct){ setPositionMS(duration*pct*1000.f); } //------------------------------------------------------------ void ofOpenALSoundPlayer::setPositionMS(int ms){ if(sources.empty()) return; #ifdef OF_USING_MPG123 if(mp3streamf){ mpg123_seek(mp3streamf,float(ms)/1000.f*samplerate,SEEK_SET); }else #endif if(streamf){ stream_samples_read = sf_seek(streamf,float(ms)/1000.f*samplerate,SEEK_SET) * channels; }else{ for(int i=0;i<(int)channels;i++){ alSourcef(sources[sources.size()-channels+i],AL_SEC_OFFSET,float(ms)/1000.f); } } } //------------------------------------------------------------ float ofOpenALSoundPlayer::getPosition() const{ if(duration==0 || sources.empty()) return 0; else return getPositionMS()/(1000.f*duration); } //------------------------------------------------------------ int ofOpenALSoundPlayer::getPositionMS() const{ if(sources.empty()) return 0; float pos; #ifdef OF_USING_MPG123 if(mp3streamf){ pos = float(mpg123_tell(mp3streamf)) / float(samplerate); }else #endif if(streamf){ pos = float(stream_samples_read) / float(channels) / float(samplerate); }else{ alGetSourcef(sources[sources.size()-1],AL_SEC_OFFSET,&pos); } return pos * 1000.f; } //------------------------------------------------------------ void ofOpenALSoundPlayer::setPan(float p){ if(sources.empty()) return; p = glm::clamp(p, -1.f, 1.f); pan = p; if(channels==1){ float pos[3] = {p,0,0}; alSourcefv(sources[sources.size()-1],AL_POSITION,pos); }else{ // calculates left/right volumes from pan-value (constant panning law) // see: Curtis Roads: Computer Music Tutorial p 460 // thanks to jasch float angle = p * 0.7853981633974483f; // in radians from -45. to +45. float cosAngle = cos(angle); float sinAngle = sin(angle); float leftVol = (cosAngle - sinAngle) * 0.7071067811865475; // multiplied by sqrt(2)/2 float rightVol = (cosAngle + sinAngle) * 0.7071067811865475; // multiplied by sqrt(2)/2 for(int i=0;i<(int)channels;i++){ if(i==0){ alSourcef(sources[sources.size()-channels+i],AL_GAIN,leftVol*volume); }else{ alSourcef(sources[sources.size()-channels+i],AL_GAIN,rightVol*volume); } } } } //------------------------------------------------------------ void ofOpenALSoundPlayer::setPaused(bool bP){ if(sources.empty()) return; std::unique_lock lock(mutex); if(bP){ alSourcePausev(sources.size(),&sources[0]); if(isStreaming){ stopThread(); } }else{ alSourcePlayv(sources.size(),&sources[0]); if(isStreaming){ startThread(); } } bPaused = bP; } //------------------------------------------------------------ void ofOpenALSoundPlayer::setSpeed(float spd){ for(int i=0;i lock(mutex); int err = alGetError(); // if the sound is set to multiplay, then create new sources, // do not multiplay on loop or we won't be able to stop it if (bMultiPlay && !bLoop){ sources.resize(sources.size()+channels); alGetError(); // Clear error. alGenSources(channels, &sources[sources.size()-channels]); err = alGetError(); if (err != AL_NO_ERROR){ ofLogError("ofOpenALSoundPlayer") << "play(): couldn't create multiplay stereo sources: " << (int) err << " " << getALErrorString(err); return; } for(int i=0;i lock(mutex); alSourceStopv(channels,&sources[sources.size()-channels]); if(isStreaming){ setPosition(0); stopThread(); } } // ---------------------------------------------------------------------------- void ofOpenALSoundPlayer::initFFT(int bands){ if(int(bins.size())==bands) return; int signalSize = (bands-1)*2; if(fftCfg!=0) kiss_fftr_free(fftCfg); fftCfg = kiss_fftr_alloc(signalSize, 0, nullptr, nullptr); cx_out.resize(bands); bins.resize(bands); createWindow(signalSize); } // ---------------------------------------------------------------------------- void ofOpenALSoundPlayer::initSystemFFT(int bands){ if(int(systemBins.size())==bands) return; int signalSize = (bands-1)*2; if(systemFftCfg!=0) kiss_fftr_free(systemFftCfg); systemFftCfg = kiss_fftr_alloc(signalSize, 0, nullptr, nullptr); systemCx_out.resize(bands); systemBins.resize(bands); createWindow(signalSize); } float * ofOpenALSoundPlayer::getCurrentBufferSum(int size){ if(int(windowedSignal.size())!=size){ windowedSignal.resize(size); } windowedSignal.assign(windowedSignal.size(),0); for(int k=0;k=(int)fftBuffers[0].size()) continue; for(int i=0;i::iterator it; for(it=players().begin();it!=players().end();it++){ if(!(*it)->isPlaying()) continue; float * buffer = (*it)->getCurrentBufferSum(signalSize); for(int i=0;i & signal){ for(int i = 0; i < (int)signal.size(); i++) signal[i] *= window[i]; } #endif