CSL  5.2
SoundFile.cpp
Go to the documentation of this file.
1 //
2 // Abst_SoundFile.cpp -- CSL's abstract sound file class
3 // See the copyright notice and acknowledgment of authors in the file COPYRIGHT
4 //
5 
6 #include "SoundFile.h"
7 
8 #ifdef USE_TAGS
9 #include <taglib/fileref.h> // libTag includes
10 #include <taglib/tag.h>
11 #endif
12 
13 using namespace csl;
14 
16 
18  if (mTitle.empty()) {
19  logMsg("No metadata");
20  return;
21  }
22  int secs = mLength % 60; // make a mins:secs display for the duration
23  int mins = (mLength - secs) / 60;
24  char dur[CSL_WORD_LEN];
25  if (secs < 10)
26  sprintf(dur, "%d:0%d", mins, secs);
27  else
28  sprintf(dur, "%d:%d", mins, secs);
29  logMsg("Tags: \"%s\" - %s - %s - %d - %s min",
30  mTitle.c_str(), mArtist.c_str(), mAlbum.c_str(), mYear, dur);
31  logMsg(" trk %d - %s - %d kbps - %d Hz - %d ch",
33 // if ( ! mComment.empty())
34 // logMsg(" Comment: %s", mComment.c_str());
35 }
36 
37 // Abst_SoundFile Constructors
38 
39 Abst_SoundFile::Abst_SoundFile(string tpath, int tstart, int tstop) :
41  mProperties(0),
42  mPath(string(tpath)),
43  mMode(kSoundFileClosed),
44  mFormat(kSoundFileFormatOther),
45  mIsValid(false),
46  mIsLooping(false),
47  mStart(tstart),
48  mStop(tstop),
49  mRate(1.0),
50  mNumFrames(0),
51  mBytesPerSample(0),
52  mBase(0) { /* no-op */}
53 
54 ///< Copy constructor -- shares sample buffer
55 
58  mProperties(otherSndFile.mProperties),
59  mMode(otherSndFile.mode()),
60  mIsValid(otherSndFile.isValid()),
61  mIsLooping(otherSndFile.isLooping()),
62  mStart(otherSndFile.startFrame()),
63  mStop(otherSndFile.stopFrame()),
64  mRate(otherSndFile.playbackRate()),
65  mBase(otherSndFile.mBase) {
66  this->setWaveform(otherSndFile.mWavetable), mCurrentFrame = 0;
67  if ( ! otherSndFile.isCached())
68  logMsg(kLogError, "Cannot copy uncached sound file \"%s\"", mPath.c_str());
69  setPath(otherSndFile.path());
70 }
71 
72 // Clean up data allocated
73 
75 // freeBuffer(); this is done by the WaveTableOsc destructor
76  if (mProperties)
77  delete mProperties;
78 }
79 
80 void Abst_SoundFile::setPath(string tpath) {
81  mPath = tpath;
82 }
83 
84 unsigned Abst_SoundFile::channels() const {
85  return mIsValid ? mNumChannels : 0;
86 }
87 
89  return mIsValid ? ((float) (mStop - mStart) / (float) mFrameRate) : 0.0;
90 }
91 
93  if (mWavetable.mAreBuffersAllocated) // if no buffer allocated
95 }
96 
97 // check if the buffer's big enough
98 
99 void Abst_SoundFile::checkBuffer(unsigned numFrames) {
100  if ( ! mWavetable.mAreBuffersAllocated) { // if no sample read buffer allocated
102  mWavetable.setSize(mNumChannels, numFrames);
104  } else if (mWavetable.mNumFrames < numFrames) { // if asking for more samples than fit in the buffer
105  logMsg("Reallocating sound file buffers (%d)", numFrames * mNumChannels);
107  mWavetable.setSize(mNumChannels, numFrames);
109  }
110 //#ifdef CSL_USE_SRConv
111 // if ( ! mSRConvBuffer.mAreBuffersAllocated) { // if no SRC buffer allocated
112 // mSRConvBuffer.setSize(1, CGestalt::maxBufferFrames() * mNumChannels);
113 // mSRConvBuffer.allocateBuffers();
114 // }
115 //#endif
116 }
117 
118 void Abst_SoundFile::checkBuffer(unsigned numChans, unsigned numFrames) {
119  if ( ! mWavetable.mAreBuffersAllocated) { // if no sample read buffer allocated
120  mNumChannels = numChans;
121  mWavetable.setSize(mNumChannels, numFrames);
123  // if asking for more samples than fit in the buffer
124  } else if ((mWavetable.mNumFrames < numFrames) || (mNumChannels != numChans)) {
125  logMsg("Reallocating sound file buffers (%d)", numFrames * mNumChannels);
126  mNumChannels = numChans;
128  mWavetable.setSize(mNumChannels, numFrames);
130  }
131 }
132 
133 // average all the channels to mono
134 
137  logMsg("Abst_SoundFile::mergeToMono - buffers not allocated");
138  return;
139  }
140  if (mNumChannels == 1) {
141  logMsg("Abst_SoundFile::mergeToMono - sound file already mono");
142  return;
143  }
144  Buffer newBuf(1, mWavetable.mNumFrames); // create a new mono buffer
145  newBuf.allocateBuffers();
146  // sum channels
147  unsigned numCh = mNumChannels;
148  for (unsigned i = 0; i < mWavetable.mNumFrames; i++) {
149  sample sum = 0.0f;
150  for (unsigned j = 0; j < numCh; j++)
151  sum += (mWavetable.buffer(j))[i];
152  // store the average
153  newBuf.setBuffer(0, i, sum / numCh);
154  } // now overwrite my own mWavetable
155  mWavetable.copyFrom(newBuf);
156  newBuf.mDidIAllocateBuffers = false;
158  mNumChannels = 1;
159 }
160 
161 // Abst_SoundFile::convertRate - perform sample-rate conversion
162 
163 void Abst_SoundFile::convertRate(int fromRate, int toRate) {
164 #ifndef USE_SRC
165  return;
166 #else
167  mWavetable.convertRate(fromRate, toRate);
168  mFrameRate = toRate;
170 #endif
171 }
172 
173 // ~~~~~~~ Accessors ~~~~~~~~
174 
175 void Abst_SoundFile::setStart(int val) {
176  mStart = val;
177  if (mStart < 0)
178  mStart = 0;
179  if ((unsigned) mStart >= duration())
180  mStart = (int) duration();
181  if (mIsValid)
182  seekTo(mStart);
183 }
184 
186  setStart((int) (val * mFrameRate));
187 }
188 
190  setStart((int) (val * duration()));
191 }
192 
193 void Abst_SoundFile::setStop(int val) {
194  mStop = val;
195  if (mStop < 0)
196  mStop = 0;
197  int du = (int) duration();
198  if (mStop > du)
199  mStop = du;
200 }
201 
202 void Abst_SoundFile::setStopSec(float val) {
203  setStop((int) (val * mFrameRate));
204 }
205 
207  setStop((int) (val * duration()));
208 }
209 
210 void Abst_SoundFile::setBase(int val) {
211  mBase = val;
212 }
213 
214 // Rate and transposition
215 
217  if ( ! mInputs[CSL_RATE])
218  this->addInput(CSL_RATE, frequency);
219  else
220  mInputs[CSL_RATE]->mUGen = & frequency;
221 }
222 
223 void Abst_SoundFile::setRate(float frequency) {
224  mRate = frequency;
225 #ifdef CSL_DEBUG
226  logMsg("FrequencyAmount::set scale input value");
227 #endif
228 }
229 
231  if ( ! mIsValid)
232  return false;
233  return (mCurrentFrame < (unsigned) mStop);
234 }
235 
237  return (mWavetable.mNumFrames == duration());
238 }
239 
240 ///< answer if file has X samples in RAM
241 
242 bool Abst_SoundFile::isCached(unsigned samps) {
243  return (mWavetable.mNumFrames < (mCurrentFrame + samps));
244 }
245 
246 // trigger the file to start
247 
249  if (mStart > 0) {
251  seekTo(mStart);
252  } else {
253  mCurrentFrame = 0;
254  seekTo(0);
255  }
256 }
257 
258 // set the pointer to the end of the file
259 
262 }
263 
264 // Read the ID3 tags. Returns true if able to read them from the file
265 
267 #ifdef USE_TAGS
268  if ( ! mProperties)
270  TagLib::FileRef tfil(mPath.c_str());
271  if ( ! tfil.isNull() && tfil.tag()) {
272  TagLib::Tag *tag = tfil.tag();
273  mProperties->mTitle = string(tag->title().toCString());
274  mProperties->mArtist = string(tag->artist().toCString());
275  mProperties->mAlbum = string(tag->album().toCString());
276  mProperties->mYear = tag->year();
277  mProperties->mComment = string(tag->comment().toCString());
278  mProperties->mTrack = tag->track();
279  mProperties->mGenre = string(tag->genre().toCString());
280  }
281  if( ! tfil.isNull() && tfil.audioProperties()) {
282  TagLib::AudioProperties *properties = tfil.audioProperties();
283  mProperties->mBitRate = properties->bitrate();
284  mProperties->mSampleRate= properties->sampleRate();
285  mProperties->mChannels = properties->channels();
286  mProperties->mLength = properties->length();
287  }
288  return !tfil.isNull();
289 #else
290  return false;
291 #endif
292 }
293 
294 // log snd file props
295 
297  const char * nam = path().c_str();
298  if (strlen(nam) > 50)
299  logMsg("SndFile \"%s\"\n\t\t%d Hz, %d ch, %5.3f sec",
300  nam, frameRate(), channels(), durationInSecs());
301  else
302  logMsg("SndFile \"%s\" - %d Hz, %d ch, %5.3f sec",
303  nam, frameRate(), channels(), durationInSecs());
304 }
305 
306 ////////////// next_buffer -- the work is done here //////////
307 
308 void Abst_SoundFile::nextBuffer(Buffer &outputBuffer) throw(CException) {
309  unsigned numFrames = outputBuffer.mNumFrames;
310  unsigned currentFrame = mCurrentFrame;
311  DECLARE_SCALABLE_CONTROLS; // declare the scale/offset buffers and values
312 
313  if (currentFrame >= (unsigned) mStop) { // if done
314  outputBuffer.zeroBuffers();
315  return;
316  }
317  if (currentFrame + numFrames >= (unsigned) mStop) { // if final window
318  numFrames = mStop - currentFrame;
319  outputBuffer.zeroBuffers();
320  }
321  if ( ! this->isCached(numFrames)) { // if not playing from cache buffer
322  this->readBufferFromFile(numFrames); // read from file
323  }
324 
325  LOAD_SCALABLE_CONTROLS; // load the scaleC and offsetC from the constant or dynamic value
326 
327  if (mRate == 1) { // if playing at normal rate
328  if (scalePort->isFixed() && offsetPort->isFixed() // if fixed scale/offset, use memcpy
329  && (scaleValue == 1) && (offsetValue == 0)) {
330  unsigned numBytes = numFrames * sizeof(sample); // buffer copy loop
331  for (unsigned i = 0; i < outputBuffer.mNumChannels; i++) {
332  int which = csl_min(i, (mNumChannels - 1));
333  SampleBuffer sndPtr = mWavetable.monoBuffer(which) + currentFrame;
334  SampleBuffer outPtr = outputBuffer.monoBuffer(i);
335  memcpy(outPtr, sndPtr, numBytes); // here's the memcpy
336  }
337  } else { // else loop applying scale/offset
338  sample samp;
339  for (unsigned i = 0; i < outputBuffer.mNumChannels; i++) {
340  SampleBuffer buffer = outputBuffer.monoBuffer(i); // get pointer to the selected output channel
341  SampleBuffer dPtr = mWavetable.monoBuffer(csl_min(i, (mNumChannels - 1))) + currentFrame;
342  for (unsigned j = 0; j < numFrames; j++) { // here's the sample loop
343  samp = (*dPtr++ * scaleValue) + offsetValue; // get and scale the file sample
344  *buffer++ = samp;
345  UPDATE_SCALABLE_CONTROLS; // update the dynamic scale/offset
346  }
347  scalePort->resetPtr();
348  offsetPort->resetPtr();
349  }
350  }
351  } else { // if mRate != 1.0,
352  // use wavetable interpolation
353  this->setFrequency(mRate);
354  for (unsigned i = 0; i < mNumChannels; i++)
355  WavetableOscillator::nextBuffer(outputBuffer, i);
356  }
357  currentFrame += numFrames; // increment buf ptr
358  if ((currentFrame >= (unsigned) mStop) && mIsLooping) // if we are past the end of the file...
359  currentFrame = 0; // this will click, have to call nextBuffer() recursively here
360  mCurrentFrame = currentFrame; // store back to member
361  return;
362 }
363 
364 
365 ////////////////////////////////////////////////////////////////////////////////////////////
366 //
367 // Sound Cue implementation
368 //
369 
370 // Generic constructors
371 
373  mFile = NULL;
374  mCurrent = 0;
375 }
376 
377 SoundCue::SoundCue(string name, Abst_SoundFile *file, int start, int stop) :
378  mName(name),
379  mFile(file),
380  mStart(start),
381  mStop(stop) {
382  mCurrent = mStart;
383  mReadRate = (UnitGenerator*) 0;
384 }
385 
387 
388 // Read an instance's data from a file
389 // The format is that used in the SeSpSp layer index file:
390 // name start stop
391 // b5b.r21e.aiff 13230080 15876096 -- NB: these are in FRAMES
392 
393 void SoundCue::readFrom (FILE *input) {
394  char cmName[128];
395  unsigned start, stop;
396 
397  if(fscanf(input, "%s %u %u\n", cmName, &start, &stop) == 3) {
398  mName = cmName;
399  mStart = start;
400  mStop = stop;
401  mCurrent = mStart;
402  }
403 }
404 
405 // Pretty-print the receiver
406 
408  logMsg("\tSC: \"%s\" %d - %d (%.3f)\n",
409  mName.c_str(), mStart, mStop,
410  ((float)(mStop - mStart) / (float)(mFile->frameRate() * mFile->channels())));
411 }
412 
413 // Copy samples from the file's sample buffer to the output
414 // I assume that the # of channels in the output is the same as in my file, i.e.,
415 // To play a mono file, "wrap" it in a Deinterleaver
416 
417 void SoundCue::nextBuffer(Buffer &outputBuffer) throw(CException) {
418  unsigned numFrames = outputBuffer.mNumFrames;
419  unsigned toCopy = 0, toZero;
420  SampleBuffer out;
421 
422  for (unsigned h = 0; h < outputBuffer.mNumChannels; h++) {
423  out = outputBuffer.buffer(h);
424  if (mCurrent >= mStop) { // if done
425  for (unsigned i = 0; i < numFrames; i++)
426  *out++ = 0.0;
427  continue;
428  }
429  SampleBuffer samps = (mFile->mWavetable.monoBuffer(0)) + (mCurrent * mFile->channels()) + h;
430  if ( ! mFile->isValid()) { // if no file data
431  fprintf(stderr, "\tCannot play uncached sound file \"%s\"n", mName.c_str());
432  for (unsigned i = 0; i < numFrames; i++)
433  *out++ = 0.0;
434  continue;
435  }
436  toCopy = numFrames; // figure out how many samples to copy
437  toZero = 0;
438  if ((mCurrent + numFrames) >= (unsigned) mStop) {
439  toCopy = mStop - mCurrent;
440  toZero = numFrames - toCopy;
441  // fprintf(stderr, "\tSample \"%s\" finished\n", mName);
442  } // Now do the copy loops
443  for (unsigned i = 0; i < toCopy; i++)
444  *out++ = *samps++;
445  for (unsigned i = 0; i < toZero; i++)
446  *out++ = 0.0;
447  }
448  mCurrent += toCopy; // increment the receiver's sample pointer
449  return;
450 }
451 
452 // Utility functions
453 
455  if (mFile == 0)
456  return (false);
457  return (mCurrent < mStop);
458 }
459 
460 void SoundCue::setToEnd(void) {
461  mCurrent = mStop;
462 }
463 
464 void SoundCue::trigger(void) {
465  mCurrent = mStart;
466  mFloatCurrent = 0.0;
467 }
468 
469 #ifdef USE_SNDFILEBUFFER
470 
471 ////////////////////////////////////////////////////////////////////////////////////////////
472 //
473 // SoundFileBuffer implementation
474 //
475 
476 #include "SoundFileL.h" // abstract class header
477 
478 
479 SoundFileBuffer::SoundFileBuffer(string path, unsigned numFrames)
480  : Buffer(CGestalt::numOutChannels(), numFrames) {
481  mFile = (Abst_SoundFile * ) (new LSoundFile(path));
482  mFile->openForRead(false);
485  mNumFrames = mFile->duration();
486  mNumAlloc = csl_min(CGestalt::sndFileFrames(), mNumFrames);
487  mMonoBufferByteSize = mNumFrames * mNumChannels * sizeof(sample);
488  mAreBuffersAllocated = true;
489  mDidIAllocateBuffers = false;
490 
491  mBuffers = new SampleBuffer[mNumChannels]; // reserve space for buffers
492  for (unsigned i = 0; i < mNumChannels; i++)
493  setBuffer(i, mFile->buffer(i));
494 }
495 
496 ///< Copy constructor -- shares sample buffer
497 
498 SoundFileBuffer::SoundFileBuffer(Abst_SoundFile & otherSndFile) {
499  // setBuffer
500 }
501 
502 SoundFileBuffer::~SoundFileBuffer() { }
503 
504 
505 /// answer a samp ptr, testing that it's in RAM
506 
507 sample * SoundFileBuffer::samplePtrFor(unsigned channel, unsigned offset){
508  if ((mFile->base() <= offset) && (mFile->base() + mFile->cacheSize() > offset))
509  return(mFile->mWavetable.buffer(channel) + (offset - mFile->base()));
510  if (mFile) {
511  mFile->seekTo(offset);
512  mFile->readBufferFromFile(mFile->mWavetable.mNumFrames);
513  return(mFile->mWavetable.buffer(channel));
514  }
515  return NULL;
516 }
517 
518 /// answer a samp ptr tested for extent (offset + maxFrame)
519 
520 sample * SoundFileBuffer::samplePtrFor(unsigned channel, unsigned offset, unsigned maxFrame){
521  if ((mFile->base() <= offset) && ((mFile->base() + mNumAlloc) > (offset + maxFrame)))
522  return (mFile->mWavetable.buffer(channel) + (offset - mFile->base()));
523  if (mFile) {
524  mFile->seekTo(offset);
525  mFile->readBufferFromFile(mFile->mWavetable.mNumFrames);
526  return(mFile->mWavetable.buffer(channel));
527  }
528  return NULL;
529 }
530 
531 #endif
532 
533 #ifdef UNDEFINED
534 
535 //class SampleFile : public Abst_SoundFile {
536 //public:
537 // SampleFile(); /// Constructor
538 // SampleFile(string name, Abst_SoundFile *file = 0, int start = 1, int stop = -1);
539 // ~SampleFile();
540 // /// Data members
541 // unsigned mMIDIKey; // sample's MIDI key #
542 // double mFrequency; // sample's actual frequency
543 // double mMinRatio; // min transp.
544 // double mMaxRatio; // max transp. (often 1.0)
545 //
546 // double ratioForKey(int desiredMIDI);
547 // double ratioForPitch(int desiredMIDI);
548 
549 SampleFile::SampleFile() {
550 
551 }
552 
553 SampleFile::SampleFile(string tpath, unsigned MIDIKey, double frequency, double minRatio, double maxRatio) {
554 
555 }
556 
557 SampleFile::~SampleFile() {
558 
559 }
560 
561 #endif