diff options
Diffstat (limited to 'src/loris/AiffFile.C')
-rw-r--r-- | src/loris/AiffFile.C | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/loris/AiffFile.C b/src/loris/AiffFile.C new file mode 100644 index 0000000..a494098 --- /dev/null +++ b/src/loris/AiffFile.C @@ -0,0 +1,591 @@ +/* + * This is the Loris C++ Class Library, implementing analysis, + * manipulation, and synthesis of digitized sounds using the Reassigned + * Bandwidth-Enhanced Additive Sound Model. + * + * Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken + * + * This program 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. + * + * This program 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 + * + * + * AiffFile.C + * + * Implementation of AiffFile class for sample import and export in Loris. + * + * Class AiffFile represents sample data in a AIFF-format samples + * file, and manages file I/O and sample conversion. Since the sound + * analysis and synthesis algorithms in Loris and the reassigned + * bandwidth-enhanced representation are monaural, AiffFile manages + * only monaural (single channel) AIFF-format samples files. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "AiffFile.h" + +#include "AiffData.h" +#include "LorisExceptions.h" +#include "Marker.h" +#include "Notifier.h" +#include "Synthesizer.h" + +#include <algorithm> +#include <climits> +#include <fstream> +#include <vector> + +// begin namespace +namespace Loris { + + +// -- construction -- + +// --------------------------------------------------------------------------- +// AiffFile constructor from filename +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile by importing sample data from +//! the file having the specified filename or path. +//! +//! \param filename is the name or path of an AIFF samples file +// +AiffFile::AiffFile( const std::string & filename ) : + notenum_( 60 ), + rate_( 1 ), // rate will be overwritten on import + numchans_( 1 ) +{ + readAiffData( filename ); +} + +// --------------------------------------------------------------------------- +// AiffFile constructor from parameters, no samples. +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile having the specified sample +//! rate, preallocating numFrames samples, initialized to zero. +//! +//! \param samplerate is the rate at which Partials are rendered +//! \param numFrames is the initial number of (zero) samples. If +//! unspecified, no samples are preallocated. +// +AiffFile::AiffFile( double samplerate, size_type numFrames /* = 0 */, + unsigned int numChannels /* = 1 */ ) : + notenum_( 60 ), + rate_( samplerate ), + numchans_( numChannels ), + samples_( numFrames * numChannels, 0 ) +{ +} + +// --------------------------------------------------------------------------- +// AiffFile constructor from sample data +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile from a buffer of sample +//! data, with the specified sample rate. +//! +//! \param buffer is a pointer to a buffer of floating point samples. +//! \param bufferlength is the number of samples in the buffer. +//! \param samplerate is the sample rate of the samples in the buffer. +// +AiffFile::AiffFile( const double * buffer, size_type bufferlength, double samplerate ) : + notenum_( 60 ), + rate_( samplerate ), + numchans_( 1 ) +{ + samples_.insert( samples_.begin(), buffer, buffer+bufferlength ); +} + +// --------------------------------------------------------------------------- +// AiffFile constructor from stereo sample data +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile from two buffers of sample +//! data, with the specified sample rate. Both buffers must store +//! the same number (bufferLength) of samples. +//! +//! \param buffer_left is a pointer to a buffer of floating point samples +//! representing the left channel samples. +//! \param buffer_right is a pointer to a buffer of floating point samples +//! representing the right channel samples. +//! \param bufferlength is the number of samples in the buffer. +//! \param samplerate is the sample rate of the samples in the buffer. +// +AiffFile::AiffFile( const double * buffer_left, const double * buffer_right, + size_type bufferlength, double samplerate ) : + notenum_( 60 ), + rate_( samplerate ), + numchans_( 2 ) +{ + // interleave the two channels in samples_ + samples_.resize( 2*bufferlength, 0. ); + size_type idx = 0; + while( idx < samples_.size() ) + { + samples_[ idx++ ] = *buffer_left++; + samples_[ idx++ ] = *buffer_right++; + } +} + +// --------------------------------------------------------------------------- +// AiffFile constructor from sample data +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile from a vector of sample +//! data, with the specified sample rate. +//! +//! \param vec is a vector of floating point samples. +//! \param samplerate is the sample rate of the samples in the vector. +// +AiffFile::AiffFile( const std::vector< double > & vec, double samplerate ) : + notenum_( 60 ), + rate_( samplerate ), + numchans_( 1 ), + samples_( vec.begin(), vec.end() ) +{ +} + +// --------------------------------------------------------------------------- +// AiffFile constructor from stereo sample data +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile from two vectors of sample +//! data, with the specified sample rate. If the two vectors have different +//! lengths, the shorter one is padded with zeros. +//! +//! \param vec_left is a vector of floating point samples representing the +//! left channel samples. +//! \param vec_right is a vector of floating point samples representing the +//! right channel samples. +//! \param samplerate is the sample rate of the samples in the vectors. +// +AiffFile::AiffFile( const std::vector< double > & vec_left, + const std::vector< double > & vec_right, + double samplerate ) : + notenum_( 60 ), + rate_( samplerate ), + numchans_( 2 ), + samples_( 2 * std::max( vec_left.size(), vec_right.size() ), 0. ) +{ + // interleave the two channels in samples_ + size_type idx = 0; + std::vector< double >::const_iterator iter_left = vec_left.begin(); + std::vector< double >::const_iterator iter_right= vec_right.begin(); + while( idx < samples_.size() ) + { + if ( iter_left != vec_left.end() ) + { + samples_[ idx ] = *iter_left++; + } + if ( iter_right != vec_right.end() ) + { + samples_[ idx+1 ] = *iter_right++; + } + idx += 2; + } +} + +// --------------------------------------------------------------------------- +// AiffFile copy constructor +// --------------------------------------------------------------------------- +//! Initialize this and AiffFile that is an exact copy, having +//! all the same sample data, as another AiffFile. +//! +//! \param other is the AiffFile to copy +// +AiffFile::AiffFile( const AiffFile & other ) : + notenum_( other.notenum_ ), + rate_( other.rate_ ), + numchans_( other.numchans_ ), + markers_( other.markers_ ), + samples_( other.samples_ ) +{ +} + +// --------------------------------------------------------------------------- +// AiffFile assignment operator +// --------------------------------------------------------------------------- +//! Assignment operator: change this AiffFile to be an exact copy +//! of the specified AiffFile, rhs, that is, having the same sample +//! data. +//! +//! \param rhs is the AiffFile to replicate +// +AiffFile & +AiffFile::operator= ( const AiffFile & rhs ) +{ + if ( &rhs != this ) + { + // before modifying anything, make + // sure there's enough space: + samples_.reserve( rhs.samples_.size() ); + markers_.reserve( rhs.markers_.size() ); + + notenum_ = rhs.notenum_; + rate_ = rhs.rate_; + numchans_ = rhs.numchans_; + markers_ = rhs.markers_; + samples_ = rhs.samples_; + } + return *this; +} + +// -- export -- + +// --------------------------------------------------------------------------- +// write +// --------------------------------------------------------------------------- +//! Export the sample data represented by this AiffFile to +//! the file having the specified filename or path. Export +//! signed integer samples of the specified size, in bits +//! (8, 16, 24, or 32). +//! +//! \param filename is the name or path of the AIFF samples file +//! to be created or overwritten. +//! \param bps is the number of bits per sample to store in the +//! samples file (8, 16, 24, or 32).If unspeicified, 16 bits +//! is assumed. +// +void +AiffFile::write( const std::string & filename, unsigned int bps ) +{ + static const unsigned int ValidSizes[] = { 8, 16, 24, 32 }; + if ( std::find( ValidSizes, ValidSizes+4, bps ) == ValidSizes+4 ) + { + Throw( InvalidArgument, "Invalid bits-per-sample." ); + } + + std::ofstream s( filename.c_str(), std::ofstream::binary ); + if ( ! s ) + { + std::string s = "Could not create file \""; + s += filename; + s += "\". Failed to write AIFF file."; + Throw( FileIOException, s ); + } + + unsigned long dataSize = 0; + + CommonCk commonChunk; + configureCommonCk( commonChunk, samples_.size() / numchans_, numchans_, bps, rate_ ); + dataSize += commonChunk.header.size + sizeof(CkHeader); + + SoundDataCk soundDataChunk; + configureSoundDataCk( soundDataChunk, samples_, bps ); + dataSize += soundDataChunk.header.size + sizeof(CkHeader); + + InstrumentCk instrumentChunk; + configureInstrumentCk( instrumentChunk, notenum_ ); + dataSize += instrumentChunk.header.size + sizeof(CkHeader); + + MarkerCk markerChunk; + if ( ! markers_.empty() ) + { + configureMarkerCk( markerChunk, markers_, rate_ ); + dataSize += markerChunk.header.size + sizeof(CkHeader); + } + + ContainerCk containerChunk; + configureContainer( containerChunk, dataSize ); + + try + { + writeContainer( s, containerChunk ); + writeCommonData( s, commonChunk ); + if ( ! markers_.empty() ) + writeMarkerData( s, markerChunk ); + writeInstrumentData( s, instrumentChunk ); + writeSampleData( s, soundDataChunk ); + + s.close(); + } + catch ( Exception & ex ) + { + ex.append( " Failed to write AIFF file." ); + throw; + } +} + +// -- access -- + +// --------------------------------------------------------------------------- +// markers +// --------------------------------------------------------------------------- +//! Return a reference to the Marker (see Marker.h) container +//! for this AiffFile. +// +AiffFile::markers_type & +AiffFile::markers( void ) +{ + return markers_; +} + +//! Return a const reference to the Marker (see Marker.h) container +//! for this AiffFile. +// +const AiffFile::markers_type & +AiffFile::markers( void ) const +{ + return markers_; +} + +// --------------------------------------------------------------------------- +// midiNoteNumber +// --------------------------------------------------------------------------- +//! Return the fractional MIDI note number assigned to this AiffFile. +//! If the sound has no definable pitch, note number 60.0 is used. +// +double +AiffFile::midiNoteNumber( void ) const +{ + return notenum_; +} + +// --------------------------------------------------------------------------- +// numChannels +// --------------------------------------------------------------------------- +//! Return the number of channels of audio samples represented by +//! this AiffFile, 1 for mono, 2 for stereo. +// +unsigned int +AiffFile::numChannels( void ) const +{ + return numchans_; +} + +// --------------------------------------------------------------------------- +// numFrames +// --------------------------------------------------------------------------- +//! Return the number of sample frames represented in this AiffFile. +//! A sample frame contains one sample per channel for a single sample +//! interval (e.g. mono and stereo samples files having a sample rate of +//! 44100 Hz both have 44100 sample frames per second of audio samples). +// + AiffFile::size_type + AiffFile::numFrames( void ) const + { + return samples_.size(); + } + +// --------------------------------------------------------------------------- +// sampleRate +// --------------------------------------------------------------------------- +//! Return the sampling freqency in Hz for the sample data in this +//! AiffFile. +// +double +AiffFile::sampleRate( void ) const +{ + return rate_; +} + +// --------------------------------------------------------------------------- +// samples +// --------------------------------------------------------------------------- +//! Return a reference (or const reference) to the vector containing +//! the floating-point sample data for this AiffFile. +// +AiffFile::samples_type & +AiffFile::samples( void ) +{ + return samples_; +} + +//! Return a const reference (or const reference) to the vector containing +//! the floating-point sample data for this AiffFile. +// +const AiffFile::samples_type & +AiffFile::samples( void ) const +{ + return samples_; +} + +// -- mutation -- + +// --------------------------------------------------------------------------- +// addPartial +// --------------------------------------------------------------------------- +//! Render the specified Partial using the (optionally) specified +//! Partial fade time (see Synthesizer.h for an examplanation +//! of fade time), and accumulate the resulting samples into +//! the sample vector for this AiffFile. Other synthesis parameters +//! are taken from the Synthesizer DefaultParameters. +//! +//! \sa Synthesizer::DefaultParameters +//! +//! \param p is the partial to render into this AiffFile +//! \param fadeTime is the Partial fade time for rendering +//! the Partials on the specified range. If unspecified, the +//! fade time is taken from the Synthesizer DefaultParameters. +// +void +AiffFile::addPartial( const Loris::Partial & p, double fadeTime ) +{ + Synthesizer synth = configureSynthesizer( fadeTime ); + synth.synthesize( p ); +} + +// --------------------------------------------------------------------------- +// setMidiNoteNumber +// --------------------------------------------------------------------------- +//! Set the fractional MIDI note number assigned to this AiffFile. +//! If the sound has no definable pitch, use note number 60.0 (the default). +//! +//! \param nn is a fractional MIDI note number, 60 is middle C. +// +void +AiffFile::setMidiNoteNumber( double nn ) +{ + if ( nn < 0 || nn > 128 ) + { + Throw( InvalidArgument, "MIDI note number outside of the valid range [1,128]" ); + } + notenum_ = nn; +} + +// -- helpers -- + +// --------------------------------------------------------------------------- +// configureSynthesizer +// --------------------------------------------------------------------------- +// Construct a Synthesizer for rendering Partials and set its fadeTime. +// Modify the default synthesizer parameters with this file's sample rate +// and, if specified (not equal to FadeTimeUnspecified), the fade time. +// +Synthesizer +AiffFile::configureSynthesizer( double fadeTime ) +{ + Synthesizer::Parameters params; + params.sampleRate = rate_; + + if ( FadeTimeUnspecified != fadeTime ) + { + params.fadeTime = fadeTime; + } + + return Synthesizer( params, samples_ ); +} + +// --------------------------------------------------------------------------- +// readAiffData +// --------------------------------------------------------------------------- +// Import data from an AIFF file on disk. +// +void +AiffFile::readAiffData( const std::string & filename ) +{ + ContainerCk containerChunk; + CommonCk commonChunk; + SoundDataCk soundDataChunk; + InstrumentCk instrumentChunk; + MarkerCk markerChunk; + + try + { + std::ifstream s( filename.c_str(), std::ifstream::binary ); + + // the Container chunk must be first, read it: + readChunkHeader( s, containerChunk.header ); + if ( !s ) + { + Throw( FileIOException, "File not found, or corrupted." ); + } + if ( containerChunk.header.id != ContainerId ) + { + Throw( FileIOException, "Found no Container chunk." ); + } + readContainer( s, containerChunk, containerChunk.header.size ); + + // read other chunks, we are only interested in + // the Common chunk, the Sound Data chunk, the Markers: + CkHeader h; + while ( readChunkHeader( s, h ) ) + { + switch (h.id) + { + case CommonId: + readCommonData( s, commonChunk, h.size ); + if ( commonChunk.channels != 1 ) + { + Throw( FileIOException, + "Loris only processes single-channel AIFF samples files." ); + } + if ( commonChunk.bitsPerSample != 8 && + commonChunk.bitsPerSample != 16 && + commonChunk.bitsPerSample != 24 && + commonChunk.bitsPerSample != 32 ) + { + Throw( FileIOException, "Unrecognized sample size." ); + } + break; + case SoundDataId: + readSampleData( s, soundDataChunk, h.size ); + break; + case InstrumentId: + readInstrumentData( s, instrumentChunk, h.size ); + break; + case MarkerId: + readMarkerData( s, markerChunk, h.size ); + break; + default: + s.ignore( h.size ); + } + } + + if ( ! commonChunk.header.id || ! soundDataChunk.header.id ) + { + Throw( FileIOException, + "Reached end of file before finding both a Common chunk and a Sound Data chunk." ); + } + } + catch ( Exception & ex ) + { + ex.append( " Failed to read AIFF file." ); + throw; + } + + + // all the chunks have been read, use them to initialize + // the AiffFile members: + rate_ = commonChunk.srate; + + if ( instrumentChunk.header.id ) + { + notenum_ = instrumentChunk.baseNote; + notenum_ -= 0.01 * instrumentChunk.detune; + } + + if ( markerChunk.header.id ) + { + for ( int j = 0; j < markerChunk.numMarkers; ++j ) + { + MarkerCk::Marker & m = markerChunk.markers[j]; + markers_.push_back( Marker( m.position / rate_, m.markerName ) ); + } + } + + convertBytesToSamples( soundDataChunk.sampleBytes, samples_, commonChunk.bitsPerSample ); + if ( samples_.size() != commonChunk.sampleFrames ) + { + notifier << "Found " << samples_.size() << " frames of " + << commonChunk.bitsPerSample << "-bit sample data." << endl; + notifier << "Header says there should be " << commonChunk.sampleFrames + << "." << endl; + } +} + + +} // end of namespace Loris |