/* Copyright (C) 2000 Stefan Westerfeld stefan@space.twc.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #ifdef HAVE_LIBAUDIOFILE #include "artsflow.h" #include "stdsynthmodule.h" #include "debug.h" #include "cachedwav.h" #include "convert.h" #include #include #include #include #include extern "C" { /* Some versions of libaudiofile seem to lack the extern "C" declaration, * so you you may need that extra one. * * Other versions of libaudiofile seem to have two closing "}" in aupvlist.h, * so if you can't compile, this, check that /usr/include/aupvlist.h contains * something like that * * #ifdef __cplusplus * } * #endif * * only once not twice. */ #include "audiofile.h" } #include #include using namespace std; using namespace Arts; CachedWav *CachedWav::load(Cache *cache, string filename) { CachedWav *wav; wav = (CachedWav *)cache->get(string("CachedWav:")+filename); if(!wav) { wav = new CachedWav(cache,filename); if(!wav->initOk) // loading failed { wav->decRef(); return 0; } } return(wav); } bool CachedWav::isValid() { if(!initOk) return false; struct stat newstat; lstat(filename.c_str(),&newstat); return(newstat.st_mtime == oldstat.st_mtime); } int CachedWav::memoryUsage() { return(bufferSize); } CachedWav::CachedWav(Cache *cache, string filename) : CachedObject(cache), filename(filename),initOk(false), buffer(0) { int sampleFormat; AFframecount frameCount; AFfilehandle file; setKey(string("CachedWav:")+filename); if(lstat(filename.c_str(),&oldstat) == -1) { arts_info("CachedWav: Can't stat file '%s'", filename.c_str()); return; } file = afOpenFile(filename.c_str(), "r", NULL); if(!file) { arts_info("CachedWav: Can't read file '%s'", filename.c_str()); return; } frameCount = afGetFrameCount(file, AF_DEFAULT_TRACK); if(frameCount <= 0 || frameCount >= INT_MAX) { arts_info("CachedWav: Invalid length for '%s'", filename.c_str()); afCloseFile(file); return; } channelCount = afGetChannels(file, AF_DEFAULT_TRACK); afGetSampleFormat(file, AF_DEFAULT_TRACK, &sampleFormat, &sampleWidth); // we want everything converted to little endian unconditionally afSetVirtualByteOrder(file,AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN); arts_debug("loaded wav %s",filename.c_str()); arts_debug(" sample format: %d, sample width: %d", sampleFormat,sampleWidth); arts_debug(" channelCount: %d",channelCount); arts_debug(" frameCount: %d",frameCount); // different handling required for other sample widths assert(sampleWidth == 16 || sampleWidth == 8); long frameSize = (sampleWidth/8)*channelCount; samplingRate = afGetRate(file, AF_DEFAULT_TRACK); /* * if we don't know the track bytes, we'll have to figure out ourselves * how many frames are stored here - it would be nicer if libaudiofile * let us know somehow whether the value returned for getFrameCount * means "don't know" or is really the correct length */ int trackBytes = afGetTrackBytes(file, AF_DEFAULT_TRACK); if(trackBytes == -1) { arts_debug("unknown length"); long fcount = 0, f = 0; list blocks; do { void *block = malloc(1024 * frameSize); f = afReadFrames(file, AF_DEFAULT_TRACK,block,1024); if(f > 0) { fcount += f; blocks.push_back(block); } else { free(block); } } while(f > 0); frameCount = fcount; arts_debug("figured out frameCount = %ld", fcount); bufferSize = frameCount * frameSize; buffer = new uchar[bufferSize]; assert(buffer); // reassemble and free the blocks while(!blocks.empty()) { void *block = blocks.front(); blocks.pop_front(); f = (fcount>1024)?1024:fcount; memcpy(&buffer[(frameCount-fcount)*frameSize],block,f*frameSize); fcount -= f; } assert(fcount == 0); } else { bufferSize = frameCount * frameSize; buffer = new uchar[bufferSize]; assert(buffer); afReadFrames(file, AF_DEFAULT_TRACK,buffer,frameCount); } afCloseFile(file); initOk = true; } CachedWav::~CachedWav() { if(buffer) delete[] buffer; } namespace Arts { class Synth_PLAY_WAV_impl : public Synth_PLAY_WAV_skel, public StdSynthModule { protected: double flpos; float _speed; string _filename; bool _finished; CachedWav *cachedwav; void unload() { if(cachedwav) { cachedwav->decRef(); cachedwav = 0; } } void load() { // unload the old file if necessary unload(); // load the new (which will reset the position) cachedwav = CachedWav::load(Cache::the(), _filename); flpos = 0.0; } public: float speed() { return _speed; } void speed(float newSpeed) { _speed = newSpeed; } string filename() { return _filename; } void filename(const string& filename) { _filename = filename; load(); } void finished(bool f) { if(_finished != f) { _finished = f; finished_changed(f); } } bool finished() { return _finished; } Synth_PLAY_WAV_impl(); ~Synth_PLAY_WAV_impl(); void streamInit(); void calculateBlock(unsigned long samples); }; REGISTER_IMPLEMENTATION(Synth_PLAY_WAV_impl); } Synth_PLAY_WAV_impl::Synth_PLAY_WAV_impl() { cachedwav = 0; _speed = 1.0; _filename = ""; _finished = false; } Synth_PLAY_WAV_impl::~Synth_PLAY_WAV_impl() { unload(); } void Synth_PLAY_WAV_impl::streamInit() { finished(false); } void Synth_PLAY_WAV_impl::calculateBlock(unsigned long samples) { unsigned long haveSamples = 0; if(cachedwav) { double speed = cachedwav->samplingRate / samplingRateFloat * _speed; haveSamples = uni_convert_stereo_2float(samples, cachedwav->buffer, cachedwav->bufferSize,cachedwav->channelCount,cachedwav->sampleWidth, left,right,speed,flpos); flpos += (double)haveSamples * speed; } if(haveSamples != samples) { unsigned long i; for(i=haveSamples;ibufferSize*8 / cachedwav->sampleWidth/cachedwav->channelCount; float fHaveSamples = allSamples - flpos; speed = cachedwav->samplingRate / (float)samplingRate * _speed; fHaveSamples /= speed; fHaveSamples -= 2.0; // one due to interpolation and another against // rounding errors if(fHaveSamples > 0) { haveSamples = (int)fHaveSamples; if(haveSamples > samples) haveSamples = samples; } } if(haveSamples) // something left to play? { if(cachedwav->channelCount == 1) { if(cachedwav->sampleWidth == 16) { interpolate_mono_16le_float(haveSamples, flpos,speed,cachedwav->buffer,left); } else { interpolate_mono_8_float(haveSamples, flpos,speed,cachedwav->buffer,left); } memcpy(right,left,sizeof(float)*haveSamples); } else if(cachedwav->channelCount == 2) { if(cachedwav->sampleWidth == 16) { interpolate_stereo_i16le_2float(haveSamples, flpos,speed,cachedwav->buffer,left,right); } else { interpolate_stereo_i8_2float(haveSamples, flpos,speed,cachedwav->buffer,left,right); } } else { assert(false); } flpos += (float)haveSamples * speed; } if(haveSamples != samples) { unsigned long i; for(i=haveSamples;i remainingSamples) haveSamples = remainingSamples; float *left = out[LEFT], *right = out[RIGHT], *done = out[DONE]; unsigned long i; if(haveSamples) { if(channelCount == 1) { if(bytesPerSample == 2) { convert_mono_16le_float(haveSamples,&buffer[position],left); } else { convert_mono_8_float(haveSamples,&buffer[position],left); } memcpy(right,left,sizeof(float)*haveSamples); } else if(channelCount == 2) { if(bytesPerSample == 2) { convert_stereo_i16le_2float(haveSamples,&buffer[position], left,right); } else { convert_stereo_i8_2float(haveSamples,&buffer[position], left,right); } } else { assert(false); } for(i=0;idecRef(); } void Synth_PLAY_WAV::Initialize() { cachedwav = CachedWav::load(Synthesizer->getCache(), getStringProperty(PROP_FILENAME)); // may take some speed to access cachedwav every time bufferSize = cachedwav->bufferSize; channelCount = cachedwav->channelCount; buffer = cachedwav->buffer; bytesPerSample = cachedwav->sampleWidth/8; haveCalculateBlock = true; position = 0; } class Synth_PLAY_PITCHED_WAV :public SynthModule { protected: CachedWav *cachedwav; float flpos; // inputs: enum { FREQUENCY,RECFREQUENCY, PROP_FILENAME }; // outputs: enum { LEFT, RIGHT, DONE }; public: void Initialize(); void DeInitialize(); void Calculate() { assert(false); } void CalculateBlock(unsigned long samples); string getParams() { return("frequency,recfrequency,_filename;left,right,done"); } static void *Creator() { return new Synth_PLAY_PITCHED_WAV; } }; ModuleClient MC_Synth_PLAY_PITCHED_WAV(SynthModule::get_MS,"Synth_PLAY_PITCHED_WAV",Synth_PLAY_PITCHED_WAV::Creator); void Synth_PLAY_PITCHED_WAV::CalculateBlock(unsigned long samples) { float frequency = in[FREQUENCY][0], recfrequency = in[RECFREQUENCY][0]; float allSamples = cachedwav->bufferSize*8 / cachedwav->sampleWidth/cachedwav->channelCount; float fHaveSamples = allSamples - flpos; float speed = cachedwav->samplingRate / (float)samplingRate * frequency / recfrequency; fHaveSamples /= speed; fHaveSamples -= 2.0; // one due to interpolation and another against // rounding errors unsigned long haveSamples; if(fHaveSamples < 0) { haveSamples = 0; } else { haveSamples = fHaveSamples; if(haveSamples > samples) haveSamples = samples; } float *left = out[LEFT], *right = out[RIGHT], *done = out[DONE]; unsigned long i; if(haveSamples) { if(cachedwav->channelCount == 1) { if(cachedwav->sampleWidth == 16) { interpolate_mono_16le_float(haveSamples, flpos,speed,cachedwav->buffer,left); } else { interpolate_mono_8_float(haveSamples, flpos,speed,cachedwav->buffer,left); } memcpy(right,left,sizeof(float)*haveSamples); } else if(cachedwav->channelCount == 2) { if(cachedwav->sampleWidth == 16) { interpolate_stereo_i16le_2float(haveSamples, flpos,speed,cachedwav->buffer,left,right); } else { interpolate_stereo_i8_2float(haveSamples, flpos,speed,cachedwav->buffer,left,right); } } else { assert(false); } for(i=0;idecRef(); } void Synth_PLAY_PITCHED_WAV::Initialize() { cachedwav = CachedWav::load(Synthesizer->getCache(), getStringProperty(PROP_FILENAME)); haveCalculateBlock = true; flpos = 0.0; } #endif #else #ifdef __GNUC__ #warning "No libaudiofile available, that means, you won't be able to play wavs" #endif #endif