/* * Copyright (C) 2002 - David W. Durham * * This file is part of ReZound, an audio editing application. * * ReZound 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. * * ReZound 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "ASoundPlayer.h" #include #include #include #include "CSound.h" #include "CSoundPlayerChannel.h" #include "settings.h" #include "unit_conv.h" ASoundPlayer::ASoundPlayer() { #ifdef HAVE_LIBRFFTW analyzerPlan=NULL; #endif } ASoundPlayer::~ASoundPlayer() { #ifdef HAVE_LIBRFFTW for(map *>::iterator i=hammingWindows.begin();i!=hammingWindows.end();i++) delete i->second; #endif } void ASoundPlayer::initialize() { for(unsigned t=0;tstop(); set::const_iterator i=soundPlayerChannels.find(soundPlayerChannel); if(i!=soundPlayerChannels.end()) soundPlayerChannels.erase(i); } void ASoundPlayer::mixSoundPlayerChannels(const unsigned nChannels,sample_t * const buffer,const size_t bufferSize) { memset(buffer,0,bufferSize*sizeof(*buffer)*nChannels); // ??? it might be nice that if no sound player channel object is playing that this method would not return // so that the caller wouldn't eat any CPU time doing anything with the silence returned for(set::iterator i=soundPlayerChannels.begin();i!=soundPlayerChannels.end();i++) (*i)->mixOntoBuffer(nChannels,buffer,bufferSize); // ??? could just schedule this to occur (by making a copy of the buffer) the next time getLevel or getAnalysis is called rather than doing it here in the callback for mixing audio // calculate the peak levels and max RMS levels for this chunk if(gLevelMetersEnabled) { for(unsigned i=0;i::iterator i=soundPlayerChannels.begin();i!=soundPlayerChannels.end();i++) (*i)->stop(); } const sample_t ASoundPlayer::getRMSLevel(unsigned channel) const { if(!isInitialized()) return 0; //return RMSLevelDetectors[channel].readCurrentLevel(); const sample_t r=maxRMSLevels[channel]; resetMaxRMSLevels[channel]=true; return r; } const sample_t ASoundPlayer::getPeakLevel(unsigned channel) const { if(!isInitialized()) return 0; const sample_t p=peakLevels[channel]; resetPeakLevels[channel]=true; return p; } const size_t ASoundPlayer::getSamplingForStereoPhaseMeters(sample_t *buffer,size_t bufferSizeInSamples) const { return samplingForStereoPhaseMeters.read(buffer,min(bufferSizeInSamples,(size_t)(gStereoPhaseMeterPointCount*devices[0].channelCount)))/devices[0].channelCount; // ??? only device zero } #ifdef HAVE_LIBRFFTW // NOTE: when I say 'band', a band is expressed in terms of an // octave (where octave is a real number) and each band is a // multiple of deltaOctave (i.e when deltaOctave is 0.5, then // the bands are defined as octaves: 0, 0.5, 1.0, 1.5, 2.0, 2.5, etc) // ??? these need to be settings in the registry and have enforced limits static const float baseOctave=40; // bottom frequency of analyzer (actually the first band contains from 0Hz to upperFreqAtOctave(0) ) static const size_t octaveStride=6; // 6 bands per octave static const float deltaOctave=1.0f/octaveStride; // returns the frequency (in Hz) given the octave static float freqAtOctave(float octave) { return baseOctave*powf(2.0f,octave); } // return middle of the previous band's frequency and our band's frequency static float lowerFreqAtOctave(float octave) { return (freqAtOctave(octave-deltaOctave)+freqAtOctave(octave))/2.0f; } // return middle of the our band's frequency and the next band's frequency static float upperFreqAtOctave(float octave) { return (freqAtOctave(octave)+freqAtOctave(octave+deltaOctave))/2.0f; } // returns the index (into an frequency domain array) given a frequency (but doesn't always return an integer, it returns what index we would wish to be there (perhaps between two elements)) static float indexAtFreq(float freq,unsigned sampleRate) { return (2.0f*(ASP_ANALYSIS_BUFFER_SIZE/2)*freq)/(float)sampleRate; } // returns the (integer) lower index of the given band (expressed as an octave) into a frequency domain array static size_t lowerIndexAtOctave(float octave,unsigned sampleRate) { return (size_t)floor(indexAtFreq(lowerFreqAtOctave(octave),sampleRate)); } // returns the (integer) upper index of the given band (expressed as an octave) into a frequency domain array static size_t upperIndexAtOctave(float octave,unsigned sampleRate) { return (size_t)(floor(indexAtFreq(upperFreqAtOctave(octave),sampleRate))); } void ASoundPlayer::calculateAnalyzerBandIndexRanges() const { bandLowerIndexes.clear(); bandUpperIndexes.clear(); float octave=0; while(freqAtOctave(octave)<(devices[0].sampleRate/2)) { bandLowerIndexes.push_back(lowerIndexAtOctave(octave,devices[0].sampleRate)); bandUpperIndexes.push_back(upperIndexAtOctave(octave,devices[0].sampleRate)); octave+=deltaOctave; } if(bandLowerIndexes.size()>0) { // make sure the first band includes the first element (but not actually 0Hz because that's just the DC offset) bandLowerIndexes[0]=1; // make sure all the indexes are in range for(size_t t=0;t *ASoundPlayer::createHammingWindow(size_t windowSize) { //printf("creating for length %d\n",windowSize); TAutoBuffer *h=new TAutoBuffer(windowSize); for(size_t t=0;t const vector ASoundPlayer::getFrequencyAnalysis() const { vector v; if(!isInitialized()) return v; #ifdef HAVE_LIBRFFTW CMutexLocker l(frequencyAnalysisBufferMutex); if(!frequencyAnalysisBufferPrepared) { // now divide by MAX_SAMPLE to normalize the values // and multiply by the Hamming window const fftw_real k=1.0/MAX_SAMPLE; if(hammingWindows.find(frequencyAnalysisBufferLength)==hammingWindows.end()) hammingWindows[frequencyAnalysisBufferLength]=createHammingWindow(frequencyAnalysisBufferLength); const fftw_real *hammingWindow=*(hammingWindows[frequencyAnalysisBufferLength]); for(size_t t=0;t prevVs[NUM_AVG]; static size_t currentPrevV=0; // overwrite the oldest prev vector prevVs[currentPrevV]=v; currentPrevV++; currentPrevV%=NUM_AVG; vector temp; if(prevVs[NUM_AVG-1].size()>0) { // create initial values in temp temp=prevVs[0]; // add all over prev vectors to temp for(size_t i=1;i // just for fprintf #include #include #include "CNULLSoundPlayer.h" #include "COSSSoundPlayer.h" #include "CALSASoundPlayer.h" #include "CPortAudioSoundPlayer.h" #include "CJACKSoundPlayer.h" #include "AStatusComm.h" #include ASoundPlayer *ASoundPlayer::createInitializedSoundPlayer() { // if the registry doesn't already contain a methods setting, then create the default one if(gSettingsRegistry->keyExists("AudioOutputMethods")!=CNestedDataFile::ktValue) { vector methods; methods.push_back("oss"); methods.push_back("alsa"); methods.push_back("jack"); methods.push_back("portaudio"); gSettingsRegistry->setValue("AudioOutputMethods",methods); } bool initializeThrewException=false; ASoundPlayer *soundPlayer=NULL; vector methods=gSettingsRegistry->getValue >("AudioOutputMethods"); // add --audio-method=... to the beginning if(gDefaultAudioMethod!="") methods.insert(methods.begin(),gDefaultAudioMethod); // try this as a last resort (it just holds the pointer place (so it's not NULL throughout the rest of the code) but it is written to fail to initialize methods.push_back("null"); // for each requested method in registry.AudioOutputMethods try each until one succeeds // 'suceeding' is true if the method was enabled at build time and it can initialize now at run-time for(size_t t=0;tinitialize(); \ break; /* no exception thrown from initialize() so we're good to go */ \ } if(method=="oss") { #ifdef ENABLE_OSS INITIALIZE_PLAYER(COSSSoundPlayer) #endif } else if(method=="alsa") { #ifdef ENABLE_ALSA INITIALIZE_PLAYER(CALSASoundPlayer) #endif } else if(method=="jack") { #ifdef ENABLE_JACK INITIALIZE_PLAYER(CJACKSoundPlayer) #endif } else if(method=="portaudio") { #ifdef ENABLE_PORTAUDIO INITIALIZE_PLAYER(CPortAudioSoundPlayer) #endif } else if(method=="null") { INITIALIZE_PLAYER(CNULLSoundPlayer) } else { Warning("unhandled method type in the registry:AudioOutputMethods[] '"+method+"'"); continue; } } catch(exception &e) { // now really give up fprintf(stderr,"Error occurred while initializing audio output method '%s' -- %s\n",method.c_str(),e.what()); initializeThrewException=true; } } if(soundPlayer) { if(initializeThrewException) Error(_("No audio output method could be initialized -- Playing will be disabled.")); return soundPlayer; } else /* ??? this should never happen anymore now with CNULLSoundPlayer */ throw runtime_error(string(__func__)+" -- "+_("Either no audio output method was enabled at configure-time, or no method was recognized in the registry:AudioOutputMethods[] setting")); }