CSL 5.2

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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
15 SoundFileMetadata::SoundFileMetadata() { }
16
17 void SoundFileMetadata::dump() {
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",
32 mTrack, mGenre.c_str(), mBitRate, mSampleRate, mChannels);
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) :
40 WavetableOscillator(), Writeable(), Seekable(),
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
56 Abst_SoundFile::Abst_SoundFile(Abst_SoundFile & otherSndFile) :
57 WavetableOscillator(), Writeable(), Seekable(),
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
74 Abst_SoundFile::~Abst_SoundFile() {
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
88 float Abst_SoundFile::durationInSecs() {
89 return mIsValid ? ((float) (mStop - mStart) / (float) mFrameRate) : 0.0;
90 }
91
92 void Abst_SoundFile::freeBuffer() {
93 if (mWavetable.mAreBuffersAllocated) // if no buffer allocated
94 mWavetable.freeBuffers();
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
101 mWavetable.freeBuffers();
102 mWavetable.setSize(mNumChannels, numFrames);
103 mWavetable.allocateBuffers();
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);
106 mWavetable.freeBuffers();
107 mWavetable.setSize(mNumChannels, numFrames);
108 mWavetable.allocateBuffers();
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);
122 mWavetable.allocateBuffers();
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;
127 mWavetable.freeBuffers();
128 mWavetable.setSize(mNumChannels, numFrames);
129 mWavetable.allocateBuffers();
130 }
131 }
132
133 // average all the channels to mono
134
135 void Abst_SoundFile::mergeToMono() {
136 if ( ! mWavetable.mAreBuffersAllocated) {
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;
157 mWavetable.mDidIAllocateBuffers = true;
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;
169 mNumFrames = mWavetable.mNumFrames;
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
185 void Abst_SoundFile::setStartSec(float val) {
186 setStart((int) (val * mFrameRate));
187 }
188
189 void Abst_SoundFile::setStartRatio(float val) {
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
206 void Abst_SoundFile::setStopRatio(float val) {
207 setStop((int) (val * duration()));
208 }
209
210 void Abst_SoundFile::setBase(int val) {
211 mBase = val;
212 }
213
214 // Rate and transposition
215
216 void Abst_SoundFile::setRate(UnitGenerator & frequency) {
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
230 bool Abst_SoundFile::isActive() {
231 if ( ! mIsValid)
232 return false;
233 return (mCurrentFrame < (unsigned) mStop);
234 }
235
236 bool Abst_SoundFile::isCached() {
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
248 void Abst_SoundFile::trigger() {
249 if (mStart > 0) {
250 mCurrentFrame = mStart;
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
260 void Abst_SoundFile::setToEnd() {
261 mCurrentFrame = mStop;
262 }
263
264 // Read the ID3 tags. Returns true if able to read them from the file
265
266 bool Abst_SoundFile::readTags() throw (CException) {
267 #ifdef USE_TAGS
268 if ( ! mProperties)
269 mProperties = new SoundFileMetadata;
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
296 void Abst_SoundFile::dump() {
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
372 SoundCue::SoundCue() {
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
386 SoundCue::~SoundCue() { }
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
407 void SoundCue::dump() {
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
454 bool SoundCue::isActive() {
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);
483 mFile->readBufferFromFile(CGestalt::sndFileFrames());
484 mNumChannels = mFile->channels();
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