diff options
author | John Glover <glover.john@gmail.com> | 2011-07-08 18:06:21 +0100 |
---|---|---|
committer | John Glover <glover.john@gmail.com> | 2011-07-08 18:06:21 +0100 |
commit | d6073e01c933c77f1e2bc3c3fe1126d617003549 (patch) | |
tree | 695d23677c5b84bf3a0f88fbd4959b4f7cbc0e90 /src | |
parent | 641688b252da468eb374674a0dbaae1bbac70b2b (diff) | |
download | simpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.tar.gz simpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.tar.bz2 simpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.zip |
Start adding Loris files
Diffstat (limited to 'src')
96 files changed, 43574 insertions, 0 deletions
diff --git a/src/loris/AiffData.C b/src/loris/AiffData.C new file mode 100644 index 0000000..6feabbf --- /dev/null +++ b/src/loris/AiffData.C @@ -0,0 +1,952 @@ +/* + * 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 + * + * + * AiffData.C + * + * Implementation of import and export functions. + * + * Kelly Fitz, 17 Sept 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "AiffData.h" + +#include "BigEndian.h" +#include "LorisExceptions.h" +#include "Marker.h" +#include "Notifier.h" + +#include <climits> +#include <cmath> +#include <fstream> +#include <iostream> + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// extended80 +// --------------------------------------------------------------------------- +// IEEE floating point conversions, functions defined at end of file +// (used to be in a separate file, but they aren't used anywhere else). +// + +/* struct extended80 defined below */ +struct extended80; + +/* conversion functions */ +extern void ConvertToIeeeExtended(double num, extended80 * x) ; +extern double ConvertFromIeeeExtended(const extended80 * x) ; + +/* struct extended80 definition, with + constructors and conversion to double + */ +struct extended80 +{ + char data[10]; + + extended80( double x = 0. ) { ConvertToIeeeExtended( x, this ); } + operator double( void ) const { return ConvertFromIeeeExtended( this ); } +}; + +// -- AIFF import -- + +// --------------------------------------------------------------------------- +// readChunkHeader +// --------------------------------------------------------------------------- +// Read the id and chunk size from the current file position. +// +std::istream & +readChunkHeader( std::istream & s, CkHeader & h ) +{ + ID id = 0; + Uint_32 sz = 0; + BigEndian::read( s, 1, sizeof(ID), (char *)&id ); + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&sz ); + + if ( s ) + { + h.id = id; + h.size = sz; + } + + return s; +} + +// --------------------------------------------------------------------------- +// readApplicationSpecifcData +// --------------------------------------------------------------------------- +// Read the data in the ApplicationSpecific chunk, assume the stream is +// correctly positioned, and that the chunk header has already been read. +// +// Look for data specific to SPC files. Any other kind of Application +// Specific data is ignored. +// +std::istream & +readApplicationSpecifcData( std::istream & s, SosEnvelopesCk & ck, unsigned long chunkSize ) +{ + Int_32 tmp_signature; + BigEndian::read( s, 1, sizeof(Int_32), (char *)&tmp_signature ); + + if ( tmp_signature == SosEnvelopesId ) + { + ck.header.id = ApplicationSpecificId; + ck.header.size = chunkSize; + ck.signature = SosEnvelopesId; + + // lookout! The format of this chunk is a mess, due + // to obsolete stuff lying around! + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.enhanced ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.validPartials ); + s.ignore( ck.validPartials * sizeof(Int_32) ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.resolution ); + s.ignore( chunkSize - (4 + ck.validPartials) * sizeof(Int_32) ); + } + else + { + s.ignore( chunkSize - sizeof(Int_32) ); + } + + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad ApplicationSpecific chunk)." ); + } + + return s; +} + +// --------------------------------------------------------------------------- +// readCommonData +// --------------------------------------------------------------------------- +// Read the data in the Common chunk, assume the stream is correctly +// positioned, and that the chunk header has already been read. +// +std::istream & +readCommonData( std::istream & s, CommonCk & ck, unsigned long chunkSize ) +{ + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.channels ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.sampleFrames ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.bitsPerSample ); + + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad Common chunk)." ); + } + + ck.header.id = CommonId; + ck.header.size = chunkSize; + + // don't let this get byte-reversed: + extended80 read_rate; + BigEndian::read( s, sizeof(extended80), sizeof(char), (char *)&read_rate ); + ck.srate = read_rate; + + return s; +} + +// --------------------------------------------------------------------------- +// readContainer +// --------------------------------------------------------------------------- +// +std::istream & +readContainer( std::istream & s, ContainerCk & ck, unsigned long chunkSize ) +{ + ck.header.id = ContainerId; + ck.header.size = chunkSize; + + // read in the chunk data: + BigEndian::read( s, 1, sizeof(ID), (char *)&ck.formType ); + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad Container chunk)." ); + } + + // make sure its really AIFF: + if ( ck.formType != AiffType ) + { + std::string err("Bad form type in AIFF file: "); + err += std::string( ck.formType, 4 ); + Throw( FileIOException, err ); + } + + return s; +} + +// --------------------------------------------------------------------------- +// readInstrumentData +// --------------------------------------------------------------------------- +// +std::istream & +readInstrumentData( std::istream & s, InstrumentCk & ck, unsigned long chunkSize ) +{ + ck.header.id = InstrumentId; + ck.header.size = chunkSize; + + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.baseNote ); + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.detune ); + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.lowNote ); + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.highNote ); + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.lowVelocity ); + BigEndian::read( s, 1, sizeof(Byte), (char *)&ck.highVelocity ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.gain ); + + // AIFFLoop is three Int_16s: + BigEndian::read( s, 1, sizeof(Uint_16), (char *)&ck.sustainLoop.playMode ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.sustainLoop.beginLoop ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.sustainLoop.endLoop ); + + BigEndian::read( s, 1, sizeof(Uint_16), (char *)&ck.releaseLoop.playMode ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.releaseLoop.beginLoop ); + BigEndian::read( s, 1, sizeof(Int_16), (char *)&ck.releaseLoop.endLoop ); + + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad Common chunk)." ); + } + + return s; +} + +// --------------------------------------------------------------------------- +// readMarkerData +// --------------------------------------------------------------------------- +// +std::istream & +readMarkerData( std::istream & s, MarkerCk & ck, unsigned long chunkSize ) +{ + ck.header.id = MarkerId; + ck.header.size = chunkSize; + + Uint_32 bytesToRead = chunkSize; + + // read in the number of Markers + BigEndian::read( s, 1, sizeof(Uint_16), (char *)&ck.numMarkers ); + bytesToRead -= sizeof(Uint_16); + + for ( int i = 0; i < ck.numMarkers; ++i ) + { + MarkerCk::Marker marker; + BigEndian::read( s, 1, sizeof(Uint_16), (char *)&marker.markerID ); + bytesToRead -= sizeof(Uint_16); + + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&marker.position ); + bytesToRead -= sizeof(Uint_32); + + // read the size of the name string, then the characters: + unsigned char namelength; + BigEndian::read( s, 1, sizeof(unsigned char), (char *)&namelength ); + bytesToRead -= sizeof(unsigned char); + + // need to add one to the length, because, like C-strings, + // Pascal strings are null-terminated, but the null character + // is _not_ counted in the length. + // + // Correction? At least one web source says that Pascal strings + // are _not_ null terminated, but are padded (with an extra byte + // that is not part of the count) if necessary to make the total + // number of bytes even (that is, the char bytes, _plus_ the count). + // + // This way seems to work with all the files I have tried (Kyma and + // Peak, mostly) for AIFF and Spc. + int ncharbytes = namelength; + if ( ncharbytes%2 == 0 ) + ++ncharbytes; + static char tmpChars[256]; + BigEndian::read( s, ncharbytes, sizeof(char), tmpChars ); + bytesToRead -= ncharbytes * sizeof(char); + tmpChars[ namelength ] = '\0'; + + // convert to a string: + marker.markerName = std::string( tmpChars ); + + ck.markers.push_back( marker ); + } + + if ( bytesToRead > 0 ) + { + s.ignore( bytesToRead ); + } + + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad Marker chunk)." ); + } + + return s; +} + +// --------------------------------------------------------------------------- +// readSamples +// --------------------------------------------------------------------------- +// Let exceptions propogate. +// +static std::istream & +readSamples( std::istream & s, std::vector< Byte > & bytes ) +{ + debugger << "reading " << bytes.size() << " bytes of sample data" << endl; + + // read integer samples without byte swapping: + BigEndian::read( s, bytes.size(), 1, (char*)(&bytes[0]) ); + + return s; +} + +// --------------------------------------------------------------------------- +// readSampleData +// --------------------------------------------------------------------------- +// Read the data in the Sound Data chunk, assume the stream is correctly +// positioned, and that the chunk header has already been read. +// +std::istream & +readSampleData( std::istream & s, SoundDataCk & ck, unsigned long chunkSize ) +{ + ck.header.id = SoundDataId; + ck.header.size = chunkSize; + + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&ck.offset ); + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&ck.blockSize ); + + // compute the actual number of bytes that + // can be read from this chunk: + // (chunkSize is everything after the header) + const unsigned long howManyBytes = + ( chunkSize - ck.offset ) - (2 * sizeof(Uint_32)); + + ck.sampleBytes.resize( howManyBytes, 0 ); // could throw bad_alloc + + // skip ahead to the samples and read them: + s.ignore( ck.offset ); + readSamples( s, ck.sampleBytes ); + + if ( !s ) + { + Throw( FileIOException, "Failed to read badly-formatted AIFF file (bad Sound Data chunk)." ); + } + + return s; +} + +// -- Chunk construction -- + +// --------------------------------------------------------------------------- +// configureCommonCk +// --------------------------------------------------------------------------- +void +configureCommonCk( CommonCk & ck, unsigned long nFrames, unsigned int nChans, + unsigned int bps, double srate ) +{ + ck.header.id = CommonId; + + // size is everything after the header: + ck.header.size = sizeof(Int_16) + // num channels + sizeof(Int_32) + // num frames + sizeof(Int_16) + // bits per sample + sizeof(extended80); // sample rate + + ck.channels = nChans; + ck.sampleFrames = nFrames; + ck.bitsPerSample = bps; + ck.srate = srate; + // ConvertToIeeeExtended( srate, &ck.srate ); + +} + +// --------------------------------------------------------------------------- +// configureContainer +// --------------------------------------------------------------------------- +// dataSize is the combined size of all other chunks in file. Configure +// them first, then add their sizes (with headers!). +// +void +configureContainer( ContainerCk & ck, unsigned long dataSize ) +{ + ck.header.id = ContainerId; + + // size is everything after the header: + ck.header.size = sizeof(Int_32) + dataSize; + + ck.formType = AiffType; +} + +// --------------------------------------------------------------------------- +// configureInstrumentCk +// --------------------------------------------------------------------------- +void +configureInstrumentCk( InstrumentCk & ck, double midiNoteNum ) +{ + ck.header.id = InstrumentId; + + // size is everything after the header: + ck.header.size = + sizeof(Byte) + // baseFrequency + sizeof(Byte) + // detune + sizeof(Byte) + // lowFrequency + sizeof(Byte) + // highFrequency + sizeof(Byte) + // lowVelocity + sizeof(Byte) + // highVelocity + sizeof(Int_16) + // gain + 2 * sizeof(Int_16) + // playmode for sustainLoop and releaseLoop + 2 * sizeof(Uint_16) + // beginLoop for sustainLoop and releaseLoop + 2 * sizeof(Uint_16); // loopEnd for sustainLoop and releaseLoop + + ck.baseNote = Byte( midiNoteNum ); + ck.detune = Byte(long( 100 * midiNoteNum ) % 100); + if (ck.detune > 50) + { + ck.baseNote++; + ck.detune -= 100; + } + ck.detune *= -1; + + ck.lowNote = 0; + ck.highNote = 127; + ck.lowVelocity = 1; + ck.highVelocity = 127; + ck.gain = 0; + ck.sustainLoop.playMode = 0; // Sustain looping done by name, not by this + ck.sustainLoop.beginLoop = 0; + ck.sustainLoop.endLoop = 0; + ck.releaseLoop.playMode = 0; // No Looping + ck.releaseLoop.beginLoop = 0; + ck.releaseLoop.endLoop = 0; +} + +// --------------------------------------------------------------------------- +// configureMarkerCk +// --------------------------------------------------------------------------- +void +configureMarkerCk( MarkerCk & ck, const std::vector< Marker > & markers, double srate ) +{ + ck.header.id = MarkerId; + + // accumulate data size + Uint_32 dataSize = sizeof(Uint_16); // num markers + + ck.numMarkers = markers.size(); + ck.markers.resize( markers.size() ); + for ( unsigned int j = 0; j < markers.size(); ++j ) + { + MarkerCk::Marker & m = ck.markers[j]; + m.markerID = j+1; + m.position = Uint_32((markers[j].time() * srate) + 0.5); + m.markerName = markers[j].name(); + + #define MAX_PSTRING_CHARS 254 + if ( m.markerName.size() > MAX_PSTRING_CHARS ) + m.markerName.resize( MAX_PSTRING_CHARS ); + + // the size of a pascal string is the number of + // characters plus the size byte, plus the terminal '\0'. + // + // Actualy, at least one web source indicates that Pascal + // strings are not null-terminated, but that they _are_ + // padded with an extra (not part of the count) byte + // if necessary to ensure that the total length (including + // count) is even, and this seems to work better with other + // programs (e.g. Kyma) + if ( m.markerName.size()%2 == 0 ) + m.markerName += '\0'; + dataSize += sizeof(Uint_16) + sizeof(Uint_32) + (m.markerName.size() + 1); + } + + // must be an even number of bytes + if ( dataSize%2 ) + ++dataSize; + + ck.header.size = dataSize; +} + +// --------------------------------------------------------------------------- +// configureSoundDataCk +// --------------------------------------------------------------------------- +// +void +configureSoundDataCk( SoundDataCk & ck, const std::vector< double > & samples, + unsigned int bps ) +{ + Uint_32 dataSize = samples.size() * (bps/8); + // must be an even number of bytes: + if ( dataSize % 2 ) + { + ++dataSize; + } + + ck.header.id = SoundDataId; + + // size is everything after the header: + ck.header.size = sizeof(Uint_32) + // offset + sizeof(Uint_32) + // block size + dataSize; // sample data + + + // no block alignment: + ck.offset = 0; + ck.blockSize = 0; + + // convert the samples to integers stored in + // big endian order in the byte vector: + convertSamplesToBytes( samples, ck.sampleBytes, bps ); +} + + +// -- AIFF export -- + +// --------------------------------------------------------------------------- +// writeCommon +// --------------------------------------------------------------------------- +// +std::ostream & +writeCommonData( std::ostream & s, const CommonCk & ck ) +{ +/* + debugger << "writing common chunk: " << endl; + debugger << "header id: " << ck.header.id << endl; + debugger << "size: " << ck.header.size << endl; + debugger << "channels: " << ck.channels << endl; + debugger << "sample frames: " << ck.sampleFrames << endl; + debugger << "bits per sample: " << ck.bitsPerSample << endl; + //debugger << "rate: " << _sampleRate << endl; +*/ + + // write it out: + try + { + BigEndian::write( s, 1, sizeof(ID), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + BigEndian::write( s, 1, sizeof(Int_16), (char *)&ck.channels ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.sampleFrames ); + BigEndian::write( s, 1, sizeof(Int_16), (char *)&ck.bitsPerSample ); + // don't let this get byte-reversed: + extended80 write_rate( ck.srate ); + //ConvertToIeeeExtended( &ck.srate, write_rate ); + + BigEndian::write( s, sizeof(extended80), sizeof(char), (char *)&write_rate ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to write AIFF file Common chunk." ); + throw; + } + + return s; +} + +// --------------------------------------------------------------------------- +// writeContainer +// --------------------------------------------------------------------------- +// +std::ostream & +writeContainer( std::ostream & s, const ContainerCk & ck ) +{ +/* + debugger << "writing container: " << endl; + debugger << "header id: " << ck.header.id << endl; + debugger << "size: " << ck.header.size << endl; + debugger << "type: " << ck.formType << endl; +*/ + + // write it out: + try + { + BigEndian::write( s, 1, sizeof(ID), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + BigEndian::write( s, 1, sizeof(ID), (char *)&ck.formType ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to write AIFF file Container chunk." ); + throw; + } + + return s; +} + +// --------------------------------------------------------------------------- +// writeInstrumentData +// --------------------------------------------------------------------------- +std::ostream & +writeInstrumentData( std::ostream & s, const InstrumentCk & ck ) +{ + try + { + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.baseNote ); + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.detune ); + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.lowNote ); + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.highNote ); + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.lowVelocity ); + BigEndian::write( s, 1, sizeof(Byte), (char *)&ck.highVelocity ); + BigEndian::write( s, 1, sizeof(Int_16), (char *)&ck.gain ); + + // AIFFLoop is three Int_16s: + BigEndian::write( s, 3, sizeof(Int_16), (char *)&ck.sustainLoop ); + BigEndian::write( s, 3, sizeof(Int_16), (char *)&ck.releaseLoop ); + } + catch( FileIOException & ex ) + { + ex.append( " Failed to write SPC file Instrument chunk." ); + throw; + } + + return s; +} + +// --------------------------------------------------------------------------- +// writeMarkerData +// --------------------------------------------------------------------------- +std::ostream & +writeMarkerData( std::ostream & s, const MarkerCk & ck ) +{ + try + { + BigEndian::write( s, 1, sizeof(ID), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + BigEndian::write( s, 1, sizeof(Uint_16), (char *)&ck.numMarkers ); + + int markerbytes = 0; + for ( unsigned int j = 0; j < ck.markers.size(); ++j ) + { + const MarkerCk::Marker & m = ck.markers[j]; + BigEndian::write( s, 1, sizeof(Uint_16), (char *)&m.markerID ); + BigEndian::write( s, 1, sizeof(Uint_32), (char *)&m.position ); + + // the size of a pascal string is the number of + // characters plus the size byte, plus the terminal '\0': + // Actualy, at least one web source indicates that Pascal + // strings are not null-terminated, but that they _are_ + // padded with an extra (not part of the count) byte + // if necessary to ensure that the total length (including + // count) is even, and this seems to work better with other + // programs (e.g. Kyma) + Uint_32 bytesToWrite = (m.markerName.size() + 1) * sizeof(char); + + // format pascal string: + static char tmpChars[256]; + tmpChars[0] = m.markerName.size(); + std::copy( m.markerName.begin(), m.markerName.end(), tmpChars + 1 ); + tmpChars[m.markerName.size()+1] = '\0'; + + BigEndian::write( s, bytesToWrite, sizeof(char), tmpChars ); + markerbytes += bytesToWrite; + } + + // be sure to write an even number of bytes + if ( markerbytes%2 ) + BigEndian::write( s, 1, sizeof(char), "\0" ); + + } + catch( FileIOException & ex ) + { + ex.append( "Failed to write AIFF file Marker chunk." ); + throw; + } + + return s; +} + +// --------------------------------------------------------------------------- +// writeSamples +// --------------------------------------------------------------------------- +// Let exceptions propogate. +// +static std::ostream & +writeSamples( std::ostream & s, const std::vector< Byte > & bytes ) +{ +// debugger << "writing " << bytes.size() << " bytes of sample data" << endl; + + // write integer samples without byte swapping, + // the bytes were constructed in the correct + // big endian order (see convertSamplesToBytes): + BigEndian::write( s, bytes.size(), 1, (char*)(&bytes[0]) ); + + return s; +} + +// --------------------------------------------------------------------------- +// writeSampleData +// --------------------------------------------------------------------------- +// +std::ostream & +writeSampleData( std::ostream & s, const SoundDataCk & ck ) +{ +/* + debugger << "writing sample data: " << endl; + debugger << "header id: " << ck.header.id << endl; + debugger << "size: " << ck.header.size << endl; + debugger << "offset: " << ck.offset << endl; + debugger << "block size: " << ck.blockSize << endl; +*/ + + // write it out: + try + { + BigEndian::write( s, 1, sizeof(ID), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.offset ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.blockSize ); + + writeSamples( s, ck.sampleBytes ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to write AIFF file Container chunk." ); + throw; + } + + return s; +} + +// -- sample conversion -- + +// --------------------------------------------------------------------------- +// convertBytesToSamples +// --------------------------------------------------------------------------- +// Convert sample bytes to double precision floating point samples +// (-1.0, 1.0). The samples vector is resized to fit exactly as many +// samples as are represented in the bytes vector, and any prior +// contents are overwritten. +// +void +convertBytesToSamples( const std::vector< Byte > & bytes, + std::vector< double > & samples, unsigned int bps ) +{ + Assert( bps <= 32 ); + + typedef std::vector< double >::size_type size_type; + + const int bytesPerSample = bps / 8; + samples.resize( bytes.size() / bytesPerSample ); + + debugger << "converting " << samples.size() << " samples of size " + << bps << " bits" << endl; + + // shift sample bytes into a long integer, and + // scale to make a double: + const double oneOverMax = std::pow(0.5, double(bps-1)); + long samp; + + std::vector< Byte >::const_iterator bytePos = bytes.begin(); + std::vector< double >::iterator samplePos = samples.begin(); + while ( samplePos != samples.end() ) + { + // assign the leading byte, so that the sign + // is preserved: + samp = static_cast<char>(*(bytePos++)); + for ( size_type j = 1; j < bytesPerSample; ++j ) + { + Assert( bytePos != bytes.end() ); + + // OR bytes after the most significant, so + // that their sign is ignored: + samp = (samp << 8) + (unsigned char)*(bytePos++); + + // cannot decide why this didn't work, + // instead of the add above. + //samp |= (unsigned long)*(bytePos++); + } + + *(samplePos++) = oneOverMax * samp; + } +} + +// --------------------------------------------------------------------------- +// convertSamplesToBytes +// --------------------------------------------------------------------------- +// Convert floating point samples (-1.0, 1.0) to bytes. +// The bytes vector is resized to fit exactly as many +// samples as are stored in the samples vector, and any prior +// contents are overwritten. +// +// This formats the samples as big endian bytes, that is, the most +// significant bytes are stored earlier in the byte vector, so +// no byte-swapping should be performed when this byte array is +// written to disk, and moreover this function is specific to +// big-endian data storage (like AIFF files). +void +convertSamplesToBytes( const std::vector< double > & samples, + std::vector< Byte > & bytes, unsigned int bps ) +{ + Assert( bps <= 32 ); + + typedef std::vector< Byte >::size_type size_type; + + // grow the bytes vector, must be an even number + // of bytes: + const int bytesPerSample = bps / 8; + size_type howManyBytes = samples.size() * bytesPerSample; + if ( howManyBytes%2 ) + ++howManyBytes; + bytes.resize( howManyBytes ); + + debugger << "converting " << samples.size() << " samples to size " + << bps << " bits" << endl; + + // shift sample bytes into a long integer, and + // scale to make a double: + const double maxSample = std::pow(2., double(bps-1)); + long samp; + + std::vector< Byte >::iterator bytePos = bytes.begin(); + std::vector< double >::const_iterator samplePos = samples.begin(); + while ( samplePos != samples.end() ) + { + samp = long(*(samplePos++) * maxSample); + + // store the sample bytes in big endian order, + // most significant byte first: + for ( size_type j = bytesPerSample; j > 0; --j ) + { + //Assert( bytePos != bytes.end() ); + // mask the lowest byte after shifting: + *(bytePos++) = 0xFF & (samp >> (8*(j-1))); + } + } +} + +// --------------------------------------------------------------------------- +// extended80 +// --------------------------------------------------------------------------- +// IEEE floating point conversions. +// + +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif /* HUGE_VAL */ + +#define FloatToUnsigned(f)((Int_32)((Int_32(f - 2147483648.0)) + (Int_32)(2147483647)) + 1) + +void ConvertToIeeeExtended(double num, extended80 * x) +{ + int sign; + int expon; + double fMant, fsMant; + Int_32 hiMant, loMant; + char * bytes = x->data; + + if (num < 0) { + sign = 0x8000; + num *= -1; + } else + sign = 0; + + if (num == 0) { + expon = 0; + hiMant = 0; + loMant = 0; + } else { + fMant = frexp(num, &expon); + if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */ + expon = sign | 0x7FFF; + hiMant = 0; + loMant = 0; + } + /* infinity */ + else { /* Finite */ + expon += 16382; + if (expon < 0) { /* denormalized */ + fMant = ldexp(fMant, expon); + expon = 0; + } + expon |= sign; + fMant = ldexp(fMant, 32); + fsMant = floor(fMant); + hiMant = FloatToUnsigned(fsMant); + fMant = ldexp(fMant - fsMant, 32); + fsMant = floor(fMant); + loMant = FloatToUnsigned(fsMant); + } + } + + bytes[0] = expon >> 8; + bytes[1] = expon; + + bytes[2] = hiMant >> 24; + bytes[3] = hiMant >> 16; + bytes[4] = hiMant >> 8; + bytes[5] = hiMant; + + bytes[6] = loMant >> 24; + bytes[7] = loMant >> 16; + bytes[8] = loMant >> 8; + bytes[9] = loMant; + +} + +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif /* HUGE_VAL */ + +# define UnsignedToFloat(u)(((double)((Int_32)(u - (Int_32)(2147483647) - 1))) + 2147483648.0) + +/**************************************************************** + * Extended precision IEEE floating-point conversion routine. + ****************************************************************/ + +double ConvertFromIeeeExtended(const extended80 * x) +{ /* LCN */ /* ? */ + double f; + int expon; + Int_32 hiMant, loMant; + const char * bytes = x->data; + + expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); + hiMant = ((int)(bytes[2] & 0xFF) << 24) + | ((int)(bytes[3] & 0xFF) << 16) + | ((int)(bytes[4] & 0xFF) << 8) + | ((int)(bytes[5] & 0xFF)); + loMant = ((int)(bytes[6] & 0xFF) << 24) + | ((int)(bytes[7] & 0xFF) << 16) + | ((int)(bytes[8] & 0xFF) << 8) + | ((int)(bytes[9] & 0xFF)); + + if (expon == 0 && hiMant == 0 && loMant == 0) + f = 0; + else { + if (expon == 0x7FFF) /* Infinity or NaN */ + f = HUGE_VAL; + else { + expon -= 16383; + f = ldexp(UnsignedToFloat(hiMant), expon -= 31); + f += ldexp(UnsignedToFloat(loMant), expon -= 32); + } + } + + if (bytes[0] & 0x80) + { + f = -f; + } + + return f; +} + + +} // end of namespace Loris diff --git a/src/loris/AiffData.h b/src/loris/AiffData.h new file mode 100644 index 0000000..fb07ed2 --- /dev/null +++ b/src/loris/AiffData.h @@ -0,0 +1,361 @@ +/* + * 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 + * + * + * AiffData.h + * + * Declarations of import and export functions. + * + * Kelly Fitz, 17 Sept 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Marker.h" + +#include <string> +#include <vector> + +// in case configure wasn't run (no config.h), +// pick some (hopefully-) reasonable values for +// these things and hope for the best... +#if ! defined( SIZEOF_SHORT ) +#define SIZEOF_SHORT 2 +#endif + +#if ! defined( SIZEOF_INT ) +#define SIZEOF_INT 4 +#endif + +#if ! defined( SIZEOF_LONG ) +#define SIZEOF_LONG 4 // not for DEC Alpha! +#endif + + +#if SIZEOF_SHORT == 2 +typedef short Int_16; +typedef unsigned short Uint_16; +#elif SIZEOF_INT == 2 +typedef int Int_16; +typedef unsigned int Uint_16; +#else +#error "cannot find an appropriate type for 16-bit integers" +#endif + +#if SIZEOF_INT == 4 +typedef int Int_32; +typedef unsigned int Uint_32; +#elif SIZEOF_LONG == 4 +typedef long Int_32; +typedef unsigned long Uint_32; +#else +#error "cannot find an appropriate type for 32-bit integers" +#endif + + +// begin namespace +namespace Loris { + +// -- chunk types -- +enum +{ + ContainerId = 0x464f524d, // 'FORM' + AiffType = 0x41494646, // 'AIFF' + CommonId = 0x434f4d4d, // 'COMM' + ApplicationSpecificId = 0x4150504c, // 'APPL' + SosEnvelopesId = 0x534f5365, // 'SOSe' + SoundDataId = 0x53534e44, // 'SSND' + InstrumentId = 0x494e5354, // 'INST' + MarkerId = 0x4d41524b // 'MARK' +}; + +typedef Uint_32 ID; +typedef char Byte; + +struct CkHeader +{ + ID id; + Uint_32 size; + + // providing this default constructor + // gives clients a way to determine whether + // a chunk has been read and assigned: + CkHeader( void ) : id(0), size(0) {} +}; + +struct ContainerCk +{ + CkHeader header; + ID formType; +}; + +struct CommonCk +{ + CkHeader header; + Int_16 channels; // number of channels + Int_32 sampleFrames; // channel independent sample frames + Int_16 bitsPerSample; // number of bits per sample + double srate; // sampling rate (stored in IEEE 10 byte format) +}; + +struct SoundDataCk +{ + CkHeader header; + Uint_32 offset; + Uint_32 blockSize; + + // sample frames follow + std::vector< Byte > sampleBytes; +}; + +struct MarkerCk +{ + CkHeader header; + Uint_16 numMarkers; + + struct Marker + { + Uint_16 markerID; + Uint_32 position; // position in uncompressed samples + std::string markerName; + }; + + std::vector< MarkerCk::Marker > markers; +}; + +struct InstrumentCk +{ + CkHeader header; + Byte baseNote; /* all notes are MIDI note numbers */ + Byte detune; /* cents off, only -50 to +50 are significant */ + Byte lowNote; + Byte highNote; + Byte lowVelocity; /* 1 to 127 */ + Byte highVelocity; /* 1 to 127 */ + Int_16 gain; /* in dB, 0 is normal */ + + struct Loop + { + Int_16 playMode; /* 0 - no loop, 1 - forward looping, 2 - backward looping */ + Uint_16 beginLoop; /* this is a reference to a markerID, so you always + ÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊ have to work with MARK and INST together!! */ + Uint_16 endLoop; + }; + + Loop sustainLoop; + Loop releaseLoop; +}; + + +struct SosEnvelopesCk +{ + CkHeader header; + Int_32 signature; // For SOS, should be 'SOSe' + Int_32 enhanced; // 0 for sine-only, 1 for bandwidth-enhanced + Int_32 validPartials; // Number of partials with data in them; the file must + // be padded out to the next higher 2**n partials; + // this number is doubled for enhanced files + + // skip validPartials * sizeof(Int_32) bytes of junk here + Int_32 resolution; // frame duration in microseconds + Int_32 quasiHarmonic; // how many of the partials are quasiharmonic + // skip + // (4*LargestLabel + 8 - validPartials - 2) * sizeof(Int_32) + // bytes of junk here + +/* + // this stuff is unbelievably nasty! + +#define initPhaseLth ( 4*LargestLabel + 8 ) + Int_32 initPhase[initPhaseLth]; // obsolete initial phase array; is VARIABLE LENGTH array + // this is big enough for a max of 512 enhanced partials plus values below +// Int_32 resolution; // frame duration in microseconds + #define SOSresolution( es ) initPhase[ spcEI.enhanced \ + ? 2 * spcEI.numPartials : spcEI.numPartials] + // follows the initPhase[] array + + +// Int_32 quasiHarmonic; // how many of the partials are quasiharmonic + #define SOSquasiHarmonic( es ) initPhase[ spcEI.enhanced \ + ? 2 * spcEI.numPartials + 1 : spcEI.numPartials + 1] + // follows the initPhase[] array +*/ +}; + +// --------------------------------------------------------------------------- +// readChunkHeader +// --------------------------------------------------------------------------- +// Read the id and chunk size from the current file position. +// Let exceptions propogate. +// +std::istream & +readChunkHeader( std::istream & s, CkHeader & h ); + + +// --------------------------------------------------------------------------- +// readApplicationSpecifcData +// --------------------------------------------------------------------------- +// Read the data in the ApplicationSpecific chunk, assume the stream is +// correctly positioned, and that the chunk header has already been read. +// +// Look for data specific to SPC files. Any other kind of Application +// Specific data is ignored. +// +std::istream & +readApplicationSpecifcData( std::istream & s, SosEnvelopesCk & ck, unsigned long chunkSize ); + + +// --------------------------------------------------------------------------- +// readCommonData +// --------------------------------------------------------------------------- +// Read the data in the Common chunk, assume the stream is correctly +// positioned, and that the chunk header has already been read. +// +std::istream & +readCommonData( std::istream & s, CommonCk & ck, unsigned long chunkSize ); + +// --------------------------------------------------------------------------- +// readContainer +// --------------------------------------------------------------------------- +// +std::istream & +readContainer( std::istream & s, ContainerCk & ck, unsigned long chunkSize ); + +// --------------------------------------------------------------------------- +// readInstrumentData +// --------------------------------------------------------------------------- +// +std::istream & +readInstrumentData( std::istream & s, InstrumentCk & ck, unsigned long chunkSize ); + +// --------------------------------------------------------------------------- +// readMarkerData +// --------------------------------------------------------------------------- +// +std::istream & +readMarkerData( std::istream & s, MarkerCk & ck, unsigned long chunkSize ); + +// --------------------------------------------------------------------------- +// readSampleData +// --------------------------------------------------------------------------- +// Read the data in the Sound Data chunk, assume the stream is correctly +// positioned, and that the chunk header has already been read. +// +std::istream & +readSampleData( std::istream & s, SoundDataCk & ck, unsigned long chunkSize ); + +// --------------------------------------------------------------------------- +// configureCommonCk +// --------------------------------------------------------------------------- +void +configureCommonCk( CommonCk & ck, unsigned long nFrames, unsigned int nChans, + unsigned int bps, double srate ); + +// --------------------------------------------------------------------------- +// configureContainer +// --------------------------------------------------------------------------- +// dataSize is the combined size of all other chunks in file. Configure +// them first, then add their sizes (with headers!). +// +void +configureContainer( ContainerCk & ck, unsigned long dataSize ); + +// --------------------------------------------------------------------------- +// configureInstrumentCk +// --------------------------------------------------------------------------- +void +configureInstrumentCk( InstrumentCk & ck, double midiNoteNum ); + +// --------------------------------------------------------------------------- +// configureMarkerCk +// --------------------------------------------------------------------------- +void +configureMarkerCk( MarkerCk & ck, const std::vector< Marker > & markers, + double srate ); + +// --------------------------------------------------------------------------- +// configureSoundDataCk +// --------------------------------------------------------------------------- +// +void +configureSoundDataCk( SoundDataCk & ck, const std::vector< double > & samples, + unsigned int bps ); + +// --------------------------------------------------------------------------- +// writeCommon +// --------------------------------------------------------------------------- +// +std::ostream & +writeCommonData( std::ostream & s, const CommonCk & ck ); + +// --------------------------------------------------------------------------- +// writeContainer +// --------------------------------------------------------------------------- +// +std::ostream & +writeContainer( std::ostream & s, const ContainerCk & ck ); + +// --------------------------------------------------------------------------- +// writeInstrumentData +// --------------------------------------------------------------------------- +std::ostream & +writeInstrumentData( std::ostream & s, const InstrumentCk & ck ); + +// --------------------------------------------------------------------------- +// writeMarkerData +// --------------------------------------------------------------------------- +std::ostream & +writeMarkerData( std::ostream & s, const MarkerCk & ck ); + +// --------------------------------------------------------------------------- +// writeSampleData +// --------------------------------------------------------------------------- +// +std::ostream & +writeSampleData( std::ostream & s, const SoundDataCk & ck ); + +// --------------------------------------------------------------------------- +// convertBytesToSamples +// --------------------------------------------------------------------------- +// Convert sample bytes to double precision floating point samples +// (-1.0, 1.0). The samples vector is resized to fit exactly as many +// samples as are represented in the bytes vector, and any prior +// contents are overwritten. +// +void +convertBytesToSamples( const std::vector< Byte > & bytes, + std::vector< double > & samples, unsigned int bps ); + +// --------------------------------------------------------------------------- +// convertSamplesToBytes +// --------------------------------------------------------------------------- +// Convert floating point samples (-1.0, 1.0) to bytes. +// The bytes vector is resized to fit exactly as many +// samples as are stored in the samples vector, and any prior +// contents are overwritten. +// +void +convertSamplesToBytes( const std::vector< double > & samples, + std::vector< Byte > & bytes, unsigned int bps ); + +} // end of namespace 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 diff --git a/src/loris/AiffFile.h b/src/loris/AiffFile.h new file mode 100644 index 0000000..55c2bc7 --- /dev/null +++ b/src/loris/AiffFile.h @@ -0,0 +1,388 @@ +/* + * 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.h + * + * Definition of AiffFile class for sample import and export in Loris. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "Marker.h" +#include "Synthesizer.h" + +#if defined(NO_TEMPLATE_MEMBERS) +#include "PartialList.h" +#endif + +#include <memory> +#include <string> +#include <vector> + +// begin namespace +namespace Loris { + +class Partial; + +// --------------------------------------------------------------------------- +// class AiffFile +// +//! 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 imports +//! only monaural (single channel) AIFF-format samples files, though +//! it can create and export a new two-channel file from a pair of +//! sample vectors. +// +class AiffFile +{ +// -- public interface -- +public: + +// -- types -- + + //! The type of the sample storage in an AiffFile. + typedef std::vector< double > samples_type; + + //! The type of all size parameters for AiffFile. + typedef samples_type::size_type size_type; + + //! The type of AIFF marker storage in an AiffFile. + typedef std::vector< Marker > markers_type; + +// -- construction -- + + //! 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 + explicit AiffFile( const std::string & filename ); + + //! Initialize an instance of AiffFile with samples rendered + //! from a sequnence of Partials. The Partials in the + //! specified half-open (STL-style) range are rendered at + //! the specified sample rate, using the (optionally) + //! specified Partial fade time (see Synthesizer.h + //! for an examplanation of fade time). Other synthesis + //! parameters are taken from the Synthesizer DefaultParameters. + //! + //! \sa Synthesizer::DefaultParameters + //! + //! \param begin_partials is the beginning of a sequence of Partials + //! \param end_partials is (one-past) the end of a sequence of + //! Partials + //! \param samplerate is the rate (Hz) at which Partials are rendered + //! \param fadeTime is the Partial fade time (seconds) for rendering + //! the Partials on the specified range. If unspecified, the + //! fade time is taken from the Synthesizer DefaultParameters. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + AiffFile( Iter begin_partials, Iter end_partials, + double samplerate, double fadeTime = FadeTimeUnspecified ); +#else + AiffFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double samplerate, double fadeTime = FadeTimeUnspecified ); +#endif + + //! 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. + //! \param numChannels is the number of channels of audio data + //! to preallocate (default 1 channel) + AiffFile( double samplerate, size_type numFrames = 0, + unsigned int numChannels = 1 ); + + //! 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( const double * buffer, size_type bufferlength, double samplerate ); + + //! 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( const double * buffer_left, const double * buffer_right, + size_type bufferlength, double samplerate ); + + //! 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( const std::vector< double > & vec, double samplerate ); + + //! 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( const std::vector< double > & vec_left, + const std::vector< double > & vec_right, + double samplerate ); + + //! 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( const AiffFile & other ); + + //! 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 & operator= ( const AiffFile & rhs ); + +// -- access -- + + //! Return a reference to the Marker (see Marker.h) container + //! for this AiffFile. + markers_type & markers( void ); + + //! Return a const reference to the Marker (see Marker.h) container + //! for this AiffFile. + const markers_type & markers( void ) const; + + //! Return the fractional MIDI note number assigned to this AiffFile. + //! If the sound has no definable pitch, note number 60.0 is used. + double midiNoteNumber( void ) const; + + //! Return the number of channels of audio samples represented by + //! this AiffFile, 1 for mono, 2 for stereo. + unsigned int numChannels( void ) const; + + //! 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). + size_type numFrames( void ) const; + + //! Bad old legacy name for numFrames. + //! \deprecated Use numFrames instead. + size_type sampleFrames( void ) const { return numFrames(); } + + //! Return the sampling freqency in Hz for the sample data in this + //! AiffFile. + double sampleRate( void ) const; + + //! Return a reference (or const reference) to the vector containing + //! the floating-point sample data for this AiffFile. + samples_type & samples( void ); + + //! Return a const reference (or const reference) to the vector containing + //! the floating-point sample data for this AiffFile. + const samples_type & samples( void ) const; + +// -- mutation -- + + //! 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 addPartial( const Loris::Partial & p, double fadeTime = FadeTimeUnspecified ); + + //! Accumulate samples rendered from a sequence of Partials. + //! The Partials in the specified half-open (STL-style) range are + //! rendered at this AiffFile's sample rate, using the (optionally) + //! specified Partial fade time (see Synthesizer.h for an examplanation + //! of fade time). Other synthesis parameters are taken from the + //! Synthesizer DefaultParameters. + //! + //! \sa Synthesizer::DefaultParameters + //! + //! \param begin_partials is the beginning of a sequence of Partials + //! \param end_partials is (one-past) the end of a sequence of + //! Partials + //! \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. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void addPartials( Iter begin_partials, Iter end_partials, + double fadeTime = FadeTimeUnspecified ); +#else + void addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double fadeTime = FadeTimeUnspecified ); +#endif + + //! 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 setMidiNoteNumber( double nn ); + +// -- export -- + + //! 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 + void write( const std::string & filename, unsigned int bps = 16 ); + +private: +// -- implementation -- + double notenum_, rate_; // MIDI note number and sample rate + unsigned int numchans_; + markers_type markers_; // AIFF Markers + samples_type samples_; // floating point samples [-1.0, 1.0] + + +// -- helpers -- + + // 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 configureSynthesizer( double fadeTime ); + + // Import data from an AIFF file on disk. + void readAiffData( const std::string & filename ); + + enum { FadeTimeUnspecified = -9999999 }; + // This is not pretty, but it is better (perhaps) than defining two + // of every member having an optional fade time parameter. + +}; // end of class AiffFile + +// -- template members -- + +// --------------------------------------------------------------------------- +// constructor from Partial range +// --------------------------------------------------------------------------- +//! Initialize an instance of AiffFile with samples rendered +//! from a sequnence of Partials. The Partials in the +//! specified half-open (STL-style) range are rendered at +//! the specified sample rate, using the (optionally) +//! specified Partial fade time (see Synthesizer.h +//! for an examplanation of fade time). Other synthesis +//! parameters are taken from the Synthesizer DefaultParameters. +//! +//! \sa Synthesizer::DefaultParameters +//! +//! \param begin_partials is the beginning of a sequence of Partials +//! \param end_partials is (one-past) the end of a sequence of +//! Partials +//! \param samplerate is the rate (Hz) at which Partials are rendered +//! \param fadeTime is the Partial fade time (seconds) for rendering +//! the Partials on the specified range. If unspecified, the +//! fade time is taken from the Synthesizer DefaultParameters. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. +// +#if !defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > + AiffFile::AiffFile( Iter begin_partials, Iter end_partials, + double samplerate, double fadeTime ) : +#else + AiffFile::AiffFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double samplerate, double fadeTime ) : +#endif +// initializers: + notenum_( 60 ), + rate_( samplerate ), + numchans_( 1 ) +{ + addPartials( begin_partials, end_partials, fadeTime ); +} + +// --------------------------------------------------------------------------- +// addPartials +// --------------------------------------------------------------------------- +//! Accumulate samples rendered from a sequence of Partials. +//! The Partials in the specified half-open (STL-style) range are +//! rendered at this AiffFile's sample rate, using the (optionally) +//! specified Partial fade time (see Synthesizer.h for an examplanation +//! of fade time). Other synthesis parameters are taken from the +//! Synthesizer DefaultParameters. +//! +//! \sa Synthesizer::DefaultParameters +//! +//! \param begin_partials is the beginning of a sequence of Partials +//! \param end_partials is (one-past) the end of a sequence of +//! Partials +//! \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. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. +// +#if !defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void + AiffFile::addPartials( Iter begin_partials, Iter end_partials, double fadeTime ) +#else +void + AiffFile::addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double fadeTime ) +#endif +{ + Synthesizer synth = configureSynthesizer( fadeTime ); + synth.synthesize( begin_partials, end_partials ); +} + +} // end of namespace Loris diff --git a/src/loris/Analyzer.C b/src/loris/Analyzer.C new file mode 100644 index 0000000..7ed7469 --- /dev/null +++ b/src/loris/Analyzer.C @@ -0,0 +1,1420 @@ +/* + * 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 + * + * + * Analyzer.C + * + * Implementation of class Loris::Analyzer. + * + * Kelly Fitz, 5 Dec 99 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Analyzer.h" + +#include "AssociateBandwidth.h" +#include "Breakpoint.h" +#include "BreakpointEnvelope.h" +#include "Envelope.h" +#include "F0Estimate.h" +#include "LorisExceptions.h" +#include "KaiserWindow.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialPtrs.h" +#include "ReassignedSpectrum.h" +#include "SpectralPeakSelector.h" +#include "PartialBuilder.h" + +#include "phasefix.h" // for frequency/phase fixing at end of analysis + + + +#include <algorithm> +#include <cmath> +#include <functional> // for std::plus +#include <memory> +#include <numeric> // for std::inner_product +#include <utility> +#include <vector> + +using namespace std; + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// helpers, used below +// --------------------------------------------------------------------------- +static double accumPeakSquaredAmps( double init, + const SpectralPeak & pk ) +{ + return init + (pk.amplitude() * pk.amplitude()); +} + +template < class Pair > +static double compare2nd( const Pair & p1, const Pair & p2 ) +{ + return p1.second < p2.second; +} + +// --------------------------------------------------------------------------- +// LinearEnvelopeBuilder +// --------------------------------------------------------------------------- +// Base class for envelope builders that add a point (possibly) at each +// analysis frame. +// +// TODO: make a dictionary of these things and allow clients to add their +// own envelope builders and builder functions, and retrieve them after +// analysis. +class LinearEnvelopeBuilder +{ +public: + virtual ~LinearEnvelopeBuilder( void ) {} + virtual LinearEnvelopeBuilder * clone( void ) const = 0; + virtual void build( const Peaks & peaks, double frameTime ) = 0; + + const LinearEnvelope & envelope( void ) const { return mEnvelope; } + + // reset (clear) envelope, override if necesssary: + virtual void reset( void ) { mEnvelope.clear(); } + +protected: + + LinearEnvelope mEnvelope; // build this +}; + +// --------------------------------------------------------------------------- +// FundamentalBuilder - for constructing an F0 envelope during analysis +// --------------------------------------------------------------------------- +class FundamentalBuilder : public LinearEnvelopeBuilder +{ + std::auto_ptr< Envelope > mFminEnv; + std::auto_ptr< Envelope > mFmaxEnv; + + double mAmpThresh, mFreqThresh; + + std::vector< double > amplitudes, frequencies; + + const double mMinConfidence; // 0.9, this could be made a parameter, + // or raised to make estimates smoother + +public: + FundamentalBuilder( double fmin, double fmax, double threshDb = -60, double threshHz = 8000 ) : + mFminEnv( new LinearEnvelope( fmin ) ), + mFmaxEnv( new LinearEnvelope( fmax ) ), + mAmpThresh( std::pow( 10., 0.05*(threshDb) ) ), + mFreqThresh( threshHz ), + mMinConfidence( 0.9 ) + {} + + FundamentalBuilder( const Envelope & fmin, const Envelope & fmax, + double threshDb = -60, double threshHz = 8000 ) : + mFminEnv( fmin.clone() ), + mFmaxEnv( fmax.clone() ), + mAmpThresh( std::pow( 10., 0.05*(threshDb) ) ), + mFreqThresh( threshHz ), + mMinConfidence( 0.9 ) + {} + + FundamentalBuilder( const FundamentalBuilder & rhs ) : + mFminEnv( rhs.mFminEnv->clone() ), + mFmaxEnv( rhs.mFmaxEnv->clone() ), + mAmpThresh( rhs.mAmpThresh ), + mFreqThresh( rhs.mFreqThresh ), + mMinConfidence( rhs.mMinConfidence ) + {} + + + FundamentalBuilder * clone( void ) const { return new FundamentalBuilder(*this); } + + void build( const Peaks & peaks, double frameTime ); +}; + +// --------------------------------------------------------------------------- +// FundamentalBuilder::build +// --------------------------------------------------------------------------- +// +void FundamentalBuilder::build( const Peaks & peaks, double frameTime ) +{ + amplitudes.clear(); + frequencies.clear(); + for ( Peaks::const_iterator spkpos = peaks.begin(); spkpos != peaks.end(); ++spkpos ) + { + if ( spkpos->amplitude() > mAmpThresh && + spkpos->frequency() < mFreqThresh ) + { + amplitudes.push_back( spkpos->amplitude() ); + frequencies.push_back( spkpos->frequency() ); + } + } + if ( ! amplitudes.empty() ) + { + const double fmin = mFminEnv->valueAt( frameTime ); + const double fmax = mFmaxEnv->valueAt( frameTime ); + + // estimate f0 + F0Estimate est( amplitudes, frequencies, fmin, fmax, 0.1 ); + + if ( est.confidence() >= mMinConfidence && + est.frequency() > fmin && est.frequency() < fmax ) + { + // notifier << "f0 is " << est.frequency << endl; + // add breakpoint to fundamental envelope + mEnvelope.insert( frameTime, est.frequency() ); + } + } + +} + +// --------------------------------------------------------------------------- +// AmpEnvBuilder - for constructing an amplitude envelope during analysis +// --------------------------------------------------------------------------- +class AmpEnvBuilder : public LinearEnvelopeBuilder +{ +public: + AmpEnvBuilder( void ) {} + + AmpEnvBuilder * clone( void ) const { return new AmpEnvBuilder(*this); } + + void build( const Peaks & peaks, double frameTime ); + +}; + +// --------------------------------------------------------------------------- +// AmpEnvBuilder::build +// --------------------------------------------------------------------------- +// +void AmpEnvBuilder::build( const Peaks & peaks, double frameTime ) +{ + double x = std::accumulate( peaks.begin(), peaks.end(), 0.0, accumPeakSquaredAmps ); + mEnvelope.insert( frameTime, std::sqrt( x ) ); +} + + +// --------------------------------------------------------------------------- +// Analyzer constructor - frequency resolution only +// --------------------------------------------------------------------------- +//! Construct a new Analyzer configured with the given +//! frequency resolution (minimum instantaneous frequency +//! difference between Partials). All other Analyzer parameters +//! are computed from the specified frequency resolution. +//! +//! \param resolutionHz is the frequency resolution in Hz. +// +Analyzer::Analyzer( double resolutionHz ) +{ + configure( resolutionHz, 2.0 * resolutionHz ); +} + +// --------------------------------------------------------------------------- +// Analyzer constructor +// --------------------------------------------------------------------------- +//! Construct a new Analyzer configured with the given +//! frequency resolution (minimum instantaneous frequency +//! difference between Partials) and analysis window width +//! (main lobe, zero-to-zero). All other Analyzer parameters +//! are computed from the specified resolution and window width. +//! +//! \param resolutionHz is the frequency resolution in Hz. +//! \param windowWidthHz is the main lobe width of the Kaiser +//! analysis window in Hz. +// +Analyzer::Analyzer( double resolutionHz, double windowWidthHz ) +{ + configure( resolutionHz, windowWidthHz ); +} + +// --------------------------------------------------------------------------- +// Analyzer constructor +// --------------------------------------------------------------------------- +//! Construct a new Analyzer configured with the given time-varying +//! frequency resolution (minimum instantaneous frequency +//! difference between Partials) and analysis window width +//! (main lobe, zero-to-zero). All other Analyzer parameters +//! are computed from the specified resolution and window width. +//! +//! \param resolutionHz is the frequency resolution in Hz. +//! \param windowWidthHz is the main lobe width of the Kaiser +//! analysis window in Hz. +// +Analyzer::Analyzer( const Envelope & resolutionEnv, double windowWidthHz ) +{ + configure( resolutionEnv, windowWidthHz ); +} + +// --------------------------------------------------------------------------- +// Analyzer copy constructor +// --------------------------------------------------------------------------- +//! Construct a new Analyzer having identical +//! parameter configuration to another Analyzer. +//! The list of collected Partials is not copied. +//! +//! \param other is the Analyzer to copy. +// +Analyzer::Analyzer( const Analyzer & other ) : + m_freqResolutionEnv( other.m_freqResolutionEnv->clone() ), + m_ampFloor( other.m_ampFloor ), + m_windowWidth( other.m_windowWidth ), + m_freqFloor( other.m_freqFloor ), + m_freqDrift( other.m_freqDrift ), + m_hopTime( other.m_hopTime ), + m_cropTime( other.m_cropTime ), + m_bwAssocParam( other.m_bwAssocParam ), + m_sidelobeLevel( other.m_sidelobeLevel ), + m_phaseCorrect( other.m_phaseCorrect ), + m_partials( other.m_partials ) +{ + m_f0Builder.reset( other.m_f0Builder->clone() ); + m_ampEnvBuilder.reset( other.m_ampEnvBuilder->clone() ); +} + +// --------------------------------------------------------------------------- +// Analyzer assignment +// --------------------------------------------------------------------------- +//! Construct a new Analyzer having identical +//! parameter configuration to another Analyzer. +//! The list of collected Partials is not copied. +//! +//! \param rhs is the Analyzer to copy. +// +Analyzer & +Analyzer::operator=( const Analyzer & rhs ) +{ + if ( this != & rhs ) + { + m_freqResolutionEnv.reset( rhs.m_freqResolutionEnv->clone() ); + m_ampFloor = rhs.m_ampFloor; + m_windowWidth = rhs.m_windowWidth; + m_freqFloor = rhs.m_freqFloor; + m_freqDrift = rhs.m_freqDrift; + m_hopTime = rhs.m_hopTime; + m_cropTime = rhs.m_cropTime; + m_bwAssocParam = rhs.m_bwAssocParam; + m_sidelobeLevel = rhs.m_sidelobeLevel; + m_phaseCorrect = rhs.m_phaseCorrect; + m_partials = rhs.m_partials; + + m_f0Builder.reset( rhs.m_f0Builder->clone() ); + m_ampEnvBuilder.reset( rhs.m_ampEnvBuilder->clone() ); + + } + return *this; +} + +// --------------------------------------------------------------------------- +// Analyzer destructor +// --------------------------------------------------------------------------- +//! Destroy this Analyzer. +// +Analyzer::~Analyzer( void ) +{ +} + +// -- configuration -- + +// --------------------------------------------------------------------------- +// configure +// --------------------------------------------------------------------------- +//! Configure this Analyzer with the given frequency resolution +//! (minimum instantaneous frequency difference between Partials, +//! in Hz). All other Analyzer parameters are (re-)computed from the +//! frequency resolution, including the window width, which is +//! twice the resolution. +//! +//! \param resolutionHz is the frequency resolution in Hz. +// +void +Analyzer::configure( double resolutionHz ) +{ + configure( resolutionHz, 2.0 * resolutionHz ); +} + +// --------------------------------------------------------------------------- +// configure +// --------------------------------------------------------------------------- +//! Configure this Analyzer with the given frequency resolution +//! (minimum instantaneous frequency difference between Partials) +//! and analysis window width (main lobe, zero-to-zero, in Hz). +//! All other Analyzer parameters are (re-)computed from the +//! frequency resolution and window width. +//! +//! \param resolutionHz is the frequency resolution in Hz. +//! \param windowWidthHz is the main lobe width of the Kaiser +//! analysis window in Hz. +//! +//! There are three categories of analysis parameters: +//! - the resolution, and params that are usually related to (or +//! identical to) the resolution (frequency floor and drift) +//! - the window width and params that are usually related to (or +//! identical to) the window width (hop and crop times) +//! - independent parameters (bw region width and amp floor) +// +void +Analyzer::configure( double resolutionHz, double windowWidthHz ) +{ + // use specified resolution: + setFreqResolution( resolutionHz ); + + // floor defaults to -90 dB: + setAmpFloor( -90. ); + + // window width should generally be approximately + // equal to, and never more than twice the + // frequency resolution: + setWindowWidth( windowWidthHz ); + + // the Kaiser window sidelobe level can be the same + // as the amplitude floor (except in positive dB): + setSidelobeLevel( - m_ampFloor ); + + // for the minimum frequency, below which no data is kept, + // use the frequency resolution by default (this makes + // Lip happy, and is always safe?) and allow the client + // to change it to anything at all. + setFreqFloor( resolutionHz ); + + // frequency drift in Hz is the maximum difference + // in frequency between consecutive Breakpoints in + // a Partial, by default, make it equal to one half + // the frequency resolution: + setFreqDrift( .5 * resolutionHz ); + + // hop time (in seconds) is the inverse of the + // window width....really. Smith and Serra (1990) cite + // Allen (1977) saying: a good choice of hop is the window + // length divided by the main lobe width in frequency samples, + // which turns out to be just the inverse of the width. + setHopTime( 1. / m_windowWidth ); + + // crop time (in seconds) is the maximum allowable time + // correction, beyond which a reassigned spectral component + // is considered unreliable, and not considered eligible for + // Breakpoint formation in extractPeaks(). By default, use + // the hop time (should it be half that?): + setCropTime( m_hopTime ); + + // bandwidth association region width + // defaults to 2 kHz, corresponding to + // 1 kHz region center spacing: + storeResidueBandwidth(); + + // configure the envelope builders using default + // parameters: + buildFundamentalEnv( 0.99 * resolutionHz, + 1.5 * resolutionHz ); + m_ampEnvBuilder.reset( new AmpEnvBuilder ); + + // enable phase-correct Partial construction: + m_phaseCorrect = true; +} + +// --------------------------------------------------------------------------- +// configure +// --------------------------------------------------------------------------- +//! Configure this Analyzer with the given time-varying frequency resolution +//! (minimum instantaneous frequency difference between Partials) +//! and analysis window width (main lobe, zero-to-zero, in Hz). +//! All other Analyzer parameters are (re-)computed from the +//! frequency resolution and window width. +//! +//! \param resolutionEnv is the time-varying frequency resolution +//! in Hz. +//! \param windowWidthHz is the main lobe width of the Kaiser +//! analysis window in Hz. +//! +//! There are three categories of analysis parameters: +//! - the resolution, and params that are usually related to (or +//! identical to) the resolution (frequency floor and drift) +//! - the window width and params that are usually related to (or +//! identical to) the window width (hop and crop times) +//! - independent parameters (bw region width and amp floor) +// +void +Analyzer::configure( const Envelope & resolutionEnv, double windowWidthHz ) +{ + // use specified resolution: + setFreqResolution( resolutionEnv ); + + // floor defaults to -90 dB: + setAmpFloor( -90. ); + + // window width should generally be approximately + // equal to, and never more than twice the + // frequency resolution: + setWindowWidth( windowWidthHz ); + + // the Kaiser window sidelobe level can be the same + // as the amplitude floor (except in positive dB): + setSidelobeLevel( - m_ampFloor ); + + // for the minimum frequency, below which no data is kept, + // use the frequency resolution by default (this makes + // Lip happy, and is always safe?) and allow the client + // to change it to anything at all. + setFreqFloor( windowWidthHz * 0.5 ); // !!!!! + + // frequency drift in Hz is the maximum difference + // in frequency between consecutive Breakpoints in + // a Partial, by default, make it equal to one half + // the frequency resolution: + setFreqDrift( windowWidthHz * 0.25 ); // !!!!! + + // hop time (in seconds) is the inverse of the + // window width....really. Smith and Serra (1990) cite + // Allen (1977) saying: a good choice of hop is the window + // length divided by the main lobe width in frequency samples, + // which turns out to be just the inverse of the width. + setHopTime( 1. / m_windowWidth ); + + // crop time (in seconds) is the maximum allowable time + // correction, beyond which a reassigned spectral component + // is considered unreliable, and not considered eligible for + // Breakpoint formation in extractPeaks(). By default, use + // the hop time (should it be half that?): + setCropTime( m_hopTime ); + + // bandwidth association region width + // defaults to 2 kHz, corresponding to + // 1 kHz region center spacing: + storeResidueBandwidth(); + + // configure the envelope builders using default + // parameters: + /* + buildFundamentalEnv( *m_freqResolutionEnv * 0.99, + *m_freqResolutionEnv * 1.5 ); + + */ // !!!!!!! + m_f0Builder.reset( + new FundamentalBuilder( *m_freqResolutionEnv * 0.99, + *m_freqResolutionEnv * 1.5, + -60., 8000. ) ); + + m_ampEnvBuilder.reset( new AmpEnvBuilder ); + + // enable phase-correct Partial construction: + m_phaseCorrect = true; +} + +// -- analysis -- +// --------------------------------------------------------------------------- +// analyze +// --------------------------------------------------------------------------- +//! Analyze a vector of (mono) samples at the given sample rate +//! (in Hz) and store the extracted Partials in the Analyzer's +//! PartialList (std::list of Partials). +//! +//! \param vec is a vector of floating point samples +//! \param srate is the sample rate of the samples in the vector +// +void +Analyzer::analyze( const std::vector<double> & vec, double srate ) +{ + BreakpointEnvelope reference( 1.0 ); + analyze( &(vec[0]), &(vec[0]) + vec.size(), srate, reference ); +} + +// --------------------------------------------------------------------------- +// analyze +// --------------------------------------------------------------------------- +//! Analyze a range of (mono) samples at the given sample rate +//! (in Hz) and store the extracted Partials in the Analyzer's +//! PartialList (std::list of Partials). +//! +//! \param bufBegin is a pointer to a buffer of floating point samples +//! \param bufEnd is (one-past) the end of a buffer of floating point +//! samples +//! \param srate is the sample rate of the samples in the buffer +// +void +Analyzer::analyze( const double * bufBegin, const double * bufEnd, double srate ) +{ + BreakpointEnvelope reference( 1.0 ); + analyze( bufBegin, bufEnd, srate, reference ); +} + +// --------------------------------------------------------------------------- +// analyze +// --------------------------------------------------------------------------- +//! Analyze a vector of (mono) samples at the given sample rate +//! (in Hz) and store the extracted Partials in the Analyzer's +//! PartialList (std::list of Partials). Use the specified envelope +//! as a frequency reference for Partial tracking. +//! +//! \param vec is a vector of floating point samples +//! \param srate is the sample rate of the samples in the vector +//! \param reference is an Envelope having the approximate +//! frequency contour expected of the resulting Partials. +// +void +Analyzer::analyze( const std::vector<double> & vec, double srate, + const Envelope & reference ) +{ + analyze( &(vec[0]), &(vec[0]) + vec.size(), srate, reference ); +} + + +// --------------------------------------------------------------------------- +// analyze +// --------------------------------------------------------------------------- +//! Analyze a range of (mono) samples at the given sample rate +//! (in Hz) and store the extracted Partials in the Analyzer's +//! PartialList (std::list of Partials). Use the specified envelope +//! as a frequency reference for Partial tracking. +//! +//! \param bufBegin is a pointer to a buffer of floating point samples +//! \param bufEnd is (one-past) the end of a buffer of floating point +//! samples +//! \param srate is the sample rate of the samples in the buffer +//! \param reference is an Envelope having the approximate +//! frequency contour expected of the resulting Partials. +// +void +Analyzer::analyze( const double * bufBegin, const double * bufEnd, double srate, + const Envelope & reference ) +{ + // configure the reassigned spectral analyzer, + // always use odd-length windows: + + // Kaiser window + double winshape = KaiserWindow::computeShape( sidelobeLevel() ); + long winlen = KaiserWindow::computeLength( windowWidth() / srate, winshape ); + if (! (winlen % 2)) + { + ++winlen; + } + debugger << "Using Kaiser window of length " << winlen << endl; + + std::vector< double > window( winlen ); + KaiserWindow::buildWindow( window, winshape ); + + std::vector< double > windowDeriv( winlen ); + KaiserWindow::buildTimeDerivativeWindow( windowDeriv, winshape ); + + ReassignedSpectrum spectrum( window, windowDeriv ); + + // configure the peak selection and partial formation policies: + SpectralPeakSelector selector( srate, m_cropTime ); + PartialBuilder builder( m_freqDrift, reference ); + + // configure bw association policy, unless + // bandwidth association is disabled: + std::auto_ptr< AssociateBandwidth > bwAssociator; + if( m_bwAssocParam > 0 ) + { + debugger << "Using bandwidth association regions of width " + << bwRegionWidth() << " Hz" << endl; + bwAssociator.reset( new AssociateBandwidth( bwRegionWidth(), srate ) ); + } + else + { + debugger << "Bandwidth association disabled" << endl; + } + + // reset envelope builders: + m_ampEnvBuilder->reset(); + m_f0Builder->reset(); + + m_partials.clear(); + + try + { + const double * winMiddle = bufBegin; + + // loop over short-time analysis frames: + while ( winMiddle < bufEnd ) + { + // compute the time of this analysis frame: + const double currentFrameTime = long(winMiddle - bufBegin) / srate; + + // compute reassigned spectrum: + // sampsBegin is the position of the first sample to be transformed, + // sampsEnd is the position after the last sample to be transformed. + // (these computations work for odd length windows only) + const double * sampsBegin = std::max( winMiddle - (winlen / 2), bufBegin ); + const double * sampsEnd = std::min( winMiddle + (winlen / 2) + 1, bufEnd ); + spectrum.transform( sampsBegin, winMiddle, sampsEnd ); + + + // extract peaks from the spectrum, and thin + Peaks peaks = selector.selectPeaks( spectrum, m_freqFloor ); + Peaks::iterator rejected = thinPeaks( peaks, currentFrameTime ); + + // fix the stored bandwidth values + // KLUDGE: need to do this before the bandwidth + // associator tries to do its job, because the mixed + // derivative is temporarily stored in the Breakpoint + // bandwidth!!! FIX!!!! + fixBandwidth( peaks ); + + if ( m_bwAssocParam > 0 ) + { + bwAssociator->associateBandwidth( peaks.begin(), rejected, peaks.end() ); + } + + // remove rejected Breakpoints (needed above to + // compute bandwidth envelopes): + peaks.erase( rejected, peaks.end() ); + + // estimate the amplitude in this frame: + m_ampEnvBuilder->build( peaks, currentFrameTime ); + + // collect amplitudes and frequencies and try to + // estimate the fundamental + m_f0Builder->build( peaks, currentFrameTime ); + + // form Partials from the extracted Breakpoints: + builder.buildPartials( peaks, currentFrameTime ); + + // slide the analysis window: + winMiddle += long( m_hopTime * srate ); // hop in samples, truncated + + } // end of loop over short-time frames + + // unwarp the Partial frequency envelopes: + builder.finishBuilding( m_partials ); + + // fix the frequencies and phases to be consistent. + if ( m_phaseCorrect ) + { + fixFrequency( m_partials.begin(), m_partials.end() ); + } + + + // for debugging: + /* + if ( ! m_ampEnv.empty() ) + { + LinearEnvelope::iterator peakpos = + std::max_element( m_ampEnv.begin(), m_ampEnv.end(), + compare2nd<LinearEnvelope::iterator::value_type> ); + notifier << "Analyzer found amp peak at time : " << peakpos->first + << " value: " << peakpos->second << endl; + } + */ + } + catch ( Exception & ex ) + { + ex.append( "analysis failed." ); + throw; + } +} + +// -- parameter access -- + +// --------------------------------------------------------------------------- +// ampFloor +// --------------------------------------------------------------------------- +//! Return the amplitude floor (lowest detected spectral amplitude), +//! in (negative) dB, for this Analyzer. +// +double +Analyzer::ampFloor( void ) const +{ + return m_ampFloor; +} + +// --------------------------------------------------------------------------- +// cropTime +// --------------------------------------------------------------------------- +//! Return the crop time (maximum temporal displacement of a time- +//! frequency data point from the time-domain center of the analysis +//! window, beyond which data points are considered "unreliable") +//! for this Analyzer. +// +double +Analyzer::cropTime( void ) const +{ + // debugger << "Analyzer::cropTime() is a deprecated member, and will be removed in a future Loris release." << endl; + return m_cropTime; +} + +// --------------------------------------------------------------------------- +// freqDrift +// --------------------------------------------------------------------------- +//! Return the maximum allowable frequency difference +//! consecutive Breakpoints in a Partial envelope for this Analyzer. +// +double +Analyzer::freqDrift( void ) const +{ + return m_freqDrift; +} + +// --------------------------------------------------------------------------- +// freqFloor +// --------------------------------------------------------------------------- +//! Return the frequency floor (minimum instantaneous Partial +//! frequency), in Hz, for this Analyzer. +// +double +Analyzer::freqFloor( void ) const +{ + return m_freqFloor; +} + +// --------------------------------------------------------------------------- +// freqResolution +// --------------------------------------------------------------------------- +//! Return the frequency resolution (minimum instantaneous frequency +//! difference between Partials) for this Analyzer at the specified +//! time in seconds. If no time is specified, then the initial resolution +//! (at 0 seconds) is returned. +//! +//! \param time is the time in seconds at which to evaluate the +//! frequency resolution +// +double +Analyzer::freqResolution( double time /* = 0.0 */ ) const +{ + return m_freqResolutionEnv->valueAt( time ); +} + +// --------------------------------------------------------------------------- +// hopTime +// --------------------------------------------------------------------------- +//! Return the hop time (which corresponds approximately to the +//! average density of Partial envelope Breakpoint data) for this +//! Analyzer. +// +double +Analyzer::hopTime( void ) const +{ + return m_hopTime; +} + +// --------------------------------------------------------------------------- +// sidelobeLevel +// --------------------------------------------------------------------------- +//! Return the sidelobe attenutation level for the Kaiser analysis window in +//! positive dB. Larger numbers (e.g. 90) give very good sidelobe +//! rejection but cause the window to be longer in time. Smaller numbers +//! (like 60) raise the level of the sidelobes, increasing the likelihood +//! of frequency-domain interference, but allow the window to be shorter +//! in time. +// +double +Analyzer::sidelobeLevel( void ) const +{ + return m_sidelobeLevel; +} + +// --------------------------------------------------------------------------- +// windowWidth +// --------------------------------------------------------------------------- +//! Return the frequency-domain main lobe width (measured between +//! zero-crossings) of the analysis window used by this Analyzer. +// +double +Analyzer::windowWidth( void ) const +{ + return m_windowWidth; +} + +// --------------------------------------------------------------------------- +// phaseCorrect +// --------------------------------------------------------------------------- +//! Return true if the phases and frequencies of the constructed +//! partials should be modified to be consistent at the end of the +//! analysis, and false otherwise. (Default is true.) +//! +//! \param TF is a flag indicating whether or not to construct +//! phase-corrected Partials +bool +Analyzer::phaseCorrect( void ) const +{ + return m_phaseCorrect; +} + +// -- parameter mutation -- + +#define VERIFY_ARG(func, test) \ + do { \ + if (!(test)) \ + Throw( Loris::InvalidArgument, #func ": " #test ); \ + } while (false) + + +// --------------------------------------------------------------------------- +// setAmpFloor +// --------------------------------------------------------------------------- +//! Set the amplitude floor (lowest detected spectral amplitude), in +//! (negative) dB, for this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setAmpFloor( double x ) +{ + VERIFY_ARG( setAmpFloor, x < 0 ); + m_ampFloor = x; +} + + +// --------------------------------------------------------------------------- +// setCropTime +// --------------------------------------------------------------------------- +//! Set the crop time (maximum temporal displacement of a time- +//! frequency data point from the time-domain center of the analysis +//! window, beyond which data points are considered "unreliable") +//! for this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setCropTime( double x ) +{ + VERIFY_ARG( setCropTime, x > 0 ); + // debugger << "Analyzer::setCropTime() is a deprecated member, and will be removed in a future Loris release." << endl; + m_cropTime = x; +} + +// --------------------------------------------------------------------------- +// setFreqDrift +// --------------------------------------------------------------------------- +//! Set the maximum allowable frequency difference between +//! consecutive Breakpoints in a Partial envelope for this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setFreqDrift( double x ) +{ + VERIFY_ARG( setFreqDrift, x > 0 ); + m_freqDrift = x; +} + +// --------------------------------------------------------------------------- +// setFreqFloor +// --------------------------------------------------------------------------- +//! Set the frequency floor (minimum instantaneous Partial +//! frequency), in Hz, for this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setFreqFloor( double x ) +{ + VERIFY_ARG( setFreqFloor, x >= 0 ); + m_freqFloor = x; +} + +// --------------------------------------------------------------------------- +// setFreqResolution (constant) +// --------------------------------------------------------------------------- +//! Set the frequency resolution (minimum instantaneous frequency +//! difference between Partials) for this Analyzer. (Does not cause +//! other parameters to be recomputed.) +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setFreqResolution( double x ) +{ + VERIFY_ARG( setFreqResolution, x > 0 ); + m_freqResolutionEnv.reset( new LinearEnvelope( x ) ); +} + +// --------------------------------------------------------------------------- +// setFreqResolution (envelope) +// --------------------------------------------------------------------------- +//! Set the time-varying frequency resolution (minimum instantaneous frequency +//! difference between Partials) for this Analyzer. (Does not cause +//! other parameters to be recomputed.) +//! +//! \param e is the envelope to copy for this parameter. +// +void +Analyzer::setFreqResolution( const Envelope & e ) +{ + // No mechanism exists to verify that the envelope never + // drops below zero, this can only be checked at analysis-time. + // VERIFY_ARG( setFreqResolution, x > 0 ); + m_freqResolutionEnv.reset( e.clone() ); +} + +// --------------------------------------------------------------------------- +// setSidelobeLevel +// --------------------------------------------------------------------------- +//! Set the sidelobe attenutation level for the Kaiser analysis window in +//! positive dB. Higher numbers (e.g. 90) give very good sidelobe +//! rejection but cause the window to be longer in time. Lower +//! numbers raise the level of the sidelobes, increasing the likelihood +//! of frequency-domain interference, but allow the window to be shorter +//! in time. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setSidelobeLevel( double x ) +{ + VERIFY_ARG( setSidelobeLevel, x > 0 ); + m_sidelobeLevel = x; +} + +// --------------------------------------------------------------------------- +// setHopTime +// --------------------------------------------------------------------------- +//! Set the hop time (which corresponds approximately to the average +//! density of Partial envelope Breakpoint data) for this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setHopTime( double x ) +{ + VERIFY_ARG( setHopTime, x > 0 ); + m_hopTime = x; +} + +// --------------------------------------------------------------------------- +// setWindowWidth +// --------------------------------------------------------------------------- +//! Set the frequency-domain main lobe width (measured between +//! zero-crossings) of the analysis window used by this Analyzer. +//! +//! \param x is the new value of this parameter. +// +void +Analyzer::setWindowWidth( double x ) +{ + VERIFY_ARG( setWindowWidth, x > 0 ); + m_windowWidth = x; +} + +// --------------------------------------------------------------------------- +// setPhaseCorrect +// --------------------------------------------------------------------------- +//! Indicate whether the phases and frequencies of the constructed +//! partials should be modified to be consistent at the end of the +//! analysis. (Default is true.) +//! +//! \param TF is a flag indicating whether or not to construct +//! phase-corrected Partials +void +Analyzer::setPhaseCorrect( bool TF ) +{ + m_phaseCorrect = TF; +} + +// -- bandwidth envelope specification -- + + +// --------------------------------------------------------------------------- +// storeResidueBandwidth +// --------------------------------------------------------------------------- +//! Construct Partial bandwidth envelopes during analysis +//! by associating residual energy in the spectrum (after +//! peak extraction) with the selected spectral peaks that +//! are used to construct Partials. +//! +//! \param regionWidth is the width (in Hz) of the bandwidth +//! association regions used by this process, must be positive. +//! If unspecified, a default value is used. +// +void +Analyzer::storeResidueBandwidth( double regionWidth ) +{ + VERIFY_ARG( storeResidueBandwidth, regionWidth > 0 ); + m_bwAssocParam = regionWidth; +} + +// --------------------------------------------------------------------------- +// storeConvergenceBandwidth +// --------------------------------------------------------------------------- +//! Construct Partial bandwidth envelopes during analysis +//! by storing the mixed derivative of short-time phase, +//! scaled and shifted so that a value of 0 corresponds +//! to a pure sinusoid, and a value of 1 corresponds to a +//! bandwidth-enhanced sinusoid with maximal energy spread +//! (minimum sinusoidal convergence). +//! +//! \param tolerance is the amount of range over which the +//! mixed derivative indicator should be allowed to drift away +//! from a pure sinusoid before saturating. This range is mapped +//! to bandwidth values on the range [0,1]. Must be positive and +//! not greater than 1. If unspecified, a default value is used. +// +void +Analyzer::storeConvergenceBandwidth( double tolerance ) +{ + if ( 1.0 < tolerance ) + { + // notify and scale, in Loris 1.5, tolerance was + // specified as a percent + notifier << "Analyzer::storeConvergenceBandwidth, conergence tolerance " + "should be positive and less than 1.0, scaling by 1/100" << endl; + tolerance *= 0.01; + } + + VERIFY_ARG( storeConvergenceBandwidth, + (tolerance > 0) && (tolerance <= 1) ); + + // store a negative value so that it can be + // identified when used: + m_bwAssocParam = -tolerance; +} + +// --------------------------------------------------------------------------- +// storeNoBandwidth +// --------------------------------------------------------------------------- +//! Disable bandwidth envelope construction. Bandwidth +//! will be zero for all Breakpoints in all Partials. +// +void +Analyzer::storeNoBandwidth( void ) +{ + m_bwAssocParam = 0; +} + +// --------------------------------------------------------------------------- +//! Return true if this Analyzer is configured to compute +//! bandwidth envelopes using the spectral residue after +//! peaks have been identified, and false otherwise. +// --------------------------------------------------------------------------- +bool +Analyzer::bandwidthIsResidue( void ) const +{ + return m_bwAssocParam > 0.; +} + +// --------------------------------------------------------------------------- +//! Return true if this Analyzer is configured to compute +//! bandwidth envelopes using the mixed derivative convergence +//! indicator, and false otherwise. +// --------------------------------------------------------------------------- +bool +Analyzer::bandwidthIsConvergence( void ) const +{ + return m_bwAssocParam < 0.; +} + + +// --------------------------------------------------------------------------- +//! Return the width (in Hz) of the Bandwidth Association regions +//! used by this Analyzer, only if the spectral residue method is +//! used to compute bandwidth envelopes. Return zero if the mixed +//! derivative method is used, or if no bandwidth is computed. +// --------------------------------------------------------------------------- +double +Analyzer::bwRegionWidth( void ) const +{ + if ( m_bwAssocParam > 0 ) + { + return m_bwAssocParam; + } + return 0; +} + +// --------------------------------------------------------------------------- +//! Return the mixed derivative convergence tolerance (percent) +//! only if the convergence indicator is used to compute +//! bandwidth envelopes. Return zero if the spectral residue +//! method is used or if no bandwidth is computed. +// --------------------------------------------------------------------------- +double +Analyzer::bwConvergenceTolerance( void ) const +{ + if ( m_bwAssocParam < 0 ) + { + return - m_bwAssocParam; + } + return 0; +} + + +// -- PartialList access -- + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +//! Return a mutable reference to this Analyzer's list of +//! analyzed Partials. +// +PartialList & +Analyzer::partials( void ) +{ + return m_partials; +} + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +//! Return an immutable (const) reference to this Analyzer's +//! list of analyzed Partials. +// +const PartialList & +Analyzer::partials( void ) const +{ + return m_partials; +} + +// --------------------------------------------------------------------------- +// buildFundamentalEnv +// --------------------------------------------------------------------------- +//! Specify parameters for constructing a fundamental frequency +//! envelope for the analyzed sound during analysis. The fundamental +//! frequency estimate can be accessed by fundamentalEnv() after the +//! analysis is complete. +//! +//! \param fmin is the lower bound on the fundamental frequency estimate +//! \param fmax is the upper bound on the fundamental frequency estimate +//! \param threshDb is the lower bound on the amplitude of a spectral peak +//! that will constribute to the fundamental frequency estimate (very +//! low amplitude peaks tend to have less reliable frequency estimates). +//! Default is -60 dB. +//! \param threshHz is the upper bound on the frequency of a spectral +//! peak that will constribute to the fundamental frequency estimate. +//! Default is 8 kHz. +// +void Analyzer::buildFundamentalEnv( double fmin, double fmax, + double threshDb, double threshHz ) +{ + m_f0Builder.reset( + new FundamentalBuilder( fmin, fmax, threshDb, threshHz ) ); +} + +// --------------------------------------------------------------------------- +// fundamentalEnv +// --------------------------------------------------------------------------- +//! Return the fundamental frequency estimate envelope constructed +//! during the most recent analysis performed by this Analyzer. +//! Will be empty unless buildFundamentalEnv was invoked to enable the +//! construction of this envelope during analysis. +// +const LinearEnvelope & +Analyzer::fundamentalEnv( void ) const +{ + return m_f0Builder->envelope(); +} + + +// --------------------------------------------------------------------------- +// ampEnv +// --------------------------------------------------------------------------- +//! Return the overall amplitude estimate envelope constructed +//! during the most recent analysis performed by this Analyzer. +//! Will be empty unless buildAmpEnv was invoked to enable the +//! construction of this envelope during analysis. +// +const LinearEnvelope & +Analyzer::ampEnv( void ) const +{ + return m_ampEnvBuilder->envelope(); +} + +// -- private helpers -- + +// --------------------------------------------------------------------------- +// can_mask +// --------------------------------------------------------------------------- +// functor used for identying peaks that are too close +// in frequency to another louder peak: +struct can_mask +{ + // masking occurs if any (louder) peak falls + // in the frequency range delimited by fmin and fmax: + bool operator()( const SpectralPeak & v ) const + { + return ( v.frequency() > _fmin ) && + ( v.frequency() < _fmax ); + } + + // constructor: + can_mask( double x, double y ) : + _fmin( x ), _fmax( y ) + { if (x>y) std::swap(x,y); } + + // bounds: +private: + double _fmin, _fmax; +}; + +// --------------------------------------------------------------------------- +// negative_time +// --------------------------------------------------------------------------- +// functor used to identify peaks that have reassigned times +// before 0: +struct negative_time +{ + // negative times occur when the reassigned time + // plus the current frame time is less than 0: + bool operator()( const Peaks::value_type & v ) const + { + return 0 > ( v.time() + _frameTime ); + } + + // constructor: + negative_time( double t ) : + _frameTime( t ) {} + + // bounds: +private: + double _frameTime; + +}; + + +// --------------------------------------------------------------------------- +// thinPeaks (HELPER) +// --------------------------------------------------------------------------- +// Reject peaks that are too quiet (low amplitude). Peaks that are retained, +// but are quiet enough to be in the specified fadeRange should be faded. +// Peaks having negative times are also rejected. +// +// This is exactly the same as the basic peak selection strategy, there +// is no tracking here. +// +// Rejected peaks are placed at the end of the peak collection. +// Return the first position in the collection containing a rejected peak, +// or the end of the collection if no peaks are rejected. +// +// This used to be part of SpectralPeakSelector, but it really had no place +// there. It _should_ remove the rejected peaks, but for now, those are needed +// by the bandwidth association strategy. +// +Peaks::iterator +Analyzer::thinPeaks( Peaks & peaks, double frameTime ) +{ + const double ampFloordB = m_ampFloor; + + // fade quiet peaks out over 10 dB: + const double fadeRangedB = 10.0; + + // compute absolute magnitude thresholds: + const double threshold = std::pow( 10., 0.05 * ampFloordB ); + const double beginFade = std::pow( 10., 0.05 * (ampFloordB+fadeRangedB) ); + + // louder peaks are preferred, so consider them + // in order of louder magnitude: + std::sort( peaks.begin(), peaks.end(), SpectralPeak::sort_greater_amplitude ); + + // negative times are not real, but still might represent + // a noisy part of the spectrum... + Peaks::iterator bogusTimes = + std::remove_if( peaks.begin(), peaks.end(), negative_time( frameTime ) ); + + // ...get rid of them anyway + peaks.erase( bogusTimes, peaks.end() ); + bogusTimes = peaks.end(); + + + Peaks::iterator it = peaks.begin(); + Peaks::iterator beginRejected = it; + + const double freqResolution = + std::max( m_freqResolutionEnv->valueAt( frameTime ), 0.0 ); + + + while ( it != peaks.end() ) + { + SpectralPeak & pk = *it; + + // keep this peak if it is loud enough and not + // too near in frequency to a louder one: + double lower = pk.frequency() - freqResolution; + double upper = pk.frequency() + freqResolution; + if ( pk.amplitude() > threshold && + beginRejected == std::find_if( peaks.begin(), beginRejected, can_mask(lower, upper) ) ) + { + // this peak is a keeper, fade its + // amplitude if it is too quiet: + if ( pk.amplitude() < beginFade ) + { + double alpha = (beginFade - pk.amplitude())/(beginFade - threshold); + pk.setAmplitude( pk.amplitude() * (1. - alpha) ); + } + + // keep retained peaks at the front of the collection: + if ( it != beginRejected ) + { + std::swap( *it, *beginRejected ); + } + ++beginRejected; + } + ++it; + } + + // debugger << "thinPeaks retained " << std::distance( peaks.begin(), beginRejected ) << endl; + + // remove rejected Breakpoints: + //peaks.erase( beginRejected, peaks.end() ); + + return beginRejected; +} + +// --------------------------------------------------------------------------- +// fixBandwidth (HELPER) +// --------------------------------------------------------------------------- +// Fix the bandwidth value stored in the specified Peaks. +// This function is invoked if the spectral residue method is +// not used to compute bandwidth (that method overwrites the +// bandwidth already). If the convergence method is used to +// compute bandwidth, the appropriate scaling is applied +// to the stored mixed phase derivative. Otherwise, the +// Peak bandwidth is set to zero. +// +// The convergence value is on the range [0,1], 0 for a sinusoid, +// and 1 for an impulse. If convergence tolerance is specified (as +// a negative value in m_bwAssocParam), it should be positive and +// less than 1, and specifies the convergence value that is to +// correspond to bandwidth equal to 1.0. This is achieved by scaling +// the convergence by the inverse of the tolerance, and saturating +// at 1.0. +void Analyzer::fixBandwidth( Peaks & peaks ) +{ + + if ( m_bwAssocParam < 0 ) + { + double scale = 1.0 / (- m_bwAssocParam); + // m_bwAssocParam stores negative tolerance + + for ( Peaks::iterator it = peaks.begin(); it != peaks.end(); ++it ) + { + SpectralPeak & pk = *it; + pk.setBandwidth( std::min( 1.0, scale * pk.bandwidth() ) ); + } + } + else if ( m_bwAssocParam == 0 ) + { + for ( Peaks::iterator it = peaks.begin(); it != peaks.end(); ++it ) + { + SpectralPeak & pk = *it; + pk.setBandwidth( 0 ); + } + } +} + +} // end of namespace Loris diff --git a/src/loris/Analyzer.h b/src/loris/Analyzer.h new file mode 100644 index 0000000..6bdfe52 --- /dev/null +++ b/src/loris/Analyzer.h @@ -0,0 +1,620 @@ +#ifndef INCLUDE_ANALYZER_H +#define INCLUDE_ANALYZER_H +/* + * 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 + * + * + * Analyzer.h + * + * Definition of class Loris::Analyzer. + * + * Kelly Fitz, 5 Dec 99 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include <memory> +#include <vector> +#include "LinearEnvelope.h" +#include "Partial.h" +#include "PartialList.h" +// #include "SpectralPeaks.h" + +// begin namespace +namespace Loris { + +class Envelope; +class LinearEnvelopeBuilder; +// class Peaks; +// class Peaks::iterator; +// oooo, this is nasty, need to fix it! +class SpectralPeak; +typedef std::vector< SpectralPeak > Peaks; + +// --------------------------------------------------------------------------- +// class Analyzer +// +//! Class Analyzer represents a configuration of parameters for +//! performing Reassigned Bandwidth-Enhanced Additive Analysis +//! of sampled sounds. The analysis process yields a collection +//! of Partials, each having a trio of synchronous, non-uniformly- +//! sampled breakpoint envelopes representing the time-varying +//! frequency, amplitude, and noisiness of a single bandwidth- +//! enhanced sinusoid. These Partials are accumulated in the +//! Analyzer. +//! +//! The core analysis parameter is the frequency resolution, the +//! minimum instantaneous frequency spacing between partials. Most +//! other parameters are initially configured according to this +//! parameter (and the analysis window width, if specified). +//! Subsequent parameter mutations are independent. +//! +//! Bandwidth enhancement: +//! Two different strategies are available for computing bandwidth +//! (or noisiness) envelope: +//! +//! One strategy is to construct bandwidth envelopes during analysis +//! by associating residual energy in the spectrum (after peak +//! extraction) with the selected spectral peaks that are used +//! to construct Partials. This is the original bandwidth enhancement +//! algorithm, and bandwidth envelopes constructed in this way may +//! be suitable for use in bandwidth-enhanced synthesis. +//! +//! Another stategy is to construct bandwidth envelopes during +//! analysis by storing the mixed derivative of short-time phase, +//! scaled and shifted so that a value of 0 corresponds +//! to a pure sinusoid, and a value of 1 corresponds to a +//! bandwidth-enhanced sinusoid with maximal energy spread +//! (minimum convergence in frequency). These bandwidth envelopes +//! are not suitable for bandwidth-enhanced synthesis, be sure +//! to set the bandwidth to 0, or to disable bandwidth enhancement +//! before rendering. +//! +//! The Analyzer may be configured to use either of these two +//! strategies for bandwidth-enhanced analysis, or to construct +//! no bandwidth envelopes at all. If unspecified, the default +//! Analyzer configuration uses spectral residue to construct +//! bandwidth envelopes. +//! +//! \sa storeResidueBandwidth, storeConvergenceBandwidth, storeNoBandwidth +//! +//! For more information about Reassigned Bandwidth-Enhanced +//! Analysis and the Reassigned Bandwidth-Enhanced Additive Sound +//! Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. +// +class Analyzer +{ +// -- public interface -- +public: + +// -- construction -- + + //! Construct a new Analyzer configured with the given + //! frequency resolution (minimum instantaneous frequency + //! difference between Partials). All other Analyzer parameters + //! are computed from the specified frequency resolution. + //! + //! \param resolutionHz is the frequency resolution in Hz. + explicit Analyzer( double resolutionHz ); + + //! Construct a new Analyzer configured with the given + //! frequency resolution (minimum instantaneous frequency + //! difference between Partials) and analysis window width + //! (main lobe, zero-to-zero). All other Analyzer parameters + //! are computed from the specified resolution and window width. + //! + //! \param resolutionHz is the frequency resolution in Hz. + //! \param windowWidthHz is the main lobe width of the Kaiser + //! analysis window in Hz. + Analyzer( double resolutionHz, double windowWidthHz ); + + //! Construct a new Analyzer configured with the given time-varying + //! frequency resolution (minimum instantaneous frequency + //! difference between Partials) and analysis window width + //! (main lobe, zero-to-zero). All other Analyzer parameters + //! are computed from the specified resolution and window width. + //! + //! \param resolutionHz is the frequency resolution in Hz. + //! \param windowWidthHz is the main lobe width of the Kaiser + //! analysis window in Hz. + Analyzer( const Envelope & resolutionEnv, double windowWidthHz ); + + //! Construct a new Analyzer having identical + //! parameter configuration to another Analyzer. + //! The list of collected Partials is not copied. + //! + //! \param other is the Analyzer to copy. + Analyzer( const Analyzer & other ); + + //! Destroy this Analyzer. + ~Analyzer( void ); + + //! Construct a new Analyzer having identical + //! parameter configuration to another Analyzer. + //! The list of collected Partials is not copied. + //! + //! \param rhs is the Analyzer to copy. + Analyzer & operator=( const Analyzer & rhs ); + +// -- configuration -- + + //! Configure this Analyzer with the given frequency resolution + //! (minimum instantaneous frequency difference between Partials, + //! in Hz). All other Analyzer parameters are (re-)computed from the + //! frequency resolution, including the window width, which is + //! twice the resolution. + //! + //! \param resolutionHz is the frequency resolution in Hz. + void configure( double resolutionHz ); + + //! Configure this Analyzer with the given frequency resolution + //! (minimum instantaneous frequency difference between Partials) + //! and analysis window width (main lobe, zero-to-zero, in Hz). + //! All other Analyzer parameters are (re-)computed from the + //! frequency resolution and window width. + //! + //! \param resolutionHz is the frequency resolution in Hz. + //! \param windowWidthHz is the main lobe width of the Kaiser + //! analysis window in Hz. + //! + //! There are three categories of analysis parameters: + //! - the resolution, and params that are usually related to (or + //! identical to) the resolution (frequency floor and drift) + //! - the window width and params that are usually related to (or + //! identical to) the window width (hop and crop times) + //! - independent parameters (bw region width and amp floor) + void configure( double resolutionHz, double windowWidthHz ); + + //! Configure this Analyzer with the given time-varying frequency resolution + //! (minimum instantaneous frequency difference between Partials) + //! and analysis window width (main lobe, zero-to-zero, in Hz). + //! All other Analyzer parameters are (re-)computed from the + //! frequency resolution and window width. + //! + //! \param resolutionEnv is the time-varying frequency resolution + //! in Hz. + //! \param windowWidthHz is the main lobe width of the Kaiser + //! analysis window in Hz. + //! + //! There are three categories of analysis parameters: + //! - the resolution, and params that are usually related to (or + //! identical to) the resolution (frequency floor and drift) + //! - the window width and params that are usually related to (or + //! identical to) the window width (hop and crop times) + //! - independent parameters (bw region width and amp floor) + // + void configure( const Envelope & resolutionEnv, double windowWidthHz ); + +// -- analysis -- + + //! Analyze a vector of (mono) samples at the given sample rate + //! (in Hz) and store the extracted Partials in the Analyzer's + //! PartialList (std::list of Partials). + //! + //! \param vec is a vector of floating point samples + //! \param srate is the sample rate of the samples in the vector + void analyze( const std::vector<double> & vec, double srate ); + + //! Analyze a range of (mono) samples at the given sample rate + //! (in Hz) and store the extracted Partials in the Analyzer's + //! PartialList (std::list of Partials). + //! + //! \param bufBegin is a pointer to a buffer of floating point samples + //! \param bufEnd is (one-past) the end of a buffer of floating point + //! samples + //! \param srate is the sample rate of the samples in the buffer + void analyze( const double * bufBegin, const double * bufEnd, double srate ); + +// -- tracking analysis -- + + //! Analyze a vector of (mono) samples at the given sample rate + //! (in Hz) and store the extracted Partials in the Analyzer's + //! PartialList (std::list of Partials). Use the specified envelope + //! as a frequency reference for Partial tracking. + //! + //! \param vec is a vector of floating point samples + //! \param srate is the sample rate of the samples in the vector + //! \param reference is an Envelope having the approximate + //! frequency contour expected of the resulting Partials. + void analyze( const std::vector<double> & vec, double srate, + const Envelope & reference ); + + //! Analyze a range of (mono) samples at the given sample rate + //! (in Hz) and store the extracted Partials in the Analyzer's + //! PartialList (std::list of Partials). Use the specified envelope + //! as a frequency reference for Partial tracking. + //! + //! \param bufBegin is a pointer to a buffer of floating point samples + //! \param bufEnd is (one-past) the end of a buffer of floating point + //! samples + //! \param srate is the sample rate of the samples in the buffer + //! \param reference is an Envelope having the approximate + //! frequency contour expected of the resulting Partials. + void analyze( const double * bufBegin, const double * bufEnd, double srate, + const Envelope & reference ); + +// -- parameter access -- + + //! Return the amplitude floor (lowest detected spectral amplitude), + //! in (negative) dB, for this Analyzer. + double ampFloor( void ) const; + + //! Return the crop time (maximum temporal displacement of a time- + //! frequency data point from the time-domain center of the analysis + //! window, beyond which data points are considered "unreliable") + //! for this Analyzer. + double cropTime( void ) const; + + //! Return the maximum allowable frequency difference between + //! consecutive Breakpoints in a Partial envelope for this Analyzer. + double freqDrift( void ) const; + + //! Return the frequency floor (minimum instantaneous Partial + //! frequency), in Hz, for this Analyzer. + double freqFloor( void ) const; + + //! Return the frequency resolution (minimum instantaneous frequency + //! difference between Partials) for this Analyzer at the specified + //! time in seconds. If no time is specified, then the initial resolution + //! (at 0 seconds) is returned. + //! + //! \param time is the time in seconds at which to evaluate the + //! frequency resolution + double freqResolution( double time = 0.0 ) const; + + //! Return the hop time (which corresponds approximately to the + //! average density of Partial envelope Breakpoint data) for this + //! Analyzer. + double hopTime( void ) const; + + //! Return the sidelobe attenutation level for the Kaiser analysis window in + //! positive dB. Larger numbers (e.g. 90) give very good sidelobe + //! rejection but cause the window to be longer in time. Smaller numbers + //! (like 60) raise the level of the sidelobes, increasing the likelihood + //! of frequency-domain interference, but allow the window to be shorter + //! in time. + double sidelobeLevel( void ) const; + + //! Return the frequency-domain main lobe width (measured between + //! zero-crossings) of the analysis window used by this Analyzer. + double windowWidth( void ) const; + + //! Return true if the phases and frequencies of the constructed + //! partials should be modified to be consistent at the end of the + //! analysis, and false otherwise. (Default is true.) + bool phaseCorrect( void ) const; + + +// -- parameter mutation -- + + //! Set the amplitude floor (lowest detected spectral amplitude), in + //! (negative) dB, for this Analyzer. + //! + //! \param x is the new value of this parameter. + void setAmpFloor( double x ); + + //! Set the crop time (maximum temporal displacement of a time- + //! frequency data point from the time-domain center of the analysis + //! window, beyond which data points are considered "unreliable") + //! for this Analyzer. + //! + //! \param x is the new value of this parameter. + void setCropTime( double x ); + + //! Set the maximum allowable frequency difference between + //! consecutive Breakpoints in a Partial envelope for this Analyzer. + //! + //! \param x is the new value of this parameter. + void setFreqDrift( double x ); + + //! Set the frequency floor (minimum instantaneous Partial + //! frequency), in Hz, for this Analyzer. + //! + //! \param x is the new value of this parameter. + void setFreqFloor( double x ); + + //! Set the frequency resolution (minimum instantaneous frequency + //! difference between Partials) for this Analyzer. (Does not cause + //! other parameters to be recomputed.) + //! + //! \param x is the new value of this parameter. + void setFreqResolution( double x ); + + //! Set the time-varying frequency resolution (minimum instantaneous frequency + //! difference between Partials) for this Analyzer. (Does not cause + //! other parameters to be recomputed.) + //! + //! \param e is the envelope to copy for this parameter. + void setFreqResolution( const Envelope & e ); + + //! Set the hop time (which corresponds approximately to the average + //! density of Partial envelope Breakpoint data) for this Analyzer. + //! + //! \param x is the new value of this parameter. + void setHopTime( double x ); + + //! Set the sidelobe attenutation level for the Kaiser analysis window in + //! positive dB. More negative numbers (e.g. -90) give very good sidelobe + //! rejection but cause the window to be longer in time. Less negative + //! numbers raise the level of the sidelobes, increasing the likelihood + //! of frequency-domain interference, but allow the window to be shorter + //! in time. + //! + //! \param x is the new value of this parameter. + void setSidelobeLevel( double x ); + + //! Set the frequency-domain main lobe width (measured between + //! zero-crossings) of the analysis window used by this Analyzer. + //! + //! \param x is the new value of this parameter. + void setWindowWidth( double x ); + + //! Indicate whether the phases and frequencies of the constructed + //! partials should be modified to be consistent at the end of the + //! analysis. (Default is true.) + //! + //! \param TF is a flag indicating whether or not to construct + //! phase-corrected Partials + void setPhaseCorrect( bool TF = true ); + + +// -- bandwidth envelope specification -- + + enum { Default_ResidueBandwidth_RegionWidth = 2000, + Default_ConvergenceBandwidth_TolerancePct = 10 }; + + //! Construct Partial bandwidth envelopes during analysis + //! by associating residual energy in the spectrum (after + //! peak extraction) with the selected spectral peaks that + //! are used to construct Partials. + //! + //! This is the default bandwidth-enhancement strategy. + //! + //! \param regionWidth is the width (in Hz) of the bandwidth + //! association regions used by this process, must be positive. + //! If unspecified, a default value is used. + void storeResidueBandwidth( double regionWidth = Default_ResidueBandwidth_RegionWidth ); + + //! Construct Partial bandwidth envelopes during analysis + //! by storing the mixed derivative of short-time phase, + //! scaled and shifted so that a value of 0 corresponds + //! to a pure sinusoid, and a value of 1 corresponds to a + //! bandwidth-enhanced sinusoid with maximal energy spread + //! (minimum sinusoidal convergence). + //! + //! \param tolerance is the amount of range over which the + //! mixed derivative indicator should be allowed to drift away + //! from a pure sinusoid before saturating. This range is mapped + //! to bandwidth values on the range [0,1]. Must be positive and + //! not greater than 1. If unspecified, a default value is used. + void storeConvergenceBandwidth( double tolerancePct = + 0.01 * (double)Default_ConvergenceBandwidth_TolerancePct ); + + //! Disable bandwidth envelope construction. Bandwidth + //! will be zero for all Breakpoints in all Partials. + void storeNoBandwidth( void ); + + //! Return true if this Analyzer is configured to compute + //! bandwidth envelopes using the spectral residue after + //! peaks have been identified, and false otherwise. + bool bandwidthIsResidue( void ) const; + + //! Return true if this Analyzer is configured to compute + //! bandwidth envelopes using the mixed derivative convergence + //! indicator, and false otherwise. + bool bandwidthIsConvergence( void ) const; + + //! Return the width (in Hz) of the Bandwidth Association regions + //! used by this Analyzer, only if the spectral residue method is + //! used to compute bandwidth envelopes. Return zero if the mixed + //! derivative method is used, or if no bandwidth is computed. + double bwRegionWidth( void ) const; + + //! Return the mixed derivative convergence tolerance (percent) + //! only if the convergence indicator is used to compute + //! bandwidth envelopes. Return zero if the spectral residue + //! method is used or if no bandwidth is computed. + double bwConvergenceTolerance( void ) const; + + //! Return true if bandwidth envelopes are to be constructed + //! by any means, that is, if either bandwidthIsResidue() or + //! bandwidthIsConvergence() are true. Otherwise, return + //! false. + bool associateBandwidth( void ) const + { return bandwidthIsResidue() || bandwidthIsConvergence(); } + + //! Deprecated, use storeResidueBandwidth or storeNoBandwidth instead. + void setBwRegionWidth( double x ) + { + if ( x != 0 ) + { + storeResidueBandwidth( x ); + } + else + { + storeNoBandwidth(); + } + } + + +// -- PartialList access -- + + //! Return a mutable reference to this Analyzer's list of + //! analyzed Partials. + PartialList & partials( void ); + + //! Return an immutable (const) reference to this Analyzer's + //! list of analyzed Partials. + const PartialList & partials( void ) const; + +// -- envelope access -- + + enum { Default_FundamentalEnv_ThreshDb = -60, + Default_FundamentalEnv_ThreshHz = 8000 }; + + //! Specify parameters for constructing a fundamental frequency + //! envelope for the analyzed sound during analysis. The fundamental + //! frequency estimate can be accessed by fundamentalEnv() after the + //! analysis is complete. + //! + //! By default, a fundamental envelope is estimated during analysis + //! between the frequency resolution and 1.5 times the resolution. + //! + //! \param fmin is the lower bound on the fundamental frequency estimate + //! \param fmax is the upper bound on the fundamental frequency estimate + //! \param threshDb is the lower bound on the amplitude of a spectral peak + //! that will constribute to the fundamental frequency estimate (very + //! low amplitude peaks tend to have less reliable frequency estimates). + //! Default is -60 dB. + //! \param threshHz is the upper bound on the frequency of a spectral + //! peak that will constribute to the fundamental frequency estimate. + //! Default is 8 kHz. + void buildFundamentalEnv( double fmin, double fmax, + double threshDb = Default_FundamentalEnv_ThreshDb, + double threshHz = Default_FundamentalEnv_ThreshHz ); + + + //! Return the fundamental frequency estimate envelope constructed + //! during the most recent analysis performed by this Analyzer. + //! + //! By default, a fundamental envelope is estimated during analysis + //! between the frequency resolution and 1.5 times the resolution. + const LinearEnvelope & fundamentalEnv( void ) const; + + //! Return the overall amplitude estimate envelope constructed + //! during the most recent analysis performed by this Analyzer. + const LinearEnvelope & ampEnv( void ) const; + + +// -- legacy support -- + + // Fundamental and amplitude envelopes are always constructed during + // analysis, these members do nothing, and are retained for backwards + // compatibility. + void buildAmpEnv( bool TF = true ) { TF = TF; } + void buildFundamentalEnv( bool TF = true ) { TF = TF; } + +// -- private member variables -- + +private: + + std::auto_ptr< Envelope > m_freqResolutionEnv; + //! in Hz, minimum instantaneous frequency distance; + //! this is the core parameter, others are, by default, + //! computed from this one + + double m_ampFloor; //! dB, relative to full amplitude sine wave, absolute + //! amplitude threshold (negative) + + double m_windowWidth; //! in Hz, width of main lobe; this might be more + //! conveniently presented as window length, but + //! the main lobe width more explicitly highlights + //! the critical interaction with resolution + + // std::auto_ptr< Envelope > m_freqFloorEnv; + double m_freqFloor; //! lowest frequency (Hz) component extracted + //! in spectral analysis + + double m_freqDrift; //! the maximum frequency (Hz) difference between two + //! consecutive Breakpoints that will be linked to + //! form a Partial + + double m_hopTime; //! in seconds, time between analysis windows in + //! successive spectral analyses + + double m_cropTime; //! in seconds, maximum time correction for a spectral + //! component to be considered reliable, and to be eligible + //! for extraction and for Breakpoint formation + + double m_bwAssocParam; //! formerly, width in Hz of overlapping bandwidth + //! association regions, or zero if bandwidth association + //! is disabled, now a catch-all bandwidth association + //! parameter that, if negative, indicates the tolerance (%) + //! level used to construct bandwidth envelopes from the + //! mixed phase derivative indicator + + double m_sidelobeLevel; //! sidelobe attenutation level for the Kaiser analysis + //! window, in positive dB + + bool m_phaseCorrect; //! flag indicating that phases/frequencies should be + //! made consistent at the end of the analysis + + PartialList m_partials; //! collect Partials here + + //! builder object for constructing a fundamental frequency + //! estimate during analysis + std::auto_ptr< LinearEnvelopeBuilder > m_f0Builder; + + //! builder object for constructing an amplitude + //! estimate during analysis + std::auto_ptr< LinearEnvelopeBuilder > m_ampEnvBuilder; + +// -- private auxiliary functions -- +// future development +/* + + // These members make up the sequence of operations in an + // analysis. If analysis were ever to be made into a + // template method, these would be the operations that + // derived classes could override. Or each of these could + // be represented by a strategy class. + + //! Compute the spectrum of the next sequence of samples. + void computeSpectrum( void ); + + //! Identify and select the spectral components that will be + //! used to form Partials. + void selectPeaks( void ); + + //! Compute the bandwidth coefficients for the Breakpoints + //! that are going to be used to form Partials. + void associateBandwidth( void ); + + //! Construct Partials from extracted spectral components. + //! Partials are built up frame by frame by appending + //! Breakpoints to Partials under construction, and giving + //! birth to new Partials using unmatched Peaks. + void formPartials( Peaks & peaks ); +*/ + // Reject peaks that are too close in frequency to a louder peak that is + // being retained, and peaks that are too quiet. Peaks that are retained, + // but are quiet enough to be in the specified fadeRange should be faded. + // + // Rejected peaks are placed at the end of the peak collection. + // Return the first position in the collection containing a rejected peak, + // or the end of the collection if no peaks are rejected. + Peaks::iterator thinPeaks( Peaks & peaks, double frameTime ); + + // Fix the bandwidth value stored in the specified Peaks. + // This function is invoked if the spectral residue method is + // not used to compute bandwidth (that method overwrites the + // bandwidth already). If the convergence method is used to + // compute bandwidth, the appropriate scaling is applied + // to the stored mixed phase derivative. Otherwise, the + // Peak bandwidth is set to zero. + void fixBandwidth( Peaks & peaks ); + +}; // end of class Analyzer + +} // end of namespace Loris + +#endif /* ndef INCLUDE_ANALYZER_H */ diff --git a/src/loris/AssociateBandwidth.C b/src/loris/AssociateBandwidth.C new file mode 100644 index 0000000..e72c52f --- /dev/null +++ b/src/loris/AssociateBandwidth.C @@ -0,0 +1,317 @@ +/* + * 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 + * + * + * AssociateBandwidth.C + * + * Implementation of a class representing a policy for associating noise + * (bandwidth) energy with reassigned spectral peaks to be used in + * Partial formation. + * + * Kelly Fitz, 20 Jan 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "AssociateBandwidth.h" +#include "Breakpoint.h" +#include "BreakpointUtils.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "SpectralPeaks.h" + +#include <algorithm> +#include <cmath> + +using namespace Loris; + +// --------------------------------------------------------------------------- +// AssociateBandwidth constructor +// --------------------------------------------------------------------------- +// Association regions are centered on all integer bin frequencies, regionWidth +// is the total width (in Hz) of the overlapping bandwidth association regions, +// the region centers are spaced at half this width. +// +AssociateBandwidth::AssociateBandwidth( double regionWidth, double srate ) : + _regionRate( 0 ) +{ + if ( ! (regionWidth>0) ) + Throw( InvalidArgument, "The regionWidth must be greater than 0 Hz." ); + if ( ! (srate>0) ) + Throw( InvalidArgument, "The sample rate must be greater than 0 Hz." ); + + + _weights.resize( int(srate/regionWidth) ); + _surplus.resize( int(srate/regionWidth) ); + _regionRate = 2./regionWidth; +} + +// --------------------------------------------------------------------------- +// AssociateBandwidth destructor +// --------------------------------------------------------------------------- +// +AssociateBandwidth::~AssociateBandwidth( void ) +{ +} + +// --------------------------------------------------------------------------- +// binFrequency +// --------------------------------------------------------------------------- +// Compute the warped fractional bin/region frequency corresponding to +// freqHz. (_regionRate is the number of regions per hertz.) +// +// Once, we used bark frequency scale warping here, but there seems to be +// no reason to do so. The best results seem to be indistinguishable from +// plain 'ol 1k bins, and some results are much worse. +// +static double binFrequency( double freqHz, double regionRate ) +{ +//#define Use_Barks +#ifndef Use_Barks + return freqHz * regionRate; +#else + // Compute Bark frequency from Hertz. + // Got this formula for Bark frequency from Sciarraba's thesis. + // Ignore region rate when using barks + double tmp = std::atan( ( 0.001 / 7.5 ) * freqHz ); + return 13. * std::atan( 0.76 * 0.001 * freqHz ) + 3.5 * ( tmp * tmp ); +#endif + +} + +// --------------------------------------------------------------------------- +// findRegionBelow +// --------------------------------------------------------------------------- +// Return the index of the last region having center frequency less than +// or equal to freq, or -1 if no region is low enough. +// +// Note: the zeroeth region is centered at bin frequency 1 and tapers +// to zero at bin frequency 0! (when booger is 1.) +// +static int findRegionBelow( double binfreq, unsigned int howManyBins ) +{ + const double booger = 0.; + if ( binfreq < booger ) + { + return -1; + } + else + { + return int( std::min( std::floor(binfreq - booger), howManyBins - 1. ) ); + } +} + +// --------------------------------------------------------------------------- +// computeAlpha +// --------------------------------------------------------------------------- +// binfreq is a warped, fractional bin frequency, and bin frequencies +// are integers. Return the relative contribution of a component at +// binfreq to the bins (bw association regions) below and above +// binfreq. +// +static double computeAlpha( double binfreq, unsigned int howManyBins ) +{ + // everything above the center of the highest + // bin is lumped into that bin; i.e it does + // not taper off at higher frequencies: + if ( binfreq > howManyBins ) + { + return 0.; + } + else + { + return binfreq - std::floor( binfreq ); + } +} + +// --------------------------------------------------------------------------- +// distribute +// --------------------------------------------------------------------------- +// +static void distribute( double fractionalBin, double x, std::vector<double> & regions ) +{ + // contribute x to two regions having center + // frequencies less and greater than freqHz: + int posBelow = findRegionBelow( fractionalBin, regions.size() ); + int posAbove = posBelow + 1; + + double alpha = computeAlpha( fractionalBin, regions.size() ); + + if ( posAbove < regions.size() ) + regions[posAbove] += alpha * x; + + if ( posBelow >= 0 ) + regions[posBelow] += (1. - alpha) * x; +} + +// --------------------------------------------------------------------------- +// computeNoiseEnergy +// --------------------------------------------------------------------------- +// Return the noise energy to be associated with a component at freqHz. +// _surplus contains the surplus spectral energy in each region, which is, +// by defintion, non-negative. +// +double +AssociateBandwidth::computeNoiseEnergy( double freq, double amp ) +{ + // don't mess with negative frequencies: + if ( freq < 0. ) + return 0.; + + // compute the fractional bin frequency + // corresponding to freqHz: + double bin = binFrequency( freq, _regionRate ); + + // contribute x to two regions having center + // frequencies less and greater than freqHz: + int posBelow = findRegionBelow( bin, _surplus.size() ); + int posAbove = posBelow + 1; + + double alpha = computeAlpha( bin, _surplus.size() ); + + double noise = 0.; + // Have to check for alpha == 0, because + // the weights will be zero (see computeAlpha()): + // (ignore lowest regions) + const int LowestRegion = 2; + /* + if ( posAbove < _surplus.size() && alpha != 0. && posAbove >= LowestRegion ) + noise += _surplus[posAbove] * alpha / _weights[posAbove]; + + if ( posBelow >= LowestRegion ) + noise += _surplus[posBelow] * (1. - alpha) / _weights[posBelow]; + */ + // new idea, weight Partials by amplitude: + if ( posAbove < _surplus.size() && alpha != 0. && posAbove >= LowestRegion ) + noise += _surplus[posAbove] * alpha * amp / _weights[posAbove]; + + if ( posBelow >= LowestRegion ) + noise += _surplus[posBelow] * (1. - alpha) * amp / _weights[posBelow]; + + return noise; +} + +// --------------------------------------------------------------------------- +// accumulateSinusoid +// --------------------------------------------------------------------------- +// Accumulate sinusoidal energy at frequency f and amplitude a. +// The amplitude isn't used for anything. +// +void +AssociateBandwidth::accumulateSinusoid( double freq, double amp ) +{ + // distribute weight at the peak frequency, + // don't mess with negative frequencies: + if ( freq > 0. ) + { + //distribute( binFrequency( freq, _regionRate ), 1., _weights ); + // new idea: weight Partials by amplitude: + distribute( binFrequency( freq, _regionRate ), amp, _weights ); + } +} + +// --------------------------------------------------------------------------- +// accumulateNoise +// --------------------------------------------------------------------------- +// Accumulate a rejected spectral peak as surplus (noise) energy. +// +void +AssociateBandwidth::accumulateNoise( double freq, double amp ) +{ + // compute energy contribution and distribute + // at frequency f, don't mess with negative + // frequencies: + if ( freq > 0. ) + { + distribute( binFrequency( freq, _regionRate ), amp * amp, _surplus ); + } +} + +// --------------------------------------------------------------------------- +// associate +// --------------------------------------------------------------------------- +// Associate bandwidth with a single SpectralPeak. +// +void +AssociateBandwidth::associate( SpectralPeak & pk ) +{ + pk.setBandwidth(0); + pk.addNoiseEnergy( computeNoiseEnergy( pk.frequency(), pk.amplitude() ) ); +} + +// --------------------------------------------------------------------------- +// reset +// --------------------------------------------------------------------------- +// This is called after each distribution of bandwidth energy. +// +void +AssociateBandwidth::reset( void ) +{ + std::fill( _weights.begin(), _weights.end(), 0. ); + std::fill( _surplus.begin(), _surplus.end(), 0. ); +} + +// --------------------------------------------------------------------------- +// associate +// --------------------------------------------------------------------------- +// Perform bandwidth association on a collection of reassigned spectral peaks +// or ridges. The range [begin, rejected) spans the Peaks selected to form +// Partials. The range [rejected, end) spans the Peaks that were found in +// the reassigned spectrum, but rejected as too weak or too close (in +// frequency) to another stronger Peak. +// +void +AssociateBandwidth::associateBandwidth( Peaks::iterator begin, // beginning of Peaks + Peaks::iterator rejected, // first rejected Peak + Peaks::iterator end ) // end of Peaks +{ + if ( begin == rejected ) + return; + + // accumulate retained Breakpoints as sinusoids, + for ( Peaks::iterator it = begin; it != rejected; ++it ) + { + accumulateSinusoid( it->frequency(), it->amplitude() ); + } + + // accumulate rejected breakpoints as noise: + for ( Peaks::iterator it = rejected; it != end; ++it ) + { + accumulateNoise( it->frequency(), it->amplitude() ); + } + + // associate bandwidth with each retained Breakpoint: + for ( Peaks::iterator it = begin; it != rejected; ++it ) + { + // sets bandwidth to zero, then calls addNoiseEnergy() + associate( *it ); + } + + // reset after association, yuk: + reset(); + +} diff --git a/src/loris/AssociateBandwidth.h b/src/loris/AssociateBandwidth.h new file mode 100644 index 0000000..475aeb0 --- /dev/null +++ b/src/loris/AssociateBandwidth.h @@ -0,0 +1,109 @@ +#ifndef INCLUDE_ASSOCIATEBANDWIDTH_H +#define INCLUDE_ASSOCIATEBANDWIDTH_H +/* + * 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 + * + * + * AssociateBandwidth.h + * + * Definition of a class representing a policy for associating noise + * (bandwidth) energy with reassigned spectral peaks to be used in + * Partial formation. + * + * Kelly Fitz, 20 Jan 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "SpectralPeaks.h" + +#include <vector> + +// begin namespace +namespace Loris { + +class Breakpoint; + +// --------------------------------------------------------------------------- +// class AssociateBandwidth +// +// In the new strategy, Breakpoints are extracted and accumulated +// as sinusoids. Spectral peaks that are not extracted (don't exceed +// the amplitude floor) or are rejected for certain reasons, are +// accumulated diectly as noise (surplus). After all spectral peaks +// have been accumulated as noise or sinusoids, the noise is distributed +// as bandwidth. +// +class AssociateBandwidth +{ +// -- instance variables -- + std::vector< double > _weights; // weights vector for recording + // frequency distribution of retained + // sinusoids + std::vector< double > _surplus; // surplus (noise) energy vector for + // accumulating the distribution of + // spectral energy to be distributed + // as noise + + double _regionRate; // inverse of region center spacing + +// -- public interface -- +public: + // construction: + AssociateBandwidth( double regionWidth, double srate ); + ~AssociateBandwidth( void ); + + // Perform bandwidth association on a collection of reassigned spectral peaks + // or ridges. The range [begin, rejected) spans the Peaks selected to form + // Partials. The range [rejected, end) spans the Peaks that were found in + // the reassigned spectrum, but rejected as too weak or too close (in + // frequency) to another stronger Peak. + void associateBandwidth( Peaks::iterator begin, // beginning of Peaks + Peaks::iterator rejected, // first rejected Peak + Peaks::iterator end ); // end of Peaks + + +// -- private helpers -- +private: + double computeNoiseEnergy( double freq, double amp ); + + // These four formerly comprised the public interface + // to this policy, now they are all hidden behind a + // single call to associateBandwidth. + + // energy accumulation: + void accumulateNoise( double freq, double amp ); + void accumulateSinusoid( double freq, double amp ); + + // bandwidth assocation: + void associate( SpectralPeak & pk ); + + // call this to wipe out the accumulated energy to + // prepare for the next frame (yuk): + void reset( void ); + +}; // end of class AssociateBandwidth + +} // end of namespace Loris + +#endif /* ndef INCLUDE_ASSOCIATEBANDWIDTH_H */ diff --git a/src/loris/BigEndian.C b/src/loris/BigEndian.C new file mode 100644 index 0000000..515b010 --- /dev/null +++ b/src/loris/BigEndian.C @@ -0,0 +1,148 @@ +/* + * 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 + * + * + * BigEndian.C + * + * Implementation of wrappers for stream-based binary file i/o. + * + * Kelly Fitz, 23 May 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "BigEndian.h" +#include "LorisExceptions.h" +#include <vector> +#include <iostream> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// bigEndianSystem +// --------------------------------------------------------------------------- +// Return true is this is a big-endian system, false otherwise. +// +static bool bigEndianSystem( void ) +{ +#if defined(WORDS_BIGENDIAN) + return true; +#elif HAVE_CONFIG_H && !defined(WORDS_BIGENDIAN) + return false; +#else + static union { + int s ; + char c[sizeof(int)] ; + } x ; + bool ret = (x.s = 1, x.c[0] != 1) ? true : false; + + return ret; // x.c[0] != 1; +#endif +} + +// --------------------------------------------------------------------------- +// swapByteOrder +// --------------------------------------------------------------------------- +// +static void swapByteOrder( char * bytes, int n ) +{ + char * beg = bytes, * end = bytes + n - 1; + while ( beg < end ) { + char tmp = *end; + *end = *beg; + *beg = tmp; + + ++beg; + --end; + } +} + +// --------------------------------------------------------------------------- +// BigEndian read +// --------------------------------------------------------------------------- +// +std::istream & +BigEndian::read( std::istream & s, long howmany, int size, char * putemHere ) +{ + // read the bytes into data: + s.read( putemHere, howmany*size ); + + // check stream state: + if ( s ) + { + // if the stream is still in a good state, then + // the correct number of bytes must have been read: + Assert( s.gcount() == howmany*size ); + + // swap byte order if nec. + if ( ! bigEndianSystem() && size > 1 ) + { + for ( long i = 0; i < howmany; ++i ) + { + swapByteOrder( putemHere + (i*size), size ); + } + } + } + + return s; +} + +// --------------------------------------------------------------------------- +// BigEndian write +// --------------------------------------------------------------------------- +// +std::ostream & +BigEndian::write( std::ostream & s, long howmany, int size, const char * stuff ) +{ + // swap byte order if nec. + if ( ! bigEndianSystem() && size > 1 ) + { + // use a temporary vector to automate storage: + std::vector<char> v( stuff, stuff + (howmany*size) ); + for ( long i = 0; i < howmany; ++i ) + { + swapByteOrder( & v[i*size], size ); + } + s.write( &v[0], howmany*size ); + } + else + { + // read the bytes into data: + s.write( stuff, howmany*size ); + } + + // check stream state: + if ( ! s.good() ) + Throw( FileIOException, "File write failed. " ); + + return s; +} + +} // end of namespace Loris + + diff --git a/src/loris/BigEndian.h b/src/loris/BigEndian.h new file mode 100644 index 0000000..d4d70e8 --- /dev/null +++ b/src/loris/BigEndian.h @@ -0,0 +1,54 @@ +#ifndef INCLUDE_BIGENDIAN_H +#define INCLUDE_BIGENDIAN_H +/* + * 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 + * + * + * BigEndian.h + * + * Definition of wrappers for stream-based binary file i/o. + * + * Kelly Fitz, 23 May 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include <iosfwd> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class BigEndian +// +class BigEndian +{ +public: + static std::istream & read( std::istream & s, long howmany, int size, char * putemHere ); + static std::ostream & write( std::ostream & s, long howmany, int size, const char * stuff ); +}; // end of class BigEndian + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_BIGENDIAN_H */ diff --git a/src/loris/Breakpoint.C b/src/loris/Breakpoint.C new file mode 100644 index 0000000..4e5659f --- /dev/null +++ b/src/loris/Breakpoint.C @@ -0,0 +1,129 @@ +/* + * 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 + * + * + * Breakpoint.C + * + * Implementation of class Loris::Breakpoint. + * + * Kelly Fitz, 16 Aug 99 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Breakpoint.h" +#include "LorisExceptions.h" +#include <cmath> + +// begin namespace +namespace Loris { + + +// - +// construction + +// --------------------------------------------------------------------------- +// Breakpoint default constructor +// --------------------------------------------------------------------------- +//! Construct a new Breakpoint with all parameters initialized to 0 +//! (needed for STL containability). +Breakpoint::Breakpoint( void ) : + _frequency( 0. ), + _amplitude( 0. ), + _bandwidth( 0. ), + _phase( 0. ) +{ +} + +// --------------------------------------------------------------------------- +// Breakpoint constructor +// --------------------------------------------------------------------------- +//! Construct a new Breakpoint with the specified parameters. +//! +//! \param f is the intial frequency. +//! \param a is the initial amplitude. +//! \param b is the initial bandwidth. +//! \param p is the initial phase, if specified (if unspecified, 0 +//! is assumed). +// +Breakpoint::Breakpoint( double f, double a, double b, double p ) : + _frequency( f ), + _amplitude( a ), + _bandwidth( b ), + _phase( p ) +{ +} + +// --------------------------------------------------------------------------- +// addNoiseEnergy +// --------------------------------------------------------------------------- +//! Add noise (bandwidth) energy to this Breakpoint by computing new +//! amplitude and bandwidth values. enoise may be negative, but +//! noise energy cannot be removed (negative energy added) in excess +//! of the current noise energy. +//! +//! \param enoise is the amount of noise energy to add to +//! this Breakpoint. +// +void +Breakpoint::addNoiseEnergy( double enoise ) +{ + // compute current energies: + double e = amplitude() * amplitude(); // current total energy + double n = e * bandwidth(); // current noise energy + + // Assert( e >= n ); + // could complain, but its recoverable, just fix it: + if ( e < n ) + { + e = n; + } + + // guard against divide-by-zero, and don't allow + // the sinusoidal energy to decrease: + if ( n + enoise > 0. ) + { + // if new noise energy is positive, total + // energy must also be positive: + // Assert( e + enoise > 0 ); + setBandwidth( (n + enoise) / (e + enoise) ); + setAmplitude( std::sqrt(e + enoise) ); + } + else + { + // if new noise energy is negative, leave + // all sinusoidal energy: + setBandwidth( 0. ); + setAmplitude( std::sqrt( e - n ) ); + } +} + +} // end of namespace Loris + + + + diff --git a/src/loris/Breakpoint.h b/src/loris/Breakpoint.h new file mode 100644 index 0000000..31123dd --- /dev/null +++ b/src/loris/Breakpoint.h @@ -0,0 +1,133 @@ +#ifndef INCLUDE_BREAKPOINT_H +#define INCLUDE_BREAKPOINT_H +/* + * 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 + * + * + * Breakpoint.h + * + * Definition of class Loris::Breakpoint. + * + * Kelly Fitz, 16 Aug 99 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// class Breakpoint +// +//! Class Breakpoint represents a single breakpoint in the +//! Partial parameter (frequency, amplitude, bandwidth) envelope. +//! Instantaneous phase is also stored, but is only used at the onset of +//! a partial, or when it makes a transition from zero to nonzero amplitude. +//! +//! Loris Partials represent reassigned bandwidth-enhanced model components. +//! A Partial consists of a chain of Breakpoints describing the time-varying +//! frequency, amplitude, and bandwidth (noisiness) of the component. +//! For more information about Reassigned Bandwidth-Enhanced +//! Analysis and the Reassigned Bandwidth-Enhanced Additive Sound +//! Model, refer to the Loris website: +//! www.cerlsoundgroup.org/Loris/. +//! +//! Breakpoint is a leaf class, do not subclass. +// +class Breakpoint +{ +// -- instance variables -- + double _frequency; //! in Hertz + double _amplitude; //! absolute + double _bandwidth; //! fraction of total energy that is noise energy + double _phase; //! radians + +// -- public Breakpoint interface -- + +public: +// -- construction -- + + //! Construct a new Breakpoint with all parameters initialized to 0 + //! (needed for STL containability). + Breakpoint( void ); + + //! Construct a new Breakpoint with the specified parameters. + //! + //! \param f is the intial frequency. + //! \param a is the initial amplitude. + //! \param b is the initial bandwidth. + //! \param p is the initial phase, if specified (if unspecified, 0 + //! is assumed). + Breakpoint( double f, double a, double b, double p = 0. ); + + // (use compiler-generated destructor, copy, and assign) + +// -- access -- + //! Return the amplitude of this Breakpoint. + double amplitude( void ) const { return _amplitude; } + + //! Return the bandwidth (noisiness) coefficient of this Breakpoint. + double bandwidth( void ) const { return _bandwidth; } + + //! Return the frequency of this Breakpoint. + double frequency( void ) const { return _frequency; } + + //! Return the phase of this Breakpoint. + double phase( void ) const { return _phase; } + +// -- mutation -- + //! Set the amplitude of this Breakpoint. + //! + //! \param x is the new amplitude + void setAmplitude( double x ) { _amplitude = x; } + + //! Set the bandwidth (noisiness) coefficient of this Breakpoint. + //! + //! \param x is the new bandwidth + void setBandwidth( double x ) { _bandwidth = x; } + + //! Set the frequency of this Breakpoint. + //! + //! \param x is the new frequency. + void setFrequency( double x ) { _frequency = x; } + + //! Set the phase of this Breakpoint. + //! + //! \param x is the new phase. + void setPhase( double x ) { _phase = x; } + + //! Add noise (bandwidth) energy to this Breakpoint by computing new + //! amplitude and bandwidth values. enoise may be negative, but + //! noise energy cannot be removed (negative energy added) in excess + //! of the current noise energy. + //! + //! \param enoise is the amount of noise energy to add to + //! this Breakpoint. + void addNoiseEnergy( double enoise ); + +}; // end of class Breakpoint + +} // end of namespace Loris + +#endif /* ndef INCLUDE_BREAKPOINT_H */ diff --git a/src/loris/BreakpointEnvelope.h b/src/loris/BreakpointEnvelope.h new file mode 100644 index 0000000..383626a --- /dev/null +++ b/src/loris/BreakpointEnvelope.h @@ -0,0 +1,47 @@ +#ifndef INCLUDE_BREAKPOINTENVELOPE_H +#define INCLUDE_BREAKPOINTENVELOPE_H +/* + * 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 + * + * + * BreakpointEnvelope.h + * + * Definition of class BreakpointEnvelope. + * This class has been renamed LinearEnvelope. The old + * name and header are preserved for compatibility. + * + * Kelly Fitz, 21 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "LinearEnvelope.h" + +// begin namespace +namespace Loris { + + typedef LinearEnvelope BreakpointEnvelope; + +} + +#endif /* ndef INCLUDE_BREAKPOINTENVELOPE_H */ diff --git a/src/loris/BreakpointUtils.C b/src/loris/BreakpointUtils.C new file mode 100644 index 0000000..ea1ab68 --- /dev/null +++ b/src/loris/BreakpointUtils.C @@ -0,0 +1,89 @@ +/* + * 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 + * + * + * BreakpointUtils.C + * + * Out-of-line Breakpoint utility functions collected in namespace BreakpointUtils. + * + * Kelly Fitz, 19 June 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "BreakpointUtils.h" +#include "Breakpoint.h" + +#include <cmath> +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// makeNullBefore +// --------------------------------------------------------------------------- +// Return a null (zero-amplitude) Breakpoint to preceed the specified +// Breakpoint, useful for fading in a Partial. +// +Breakpoint +BreakpointUtils::makeNullBefore( const Breakpoint & bp, double fadeTime ) +{ + Breakpoint ret( bp ); + // adjust phase + double dp = 2. * Pi * fadeTime * bp.frequency(); + ret.setPhase( std::fmod( bp.phase() - dp, 2. * Pi ) ); + ret.setAmplitude(0.); + ret.setBandwidth(0.); + + return ret; +} + +// --------------------------------------------------------------------------- +// makeNullAfter +// --------------------------------------------------------------------------- +// Return a null (zero-amplitude) Breakpoint to succeed the specified +// Breakpoint, useful for fading out a Partial. +// +Breakpoint +BreakpointUtils::makeNullAfter( const Breakpoint & bp, double fadeTime ) +{ + Breakpoint ret( bp ); + // adjust phase + double dp = 2. * Pi * fadeTime * bp.frequency(); + ret.setPhase( std::fmod( bp.phase() + dp, 2. * Pi ) ); + ret.setAmplitude(0.); + ret.setBandwidth(0.); + + return ret; +} + +} // end of namespace Loris diff --git a/src/loris/BreakpointUtils.h b/src/loris/BreakpointUtils.h new file mode 100644 index 0000000..41fd106 --- /dev/null +++ b/src/loris/BreakpointUtils.h @@ -0,0 +1,217 @@ +#ifndef INCLUDE_BREAKPOINTUTILS_H +#define INCLUDE_BREAKPOINTUTILS_H +/* + * 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 + * + * + * BreakpointUtils.h + * + * Breakpoint utility functions collected in namespace BreakpointUtils. + * + * Kelly Fitz, 6 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Breakpoint.h" +#include <algorithm> +#include <functional> + +// begin namespace +namespace Loris { + +namespace BreakpointUtils { + +// -- free functions -- + +// --------------------------------------------------------------------------- +// addNoiseEnergy +// --------------------------------------------------------------------------- +//! Add noise (bandwidth) energy to a Breakpoint by computing new +//! amplitude and bandwidth values. enoise may be negative, but +//! noise energy cannot be removed (negative energy added) in excess +//! of the current noise energy. +//! +//! \deprecated This operation is now part of the Breakpoint interface. +//! Use Breakpoint::addNoiseEnergy instead. +// +inline void addNoiseEnergy( Breakpoint & bp, double enoise ) +{ + bp.addNoiseEnergy(enoise); +} + +// --------------------------------------------------------------------------- +// makeNullBefore +// --------------------------------------------------------------------------- +//! Return a null (zero-amplitude) Breakpoint to preceed the specified +//! Breakpoint, useful for fading in a Partial. +//! +//! \param bp make a null Breakpoint to preceed this one +//! \param fadeTime the time (in seconds) by which the new null Breakpoint +//! should preceed bp +//! \return a new null Breakpoint, having zero amplitude, frequency equal +//! to that of bp, and phase computed back from that of bp +// +Breakpoint makeNullBefore( const Breakpoint & bp, double fadeTime ); // see BreakpointUtils.C + + +// --------------------------------------------------------------------------- +// addNoiseEnergy +// --------------------------------------------------------------------------- +//! Return a null (zero-amplitude) Breakpoint to succeed the specified +//! Breakpoint, useful for fading out a Partial. +//! +//! \param bp make a null Breakpoint to succeed this one +//! \param fadeTime the time (in seconds) by which the new null Breakpoint +//! should succeed bp +//! \return a new null Breakpoint, having zero amplitude, frequency equal +//! to that of bp, and phase computed forward from that of bp +// +Breakpoint makeNullAfter( const Breakpoint & bp, double fadeTime ); // see BreakpointUtils.C + +// -- predicates -- + +// --------------------------------------------------------------------------- +// isFrequencyBetween +// +//! Predicate functor returning true if its Breakpoint argument +//! has frequency between specified bounds, and false otherwise. +// +class isFrequencyBetween : + public std::unary_function< const Breakpoint, bool > +{ +public: + //! Return true if its Breakpoint argument has frequency + //! between specified bounds, and false otherwise. + bool operator()( const Breakpoint & b ) const + { + return (b.frequency() > _fmin) && + (b.frequency() < _fmax); + } + +// constructor: + + //! Construct a predicate functor, specifying two frequency bounds. + isFrequencyBetween( double x, double y ) : + _fmin( x ), _fmax( y ) + { + if (x>y) std::swap(x,y); + } + +// bounds: +private: + double _fmin, _fmax; +}; + +//! Old name for isFrequencyBetween. +//! \deprecated use isFrequencyBetween instead. +typedef isFrequencyBetween frequency_between; + +// --------------------------------------------------------------------------- +// isNonNull +// +//! Predicate functor returning true if a Breakpoint has non-zero +//! amplitude, false otherwise. +// +static bool isNonNull( const Breakpoint & bp ) +{ + return bp.amplitude() != 0.; +} + +// --------------------------------------------------------------------------- +// isNull +// +//! Predicate functor returning true if a Breakpoint has zero +//! amplitude, false otherwise. +// +static bool isNull( const Breakpoint & bp ) +{ + return ! isNonNull( bp ); +} + +// -- comparitors -- + +// --------------------------------------------------------------------------- +// compareFrequencyLess +// +//! Comparitor (binary) functor returning true if its first Breakpoint +//! argument has frequency less than that of its second Breakpoint argument, +//! and false otherwise. +// +class compareFrequencyLess : + public std::binary_function< const Breakpoint, const Breakpoint, bool > +{ +public: + //! Return true if its first Breakpoint argument has frequency less + //! than that of its second Breakpoint argument, and false otherwise. + bool operator()( const Breakpoint & lhs, const Breakpoint & rhs ) const + { return lhs.frequency() < rhs.frequency(); } +}; + +//! Old name for compareFrequencyLess. +//! \deprecated use compareFrequencyLess instead. +typedef compareFrequencyLess less_frequency; + +// --------------------------------------------------------------------------- +// compareAmplitudeGreater +// +//! Comparitor (binary) functor returning true if its first Breakpoint +//! argument has amplitude greater than that of its second Breakpoint argument, +//! and false otherwise. +// +class compareAmplitudeGreater : + public std::binary_function< const Breakpoint, const Breakpoint, bool > +{ +public: + //! Return true if its first Breakpoint argument has amplitude greater + //! than that of its second Breakpoint argument, and false otherwise. + bool operator()( const Breakpoint & lhs, const Breakpoint & rhs ) const + { return lhs.amplitude() > rhs.amplitude(); } +}; + +//! Old name for compareAmplitudeGreater. +//! \deprecated use compareAmplitudeGreater instead. +typedef compareAmplitudeGreater greater_amplitude; + +// --------------------------------------------------------------------------- +// compareAmplitudeLess +// +//! Comparitor (binary) functor returning true if its first Breakpoint +//! argument has amplitude less than that of its second Breakpoint argument, +//! and false otherwise. +// +class compareAmplitudeLess : + public std::binary_function< const Breakpoint, const Breakpoint, bool > +{ +public: + //! Return true if its first Breakpoint argument has amplitude greater + //! than that of its second Breakpoint argument, and false otherwise. + bool operator()( const Breakpoint & lhs, const Breakpoint & rhs ) const + { return lhs.amplitude() < rhs.amplitude(); } +}; + +} // end of namespace BreakpointUtils + +} // end of namespace Loris + +#endif /* ndef INCLUDE_BREAKPOINTUTILS_H */ diff --git a/src/loris/Channelizer.C b/src/loris/Channelizer.C new file mode 100644 index 0000000..5ab6484 --- /dev/null +++ b/src/loris/Channelizer.C @@ -0,0 +1,590 @@ +/* + * 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 + * + * + * Channelizer.C + * + * Implementation of class Channelizer. + * + * Kelly Fitz, 21 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Channelizer.h" +#include "Envelope.h" +#include "LinearEnvelope.h" +#include "Partial.h" +#include "PartialList.h" +#include "Notifier.h" + +#include <cmath> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// Channelizer constructor from reference envelope +// --------------------------------------------------------------------------- +//! Construct a new Channelizer using the specified reference +//! Envelope to represent the a numbered channel. If the sound +//! being channelized is known to have detuned harmonics, a +//! stretching factor can be specified (defaults to 0 for no +//! stretching). The stretching factor can be computed using +//! the static member computeStretchFactor. +//! +//! \param refChanFreq is an Envelope representing the center frequency +//! of a channel. +//! \param refChanLabel is the corresponding channel number (i.e. 1 +//! if refChanFreq is the lowest-frequency channel, and all +//! other channels are harmonics of refChanFreq, or 2 if +//! refChanFreq tracks the second harmonic, etc.). +//! \param stretchFactor is a stretching factor to account for detuned +//! harmonics, default is 0. +//! +//! \throw InvalidArgument if refChanLabel is not positive. +//! \throw InvalidArgument if stretchFactor is negative. +// +Channelizer::Channelizer( const Envelope & refChanFreq, int refChanLabel, double stretchFactor ) : + _refChannelFreq( refChanFreq.clone() ), + _refChannelLabel( refChanLabel ), + _stretchFactor( stretchFactor ), + _ampWeighting( 0 ) +{ + if ( refChanLabel <= 0 ) + { + Throw( InvalidArgument, "Channelizer reference label must be positive." ); + } + if ( stretchFactor < 0. ) + { + Throw( InvalidArgument, "Channelizer stretch factor must be non-negative." ); + } +} + +// --------------------------------------------------------------------------- +// Channelizer constructor from constant reference frequency +// --------------------------------------------------------------------------- +//! Construct a new Channelizer having a constant reference frequency. +//! The specified frequency is the center frequency of the lowest-frequency +//! channel (for a harmonic sound, the channel containing the fundamental +//! Partial. +//! +//! \param refFreq is the reference frequency (in Hz) corresponding +//! to the first frequency channel. +//! \param stretchFactor is a stretching factor to account for detuned +//! harmonics, default is 0. +//! +//! \throw InvalidArgument is the reference frequency is not positive +Channelizer::Channelizer( double refFreq, double stretchFactor ) : + _refChannelFreq( new LinearEnvelope( refFreq ) ), + _refChannelLabel( 1 ), + _stretchFactor( stretchFactor ), + _ampWeighting( 0 ) +{ + if ( refFreq <= 0 ) + { + Throw( InvalidArgument, "Channelizer reference frequency must be positive." ); + } + if ( stretchFactor < 0. ) + { + Throw( InvalidArgument, "Channelizer stretch factor must be non-negative." ); + } +} + + +// --------------------------------------------------------------------------- +// Channelizer copy constructor +// --------------------------------------------------------------------------- +//! Construct a new Channelizer that is an exact copy of another. +//! The copy represents the same set of frequency channels, constructed +//! from the same reference Envelope and channel number. +//! +//! \param other is the Channelizer to copy +// +Channelizer::Channelizer( const Channelizer & other ) : + _refChannelFreq( other._refChannelFreq->clone() ), + _refChannelLabel( other._refChannelLabel ), + _stretchFactor( other._stretchFactor ), + _ampWeighting( other._ampWeighting ) +{ +} + +// --------------------------------------------------------------------------- +// Channelizer assignment +// --------------------------------------------------------------------------- +//! Assignment operator: make this Channelizer an exact copy of another. +//! This Channelizer is made to represent the same set of frequency channels, +//! constructed from the same reference Envelope and channel number as @a rhs. +//! +//! \param rhs is the Channelizer to copy +// +Channelizer & +Channelizer::operator=( const Channelizer & rhs ) +{ + if ( &rhs != this ) + { + _refChannelFreq.reset( rhs._refChannelFreq->clone() ); + _refChannelLabel = rhs._refChannelLabel; + _stretchFactor = rhs._stretchFactor; + _ampWeighting = rhs._ampWeighting; + } + return *this; +} + + +// --------------------------------------------------------------------------- +// Channelizer destructor +// --------------------------------------------------------------------------- +//! Destroy this Channelizer. +// +Channelizer::~Channelizer( void ) +{ +} + +// --------------------------------------------------------------------------- +// amplitudeWeighting +// --------------------------------------------------------------------------- +//! Return the exponent applied to amplitude before weighting +//! the instantaneous estimate of the frequency channel number +//! for a Partial. zero (default) for no weighting, 1 for linear +//! amplitude weighting, 2 for power weighting, etc. +//! Amplitude weighting is a bad idea for many sounds, particularly +//! those with transients, for which it may emphasize the part of +//! the Partial having the least reliable frequency estimate. +// +double Channelizer::amplitudeWeighting( void ) const +{ + return _ampWeighting; +} + +// --------------------------------------------------------------------------- +// setAmplitudeWeighting +// --------------------------------------------------------------------------- +//! Set the exponent applied to amplitude before weighting +//! the instantaneous estimate of the frequency channel number +//! for a Partial. zero (default) for no weighting, 1 for linear +//! amplitude weighting, 2 for power weighting, etc. +//! Amplitude weighting is a bad idea for many sounds, particularly +//! those with transients, for which it may emphasize the part of +//! the Partial having the least reliable frequency estimate. +// +void Channelizer::setAmplitudeWeighting( double x ) +{ + _ampWeighting = x; +} + +// --------------------------------------------------------------------------- +// stretchFactor +// --------------------------------------------------------------------------- +//! Return the stretching factor used to account for detuned +//! harmonics, as in a piano tone. Normally set to 0 for +//! in-tune harmonics. +//! +//! The stretching factor is a small positive number for +//! heavy vibrating strings (as in pianos) for which the +//! mass of the string significantly affects the frequency +//! of the vibrating modes. See Martin Keane, "Understanding +//! the complex nature of the piano tone", 2004, for a discussion +//! and the source of the mode frequency stretching algorithms +//! implemented here. +// +double Channelizer::stretchFactor( void ) const +{ + return _stretchFactor; +} + +// --------------------------------------------------------------------------- +// setStretchFactor +// --------------------------------------------------------------------------- +//! Set the stretching factor used to account for detuned +//! harmonics, as in a piano tone. Normally set to 0 for +//! in-tune harmonics. The stretching factor for massy +//! vibrating strings (like pianos) can be computed from +//! the physical characteristics of the string, or using +//! computeStretchFactor(). +//! +//! The stretching factor is a small positive number for +//! heavy vibrating strings (as in pianos) for which the +//! mass of the string significantly affects the frequency +//! of the vibrating modes. See Martin Keane, "Understanding +//! the complex nature of the piano tone", 2004, for a discussion +//! and the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! \throw InvalidArgument if stretch is negative. +// +void Channelizer::setStretchFactor( double stretch ) +{ + if ( stretch < 0. ) + { + Throw( InvalidArgument, "Channelizer stretch factor must be non-negative." ); + } + _stretchFactor = stretch; +} + +// --------------------------------------------------------------------------- +// setStretchFactor +// --------------------------------------------------------------------------- +//! Set the stretching factor used to account for (consistently) +//! detuned harmonics, as in a piano tone, from a pair of +//! mode (harmonic) frequencies and numbers. +//! +//! The stretching factor is a small positive number for +//! heavy vibrating strings (as in pianos) for which the +//! mass of the string significantly affects the frequency +//! of the vibrating modes. See Martin Keane, "Understanding +//! the complex nature of the piano tone", 2004, for a discussion +//! and the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! The stretching factor is computed using computeStretchFactor, +//! but only a valid stretch factor will ever be assigned. If an +//! invalid (negative) stretching factor is computed for the +//! specified frequencies and mode numbers, the stretch factor +//! will be set to zero. +//! +//! \param fm is the frequency of the Mth stretched harmonic +//! \param m is the harmonic number of the harmonic whose frequnecy is fm +//! \param fn is the frequency of the Nth stretched harmonic +//! \param n is the harmonic number of the harmonic whose frequnecy is fn +void Channelizer::setStretchFactor( double fm, int m, double fn, int n ) +{ + const double B = computeStretchFactor( fm, m, fn, n ); + if ( 0 < B ) + { + _stretchFactor = B; + } + else + { + _stretchFactor = 0; + } +} + +// --------------------------------------------------------------------------- +// computeStretchFactor (STATIC class member) +// --------------------------------------------------------------------------- +//! Static member to compute the stretch factor for a sound having +//! (consistently) detuned harmonics, like piano tones. +//! +//! The stretching factor is a small positive number for +//! heavy vibrating strings (as in pianos) for which the +//! mass of the string significantly affects the frequency +//! of the vibrating modes. See Martin Keane, "Understanding +//! the complex nature of the piano tone", 2004, for a discussion +//! and the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! The value returned by this function MAY NOT be a valid stretch +//! factor. If this function returns a negative stretch factor, +//! then the specified pair of frequencies and mode numbers cannot +//! be used to estimate the effects of string mass on mode frequency +//! (because the negative stretch factor implies a physical +//! impossibility, like negative mass or negative length). +//! +//! \param fm is the frequency of the Mth stretched harmonic +//! \param m is the harmonic number of the harmonic whose frequnecy is fm +//! \param fn is the frequency of the Nth stretched harmonic +//! \param n is the harmonic number of the harmonic whose frequnecy is fn +//! \returns the stretching factor, usually a very small positive +//! floating point number, or 0 for pefectly tuned harmonics +//! (that is, if fn = n*f1). +// +double +Channelizer::computeStretchFactor( double fm, int m, double fn, int n ) +{ + if ( fm <= 0. || fn <= 0. ) + { + Throw( InvalidArgument, "Channelizer stretched harmonic frequencies must be positive." ); + } + if ( m <= 0 || n <= 0 ) + { + Throw( InvalidArgument, "Channelizer stretched harmonic numbers must be positive." ); + } + + // K is a factor that depends on the frequencies + // of the two stretched harmonics, equal to 1.0 for + // perfectly tuned (not stretched) harmonics + const double K = (m*fn) / (n*fm); + + const double num = 1. - (K*K); + const double denom = (K*K*m*m) - (n*n); +/* + OLD and wrong I think + double num = (fn*fn) - (n*n*fref*fref); + double denom = (n*n*n*n)*(fref*fref); +*/ + + return num / denom; +} + +// --------------------------------------------------------------------------- +// computeStretchFactor (STATIC class member) +// --------------------------------------------------------------------------- +//! Static member to compute the stretch factor for a sound having +//! (consistently) detuned harmonics, like piano tones. Legacy version +//! that assumes the first argument corresponds to the first partial. +//! +//! \param f1 is the frequency of the lowest numbered (1) partial. +//! \param fn is the frequency of the Nth stretched harmonic +//! \param n is the harmonic number of the harmonic whose frequnecy is fn +//! \returns the stretching factor, usually a very small positive +//! floating point number, or 0 for pefectly tuned harmonics +//! (that is, for harmonic frequencies fn = n*f1). +// +double +Channelizer::computeStretchFactor( double f1, double fn, double n ) +{ + return computeStretchFactor( f1, 1, fn, int(n + 0.5) ); +} + +// --------------------------------------------------------------------------- +// referenceFrequencyAt +// --------------------------------------------------------------------------- +//! Compute the reference frequency at the specified time. For non-stretched +//! harmonics, this is simply the ratio of the reference envelope evaluated +//! at that time to the reference channel number, and is the center frequecy +//! for the lowest channel. For stretched harmonics, the reference frequency +//! is NOT equal to the center frequency of any of the channels, and is also +//! a function of the stretch factor. +// +double Channelizer::referenceFrequencyAt( double time ) const +{ + const double N = _refChannelLabel; + double fref = _refChannelFreq->valueAt( time ) / N; + + if ( 0 != _stretchFactor ) + { + double divisor = std::sqrt( 1.0 + ( _stretchFactor*N*N) ); + fref = fref / divisor; + } + + return fref; +} + +// --------------------------------------------------------------------------- +// computeFractionalChannelNumber +// --------------------------------------------------------------------------- +//! Compute the (fractional) channel number estimate for a Partial having a +//! given frequency at a specified time. For ordinary harmonics, this +//! is simply the ratio of the specified frequency to the reference +//! frequency at the specified time. For stretched harmonics (as in +//! a piano), the stretching factor is used to compute the frequency +//! of the corresponding modes of a massy string. See Martin Keane, +//! "Understanding the complex nature of the piano tone", 2004, for +//! the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! The fractional channel number is used internally to determine +//! a best estimate for the channel number (label) for a Partial +//! having time-varying frequency. +//! +//! \param time is the time (in seconds) at which to evalute +//! the reference envelope +//! \param frequency is the frequency (in Hz) for wihch the channel +//! number is to be determined +//! \return the fractional channel number corresponding to the specified +//! frequency and time +// +double +Channelizer::computeFractionalChannelNumber( double time, double frequency ) const +{ + double refFreq = referenceFrequencyAt( time ); + + if ( 0 == _stretchFactor ) + { + return frequency / refFreq; + } + + /* + const double frefsqrd = fref*fref; + double num = sqrt( (frefsqrd*frefsqrd) + (4*stretch*frefsqrd*fn*fn) ) - (frefsqrd); + double denom = 2*stretch*frefsqrd; + return sqrt( num / denom ); + */ + + // else: + // avoid squaring big numbers... two sqrts kind of sucks too. + const double rB = 1. / _stretchFactor; // reciprocal of B, the stretch factor + const double fratio = frequency / refFreq; + return std::sqrt( std::sqrt( (.25 * rB * rB) + (fratio * fratio * rB) ) - (.5 * rB) ); +} + +// --------------------------------------------------------------------------- +// computeChannelNumber +// --------------------------------------------------------------------------- +//! Compute the (fractional) channel number estimate for a Partial having a +//! given frequency at a specified time. For ordinary harmonics, this +//! is simply the ratio of the specified frequency to the reference +//! frequency at the specified time. For stretched harmonics (as in +//! a piano), the stretching factor is used to compute the frequency +//! of the corresponding modes of a massy string. See Martin Keane, +//! "Understanding the complex nature of the piano tone", 2004, for +//! the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! \param time is the time (in seconds) at which to evalute +//! the reference envelope +//! \param frequency is the frequency (in Hz) for wihch the channel +//! number is to be determined +//! \return the channel number corresponding to the specified +//! frequency and time +// +int +Channelizer::computeChannelNumber( double time, double frequency ) const +{ + return int( computeFractionalChannelNumber( time, frequency ) + 0.5 ); +} + +// --------------------------------------------------------------------------- +// channelFrequencyAt +// --------------------------------------------------------------------------- +//! Compute the center frequency of one a channel at the specified +//! time. For non-stretched harmonics, this is simply the value +//! of the reference envelope scaled by the ratio of the specified +//! channel number to the reference channel number. For stretched +//! harmonics, the channel center frequency is computed using the +//! stretch factor. See Martin Keane, "Understanding +//! the complex nature of the piano tone", 2004, for a discussion +//! and the source of the mode frequency stretching algorithms +//! implemented here. +//! +//! \param time is the time (in seconds) at which to evalute +//! the reference envelope +//! \param channel is the frequency channel (or harmonic, or vibrational +//! mode) number whose frequency is to be determined +//! \return the center frequency in Hz of the specified frequency channel +//! at the specified time +// +double +Channelizer::channelFrequencyAt( double time, int channel ) const +{ + const double fref = referenceFrequencyAt( time ); + double fn = channel * fref; + + if ( 0 != _stretchFactor ) + { + const double scale = std::sqrt( 1.0 + (_stretchFactor*channel*channel) ); + fn = fn * scale; + } + return fn; +} + +// --------------------------------------------------------------------------- +// channelize (one Partial) +// --------------------------------------------------------------------------- +//! Label a Partial with the number of the frequency channel corresponding to +//! the average frequency over all the Partial's Breakpoints. +//! +//! \param partial is the Partial to label. +// +void +Channelizer::channelize( Partial & partial ) const +{ + using std::pow; + + debugger << "channelizing Partial with " << partial.numBreakpoints() << " Breakpoints" << endl; + + // compute an amplitude-weighted average channel + // label for each Partial: + //double ampsum = 0.; + double weightedlabel = 0.; + Partial::const_iterator bp; + for ( bp = partial.begin(); bp != partial.end(); ++bp ) + { + + double f = bp.breakpoint().frequency(); + double t = bp.time(); + + double weight = 1; + if ( 0 != _ampWeighting ) + { + // This used to be an amplitude-weighted avg, but for many sounds, + // particularly those for which the weighted avg would be very + // different from the simple avg, the amplitude-weighted avg + // emphasized the part of the sound in which the frequency estimates + // are least reliable (e.g. a piano tone). The unweighted + // average should give more intuitive results in most cases. + + // use sinusoidal amplitude: + double a = bp.breakpoint().amplitude() * std::sqrt( 1. - bp.breakpoint().bandwidth() ); + weight = pow( a, _ampWeighting ); + } + + weightedlabel += weight * computeFractionalChannelNumber( t, f ); + } + + int label = 0; + if ( 0 < partial.numBreakpoints() ) // should always be the case + { + label = (int)((weightedlabel / partial.numBreakpoints()) + 0.5); + } + Assert( label >= 0 ); + + // assign label, and remember it, but + // only if it is a valid (positive) + // distillation label: + partial.setLabel( label ); + +} + +// -- simplified interface -- + + +// --------------------------------------------------------------------------- +// channelize (static simplified interface) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a PartialList (simplified interface). +//! +//! Construct a Channelizer using the specified Envelope +//! and reference label, and use it to channelize a +//! sequence of Partials. +//! +//! \param partials is the sequence of Partials to +//! channelize. +//! \param refChanFreq is an Envelope representing the center frequency +//! of a channel. +//! \param refChanLabel is the corresponding channel number (i.e. 1 +//! if refChanFreq is the lowest-frequency channel, and all +//! other channels are harmonics of refChanFreq, or 2 if +//! refChanFreq tracks the second harmonic, etc.). +//! \throw InvalidArgument if refChanLabel is not positive. +// +void +Channelizer::channelize( PartialList & partials, + const Envelope & refChanFreq, int refChanLabel ) +{ + Channelizer instance( refChanFreq, refChanLabel ); + + for ( PartialList::iterator it = partials.begin(); it != partials.end(); ++it ) + { + instance.channelize( *it ); + } +} + + +} // end of namespace Loris diff --git a/src/loris/Channelizer.h b/src/loris/Channelizer.h new file mode 100644 index 0000000..d57932b --- /dev/null +++ b/src/loris/Channelizer.h @@ -0,0 +1,526 @@ +#ifndef INCLUDE_CHANNELIZER_H +#define INCLUDE_CHANNELIZER_H +/* + * 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 + * + * + * Channelizer.h + * + * Definition of class Loris::Channelizer. + * + * Kelly Fitz, 21 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "PartialList.h" + +#include <memory> + +// begin namespace +namespace Loris { + +class Envelope; +class Partial; + +// --------------------------------------------------------------------------- +// Channelizer +// +//! Class Channelizer represents an algorithm for automatic labeling of +//! a sequence of Partials. Partials must be labeled in +//! preparation for morphing (see Morpher) to establish correspondences +//! between Partials in the morph source and target sounds. +//! +//! Channelized partials are labeled according to their adherence to a +//! harmonic frequency structure with a time-varying fundamental +//! frequency. The frequency spectrum is partitioned into +//! non-overlapping channels having time-varying center frequencies that +//! are harmonic (integer) multiples of a specified reference frequency +//! envelope, and each channel is identified by a unique label equal to +//! its harmonic number. Each Partial is assigned the label +//! corresponding to the channel containing the greatest portion of its +//! (the Partial's) energy. +//! +//! A reference frequency Envelope for channelization and the channel +//! number to which it corresponds (1 for an Envelope that tracks the +//! Partial at the fundamental frequency) must be specified. The +//! reference Envelope can be constructed explcitly, point by point +//! (using, for example, the BreakpointEnvelope class), or constructed +//! automatically using the FrequencyReference class. +//! +//! The Channelizer can be configured with a stretch factor, to accomodate +//! detuned harmonics, as in the case of piano tones. The static member +//! computeStretchFactor can compute the apppropriate stretch factor, given +//! a pair of partials. This computation is based on formulae given in +//! "Understanding the complex nature of the piano tone" by Martin Keane +//! at the Acoustics Research Centre at the University of Aukland (Feb 2004). +//! The stretching factor must be non-negative (and is zero for perfectly +//! tunes harmonics). Even in the case of stretched harmonics, the +//! reference frequency envelope is assumed to track the frequency of +//! one of the partials, and the center frequency of the corresponding +//! channel, even though it may represent a stretched harmonic. +//! +//! Channelizer is a leaf class, do not subclass. +// +class Channelizer +{ +// -- implementaion -- + std::auto_ptr< Envelope > _refChannelFreq; //! the reference frequency envelope + + int _refChannelLabel; //! the channel number corresponding to the + //! reference frequency (1 for the fundamental) + + double _stretchFactor; //! stretching factor to account for + //! detuned harmonics, as in the case of the piano; + //! can be computed using the static member + //! computeStretchFactor. Should be 0 for most + //! (strongly harmonic) sounds. + + double _ampWeighting; //! exponent for amplitude weighting in channel + //! computation, 0 for no weighting, 1 for linear + //! amplitude weighting, 2 for power weighting, etc. + //! default is 0, amplitude weighting is a bad idea + //! for many sounds + +// -- public interface -- +public: +// -- construction -- + + //! Construct a new Channelizer using the specified reference + //! Envelope to represent the a numbered channel. If the sound + //! being channelized is known to have detuned harmonics, a + //! stretching factor can be specified (defaults to 0 for no + //! stretching). The stretching factor can be computed using + //! the static member computeStretchFactor. + //! + //! \param refChanFreq is an Envelope representing the center frequency + //! of a channel. + //! \param refChanLabel is the corresponding channel number (i.e. 1 + //! if refChanFreq is the lowest-frequency channel, and all + //! other channels are harmonics of refChanFreq, or 2 if + //! refChanFreq tracks the second harmonic, etc.). + //! \param stretchFactor is a stretching factor to account for detuned + //! harmonics, default is 0. + //! + //! \throw InvalidArgument if refChanLabel is not positive. + //! \throw InvalidArgument if stretchFactor is negative. + Channelizer( const Envelope & refChanFreq, int refChanLabel, double stretchFactor = 0 ); + + //! Construct a new Channelizer having a constant reference frequency. + //! The specified frequency is the center frequency of the lowest-frequency + //! channel (for a harmonic sound, the channel containing the fundamental + //! Partial. + //! + //! \param refFreq is the reference frequency (in Hz) corresponding + //! to the first frequency channel. + //! \param stretchFactor is a stretching factor to account for detuned + //! harmonics, default is 0. + //! + //! \throw InvalidArgument if refChanLabel is not positive. + //! \throw InvalidArgument if stretchFactor is negative. + Channelizer( double refFreq, double stretchFactor = 0 ); + + //! Construct a new Channelizer that is an exact copy of another. + //! The copy represents the same set of frequency channels, constructed + //! from the same reference Envelope and channel number. + //! + //! \param other is the Channelizer to copy + Channelizer( const Channelizer & other ); + + //! Assignment operator: make this Channelizer an exact copy of another. + //! This Channelizer is made to represent the same set of frequency channels, + //! constructed from the same reference Envelope and channel number as rhs. + //! + //! \param rhs is the Channelizer to copy + Channelizer & operator=( const Channelizer & rhs ); + + //! Destroy this Channelizer. + ~Channelizer( void ); + +// -- channelizing -- + + //! Label a Partial with the number of the frequency channel containing + //! the greatest portion of its (the Partial's) energy. + //! + //! \param partial is the Partial to label. + void channelize( Partial & partial ) const; + + //! Assign each Partial in the specified half-open (STL-style) range + //! the label corresponding to the frequency channel containing the + //! greatest portion of its (the Partial's) energy. + //! + //! \param begin is the beginning of the range of Partials to channelize + //! \param end is (one-past) the end of the range of Partials to channelize + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void channelize( Iter begin, Iter end ) const; +#else + void channelize( PartialList::iterator begin, PartialList::iterator end ) const; +#endif + + //! Function call operator: same as channelize(). +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void operator() ( Iter begin, Iter end ) const +#else + inline + void operator() ( PartialList::iterator begin, PartialList::iterator end ) const +#endif + { channelize( begin, end ); } + + //! Compute the center frequency of one a channel at the specified + //! time. For non-stretched harmonics, this is simply the value + //! of the reference envelope scaled by the ratio of the specified + //! channel number to the reference channel number. For stretched + //! harmonics, the channel center frequency is computed using the + //! stretch factor. See Martin Keane, "Understanding + //! the complex nature of the piano tone", 2004, for a discussion + //! and the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! \param time is the time (in seconds) at which to evalute + //! the reference envelope + //! \param channel is the frequency channel (or harmonic, or vibrational + //! mode) number whose frequency is to be determined + //! \return the center frequency in Hz of the specified frequency channel + //! at the specified time + double channelFrequencyAt( double time, int channel ) const; + + //! Compute the (fractional) channel number estimate for a Partial having a + //! given frequency at a specified time. For ordinary harmonics, this + //! is simply the ratio of the specified frequency to the reference + //! frequency at the specified time. For stretched harmonics (as in + //! a piano), the stretching factor is used to compute the frequency + //! of the corresponding modes of a massy string. See Martin Keane, + //! "Understanding the complex nature of the piano tone", 2004, for + //! the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! \param time is the time (in seconds) at which to evalute + //! the reference envelope + //! \param frequency is the frequency (in Hz) for wihch the channel + //! number is to be determined + //! \return the channel number corresponding to the specified + //! frequency and time + int computeChannelNumber( double time, double frequency ) const; + + //! Compute the (fractional) channel number estimate for a Partial having a + //! given frequency at a specified time. For ordinary harmonics, this + //! is simply the ratio of the specified frequency to the reference + //! frequency at the specified time. For stretched harmonics (as in + //! a piano), the stretching factor is used to compute the frequency + //! of the corresponding modes of a massy string. See Martin Keane, + //! "Understanding the complex nature of the piano tone", 2004, for + //! the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! The fractional channel number is used internally to determine + //! a best estimate for the channel number (label) for a Partial + //! having time-varying frequency. + //! + //! \param time is the time (in seconds) at which to evalute + //! the reference envelope + //! \param frequency is the frequency (in Hz) for wihch the channel + //! number is to be determined + //! \return the fractional channel number corresponding to the specified + //! frequency and time + double computeFractionalChannelNumber( double time, double frequency ) const; + + + //! Compute the reference frequency at the specified time. For non-stretched + //! harmonics, this is simply the ratio of the reference envelope evaluated + //! at that time to the reference channel number, and is the center frequecy + //! for the lowest channel. For stretched harmonics, the reference frequency + //! is NOT equal to the center frequency of any of the channels, and is also + //! a function of the stretch factor. + //! + //! \param time is the time (in seconds) at which to evalute + //! the reference envelope + double referenceFrequencyAt( double time ) const; + +// -- access/mutation -- + + //! Return the exponent applied to amplitude before weighting + //! the instantaneous estimate of the frequency channel number + //! for a Partial. zero (default) for no weighting, 1 for linear + //! amplitude weighting, 2 for power weighting, etc. + //! Amplitude weighting is a bad idea for many sounds, particularly + //! those with transients, for which it may emphasize the part of + //! the Partial having the least reliable frequency estimate. + double amplitudeWeighting( void ) const; + + //! Set the exponent applied to amplitude before weighting + //! the instantaneous estimate of the frequency channel number + //! for a Partial. zero (default) for no weighting, 1 for linear + //! amplitude weighting, 2 for power weighting, etc. + //! Amplitude weighting is a bad idea for many sounds, particularly + //! those with transients, for which it may emphasize the part of + //! the Partial having the least reliable frequency estimate. + void setAmplitudeWeighting( double expon ); + + //! Return the stretching factor used to account for detuned + //! harmonics, as in a piano tone. Normally set to 0 for + //! in-tune harmonics. + //! + //! The stretching factor is a small positive number for + //! heavy vibrating strings (as in pianos) for which the + //! mass of the string significantly affects the frequency + //! of the vibrating modes. See Martin Keane, "Understanding + //! the complex nature of the piano tone", 2004, for a discussion + //! and the source of the mode frequency stretching algorithms + //! implemented here. + double stretchFactor( void ) const; + + //! Set the stretching factor used to account for detuned + //! harmonics, as in a piano tone. Normally set to 0 for + //! in-tune harmonics. The stretching factor for massy + //! vibrating strings (like pianos) can be computed from + //! the physical characteristics of the string, or using + //! computeStretchFactor(). + //! + //! The stretching factor is a small positive number for + //! heavy vibrating strings (as in pianos) for which the + //! mass of the string significantly affects the frequency + //! of the vibrating modes. See Martin Keane, "Understanding + //! the complex nature of the piano tone", 2004, for a discussion + //! and the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! \throw InvalidArgument if stretch is negative. + void setStretchFactor( double stretch ); + + +// -- static members -- + + + //! Static member to compute the stretch factor for a sound having + //! (consistently) detuned harmonics, like piano tones. + //! + //! The stretching factor is a small positive number for + //! heavy vibrating strings (as in pianos) for which the + //! mass of the string significantly affects the frequency + //! of the vibrating modes. See Martin Keane, "Understanding + //! the complex nature of the piano tone", 2004, for a discussion + //! and the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! The value returned by this function MAY NOT be a valid stretch + //! factor. If this function returns a negative stretch factor, + //! then the specified pair of frequencies and mode numbers cannot + //! be used to estimate the effects of string mass on mode frequency + //! (because the negative stretch factor implies a physical + //! impossibility, like negative mass or negative length). + //! + //! \param fm is the frequency of the Mth stretched harmonic + //! \param m is the harmonic number of the harmonic whose frequnecy is fm + //! \param fn is the frequency of the Nth stretched harmonic + //! \param n is the harmonic number of the harmonic whose frequnecy is fn + //! \returns the stretching factor, usually a very small positive + //! floating point number, or 0 for pefectly tuned harmonics + //! (that is, if fn = n*f1). + static double computeStretchFactor( double fm, int m, double fn, int n ); + + +// -- simplified interface -- + + //! Static member that constructs an instance and applies + //! it to a PartialList (simplified interface). + //! + //! Construct a Channelizer using the specified Envelope + //! and reference label, and use it to channelize a + //! sequence of Partials. + //! + //! \param partials is the sequence of Partials to + //! channelize. + //! \param refChanFreq is an Envelope representing the center frequency + //! of a channel. + //! \param refChanLabel is the corresponding channel number (i.e. 1 + //! if refChanFreq is the lowest-frequency channel, and all + //! other channels are harmonics of refChanFreq, or 2 if + //! refChanFreq tracks the second harmonic, etc.). + //! \throw InvalidArgument if refChanLabel is not positive. + static + void channelize( PartialList & partials, + const Envelope & refChanFreq, int refChanLabel ); + + + +// -- DEPRECATED members -- + + //! DEPRECATED + //! + //! Set the stretching factor used to account for (consistently) + //! detuned harmonics, as in a piano tone, from a pair of + //! mode (harmonic) frequencies and numbers. + //! + //! The stretching factor is a small positive number for + //! heavy vibrating strings (as in pianos) for which the + //! mass of the string significantly affects the frequency + //! of the vibrating modes. See Martin Keane, "Understanding + //! the complex nature of the piano tone", 2004, for a discussion + //! and the source of the mode frequency stretching algorithms + //! implemented here. + //! + //! The stretching factor is computed using computeStretchFactor, + //! but only a valid stretch factor will ever be assigned. If an + //! invalid (negative) stretching factor is computed for the + //! specified frequencies and mode numbers, the stretch factor + //! will be set to zero. + //! + //! \param fm is the frequency of the Mth stretched harmonic + //! \param m is the harmonic number of the harmonic whose frequnecy is fm + //! \param fn is the frequency of the Nth stretched harmonic + //! \param n is the harmonic number of the harmonic whose frequnecy is fn + void setStretchFactor( double fm, int m, double fn, int n ); + + + //! DEPRECATED + //! + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Channelizer using the specified Envelope + //! and reference label, and use it to channelize a + //! sequence of Partials. + //! + //! \param begin is the beginning of a sequence of Partials to + //! channelize. + //! \param end is the end of a sequence of Partials to + //! channelize. + //! \param refChanFreq is an Envelope representing the center frequency + //! of a channel. + //! \param refChanLabel is the corresponding channel number (i.e. 1 + //! if refChanFreq is the lowest-frequency channel, and all + //! other channels are harmonics of refChanFreq, or 2 if + //! refChanFreq tracks the second harmonic, etc.). + //! \throw InvalidArgument if refChanLabel is not positive. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + static + void channelize( Iter begin, Iter end, + const Envelope & refChanFreq, int refChanLabel ); +#else + static inline + void channelize( PartialList::iterator begin, PartialList::iterator end, + const Envelope & refChanFreq, int refChanLabel ); +#endif + + + //! DEPRECATED + //! + //! Static member to compute the stretch factor for a sound having + //! (consistently) detuned harmonics, like piano tones. Legacy version + //! that assumes the first argument corresponds to the first partial. + //! + //! \param f1 is the frequency of the lowest numbered (1) partial. + //! \param fn is the frequency of the Nth stretched harmonic + //! \param n is the harmonic number of the harmonic whose frequnecy is fn + //! \returns the stretching factor, usually a very small positive + //! floating point number, or 0 for pefectly tuned harmonics + //! (that is, for harmonic frequencies fn = n*f1). + static double computeStretchFactor( double f1, double fn, double n ); + +}; // end of class Channelizer + +// --------------------------------------------------------------------------- +// channelize (sequence of Partials) +// --------------------------------------------------------------------------- +//! Assign each Partial in the specified half-open (STL-style) range +//! the label corresponding to the frequency channel containing the +//! greatest portion of its (the Partial's) energy. +//! +//! \param begin is the beginning of the range of Partials to channelize +//! \param end is (one-past) the end of the range of Partials o channelize +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Channelizer::channelize( Iter begin, Iter end ) const +#else +inline +void Channelizer::channelize( PartialList::iterator begin, PartialList::iterator end ) const +#endif +{ + while ( begin != end ) + { + channelize( *begin++ ); + } +} + +// --------------------------------------------------------------------------- +// channelize (static) +// --------------------------------------------------------------------------- +//! DEPRECATED +//! +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! Construct a Channelizer using the specified Envelope +//! and reference label, and use it to channelize a +//! sequence of Partials. +//! +//! \param begin is the beginning of a sequence of Partials to +//! channelize. +//! \param end is the end of a sequence of Partials to +//! channelize. +//! \param refChanFreq is an Envelope representing the center frequency +//! of a channel. +//! \param refChanLabel is the corresponding channel number (i.e. 1 +//! if refChanFreq is the lowest-frequency channel, and all +//! other channels are harmonics of refChanFreq, or 2 if +//! refChanFreq tracks the second harmonic, etc.). +//! \throw InvalidArgument if refChanLabel is not positive. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Channelizer::channelize( Iter begin, Iter end, + const Envelope & refChanFreq, int refChanLabel ) +#else +inline +void Channelizer::channelize( PartialList::iterator begin, PartialList::iterator end, + const Envelope & refChanFreq, int refChanLabel ) +#endif +{ + Channelizer instance( refChanFreq, refChanLabel ); + while ( begin != end ) + { + instance.channelize( *begin++ ); + } +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_CHANNELIZER_H */ diff --git a/src/loris/Collator.C b/src/loris/Collator.C new file mode 100644 index 0000000..1cbddbf --- /dev/null +++ b/src/loris/Collator.C @@ -0,0 +1,190 @@ +/* + * 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 + * + * + * Collator.h + * + * Definition of class Collator. + * + * Kelly Fitz, 29 April 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Collator.h" +#include "Breakpoint.h" +#include "BreakpointUtils.h" +#include "LorisExceptions.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" +#include "Notifier.h" + +#include <algorithm> +#include <functional> +#include <utility> + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// Collator constructor +// --------------------------------------------------------------------------- +//! Construct a new Collator using the specified fade and gap times +//! between Partials. When two Partials are joined, the collated Partial +//! fades out at the end of the earlier Partial and back in again +//! at the onset of the later one. The fade time is the time over +//! which these fades occur. By default, use a 5 ms fade time. +//! The gap time is the additional time over which a Partial faded +//! out must remain at zero amplitude before it can fade back in. +//! By default, use a gap time of one millisecond, to +//! prevent a pair of arbitrarily close null Breakpoints being +//! inserted. (Defaults are copied from the Distiller.) +//! +//! \param partialFadeTime is the time (in seconds) over +//! which Partials joined by distillation fade to +//! and from zero amplitude. Default is 0.005 (one +//! millisecond). +//! \param partialSilentTime is the minimum duration (in seconds) +//! of the silent (zero-amplitude) gap between two +//! Partials joined by distillation. (Default is +//! 0.001 (one millisecond). +Collator::Collator( double partialFadeTime, double partialSilentTime ) : + _fadeTime( partialFadeTime ), + _gapTime( partialSilentTime ) +{ + if ( _fadeTime <= 0.0 ) + { + Throw( InvalidArgument, "Collator fade time must be positive." ); + } + if ( _gapTime <= 0.0 ) + { + Throw( InvalidArgument, "Collator gap time must be positive." ); + } +} + +// -- helpers -- + +// --------------------------------------------------------------------------- +// helper predicates +// --------------------------------------------------------------------------- +static bool ends_earlier( const Partial & lhs, const Partial & rhs ) +{ + return lhs.endTime() < rhs.endTime(); +} + +struct ends_before : public std::unary_function< const Partial, bool > +{ + double t; + ends_before( double time ) : t( time ) {} + + bool operator() ( const Partial & p ) const + { return p.endTime() < t; } +}; + +// --------------------------------------------------------------------------- +// collateAux +// --------------------------------------------------------------------------- +//! Collate unlabeled (zero labeled) Partials into the smallest +//! possible number of Partials that does not combine any temporally +//! overlapping Partials. The unlabeled Partials are +//! collated in-place. +// +void Collator::collateAux( PartialList & unlabeled ) +{ + debugger << "Collator found " << unlabeled.size() + << " unlabeled Partials, collating..." << endl; + + // sort Partials by end time: + // thanks to Ulrike Axen for this optimal algorithm! + unlabeled.sort( ends_earlier ); + + // invariant: + // Partials in the range [partials.begin(), endcollated) + // are the collated Partials. + PartialList::iterator endcollated = unlabeled.begin(); + while ( endcollated != unlabeled.end() ) + { + // find a collated Partial that ends + // before this one begins. + // There must be a gap of at least + // twice the _fadeTime, because this algorithm + // does not remove any null Breakpoints, and + // because Partials joined in this way might + // be far apart in frequency. + const double clearance = (2.*_fadeTime) + _gapTime; + PartialList::iterator it = + std::find_if( unlabeled.begin(), endcollated, + ends_before( endcollated->startTime() - clearance) ); + + // if no such Partial exists, then this Partial + // becomes one of the collated ones, otherwise, + // insert two null Breakpoints, and then all + // the Breakpoints in this Partial: + if ( it != endcollated ) + { + Partial & addme = *endcollated; + Partial & collated = *it; + Assert( &addme != &collated ); + + // insert a null at the (current) end + // of collated: + double nulltime1 = collated.endTime() + _fadeTime; + Breakpoint null1( collated.frequencyAt(nulltime1), 0., + collated.bandwidthAt(nulltime1), collated.phaseAt(nulltime1) ); + collated.insert( nulltime1, null1 ); + + // insert a null at the beginning of + // of the current Partial: + double nulltime2 = addme.startTime() - _fadeTime; + Assert( nulltime2 >= nulltime1 ); + Breakpoint null2( addme.frequencyAt(nulltime2), 0., + addme.bandwidthAt(nulltime2), addme.phaseAt(nulltime2) ); + collated.insert( nulltime2, null2 ); + + // insert all the Breakpoints in addme + // into collated: + Partial::iterator addme_it; + for ( addme_it = addme.begin(); addme_it != addme.end(); ++addme_it ) + { + collated.insert( addme_it.time(), addme_it.breakpoint() ); + } + + // remove this Partial from the list: + endcollated = unlabeled.erase( endcollated ); + } + else + { + ++endcollated; + } + } + + debugger << "...now have " << unlabeled.size() << endl; +} + +} // end of namespace Loris diff --git a/src/loris/Collator.h b/src/loris/Collator.h new file mode 100644 index 0000000..e1d441c --- /dev/null +++ b/src/loris/Collator.h @@ -0,0 +1,366 @@ +#ifndef INCLUDE_COLLATOR_H +#define INCLUDE_COLLATOR_H +/* + * 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 + * + * + * Collator.h + * + * Definition of class Collator. + * + * Kelly Fitz, 29 April 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Distiller.h" // for default fade time and silent time +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include <algorithm> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Collator +// +//! Class Collator represents an algorithm for reducing a collection +//! of Partials into the smallest collection of "equivalent" Partials +//! by joining non-overlapping Partials end to end. +//! +//! Partials that are not labeled, that is, Partials having label 0, +//! are "collated " into groups of non-overlapping (in time) +//! Partials, and fused into a single Partial per group. +//! "Collating" is a bit like "distilling" but non-overlapping +//! Partials are grouped without regard to frequency proximity. This +//! algorithm produces the smallest-possible number of collated Partials. +//! Thanks to Ulrike Axen for providing this optimal algorithm. +//! +//! Collating modifies the Partial container (a PartialList). Only +//! unlabeled (labeled 0) Partials are affected by the collating +//! operation. Collated Partials are moved to the end of the +//! collection of Partials. +// +class Collator +{ +// -- instance variables -- + + double _fadeTime, _gapTime; + +// -- public interface -- +public: + +// -- global defaults and constants -- + + enum + { + + //! Default time in milliseconds over which Partials joined by + //! distillation fade to and from zero amplitude. Divide by + //! 1000 to use as a member function parameter. This parameter + //! should be the same in Distiller, Sieve, and Collator. + DefaultFadeTimeMs = Distiller::DefaultFadeTimeMs, + + //! Default minimum duration in milliseconds of the silent + //! (zero-amplitude) gap between two Partials joined by + //! distillation. Divide by 1000 to use as a member function + //! parameter. This parameter should be the same in Distiller, + //! Sieve, and Collator. + DefaultSilentTimeMs = Distiller::DefaultSilentTimeMs + }; + +// -- construction -- + + //! Construct a new Collator using the specified fade and gap times + //! between Partials. When two Partials are joined, the collated Partial + //! fades out at the end of the earlier Partial and back in again + //! at the onset of the later one. The fade time is the time over + //! which these fades occur. By default, use a 5 ms fade time. + //! The gap time is the additional time over which a Partial faded + //! out must remain at zero amplitude before it can fade back in. + //! By default, use a gap time of one millisecond, to + //! prevent a pair of arbitrarily close null Breakpoints being + //! inserted. (Defaults are copied from the Distiller.) + //! + //! \param partialFadeTime is the time (in seconds) over + //! which Partials joined by distillation fade to + //! and from zero amplitude. Default is 0.005 (one + //! millisecond). + //! \param partialSilentTime is the minimum duration (in seconds) + //! of the silent (zero-amplitude) gap between two + //! Partials joined by distillation. (Default is + //! 0.001 (one millisecond). + explicit + Collator( double partialFadeTime = Collator::DefaultFadeTimeMs/1000.0, + double partialSilentTime = Collator::DefaultSilentTimeMs/1000.0 ); + + // Use compiler-generated copy, assign, and destroy. + +// -- collating -- + + //! Collate unlabeled (zero-labeled) Partials into the smallest-possible + //! number of Partials that does not combine any overlapping Partials. + //! Collated Partials assigned labels higher than any label in the original + //! list, and appear at the end of the sequence, after all previously-labeled + //! Partials. + //! + //! + //! Return an iterator refering to the position of the first collated Partial, + //! or the end of the collated collection if there are no collated Partials. + //! Since collating is in-place, the Partials collection may be smaller + //! (fewer Partials) after collating, and any iterators on the collection + //! may be invalidated. + //! + //! \param partials is the collection of Partials to collate in-place + //! \return the position of the end of the range of labeled Partials, + //! which is either the end of the collection, or the position + //! of the first collated Partial, composed of unlabeled Partials + //! in the original collection. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then partials + //! must be a PartialList, otherwise it can be any container type + //! storing Partials that supports at least bidirectional iterators. + //! + //! \sa Collator::collate( Container & partials ) +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + typename Container::iterator collate( Container & partials ); +#else + inline + PartialList::iterator collate( PartialList & partials ); +#endif + + //! Function call operator: same as collate( PartialList & partials ). +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + typename Container::iterator operator() ( Container & partials ); +#else + PartialList::iterator operator() ( PartialList & partials ); +#endif + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. Collated Partials are + //! labeled beginning with the label one more than the + //! largest label in the orignal Partials. + //! + //! \param partials is the collection of Partials to collate in-place + //! \param partialFadeTime is the time (in seconds) over + //! which Partials joined by collating fade to + //! and from zero amplitude. + //! \param partialSilentTime is the minimum duration (in seconds) + //! of the silent (zero-amplitude) gap between two + //! Partials joined by collating. + //! \return the position of the first collated Partial + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then partials + //! must be a PartialList, otherwise it can be any container type + //! storing Partials that supports at least bidirectional iterators. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + static typename Container::iterator + collate( Container & partials, double partialFadeTime, + double partialSilentTime ); +#else + static inline PartialList::iterator + collate( PartialList & partials, double partialFadeTime, + double partialSilentTime ); +#endif + + +private: + +// -- helpers -- + + //! Collate unlabeled (zero labeled) Partials into the smallest + //! possible number of Partials that does not combine any temporally + //! overlapping Partials. Give each collated Partial a label, starting + //! with startlabel, and incrementing. If startLabel is zero, then + //! give each collated Partial the label zero. The unlabeled Partials are + //! collated in-place. + void collateAux( PartialList & unlabled ); + +}; // end of class Collator + +// --------------------------------------------------------------------------- +// collate +// --------------------------------------------------------------------------- +//! Collate unlabeled (zero-labeled) Partials into the smallest-possible +//! number of Partials that does not combine any overlapping Partials. +//! Collated Partials assigned labels higher than any label in the original +//! list, and appear at the end of the sequence, after all previously-labeled +//! Partials. +//! +//! Return an iterator refering to the position of the first collated Partial, +//! or the end of the collated collection if there are no collated Partials. +//! Since collating is in-place, the Partials collection may be smaller +//! (fewer Partials) after collating, and any iterators on the collection +//! may be invalidated. +//! +//! \param partials is the collection of Partials to collate in-place +//! \return the position of the end of the range of labeled Partials, +//! which is either the end of the collection, or the position +//! of the first collated Partial, composed of unlabeled Partials +//! in the original collection. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then partials +//! must be a PartialList, otherwise it can be any container type +//! storing Partials that supports at least bidirectional iterators. +//! +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator +Collator::collate( Container & partials ) +#else +inline +PartialList::iterator +Collator::collate( PartialList & partials ) +#endif +{ +#if ! defined(NO_TEMPLATE_MEMBERS) + typedef typename Container::iterator Iterator; +#else + typedef PartialList::iterator Iterator; +#endif + + // Partition the Partials into labeled and unlabeled, + // and collate the unlabeled ones and replace the + // unlabeled range. + // (This requires bidirectional iterator support.) + Iterator beginUnlabeled = + std::partition( partials.begin(), partials.end(), + std::not1( PartialUtils::isLabelEqual(0) ) ); + // this used to be a stable partition, which + // is very much slower and seems unnecessary + + // cannot splice if this operation is to be generic + // with respect to container, have to copy: + PartialList collated( beginUnlabeled, partials.end() ); + // collated.splice( collated.end(), beginUnlabeled, partials.end() ); + + // determine the label for the first collated Partial: + Partial::label_type labelCollated = 1; + if ( partials.begin() != beginUnlabeled ) + { + labelCollated = + 1 + std::max_element( partials.begin(), beginUnlabeled, + PartialUtils::compareLabelLess() )->label(); + } + if ( labelCollated < 1 ) + { + labelCollated = 1; + } + + // collate unlabeled (zero-labeled) Partials: + collateAux( collated ); + + // label the collated Partials: + for ( Iterator it = collated.begin(); it != collated.end(); ++it ) + { + it->setLabel( labelCollated++ ); + } + + // copy the collated Partials back into the source container + // after the range of labeled Partials + Iterator endCollated = + std::copy( collated.begin(), collated.end(), beginUnlabeled ); + + // remove extra Partials from the end of the source container + if ( endCollated != partials.end() ) + { + typename Iterator::difference_type numLabeled = + std::distance( partials.begin(), beginUnlabeled ); + + partials.erase( endCollated, partials.end() ); + + // restore beginUnlabeled: + beginUnlabeled = partials.begin(); + std::advance( beginUnlabeled, numLabeled ); + } + return beginUnlabeled; +} + +// --------------------------------------------------------------------------- +// Function call operator +// --------------------------------------------------------------------------- +//! Function call operator: same as collate( PartialList & partials ). +//! +//! \sa Collator::collate( Container & partials ) +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator Collator::operator()( Container & partials ) +#else +inline +PartialList::iterator Collator::operator()( PartialList & partials ) +#endif +{ + return collate( partials ); +} + +// --------------------------------------------------------------------------- +// collate +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. Collated Partials are +//! labeled beginning with the label one more than the +//! largest label in the orignal Partials. +//! +//! \post All Partials in the collection are uniquely-labeled, +//! collated Partials are all at the end of the collection +//! (after all labeled Partials). +//! \param partials is the collection of Partials to collate in-place +//! \param partialFadeTime is the time (in seconds) over +//! which Partials joined by collating fade to +//! and from zero amplitude. +//! \param partialSilentTime is the minimum duration (in seconds) +//! of the silent (zero-amplitude) gap between two +//! Partials joined by collateation. (Default is +//! 0.0001 (one tenth of a millisecond). +//! \return the position of the first collated Partial +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then partials +//! must be a PartialList, otherwise it can be any container type +//! storing Partials that supports at least bidirectional iterators. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator +Collator::collate( Container & partials, double partialFadeTime, + double partialSilentTime ) +#else +inline +PartialList::iterator +Collator::collate( PartialList & partials, double partialFadeTime, + double partialSilentTime ) +#endif +{ + Collator instance( partialFadeTime, partialSilentTime ); + return instance.collate( partials ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_COLLATOR_H */ diff --git a/src/loris/Dilator.C b/src/loris/Dilator.C new file mode 100644 index 0000000..733915f --- /dev/null +++ b/src/loris/Dilator.C @@ -0,0 +1,282 @@ +/* + * 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 + * + * + * Dilator.C + * + * Implementation of class Dilator. + * + * Kelly Fitz, 26 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Dilator.h" +#include "Breakpoint.h" +#include "LorisExceptions.h" +#include "Marker.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" + +#include <algorithm> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new Dilator with +//! no time points. +Dilator::Dilator( void ) +{ +} + +// --------------------------------------------------------------------------- +// insert +// --------------------------------------------------------------------------- +//! Insert a pair of initial and target time points. +//! +//! Specify a pair of initial and target time points to be used +//! by this Dilator, corresponding, for example, to the initial +//! and desired time of a particular temporal feature in an +//! analyzed sound. +//! +//! \param i is an initial, or source, time point +//! \param t is a target time point +//! +//! The time points will be sorted before they are used. +//! If, in the sequences of initial and target time points, there are +//! exactly the same number of initial time points preceding i as +//! target time points preceding t, then time i will be warped to +//! time t in the dilation process. +// +void +Dilator::insert( double i, double t ) +{ + _initial.push_back(i); + _target.push_back(t); + + // sort the time points before dilating: + std::sort( _initial.begin(), _initial.end() ); + std::sort( _target.begin(), _target.end() ); +} + +// --------------------------------------------------------------------------- +// warpTime +// -------------------------------------------------------------------------- +//! Return the dilated time value corresponding to the specified initial time. +//! +//! \param currentTime is a pre-dilated time. +//! \return the dilated time corresponding to the initial time currentTime +// +double +Dilator::warpTime( double currentTime ) const +{ + int idx = std::distance( _initial.begin(), + std::lower_bound( _initial.begin(), _initial.end(), currentTime ) ); + Assert( idx == _initial.size() || currentTime <= _initial[idx] ); + + // compute a new time for the Breakpoint at pIter: + double newtime = 0; + if ( idx == 0 ) + { + // all time points in _initial are later than + // the currentTime; stretch if no zero time + // point has been specified, otherwise, shift: + if ( _initial[idx] != 0. ) + newtime = currentTime * _target[idx] / _initial[idx]; + else + newtime = _target[idx] + (currentTime - _initial[idx]); + } + else if ( idx == _initial.size() ) + { + // all time points in _initial are earlier than + // the currentTime; shift: + // + // note: size is already known to be > 0, so + // idx-1 is safe + newtime = _target[idx-1] + (currentTime - _initial[idx-1]); + } + else + { + // currentTime is between the time points at idx and + // idx-1 in _initial; shift and stretch: + // + // note: size is already known to be > 0, so + // idx-1 is safe + Assert( _initial[idx-1] < _initial[idx] ); // currentTime can't wind up + // between two equal times + + double stretch = (_target[idx] - _target[idx-1]) / (_initial[idx] - _initial[idx-1]); + newtime = _target[idx-1] + ((currentTime - _initial[idx-1]) * stretch); + } + + return newtime; +} + +// --------------------------------------------------------------------------- +// dilate +// --------------------------------------------------------------------------- +//! Replace the Partial envelope with a new envelope having the +//! same Breakpoints at times computed to align temporal features +//! in the sorted sequence of initial time points with their +//! counterparts the sorted sequence of target time points. +//! +//! Depending on the specification of initial and target time +//! points, the dilated Partial may have Breakpoints at times +//! less than 0, even if the original Partial did not. +//! +//! It is possible to have duplicate time points in either sequence. +//! Duplicate initial time points result in very localized stretching. +//! Duplicate target time points result in very localized compression. +//! +//! If all initial time points are greater than 0, then an implicit +//! time point at 0 is assumed in both initial and target sequences, +//! so the onset of a sound can be stretched without explcitly specifying a +//! zero point in each vector. (This seems most intuitive, and only looks +//! like an inconsistency if clients are using negative time points in +//! their Dilator, or Partials having Breakpoints before time 0, both +//! of which are probably unusual circumstances.) +//! +//! \param p is the Partial to dilate. +// +void +Dilator::dilate( Partial & p ) const +{ + debugger << "dilating Partial having " << p.numBreakpoints() + << " Breakpoints" << endl; + + // sanity check: + Assert( _initial.size() == _target.size() ); + + // don't dilate if there's no time points, or no Breakpoints: + if ( 0 == _initial.size() || + 0 == p.numBreakpoints() ) + { + return; + } + + // create the new Partial: + Partial newp; + newp.setLabel( p.label() ); + + // timepoint index: + int idx = 0; + for ( Partial::const_iterator iter = p.begin(); iter != p.end(); ++iter ) + { + // find the first initial time point later + // than the currentTime: + double currentTime = iter.time(); + idx = std::distance( _initial.begin(), + std::lower_bound( _initial.begin(), _initial.end(), currentTime ) ); + Assert( idx == _initial.size() || currentTime <= _initial[idx] ); + + // compute a new time for the Breakpoint at pIter: + double newtime = 0; + if ( idx == 0 ) + { + // all time points in _initial are later than + // the currentTime; stretch if no zero time + // point has been specified, otherwise, shift: + if ( _initial[idx] != 0. ) + newtime = currentTime * _target[idx] / _initial[idx]; + else + newtime = _target[idx] + (currentTime - _initial[idx]); + } + else if ( idx == _initial.size() ) + { + // all time points in _initial are earlier than + // the currentTime; shift: + // + // note: size is already known to be > 0, so + // idx-1 is safe + newtime = _target[idx-1] + (currentTime - _initial[idx-1]); + } + else + { + // currentTime is between the time points at idx and + // idx-1 in _initial; shift and stretch: + // + // note: size is already known to be > 0, so + // idx-1 is safe + Assert( _initial[idx-1] < _initial[idx] ); // currentTime can't wind up + // between two equal times + + double stretch = (_target[idx] - _target[idx-1]) / (_initial[idx] - _initial[idx-1]); + newtime = _target[idx-1] + ((currentTime - _initial[idx-1]) * stretch); + } + + // add a Breakpoint at the computed time: + newp.insert( newtime, iter.breakpoint() ); + } + + // new Breakpoints need to be added to the Partial at times corresponding + // to all target time points that are after the first Breakpoint and + // before the last, otherwise, Partials may be briefly out of tune with + // each other, since our Breakpoints are non-uniformly distributed in time: + for ( idx = 0; idx < _initial.size(); ++ idx ) + { + if ( _initial[idx] <= p.startTime() ) + { + continue; + } + else if ( _initial[idx] >= p.endTime() ) + { + break; + } + else + { + newp.insert( _target[idx], + Breakpoint( p.frequencyAt(_initial[idx]), p.amplitudeAt(_initial[idx]), + p.bandwidthAt(_initial[idx]), p.phaseAt(_initial[idx]) ) ); + } + } + + // store the new Partial: + p = newp; +} + + +// --------------------------------------------------------------------------- +// dilate +// --------------------------------------------------------------------------- +//! Compute a new time for the specified Marker using +//! warpTime(), exactly as Partial Breakpoint times are +//! recomputed. This can be used to dilate the Markers +//! corresponding to a collection of Partials. +//! +//! \param m is the Marker whose time should be recomputed. +// +void +Dilator::dilate( Marker & m ) const +{ + m.setTime( warpTime( m.time() ) ); +} + +} // end of namespace Loris diff --git a/src/loris/Dilator.h b/src/loris/Dilator.h new file mode 100644 index 0000000..3370788 --- /dev/null +++ b/src/loris/Dilator.h @@ -0,0 +1,410 @@ +#ifndef INCLUDE_DILATOR_H +#define INCLUDE_DILATOR_H +/* + * 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 + * + * + * Dilator.h + * + * Definition of class Dilator. + * + * Kelly Fitz, 26 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if defined(NO_TEMPLATE_MEMBERS) +#include "PartialList.h" +#endif + +#include <vector> + +// begin namespace +namespace Loris { + +class Marker; +class Partial; + +// --------------------------------------------------------------------------- +// class Dilator +// +//! Class Dilator represents an algorithm for non-uniformly expanding +//! and contracting the Partial parameter envelopes according to the initial +//! and target (desired) times of temporal features. +//! +//! It is frequently necessary to redistribute temporal events in this way +//! in preparation for a sound morph. For example, when morphing instrument +//! tones, it is common to align the attack, sustain, and release portions +//! of the source sounds by dilating or contracting those temporal regions. +//! +//! This same procedure can be applied to the Markers stored in AiffFile, +//! SdifFile, and SpcFile (see Marker.h). +// +class Dilator +{ +// -- instance variables -- + + std::vector< double > _initial, _target; // time points + +// -- public interface -- +public: +// -- construction -- + + //! Construct a new Dilator with no time points. + Dilator( void ); + + //! Construct a new Dilator using a range of initial time points + //! and a range of target (desired) time points. The client must + //! ensure that the target range has at least as many elements as + //! the initial range. + //! + //! \param ibegin is the beginning of a sequence of initial, or source, + //! time points. + //! \param iend is (one-past) the end of a sequence of initial, or + //! source, time points. + //! \param tbegin is the beginning of a sequence of target time points; + //! this sequence must be as long as the sequence of initial time + //! point described by ibegin and iend. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only const double * arguments. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter1, typename Iter2> + Dilator( Iter1 ibegin, Iter1 iend, Iter2 tbegin ); +#else + inline + Dilator( const double * ibegin, const double * iend, const double * tbegin ); +#endif + + // Use compiler-generated copy, assign, and destroy. + +// -- mutation -- + + //! Insert a pair of initial and target time points. + //! + //! Specify a pair of initial and target time points to be used + //! by this Dilator, corresponding, for example, to the initial + //! and desired time of a particular temporal feature in an + //! analyzed sound. + //! + //! \param i is an initial, or source, time point + //! \param t is a target time point + //! + //! The time points will be sorted before they are used. + //! If, in the sequences of initial and target time points, there are + //! exactly the same number of initial time points preceding i as + //! target time points preceding t, then time i will be warped to + //! time t in the dilation process. + void insert( double i, double t ); + +// -- dilation -- + + //! Replace the Partial envelope with a new envelope having the + //! same Breakpoints at times computed to align temporal features + //! in the sorted sequence of initial time points with their + //! counterparts the sorted sequence of target time points. + //! + //! Depending on the specification of initial and target time + //! points, the dilated Partial may have Breakpoints at times + //! less than 0, even if the original Partial did not. + //! + //! It is possible to have duplicate time points in either sequence. + //! Duplicate initial time points result in very localized stretching. + //! Duplicate target time points result in very localized compression. + //! + //! If all initial time points are greater than 0, then an implicit + //! time point at 0 is assumed in both initial and target sequences, + //! so the onset of a sound can be stretched without explcitly specifying a + //! zero point in each vector. (This seems most intuitive, and only looks + //! like an inconsistency if clients are using negative time points in + //! their Dilator, or Partials having Breakpoints before time 0, both + //! of which are probably unusual circumstances.) + //! + //! \param p is the Partial to dilate. + void dilate( Partial & p ) const; + + //! Function call operator: same as dilate( Partial & p ). + void operator() ( Partial & p ) const; + + //! Compute a new time for the specified Marker using + //! warpTime(), exactly as Partial Breakpoint times are + //! recomputed. This can be used to dilate the Markers + //! corresponding to a collection of Partials. + //! + //! \param m is the Marker whose time should be recomputed. + void dilate( Marker & m ) const; + + //! Function call operator: same as dilate( Marker & p ). + void operator() ( Marker & m ) const; + + //! Non-uniformly expand and contract the parameter envelopes of the each + //! Partial in the specified half-open range according to this Dilator's + //! stored initial and target (desired) times. + //! + //! \param dilate_begin is the beginning of a sequence of Partials to dilate. + //! \param dilate_end is (one-past) the end of a sequence of Partials to dilate. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. Otherwise, this member + //! also works for sequences of Markers. + //! + //! \sa Dilator::dilate( Partial & p ) const + //! \sa Dilator::dilate( Marker & m ) const +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void dilate( Iter dilate_begin, Iter dilate_end ) const; +#else + inline + void dilate( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end ) const; +#endif + + //! Function call operator: same as + //! dilate( Iter dilate_begin, Iter dilate_end ) + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. Otherwise, this member + //! also works for sequences of Markers. + //! + //! \sa Dilator::dilate( Partial & p ) const + //! \sa Dilator::dilate( Marker & m ) const +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void operator() ( Iter dilate_begin, Iter dilate_end ) const; +#else + inline + void operator() ( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end ) const; +#endif + + //! Return the dilated time value corresponding to the specified initial time. + //! + //! \param currentTime is a pre-dilated time. + //! \return the dilated time corresponding to the initial time currentTime + double warpTime( double currentTime ) const; + +// -- static members -- + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Dilator using the specified initial and + //! target times, and apply it to a sequence of Partials. + //! + //! \param dilate_begin is the beginning of a sequence of Partials to dilate. + //! \param dilate_end is (one-past) the end of a sequence of Partials to dilate. + //! \param ibegin is the beginning of a sequence of initial, or source, + //! time points. + //! \param iend is (one-past) the end of a sequence of initial, or + //! source, time points. + //! \param tbegin is the beginning of a sequence of target time points; + //! this sequence must be as long as the sequence of initial time + //! point described by ibegin and iend. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. Otherwise, this member + //! also works for sequences of Markers. + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only const double * arguments for the times, otherwise, any iterator + //! will do.. + //! + //! \sa Dilator::dilate( Partial & p ) const + //! \sa Dilator::dilate( Marker & m ) const +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename PartialsIter, typename TimeIter1, typename TimeIter2 > + static + void dilate( PartialsIter dilate_begin, PartialsIter dilate_end, + TimeIter1 ibegin, TimeIter1 iend, TimeIter2 tbegin ); +#else + static inline + void dilate( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end, + const double * ibegin, const double * iend, + const double * tbegin ); +#endif + +}; // end of class Dilator + + +// --------------------------------------------------------------------------- +// constructor (sequences of time points) +// --------------------------------------------------------------------------- +//! Construct a new Dilator using a range of initial time points +//! and a range of target (desired) time points. The client must +//! ensure that the target range has at least as many elements as +//! the initial range. +//! +//! \param ibegin is the beginning of a sequence of initial, or source, +//! time points. +//! \param iend is (one-past) the end of a sequence of initial, or +//! source, time points. +//! \param tbegin is the beginning of a sequence of target time points; +//! this sequence must be as long as the sequence of initial time +//! point described by ibegin and iend. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only const double * arguments. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter1, typename Iter2> +Dilator::Dilator( Iter1 ibegin, Iter1 iend, Iter2 tbegin ) +#else +inline +Dilator::Dilator( const double * ibegin, const double * iend, const double * tbegin ) +#endif +{ + while ( ibegin != iend ) + { + insert( *ibegin++, *tbegin++ ); + } +} + +// --------------------------------------------------------------------------- +// dilate (sequence of Partials or Markers) +// --------------------------------------------------------------------------- +//! Non-uniformly expand and contract the parameter envelopes of the each +//! Partial in the specified half-open range according to this Dilator's +//! stored initial and target (desired) times. +//! +//! \param dilate_begin is the beginning of a sequence of Partials to dilate. +//! \param dilate_end is (one-past) the end of a sequence of Partials to dilate. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. Otherwise, this member +//! also works for sequences of Markers. +//! +//! \sa Dilator::dilate( Partial & p ) const +//! \sa Dilator::dilate( Marker & m ) const +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Dilator::dilate( Iter dilate_begin, Iter dilate_end ) const +#else +inline +void Dilator::dilate( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end ) const +#endif +{ + while ( dilate_begin != dilate_end ) + { + dilate( *(dilate_begin++) ); + } +} + +// --------------------------------------------------------------------------- +// Function call operator (sequence of Partials or Markers) +// --------------------------------------------------------------------------- +//! Function call operator: same as +//! dilate( Iter dilate_begin, Iter dilate_end ) +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. Otherwise, this member +//! also works for sequences of Markers. +//! +//! \sa Dilator::dilate( Partial & p ) const +//! \sa Dilator::dilate( Marker & m ) const +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Dilator::operator() ( Iter dilate_begin, Iter dilate_end ) const +#else +inline +void Dilator::operator() ( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end ) const +#endif +{ + dilate( dilate_begin, dilate_end ); +} + +// --------------------------------------------------------------------------- +// Function call operator (single Partial) +// --------------------------------------------------------------------------- +//! Function call operator: same as dilate( Partial & p ). +//! +//! \sa Dilator::dilate( Partial & p ) const +// +inline +void Dilator::operator() ( Partial & p ) const +{ + dilate( p ); +} + +// --------------------------------------------------------------------------- +// Function call operator (single Marker) +// --------------------------------------------------------------------------- +//! Function call operator: same as dilate( Marker & m ). +//! +//! \sa Dilator::dilate( Marker & m ) const +// +inline +void Dilator::operator() ( Marker & m ) const +{ + dilate( m ); +} + +// --------------------------------------------------------------------------- +// dilate (static) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! Construct a Dilator using the specified initial and +//! target times, and apply it to a sequence of Partials. +//! +//! \param dilate_begin is the beginning of a sequence of Partials to dilate. +//! \param dilate_end is (one-past) the end of a sequence of Partials to dilate. +//! \param ibegin is the beginning of a sequence of initial, or source, +//! time points. +//! \param iend is (one-past) the end of a sequence of initial, or +//! source, time points. +//! \param tbegin is the beginning of a sequence of target time points; +//! this sequence must be as long as the sequence of initial time +//! point described by ibegin and iend. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. Otherwise, this member +//! also works for sequences of Markers. +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only const double * arguments for the times, otherwise, any iterator +//! will do.. +//! +//! \sa Dilator::dilate( Partial & p ) const +//! \sa Dilator::dilate( Marker & m ) const +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename PartialsIter, typename TimeIter1, typename TimeIter2 > +void Dilator::dilate( PartialsIter dilate_begin, PartialsIter dilate_end, + TimeIter1 ibegin, TimeIter1 iend, TimeIter2 tbegin ) +#else +inline +void Dilator::dilate( PartialList::iterator dilate_begin, + PartialList::iterator dilate_end, + const double * ibegin, const double * iend, + const double * tbegin ) +#endif +{ + Dilator instance( ibegin, iend, tbegin ); + instance.dilate( dilate_begin, dilate_end ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_DILATOR_H */ diff --git a/src/loris/Distiller.C b/src/loris/Distiller.C new file mode 100644 index 0000000..09c9d19 --- /dev/null +++ b/src/loris/Distiller.C @@ -0,0 +1,483 @@ +/* + * 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 + * + * + * Distiller.C + * + * Implementation of class Distiller. + * + * Kelly Fitz, 20 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Distiller.h" +#include "Breakpoint.h" +#include "BreakpointUtils.h" +#include "LorisExceptions.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" +#include "Notifier.h" + +#include <algorithm> +#include <functional> +#include <utility> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// Distiller constructor +// --------------------------------------------------------------------------- +//! Construct a new Distiller using the specified fade time +//! for gaps between Partials. When two non-overlapping Partials +//! are distilled into a single Partial, the distilled Partial +//! fades out at the end of the earlier Partial and back in again +//! at the onset of the later one. The fade time is the time over +//! which these fades occur. By default, use a 5 ms fade time. +//! The gap time is the additional time over which a Partial faded +//! out must remain at zero amplitude before it can fade back in. +//! By default, use a gap time of one millisecond, to +//! prevent a pair of arbitrarily close null Breakpoints being +//! inserted. +//! +//! \param partialFadeTime is the time (in seconds) over +//! which Partials joined by distillation fade to +//! and from zero amplitude. Default is 0.005 (one +//! millisecond). +//! \param partialSilentTime is the minimum duration (in seconds) +//! of the silent (zero-amplitude) gap between two +//! Partials joined by distillation. (Default is +//! 0.001 (one millisecond). +// +Distiller::Distiller( double partialFadeTime, double partialSilentTime ) : + _fadeTime( partialFadeTime ), + _gapTime( partialSilentTime ) +{ + if ( _fadeTime <= 0.0 ) + { + Throw( InvalidArgument, "Distiller fade time must be positive." ); + } + if ( _gapTime <= 0.0 ) + { + Throw( InvalidArgument, "Distiller gap time must be positive." ); + } +} + +// -- helpers -- + +// --------------------------------------------------------------------------- +// fadeInAndOut (STATIC) +// --------------------------------------------------------------------------- +// Add zero-amplitude Breakpoints to the ends of a Partial if necessary. +// Do this to all Partials before distilling to make distillation easier. +// +static void fadeInAndOut( Partial & p, double fadeTime ) +{ + if ( p.first().amplitude() != 0 ) + { + p.insert( + p.startTime() - fadeTime, + BreakpointUtils::makeNullBefore( p.first(), fadeTime ) ); + } + + if ( p.last().amplitude() != 0 ) + { + p.insert( + p.endTime() + fadeTime, + BreakpointUtils::makeNullAfter( p.last(), fadeTime ) ); + } +} + +// --------------------------------------------------------------------------- +// merge (STATIC) +// --------------------------------------------------------------------------- +// Merge the Breakpoints in the specified iterator range into the +// distilled Partial. The beginning of the range may overlap, and +// will replace, some non-zero-amplitude portion of the distilled +// Partial. Assume that there is no such overlap at the end of the +// range (could check), because findContribution only leaves overlap +// at the beginning of the range. +// +static void merge( Partial::const_iterator beg, + Partial::const_iterator end, + Partial & destPartial, double fadeTime, + double gapTime = 0. ) +{ + // absorb energy in distilled Partial that overlaps the + // range to merge: + Partial toMerge( beg, end ); + toMerge.absorb( destPartial ); + fadeInAndOut( toMerge, fadeTime ); + + + // find the first Breakpoint in destPartial that is after the + // range of merged Breakpoints, plus the required gap: + Partial::iterator removeEnd = destPartial.findAfter( toMerge.endTime() + gapTime ); + + // if this Breakpoint has non-zero amplitude, need to leave time + // for a fade in: + while ( removeEnd != destPartial.end() && + removeEnd.breakpoint().amplitude() != 0 && + removeEnd.time() < toMerge.endTime() + gapTime + fadeTime ) + { + ++removeEnd; + } + + // find the first Breakpoint in the destination Partial that needs + // to be removed because it is in the overlap region: + Partial::iterator removeBegin = destPartial.findAfter( toMerge.startTime() - gapTime ); + + // if beforeMerge has non-zero amplitude, need to leave time + // for a fade out: + if ( removeBegin != destPartial.begin() ) + { + Partial::iterator beforeMerge = --Partial::iterator(removeBegin); + + while ( removeBegin != destPartial.begin() && + beforeMerge.breakpoint().amplitude() != 0 && + beforeMerge.time() > toMerge.startTime() - gapTime - fadeTime ) + { + --removeBegin; + if ( beforeMerge != destPartial.begin() ) + { + --beforeMerge; + } + } + + } + + // remove the Breakpoints in the merge range from destPartial: + double rbt = (removeBegin != destPartial.end())?(removeBegin.time()):(destPartial.endTime()); + double ret = (removeEnd != destPartial.end())?(removeEnd.time()):(destPartial.endTime()); + Assert( rbt <= ret ); + destPartial.erase( removeBegin, removeEnd ); + + // how about doing the fades here instead? + // fade in if necessary: + if ( removeEnd != destPartial.end() && + removeEnd.breakpoint().amplitude() != 0 ) + { + Assert( removeEnd.time() - fadeTime > toMerge.endTime() ); + + // update removeEnd so that we don't remove this + // null we are inserting: + destPartial.insert( + removeEnd.time() - fadeTime, + BreakpointUtils::makeNullBefore( removeEnd.breakpoint(), fadeTime ) ); + } + + if ( removeEnd != destPartial.begin() ) + { + Partial::iterator beforeMerge = --Partial::iterator(removeEnd); + if ( beforeMerge.breakpoint().amplitude() > 0 ) + { + Assert( beforeMerge.time() + fadeTime < toMerge.startTime() ); + + destPartial.insert( + beforeMerge.time() + fadeTime, + BreakpointUtils::makeNullAfter( beforeMerge.breakpoint(), fadeTime ) ); + } + } + + // insert the Breakpoints in the range: + for ( Partial::const_iterator insert = toMerge.begin(); insert != toMerge.end(); ++insert ) + { + destPartial.insert( insert.time(), insert.breakpoint() ); + } +} + +// --------------------------------------------------------------------------- +// findContribution (STATIC) +// --------------------------------------------------------------------------- +// Find and return an iterator range delimiting the portion of pshort that +// should be spliced into the distilled Partial plong. If any Breakpoint +// falls in a zero-amplitude region of plong, then pshort should contribute, +// AND its onset should be retained (!!! This is the weird part!!!). +// Therefore, if cbeg is not equal to cend, then cbeg is pshort.begin(). +// +std::pair< Partial::iterator, Partial::iterator > +findContribution( Partial & pshort, const Partial & plong, + double fadeTime, double gapTime ) +{ + // a Breakpoint can only fit in the gap if there's + // enough time to fade out pshort, introduce a + // space of length gapTime, and fade in the rest + // of plong (don't need to worry about the fade + // in, because we are checking that plong is zero + // at cbeg.time() + clearance anyway, so the fade + // in must occur after that, and already be part of + // plong): + // + // WRONG if cbeg is before the start of plong. + // Changed so that all Partials are faded in and + // out before distilling, so now the clearance + // need only be the gap time: + double clearance = gapTime; // fadeTime + gapTime; + + Partial::iterator cbeg = pshort.begin(); + while ( cbeg != pshort.end() && + ( plong.amplitudeAt( cbeg.time() ) > 0 || + plong.amplitudeAt( cbeg.time() + clearance ) > 0 ) ) + { + ++cbeg; + } + + Partial::iterator cend = cbeg; + + // if a gap is found, find the end of the + // range of Breakpoints that fit in that + // gap: + while ( cend != pshort.end() && + plong.amplitudeAt( cend.time() ) == 0 && + plong.amplitudeAt( cend.time() + clearance ) == 0 ) + { + ++cend; + } + + // if a gap is found, and it is big enough for at + // least one Breakpoint, then include the + // onset of the Partial: + if ( cbeg != pshort.end() ) + { + cbeg = pshort.begin(); + } + + return std::make_pair( cbeg, cend ); +} + +// --------------------------------------------------------------------------- +// distillSorter (static helper) +// --------------------------------------------------------------------------- +// Helper for sorting Partials for distilling. +// +static bool distillSorter( const Partial & lhs, const Partial & rhs ) +{ + double ldur = lhs.duration(), rdur = rhs.duration(); + if ( ldur != rdur ) + { + return ldur > rdur; + } + else + { + // What to do for same-duration Partials? + // Need to do something consistent, should look + // for energy? + return lhs.startTime() < rhs.startTime(); + /* + double lpeak = PartialUtils::peakAmp( lhs ); + double rpeak = PartialUtils::peakAmp( rhs ); + return lhs > rhs; + */ + } +} + +// --------------------------------------------------------------------------- +// distillOne +// --------------------------------------------------------------------------- +// Distill a list of Partials having a common label +// into a single Partial with that label, and append it +// to the distilled collection. If an empty list of Partials +// is passed, then an empty Partial having the specified +// label is appended. +// +void Distiller::distillOne( PartialList & partials, Partial::label_type label, + PartialList & distilled ) +{ + debugger << "Distiller found " << partials.size() + << " Partials labeled " << label << endl; + + Partial newp; + newp.setLabel( label ); + + if ( partials.size() == 1 ) + { + // trivial if there is only one partial to distill + newp = partials.front(); + } + else if ( partials.size() > 0 ) // it will be an empty Partial otherwise + { + // sort Partials by duration, longer + // Partials will be prefered: + partials.sort( distillSorter ); + + // keep the longest Partial: + PartialList::iterator it = partials.begin(); + newp = *it; + fadeInAndOut( newp, _fadeTime ); + + // Iterate over remaining Partials: + for ( ++it; it != partials.end(); ++it ) + { + fadeInAndOut( *it, _fadeTime ); + + std::pair< Partial::iterator, Partial::iterator > range = + findContribution( *it, newp, _fadeTime, _gapTime ); + Partial::iterator cb = range.first, ce = range.second; + + // There can only be one contributing regions because + // (and only because) the partials are sorted by length + // first! + + // merge Breakpoints into the new Partial, if + // there are any that contribute, otherwise + // just absorb the current Partial as noise: + if ( cb != ce ) + { + // absorb the non-contributing part: + if ( ce != it->end() ) + { + Partial absorbMe( --Partial::iterator(ce), it->end() ); + // shouldn't this just be ce? + newp.absorb( absorbMe ); + + // There cannot be a non-contributing part + // at the beginning of the Partial too, because + // we always retain the beginning of the Partial. + // If findContribution were changed, then + // we might also want to absorb the beginning. + // + // Partial absorbMeToo( it->begin(), cb ); + // newp.absorb( absorbMeToo ); + } + + // merge the contributing part: + merge( cb, ce, newp, _fadeTime, _gapTime ); + } + else + { + // no contribution, absorb the whole thing: + newp.absorb( *it ); + } + } + } + + // take the null Breakpoints off the ends + // should check whether this is appropriate? + // + // This is a bit of a kludge here, sometimes we must + // be inserting more than one null Breakpoint at the + // front end (at least), sometimes shows up as a Partial + // that begins before 0. The idea is to have no nulls at + // the ends, so just remove all nulls at the ends. + while ( 0 < newp.numBreakpoints() && + 0 == newp.begin().breakpoint().amplitude() ) + { + newp.erase( newp.begin() ); + } + + Partial::iterator lastBpPos = newp.end(); + while ( 0 < newp.numBreakpoints() && + 0 == (--lastBpPos).breakpoint().amplitude() ) + { + lastBpPos = newp.erase( lastBpPos ); + } + + // insert the new Partial in the distilled collection + // in label order: + distilled.insert( std::lower_bound( distilled.begin(), distilled.end(), + newp, + PartialUtils::compareLabelLess() ), + newp ); +} + +// --------------------------------------------------------------------------- +// distill_list +// --------------------------------------------------------------------------- +//! Distill labeled Partials in a PartialList leaving only a single +//! Partial per non-zero label. +//! +//! Unlabeled (zero-labeled) Partials are left unmodified at +//! the end of the distilled Partials. +//! +//! Return an iterator refering to the position of the first unlabeled Partial, +//! or the end of the distilled collection if there are no unlabeled Partials. +//! Since distillation is in-place, the Partials collection may be smaller +//! (fewer Partials) after distillation, and any iterators on the collection +//! may be invalidated. +//! +//! \post All labeled Partials in the collection are uniquely-labeled, +//! and all unlabeled Partials have been moved to the end of the +//! sequence. +//! \param partials is the collection of Partials to distill in-place +//! \return the position of the end of the range of distilled Partials, +//! which is either the end of the collection, or the position +//! or the first unlabeled Partial. +// +PartialList::iterator Distiller::distill_list( PartialList & partials ) +{ + // sort the Partials by label, this is why it + // is so much better to distill a list! + partials.sort( PartialUtils::compareLabelLess() ); + + // temporary container of distilled Partials: + PartialList distilled; + + PartialList::iterator lower = partials.begin(); + while ( lower != partials.end() ) + { + Partial::label_type label = lower->label(); + + // identify a sequence of Partials having the same label: + PartialList::iterator upper = + std::find_if( lower, partials.end(), + std::not1( PartialUtils::isLabelEqual( label ) ) ); + + // upper is the first Partial after lower whose label is not + // equal to that of lower. + // [lower, upper) is a range of all the + // partials labeled `label'. + + if ( 0 != label ) + { + // make a container of the Partials having the same + // label, and distill them: + PartialList samelabel; + samelabel.splice( samelabel.begin(), partials, lower, upper ); + distillOne( samelabel, label, distilled ); + } + lower = upper; + } + +#if defined(Debug_Loris) && Debug_Loris + // only unlabeled Partials should remain in partials: + Assert( partials.end() == + std::find_if( partials.begin(), partials.end(), + std::not1( PartialUtils::isLabelEqual( 0 ) ) ) ); +#endif + + // remember where the unlabeled Partials start: + PartialList::iterator beginUnlabeled = partials.begin(); + + // splice in the distilled Partials at the beginning: + partials.splice( partials.begin(), distilled ); + + return beginUnlabeled; +} + +} // end of namespace Loris diff --git a/src/loris/Distiller.h b/src/loris/Distiller.h new file mode 100644 index 0000000..f1672ce --- /dev/null +++ b/src/loris/Distiller.h @@ -0,0 +1,370 @@ +#ifndef INCLUDE_DISTILLER_H +#define INCLUDE_DISTILLER_H +/* + * 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 + * + * + * Distiller.h + * + * Definition of class Distiller. + * + * Kelly Fitz, 20 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include "Notifier.h" // for debugging only + +#include <algorithm> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Distiller +// +//! Class Distiller represents an algorithm for "distilling" a group of +//! Partials that logically represent a single component into a single +//! Partial. +//! +//! The sound morphing algorithm in Loris requires that Partials in a +//! given source be labeled uniquely, that is, no two Partials can have +//! the same label. The Distiller enforces this condition. All Partials +//! identified with a particular frequency channel (see Channelizer), and, +//! therefore, having a common label, are distilled into a single Partial, +//! leaving at most a single Partial per frequency channel and label. +//! Channels that contain no Partials are not represented in the distilled +//! data. Partials that are not labeled, that is, Partials having label 0, +//! are are left unmodified at the end of the Partial sequence. +//! +//! Distillation modifies the Partial container (a PartialList). All +//! Partials in the distilled range having a common label are replaced by +//! a single Partial in the distillation process. Only labeled +//! Partials are affected by distillation. +// +class Distiller +{ +// -- instance variables -- + + double _fadeTime, _gapTime; // distillation parameters + +// -- public interface -- +public: + +// -- global defaults and constants -- + + enum + { + + //! Default time in milliseconds over which Partials joined by + //! distillation fade to and from zero amplitude. Divide by + //! 1000 to use as a member function parameter. This parameter + //! should be the same in Distiller, Sieve, and Collator. + DefaultFadeTimeMs = 5, + + //! Default minimum duration in milliseconds of the silent + //! (zero-amplitude) gap between two Partials joined by + //! distillation. Divide by 1000 to use as a member function + //! parameter. This parameter should be the same in Distiller, + //! Sieve, and Collator. + DefaultSilentTimeMs = 1 + }; + +// -- construction -- + + //! Construct a new Distiller using the specified fade time + //! for gaps between Partials. When two non-overlapping Partials + //! are distilled into a single Partial, the distilled Partial + //! fades out at the end of the earlier Partial and back in again + //! at the onset of the later one. The fade time is the time over + //! which these fades occur. By default, use a 1 ms fade time. + //! The gap time is the additional time over which a Partial faded + //! out must remain at zero amplitude before it can fade back in. + //! By default, use a gap time of one tenth of a millisecond, to + //! prevent a pair of arbitrarily close null Breakpoints being + //! inserted. + //! + //! \param partialFadeTime is the time (in seconds) over + //! which Partials joined by distillation fade to + //! and from zero amplitude. (Default is + //! Distiller::DefaultFadeTime). + //! \param partialSilentTime is the minimum duration (in seconds) + //! of the silent (zero-amplitude) gap between two + //! Partials joined by distillation. (Default is + //! Distiller::DefaultSilentTime). + explicit + Distiller( double partialFadeTime = Distiller::DefaultFadeTimeMs/1000.0, + double partialSilentTime = Distiller::DefaultSilentTimeMs/1000.0 ); + + // Use compiler-generated copy, assign, and destroy. + +// -- distillation -- + + //! Distill labeled Partials in a collection leaving only a single + //! Partial per non-zero label. + //! + //! Unlabeled (zero-labeled) Partials are left unmodified at + //! the end of the distilled Partials. + //! + //! Return an iterator refering to the position of the first unlabeled Partial, + //! or the end of the distilled collection if there are no unlabeled Partials. + //! Since distillation is in-place, the Partials collection may be smaller + //! (fewer Partials) after distillation, and any iterators on the collection + //! may be invalidated. + //! + //! \post All labeled Partials in the collection are uniquely-labeled, + //! and all unlabeled Partials have been moved to the end of the + //! sequence. + //! \param partials is the collection of Partials to distill in-place + //! \return the position of the end of the range of distilled Partials, + //! which is either the end of the collection, or the position + //! or the first unlabeled Partial. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then partials + //! must be a PartialList, otherwise it can be any container type + //! storing Partials that supports at least bidirectional iterators. + //! + //! \sa Distiller::distill( Container & partials ) +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + typename Container::iterator distill( Container & partials ); +#else + inline + PartialList::iterator distill( PartialList & partials ); +#endif + + //! Function call operator: same as distill( PartialList & partials ). +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + typename Container::iterator operator() ( Container & partials ); +#else + PartialList::iterator operator() ( PartialList & partials ); +#endif + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! + //! \post All labeled Partials in the collection are uniquely-labeled, + //! and all unlabeled Partials have been moved to the end of the + //! sequence. + //! \param partials is the collection of Partials to distill in-place + //! \param partialFadeTime is the time (in seconds) over + //! which Partials joined by distillation fade to + //! and from zero amplitude. + //! \param partialSilentTime is the minimum duration (in seconds) + //! of the silent (zero-amplitude) gap between two + //! Partials joined by distillation. (Default is + //! Distiller::DefaultSilentTime). + //! \return the position of the end of the range of distilled Partials, + //! which is either the end of the collection, or the position + //! or the first unlabeled Partial. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then partials + //! must be a PartialList, otherwise it can be any container type + //! storing Partials that supports at least bidirectional iterators. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + static typename Container::iterator + distill( Container & partials, double partialFadeTime, + double partialSilentTime = DefaultSilentTimeMs/1000.0 ); +#else + static inline PartialList::iterator + distill( PartialList & partials, double partialFadeTime, + double partialSilentTime = DefaultSilentTimeMs/1000.0 ); +#endif + +private: + +// -- helpers -- + + //! Distill labeled Partials in a PartialList leaving only a single + //! Partial per non-zero label. + //! + //! Unlabeled (zero-labeled) Partials are left unmodified at + //! the end of the distilled Partials. + //! + //! Return an iterator refering to the position of the first unlabeled Partial, + //! or the end of the distilled collection if there are no unlabeled Partials. + //! Since distillation is in-place, the Partials collection may be smaller + //! (fewer Partials) after distillation, and any iterators on the collection + //! may be invalidated. + //! + //! \post All labeled Partials in the collection are uniquely-labeled, + //! and all unlabeled Partials have been moved to the end of the + //! sequence. + //! \param partials is the collection of Partials to distill in-place + //! \return the position of the end of the range of distilled Partials, + //! which is either the end of the collection, or the position + //! or the first unlabeled Partial. + PartialList::iterator distill_list( PartialList & partials ); + + //! Distill a list of Partials having a common label + //! into a single Partial with that label, and append it + //! to the distilled collection. If an empty list of Partials + //! is passed, then an empty Partial having the specified + //! label is appended. + void distillOne( PartialList & partials, Partial::label_type label, + PartialList & distilled ); + +}; // end of class Distiller + +// --------------------------------------------------------------------------- +// distill +// --------------------------------------------------------------------------- +//! Distill labeled Partials in a collection leaving only a single +//! Partial per non-zero label. +//! +//! Unlabeled (zero-labeled) Partials are left unmodified at +//! the end of the distilled Partials. +//! +//! Return an iterator refering to the position of the first unlabeled Partial, +//! or the end of the distilled collection if there are no unlabeled Partials. +//! Since distillation is in-place, the Partials collection may be smaller +//! (fewer Partials) after distillation, and any iterators on the collection +//! may be invalidated. +//! +//! \post All labeled Partials in the collection are uniquely-labeled, +//! and all unlabeled Partials have been moved to the end of the +//! sequence. +//! \param partials is the collection of Partials to distill in-place +//! \return the position of the end of the range of distilled Partials, +//! which is either the end of the collection, or the position +//! or the first unlabeled Partial. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then partials +//! must be a PartialList, otherwise it can be any container type +//! storing Partials that supports at least bidirectional iterators. +//! +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator Distiller::distill( Container & partials ) +{ + // This can be done so much more easily and + // efficiently on a list than on other containers + // that it is worth copying the Partials to a + // list for distillation, and then transfering + // them back. + // + // See below for a specialization for the case + // of the Container being a list, so no copy + // is needed. + PartialList pl( partials.begin(), partials.end() ); + PartialList::iterator it = distill_list( pl ); + + // pl has distilled Partials at beginning, and + // unlabeled Partials at end: + typename Container::iterator beginUnlabeled = + std::copy( pl.begin(), it, partials.begin() ); + + typename Container::iterator endUnlabeled = + std::copy( it, pl.end(), beginUnlabeled ); + + + partials.erase( endUnlabeled, partials.end() ); + + return beginUnlabeled; +} + +// specialization for PartialList container +template< > +inline +PartialList::iterator Distiller::distill( PartialList & partials ) +{ + debugger << "using PartialList version of distill to avoid copying" << endl; + return distill_list( partials ); +} +#else +inline +PartialList::iterator Distiller::distill( PartialList & partials ) +{ + return distill_list( partials ); +} +#endif + +// --------------------------------------------------------------------------- +// Function call operator +// --------------------------------------------------------------------------- +//! Function call operator: same as distill( PartialList & partials ). +//! +//! \sa Distiller::distill( Container & partials ) +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator Distiller::operator()( Container & partials ) +#else +inline +PartialList::iterator Distiller::operator()( PartialList & partials ) +#endif +{ + return distill( partials ); +} + +// --------------------------------------------------------------------------- +// distill +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! +//! \post All labeled Partials in the collection are uniquely-labeled, +//! and all unlabeled Partials have been moved to the end of the +//! sequence. +//! \param partials is the collection of Partials to distill in-place +//! \param partialFadeTime is the time (in seconds) over +//! which Partials joined by distillation fade to +//! and from zero amplitude. +//! \param partialSilentTime is the minimum duration (in seconds) +//! of the silent (zero-amplitude) gap between two +//! Partials joined by distillation. (Default is +//! Distiller::DefaultSilentTime). +//! \return the position of the end of the range of distilled Partials, +//! which is either the end of the collection, or the position +//! or the first unlabeled Partial. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then partials +//! must be a PartialList, otherwise it can be any container type +//! storing Partials that supports at least bidirectional iterators. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Container > +typename Container::iterator +Distiller::distill( Container & partials, double partialFadeTime, + double partialSilentTime ) +#else +inline +PartialList::iterator +Distiller::distill( PartialList & partials, double partialFadeTime, + double partialSilentTime ) +#endif +{ + Distiller instance( partialFadeTime, partialSilentTime ); + return instance.distill( partials ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_DISTILLER_H */ diff --git a/src/loris/Envelope.C b/src/loris/Envelope.C new file mode 100644 index 0000000..019a7c2 --- /dev/null +++ b/src/loris/Envelope.C @@ -0,0 +1,54 @@ +/* + * 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 + * + * + * Envelope.C + * + * Implementation of class Envelope. + * + * Kelly Fitz, 21 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Envelope.h" + +// Since Envelope is just an interface, there's nothing interesting in +// the implementation file. + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// destructor +// --------------------------------------------------------------------------- +// +Envelope::~Envelope(void) +{ +} + +} // end of namespace Loris diff --git a/src/loris/Envelope.h b/src/loris/Envelope.h new file mode 100644 index 0000000..832619a --- /dev/null +++ b/src/loris/Envelope.h @@ -0,0 +1,170 @@ +#ifndef INCLUDE_ENVELOPE_H +#define INCLUDE_ENVELOPE_H +/* + * 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 + * + * + * Envelope.h + * + * Definition of abstract interface class Envelope. + * + * Kelly Fitz, 21 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include <memory> // for autoptr + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Envelope +// +//! Envelope is an base class for objects representing real functions +//! of time. +//! +//! Class Envelope is an abstract base class, specifying interface for +//! prototypable (clonable) objects representing generic, real-valued +//! (double) functions of one real-valued (double) time argument. Derived +//! classes (like BreakpointEnvelope) must implement valueAt() and +//! clone(), the latter to support the Prototype pattern. Clients of +//! Envelope, like Morpher and Distiller, can use prototype Envelopes to +//! make their own private Envelopes. +//! +//! \sa Distiller, Envelope, Morpher +// +class Envelope +{ +// -- public interface -- +public: +// -- construction -- + + // allow compiler to generate constructors + + //! Destroy this Envelope (virtual to allow subclassing). + virtual ~Envelope( void ); + +// -- Envelope interface -- + + //! Return an exact copy of this Envelope (following the Prototype + //! pattern). + virtual Envelope * clone( void ) const = 0; + + //! Return the value of this Envelope at the specified time. + virtual double valueAt( double x ) const = 0; + +}; // end of abstract class Envelope + + +// --------------------------------------------------------------------------- +// class ScaleAndOffsetEnvelope +// +//! ScaleAndOffsetEnvelope is an derived Envelope class for objects +//! representing envelopes having a scale and offset applied (in that order). + +class ScaleAndOffsetEnvelope : public Envelope +{ +// -- public interface -- +public: +// -- construction -- + + //! Construct a new envelope that is a scaled and offset + //! version of another. + ScaleAndOffsetEnvelope( const Envelope & e, double scale, double offset ) : + m_env( e.clone() ), + m_scale( scale ), + m_offset( offset ) + { + } + + //! Construct a copy of an envelope. + ScaleAndOffsetEnvelope( const ScaleAndOffsetEnvelope & rhs ) : + m_env( rhs.m_env->clone() ), + m_scale( rhs.m_scale ), + m_offset( rhs.m_offset ) + { + } + + //! Assignment from another envelope. + ScaleAndOffsetEnvelope & + operator=( const ScaleAndOffsetEnvelope & rhs ) + { + if ( &rhs != this ) + { + m_env.reset( rhs.m_env->clone() ); + m_scale = rhs.m_scale; + m_offset = rhs.m_offset; + } + return *this; + } + +// -- Envelope interface -- + + //! Return an exact copy of this Envelope (following the Prototype + //! pattern). + ScaleAndOffsetEnvelope * clone( void ) const + { + return new ScaleAndOffsetEnvelope( *this ); + } + + //! Return the value of this Envelope at the specified time. + virtual double valueAt( double x ) const + { + return m_offset + ( m_scale * m_env->valueAt( x ) ); + } + +// -- private member variables -- + +private: + + std::auto_ptr< Envelope > m_env; + double m_scale, m_offset; + +}; // end of class ScaleAndOffsetEnvelope + + +// --------------------------------------------------------------------------- +// math operators +// --------------------------------------------------------------------------- + +inline +ScaleAndOffsetEnvelope +operator*( const Envelope & e, double x ) +{ + return ScaleAndOffsetEnvelope( e, x, 0 ); +} + +inline +ScaleAndOffsetEnvelope +operator*( double x, const Envelope & e ) +{ + return e * x; +} + + + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_ENVELOPE_H */ diff --git a/src/loris/Exception.h b/src/loris/Exception.h new file mode 100644 index 0000000..e21355c --- /dev/null +++ b/src/loris/Exception.h @@ -0,0 +1,41 @@ +#ifndef INCLUDE_EXCEPTION_H_DEPRECATED +#define INCLUDE_EXCEPTION_H_DEPRECATED +/* + * 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 + * + * + * Exception.h + * + * This file formerly defined class Exception, a generic exception class, and + * commonly-used derived exception classes, but the name caused build problems + * on case-insensitive systems having a system header called exception.h, so + * the name has been changed to LorisExceptions.h. This file is included as + * legacy support. New code should use LorisExceptions.h. + * + * Kelly Fitz, 17 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "LorisExceptions.h" + +#endif /* ndef INCLUDE_EXCEPTION_H_DEPRECATED */ diff --git a/src/loris/F0Estimate.C b/src/loris/F0Estimate.C new file mode 100644 index 0000000..d03c136 --- /dev/null +++ b/src/loris/F0Estimate.C @@ -0,0 +1,697 @@ +/* + * 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 + * + * + * F0Estimate.C + * + * Implementation of an iterative alrogithm for computing an + * estimate of fundamental frequency from a sequence of sinusoidal + * frequencies and amplitudes using a likelihood estimator + * adapted from Quatieri's Speech Signal Processing text. The + * algorithm here takes advantage of the fact that spectral peaks + * have already been identified and extracted in the analysis/modeling + * process. + * + * Kelly Fitz, 28 March 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "F0Estimate.h" + +#include "LorisExceptions.h" // for Assert + +#include "Notifier.h" + +#include <algorithm> +#include <cmath> +#include <numeric> + +#include <vector> +using std::vector; + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// #if defined(HAVE_ISFINITE) && (HAVE_ISFINITE) +// using std::isfinite; +// +// isfinite is not, after all, part of the standard, +// it is an extension. If it is not provided, the following +// checks for NaN and finite-ness. +// +// Use this instead. +// This code is taken from +// http://www.johndcook.com/IEEE_exceptions_in_cpp.html + +#include <float.h> +inline bool IsFiniteNumber( double x ) +{ + // DBL_MAX is defined in float.h. + // Comparisons with NaN always fail. + + return (x <= DBL_MAX && x >= -DBL_MAX); +} + + + + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// forward declarations for helpers, defined below +// Q is the likelihood function, Qprime is its derivative w.r.t. frequency + +static double +secant_method( const vector<double> & amps, + const vector<double> & freqs, + double f1, double f2, + double precision ); + +static void +compute_candidate_freqs( const vector< double > & peak_freqs, + double fmin, double fmax, + vector< double > & eval_freqs ); +static void +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + const vector<double> & eval_freqs, + vector<double> & Q ); + +static double +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + double eval_freq, + double norm ); + +static double +evaluate_Qprime( const vector<double> & amps, + const vector<double> & freqs, + double eval_freq ); + +static void +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + const vector<double> & eval_freqs, + vector<double> & Q, + double norm ); + +// --------------------------------------------------------------------------- + + +// --------------------------------------------------------------------------- +// F0Estimate constructor +// --------------------------------------------------------------------------- +// Construct from parameters of the iterative F0 estimation +// algorithm. Find candidate F0 estimates as integer divisors +// of the peak frequencies, pick the highest frequency of the +// most likely candidates, and refine that estiamte using the +// secant method. +// +// Store the frequency and the normalized value of the +// likelihood function at that frequency (1.0 indicates that +// all the peaks are perfect harmonics of the estimated +// frequency). +// +// See the F0Estimate.h for a description of the algorithm, also +// outlined inline below. + +F0Estimate::F0Estimate( const vector<double> & amps, + const vector<double> & freqs, + double fmin, double fmax, + double resolution ) : + m_frequency( 0 ), + m_confidence( 0 ) +{ + if ( fmin > fmax ) + { + std::swap( fmin, fmax ); + } + // never consider DC (0 Hz) to be a valid fundamental + fmin = std::max( 1., fmin ); + + // ------------------------------------------------------------------------- + // 1) Identify candidate F0s as the integer divisors of the sinusoidal + // frequencies provided, within the specified range (this algorithm + // relies on the reasonable assumption that for any frequency recognized + // as a likely F0, at least one of the sinusoidal frequencies must + // represent a harmonic, the likelihood function makes this same + + // First collect candidate frequencies: all integer + // divisors of the peak frequencies that are between + // fmin and fmax. + vector< double > eval_freqs; + compute_candidate_freqs( freqs, fmin, fmax, eval_freqs ); + + if ( ! eval_freqs.empty() ) + { + // Compute a normalization factor equal to the total + // energy represented by all the peaks passed in + // amps and freqs, so that the value of the likelihood + // function does not depend on the overall signal + // amplitude, but instead depends only on the quality + // of the estimate, or the confidence in the result, + // and the quality of the final estimate can be evaluated + // by the value of the likelihood function. + double normalization = + 1.0 / std::inner_product( amps.begin(), amps.end(), amps.begin(), 0.0 ); + + // Evaluate the likelihood function at the candidate frequencies. + vector < double > Q( eval_freqs.size() ); + evaluate_Q( amps, freqs, eval_freqs, Q, normalization ); + + // ------------------------------------------------------------------------- + // 2) Select the highest frequency candidate that nearly maximizes the + // likelihood function (because all subharmonics of the true F0 will + // be equal in likelihood to the true F0, but no higher frequency can + // be as likely). + + // Find the highest frequency corresponding to a high value of Q + // (the most likely candidate). + + vector<double>::size_type idx = + std::max_element( Q.begin(), Q.end() ) - Q.begin(); + + double bestFreq = eval_freqs[ idx ]; + double bestQ = Q[ idx ]; + + // ------------------------------------------------------------------------- + // 2a) Check the likelihood of integer multiples of the best candidate, + // choose the highest multiple (within the specified range) that + // as likely as the best candidate frequency to be the new best + // candidate. + + // Check integer multiples of the best candidate frequency, + // so that we can be certain that the peak doesn't + // correspond to a subharmonic of the true most-likely + // F0 in the range [fmin,fmax]. + // + // While the next octave up is in range, and its likelihood + // is within 5% of the previously found peak, accept the + // octave as the better candidate (when we reach the true + // best candidate frequency, the next multiple should be + // much less likely). + + double nextF = 2 * bestFreq; + double nextQ = evaluate_Q( amps, freqs, nextF, normalization ); + + while ( fmax > nextF && ( 0.95 * bestQ ) < nextQ ) + { + // update best candidate: + bestFreq = nextF; + bestQ = nextQ; + + // consider the next multiple + nextF += bestFreq; + nextQ = evaluate_Q( amps, freqs, nextF, normalization ); + } + +// notifier << "peak is : " << bestFreq +// << " Hz, Q: " << bestQ << endl; + + // ------------------------------------------------------------------------- + // 3) Refine the best candidate using the secant method for refining the + // root of the derivative of the likelihood function in the neighborhood + // of the best candidate (because a peak in the likelihood function is + // a root of the derivative of that function). + + + // Refine this estimate using the secant method. + // + // Check the derivative function: if the slope (derivative) + // is positive, then assume that bestFreq is just below the + // root, and choose a second frequency just greater than + // bestFreq. Otherwise, assume that bestFreq is just above + // the root of the derivative function, and choose a second + // frequency just below bestFreq. + + double altFreq = bestFreq - resolution; + if ( 0 < evaluate_Qprime( amps, freqs, bestFreq ) ) + { + altFreq = bestFreq + resolution; + } + + // Now invoke the secant method to attempt to refine + // the root estimate: + m_frequency = secant_method( amps, freqs, + bestFreq, altFreq, + resolution ); + + +// notifier << "best candidate is : " << bestFreq +// << " Hz, Q: " << bestQ << endl; +// notifier << "secant method found : " << m_frequency +// << " Hz, Q: " +// << evaluate_Q( amps, freqs, m_frequency, normalization ) << endl; +// + + // What if the secant method flies off to some other root? + // Check that the root is still between fmin and fmax. + if ( m_frequency < fmin || m_frequency > fmax ) + { + // If refining fails, just use the best candidate estimate. + m_frequency = bestFreq; + } + + + // + // Could also use the bisection method, or the false position method, which + // always converge. All that is required is that two points on the + // function (the derivative of the likelihood function, in this case) + // having opposite signs are used to begin the search. So we need + // to first find a nearby freq at which the derivative of Q evaluates + // with the opposite sign as bestFreq. + // + + + // Compute the value of the likelihood function at this frequency. + m_confidence = evaluate_Q( amps, freqs, m_frequency, normalization ); + + // If the secant method makes things worse, then just go with the + // the most likely candidate. + // + // This is a sanity measure, should never happen. + if ( bestQ > m_confidence ) + { + m_confidence = bestQ; + m_frequency = bestFreq; + } + +// notifier << "refined to: " << m_frequency +// << " Hz, Q: " << m_confidence << endl; + + } + +} + + +// --------------------------------------------------------------------------- +// --- local helpers --- +// --------------------------------------------------------------------------- + + +// Collect candidate frequencies in eval_freqs. +// Candidates are all integer divisors +// between fmin and fmax of any frequency in the +// vector of peak frequencies provided. + +static void +compute_candidate_freqs( const vector< double > & peak_freqs, + double fmin, double fmax, + vector< double > & eval_freqs ) +{ + Assert( fmax > fmin ); + + eval_freqs.clear(); + + for ( vector< double >::const_iterator pk = peak_freqs.begin(); + pk != peak_freqs.end(); + ++pk ) + { + // check all integer divisors of *pk + double div = 1; + double f = *pk; + + // reject all the ones greater than fmax + while( f > fmax ) + { + ++div; + f = *pk / div; + } + + // keep the the ones that are between fmin + // and fmax + while( f >= fmin ) + { + eval_freqs.push_back( f ); + ++div; + f = *pk / div; + } + } + + // sort the candidats + sort( eval_freqs.begin(), eval_freqs.end() ); +} + + +// --------------------------------------------------------------------------- +// --- likelihood function evaluation --- +// --------------------------------------------------------------------------- + + +// Qterm is a functor to help compute terms +// in the likelihood function sum. + +struct Qterm +{ + double f0; + Qterm( double f ) : f0(f) {} + + double operator()( double amp, double freq ) const + { + double arg = 2*Pi*freq/f0; + return amp*amp*std::cos(arg); + } +}; + +// evaluate_Q +// +// Evaluate the likelihood function at the specified +// frequency. + +static double +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + double eval_freq, + double norm ) +{ + double prod = + std::inner_product( amps.begin(), amps.end(), + freqs.begin(), + 0., + std::plus< double >(), + Qterm( eval_freq ) ); + + return prod * norm; +} + +// evaluate_Q +// +// Evaluate the normalized likelihood function at a range of +// frequencies, return the results in the vector Q. + +static void +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + const vector<double> & eval_freqs, + vector<double> & Q ) +{ + Assert( eval_freqs.size() == Q.size() ); + Assert( amps.size() == freqs.size() ); + + // Compute a normalization factor equal to the total + // energy represented by all the peaks passed in + // amps and freqs, so that the value of the likelihood + // function does not depend on the overall signal + // amplitude, but instead depends only on the quality + // of the estimate, or the confidence in the result, + // and the quality of the final estimate can be evaluated + // by the value of the likelihood function. + double etotal = std::inner_product( amps.begin(), amps.end(), amps.begin(), 0.0 ); + double norm = 1.0 / etotal; + + evaluate_Q( amps, freqs, eval_freqs, Q, norm ); +} + + + +// evaluate_Q +// +// Evaluate the normalized likelihood function at a range of +// frequencies, using the normalization factor provided, and +// return the results in the vector Q. + +static void +evaluate_Q( const vector<double> & amps, + const vector<double> & freqs, + const vector<double> & eval_freqs, + vector<double> & Q, + double norm ) +{ + Assert( eval_freqs.size() == Q.size() ); + Assert( amps.size() == freqs.size() ); + + // iterate over the frequencies at which to + // evaluate the likelihood function: + vector<double>::const_iterator freq_it = eval_freqs.begin(); + vector<double>::iterator Q_it = Q.begin(); + while ( freq_it != eval_freqs.end() ) + { + double f = *freq_it; + + double result = evaluate_Q( amps, freqs, f, norm ); + + *Q_it++ = result; + ++freq_it; + } +} + +// --------------------------------------------------------------------------- +// --- likelihood function derivative evaluation --- +// --------------------------------------------------------------------------- + + +// Qprimeterm is a functor to help compute terms +// in the likelihood function derivative sum, used +// in the secant method of root refinement. + +struct Qprimeterm +{ + double f0; + Qprimeterm( double f ) : f0(f) {} + + double operator()( double amp, double freq ) const + { + double arg = 2*Pi*freq/f0; + return amp*amp*std::sin(arg)*arg/f0; + } +}; + + +// evaluate_Qprime +// +// Evaluate the derivative of the likelihood function (w.r.t. frequency) +// at the specified frequency. + +static double +evaluate_Qprime( const vector<double> & amps, + const vector<double> & freqs, + double eval_freq ) +{ + double prod = + std::inner_product( amps.begin(), amps.end(), + freqs.begin(), + 0., + std::plus< double >(), + Qprimeterm( eval_freq ) ); + + return prod; +} + +// --------------------------------------------------------------------------- +// --- secant method of refining a root/peak estimate --- +// --------------------------------------------------------------------------- + +// secant_method +// +// Find roots of the derivative of the likelihood +// function using the secant method, return the +// value of x (frequency) at which the roots is found. + +static double +secant_method( const vector<double> & amps, + const vector<double> & freqs, + double f1, double f2, double precision ) +{ + double xn = f1; + double xnm1 = f2; + double fxnm1 = evaluate_Qprime( amps, freqs, xnm1 ); + + const unsigned int MaxIters = 20; + + unsigned int iters = 0; + double deltax = 0.0; + + // Iterate until delta is small, or blows up, + // or we have iterated too many times. + do + { + double fxn = evaluate_Qprime( amps, freqs, xn ); + + deltax = fxn * (xn - xnm1)/(fxn - fxnm1); + + xnm1 = xn; + xn = xn - deltax; + + fxnm1 = fxn; + + } while( // fabs( deltax ) > precision && + IsFiniteNumber( deltax ) && + ++iters < MaxIters ); + + + // Check whether delta blew up. If it did, revert to the + // previous value of x. + + if ( ! IsFiniteNumber( deltax ) ) + { + xn = xnm1; + } + + return xn; +} + +#if 0 +// --------------------------------------------------------------------------- +// --- local helpers - dumb old way --- +// --------------------------------------------------------------------------- + + +static void +compute_eval_freqs( double fmin, double fmax, + vector<double> & eval_freqs ); + +static vector<double>::const_iterator +choose_peak( const vector<double> & Q ); + +// --------------------------------------------------------------------------- +// F0Estimate constructor -- iterative method +// --------------------------------------------------------------------------- +// Iteratively compute the value of the likelihood function +// at a range of frequencies around the peak likelihood. +// Store the maximum value when the range of likelihood +// values computed is less than the specified resolution. +// Store the frequency and the normalized value of the +// likelihood function at that frequency (1.0 indicates that +// all the peaks are perfect harmonics of the estimated +// frequency). + +void +F0Estimate::construct_iterative_method( const vector<double> & amps, + const vector<double> & freqs, + double fmin, double fmax, + double resolution ) +{ + + // when the frequency range is small, few samples are + // needed, but initially make sure to sample at least + // every 20 Hz. + // Scratch that, 20 Hz isn't fine enough, could miss a + // peak that way, try 2 Hz. There might be some room to + // adjust this parameter to trade off speed for robustness. + int Nsamps = std::max( 8, (int)std::ceil((fmax-fmin)*0.5) ); + vector<double> eval_freqs, Q; + double peak_freq = fmin, peak_Q = 0; + + // invariant: + // the likelihood function for the estimate of the fundamental + // frequency is maximized at some frequency between + // fmin and fmax (stop when that range is smaller + // than the resolution) + do + { + // determine the frequencies at which to evaluate + // the likelihood function + eval_freqs.resize( Nsamps ); + compute_eval_freqs( fmin, fmax, eval_freqs ); + + // evaluate the likelihood function at those + // frequencies: + Q.resize( Nsamps ); + evaluate_Q( amps, freqs, eval_freqs, Q ); + + // find the highest frequency at which the likelihood + // function peaks: + vector<double>::const_iterator peak = choose_peak( Q ); + int peak_idx = peak - Q.begin(); + peak_Q = *peak; + peak_freq = eval_freqs[ peak_idx ]; + + // update search range: + fmin = eval_freqs[ std::max(peak_idx - 1, 0) ]; + fmax = eval_freqs[ std::min(peak_idx + 1, Nsamps - 1) ]; + Nsamps = std::max( 8, (int)std::ceil((fmax-fmin)*0.05) ); + + } while ( (fmax - fmin) > resolution ); + + m_frequency = peak_freq; + m_confidence = peak_Q; +} + +// compute_eval_freqs +// +// Fill the frequency vector with a sampling +// of the range [fmin,fmax]. +// +// (used by dumb old iterative method) +// +static void +compute_eval_freqs( double fmin, double fmax, + vector<double> & eval_freqs ) +{ + Assert( fmax > fmin ); + + double delta = (fmax-fmin)/(eval_freqs.size()-1); + double f = fmin; + vector<double>::iterator it = eval_freqs.begin(); + while( it != eval_freqs.end() ) + { + *it++ = f; + f += delta; + } + eval_freqs.back() = fmax; +} + +// choose_peak +// +// Return the position of last peak that +// in the vector Q. +// +static vector<double>::const_iterator +choose_peak( const vector<double> & Q ) +{ + Assert( !Q.empty() ); + + double Qmax = *std::max_element( Q.begin(), Q.end() ); + vector<double>::const_iterator it = (Q.end()) - 1; + double tmp = *it; + + // this threshold determines how strong the + // highest-frequency peak in the likelihood + // function needs to be relative to the overall + // peak. For strongly periodic signals, this can + // be quite near to 1, but for things that are + // somewhat non-harmonic, setting it too high + // gives octave errors. Cannot tell whether errors + // will be introduced by having it too low. + const double threshold = 0.85 * Qmax; + while( (it != Q.begin()) && ((*it < threshold) || (*it < *(it-1))) ) + { + --it; + tmp = *it; + } + + return it; +} + +#endif + +} // end of namespace Loris diff --git a/src/loris/F0Estimate.h b/src/loris/F0Estimate.h new file mode 100644 index 0000000..a20e81e --- /dev/null +++ b/src/loris/F0Estimate.h @@ -0,0 +1,135 @@ +#ifndef INCLUDE_F0ESTIMATE_H +#define INCLUDE_F0ESTIMATE_H +/* + * 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 + * + * + * F0Estimate.h + * + * Implementation of an iterative alrogithm for computing an + * estimate of fundamental frequency from a sequence of sinusoidal + * frequencies and amplitudes using a likelihood estimator + * adapted from Quatieri's Speech Signal Processing text. The + * algorithm here takes advantage of the fact that spectral peaks + * have already been identified and extracted in the analysis/modeling + * process. + * + * Kelly Fitz, 28 March 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class F0Estimate +// +//! Represents a configuration of an iterative alrogithm for computing an +//! estimate of fundamental frequency from a sequence of sinusoidal +//! frequencies and amplitudes using a likelihood estimator adapted +//! from Quatieri's Speech Signal Processing text. This algorithm takes +//! advantage of the fact that spectral peaks have already been identified +//! and extracted in the analysis/modeling process. +//! +//! The algorithm consists of the following steps: +//! 1) Identify candidate F0s as the integer divisors of the sinusoidal +//! frequencies provided, within the specified range (this algorithm +//! relies on the reasonable assumption that for any frequency recognized +//! as a likely F0, at least one of the sinusoidal frequencies must +//! represent a harmonic, the likelihood function makes this same +//! assumption) +//! 2) Select the highest frequency candidate (within range) that maximizes +//! the likelihood function (because all subharmonics of the true F0 will +//! be equal in likelihood to the true F0, but no higher frequency can +//! be as likely). +//! 2a) Check the likelihood of integer multiples of the best candidate, +//! choose the highest multiple (within the specified range) that +//! as likely as the best candidate frequency to be the new best +//! candidate. +//! 3) Refine the best candidate using the secant method for refining the +//! root of the derivative of the likelihood function in the neighborhood +//! of the best candidate (because a peak in the likelihood function is +//! a root of the derivative of that function). +// + +class F0Estimate +{ +private: + + double m_frequency; //! estimated fundamental frequency in Hz + double m_confidence; //! normalized confidence for this estimate, + //! equal to 1.0 when all frequencies are perfect + //! harmonics of this estimate's frequency + +public: + + // --- lifecycle --- + + //! Construct from parameters of the iterative F0 estimation + //! algorithm. Find candidate F0 estimates as integer divisors + //! of the peak frequencies, pick the highest frequency of the + //! most likely candidates, and refine that estiamte using the + //! secant method. + //! + //! Store the frequency and the normalized value of the + //! likelihood function at that frequency (1.0 indicates that + //! all the peaks are perfect harmonics of the estimated + //! frequency). + + F0Estimate( const std::vector<double> & amps, + const std::vector<double> & freqs, + double fmin, double fmax, + double resolution ); + + // default copy/assign/destroy are OK + + + // Not sure whether or why these would be useful. + // + // F0Estimate( void ) : m_frequency( 0 ), m_confidence( 0 ) {} + // F0Estimate( double f, double c ) : m_frequency( f ), m_confidence( c ) {} + + + // --- accessors --- + + //! Return the F0 frequency estimate, in Hz, for this estimate. + + double frequency( void ) const { return m_frequency; } + + //! Return the normalized confidence for this estimate, + //! equal to 1.0 when all frequencies are perfect + //! harmonics of this estimate's frequency. + + double confidence( void ) const { return m_confidence; } + + + +}; // end of class F0Estimate + + +} // end of namespace Loris + +#endif // ndef INCLUDE_F0ESTIMATE_H diff --git a/src/loris/Filter.C b/src/loris/Filter.C new file mode 100644 index 0000000..c60fddd --- /dev/null +++ b/src/loris/Filter.C @@ -0,0 +1,204 @@ +/* + * 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 + * + * + * Filter.C + * + * Implementation of class Loris::Filter, a generic digital filter of + * arbitrary order having both feed-forward and feedback coefficients. + * + * Kelly Fitz, 1 Sept 1999 + * revised 9 Oct 2009 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Filter.h" + +#include <algorithm> +#include <numeric> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// default construction +// --------------------------------------------------------------------------- +//! Construct a filter with an all-pass unity gain response. +// +Filter::Filter( void ) : + m_ffwdcoefs( 1, 1.0 ), + m_fbackcoefs( 1, 1.0 ), + m_delayline( 1, 0 ), + m_gain( 1.0 ) +{ +} + +// --------------------------------------------------------------------------- +// copy construction +// --------------------------------------------------------------------------- +//! Make a copy of another digital filter. +//! Do not copy the filter state (delay line). +// +Filter::Filter( const Filter & other ) : + m_delayline( other.m_delayline.size(), 0. ), + m_ffwdcoefs( other.m_ffwdcoefs ), + m_fbackcoefs( other.m_fbackcoefs ), + m_gain( other.m_gain ) +{ + Assert( m_delayline.size() >= m_ffwdcoefs.size() - 1 ); + Assert( m_delayline.size() >= m_fbackcoefs.size() - 1 ); +} + +// --------------------------------------------------------------------------- +// assignment +// --------------------------------------------------------------------------- +//! Make a copy of another digital filter. +//! Do not copy the filter state (delay line). +// +Filter & +Filter::operator=( const Filter & rhs ) +{ + if ( &rhs != this ) + { + m_delayline.resize( rhs.m_delayline.size() ); + clear(); + + m_ffwdcoefs = rhs.m_ffwdcoefs; + m_fbackcoefs = rhs.m_fbackcoefs; + m_gain = rhs.m_gain; + + Assert( m_delayline.size() >= m_ffwdcoefs.size() - 1 ); + Assert( m_delayline.size() >= m_fbackcoefs.size() - 1 ); + } + return *this; +} + +// --------------------------------------------------------------------------- +// destructor +// --------------------------------------------------------------------------- +//! Destructor is virtual to enable subclassing. Subclasses may specialize +//! construction, and may add functionality, but for efficiency, the filtering +//! operation is non-virtual. +// +Filter::~Filter( void ) +{ +} + + +// --------------------------------------------------------------------------- +// apply +// --------------------------------------------------------------------------- +//! Compute a filtered sample from the next input sample. +//! +// +double +Filter::apply( double input ) +{ + // Implement the recurrence relation. m_ffwdcoefs holds the feed-forward + // coefficients, m_fbackcoefs holds the feedback coeffs. The coefficient + // vectors and delay lines are ordered by increasing age. + + double wn = - std::inner_product( m_fbackcoefs.begin()+1, m_fbackcoefs.end(), + m_delayline.begin(), -input ); + // negate input, then negate the inner product + + m_delayline.push_front( wn ); + + double output = std::inner_product( m_ffwdcoefs.begin(), m_ffwdcoefs.end(), + m_delayline.begin(), 0. ); + m_delayline.pop_back(); + + return output * m_gain; +} + +// --- access/mutation --- + +// --------------------------------------------------------------------------- +// numerator +// --------------------------------------------------------------------------- +//! Provide access to the numerator (feed-forward) coefficients +//! of this filter. The coefficients are stored in order of increasing +//! delay (lowest order coefficient first). + +std::vector< double > +Filter::numerator( void ) +{ + return m_ffwdcoefs; +} + +// --------------------------------------------------------------------------- +// numerator +// --------------------------------------------------------------------------- +//! Provide access to the numerator (feed-forward) coefficients +//! of this filter. The coefficients are stored in order of increasing +//! delay (lowest order coefficient first). + +const std::vector< double > +Filter::numerator( void ) const +{ + return m_ffwdcoefs; +} + +// --------------------------------------------------------------------------- +// denominator +// --------------------------------------------------------------------------- +//! Provide access to the denominator (feedback) coefficients +//! of this filter. The coefficients are stored in order of increasing +//! delay (lowest order coefficient first). + +std::vector< double > +Filter::denominator( void ) +{ + return m_fbackcoefs; +} + +// --------------------------------------------------------------------------- +// denominator +// --------------------------------------------------------------------------- +//! Provide access to the denominator (feedback) coefficients +//! of this filter. The coefficients are stored in order of increasing +//! delay (lowest order coefficient first). + +const std::vector< double > +Filter::denominator( void ) const +{ + return m_fbackcoefs; +} + +// --------------------------------------------------------------------------- +// clear +// --------------------------------------------------------------------------- +//! Clear the filter state. +// +void +Filter::clear( void ) +{ + std::fill( m_delayline.begin(), m_delayline.end(), 0 ); +} + +} // end of namespace Loris diff --git a/src/loris/Filter.h b/src/loris/Filter.h new file mode 100644 index 0000000..5a23a3e --- /dev/null +++ b/src/loris/Filter.h @@ -0,0 +1,249 @@ +#ifndef INCLUDE_FILTER_H +#define INCLUDE_FILTER_H +/* + * 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 + * + * + * Filter.h + * + * Definition of class Loris::Filter, a generic digital filter of + * arbitrary order having both feed-forward and feedback coefficients. + * + * Kelly Fitz, 1 Sept 1999 + * revised 9 Oct 2009 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "LorisExceptions.h" +#include "Notifier.h" + +#include <algorithm> +#include <deque> +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Filter +// +//! Filter is an Direct Form II realization of a filter specified +//! by its difference equation coefficients and (optionally) gain, +//! applied to the filter output (defaults to 1.). Coefficients are +//! specified and stored in order of increasing delay. +//! +//! Implements the rational transfer function +//! +//! -1 -nb +//! b[0] + b[1]z + ... + b[nb] z +//! Y(z) = G ---------------------------------- X(z) +//! -1 -na +//! a[0] + a[1]z + ... + a[na] z +//! +//! where b[k] are the feed forward coefficients, and a[k] are the feedback +//! coefficients. If a[0] is not 1, then both a and b are normalized by a[0]. +//! G is the additional filter gain, and is unity if unspecified. +//! +//! +//! Filter is implemented using a std::deque to store the filter state, +//! and relies on the efficiency of that class. If deque is not implemented +//! using some sort of circular buffer (as it should be -- deque is guaranteed +//! to be efficient for repeated insertion and removal at both ends), then +//! this filter class will be slow. +// +class Filter +{ +public: + +// --- lifecycle --- + + // default construction + //! Construct a filter with an all-pass unity gain response. + Filter( void ); + + // initialized construction + //! Initialize a Filter having the specified coefficients, and + //! order equal to the larger of the two coefficient ranges. + //! Coefficients in the sequences are stored in increasing order + //! (lowest order coefficient first). + //! + //! If template members are allowed, then the coefficients + //! can be stored in any kind of iterator range, otherwise, + //! they must be in an array of doubles. + //! + //! \param ffwdbegin is the beginning of a sequence of feed-forward coefficients + //! \param ffwdend is the end of a sequence of feed-forward coefficients + //! \param fbackbegin is the beginning of a sequence of feedback coefficients + //! \param fbackend is the end of a sequence of feedback coefficients + //! \param gain is an optional gain scale applied to the filtered signal + // +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename IterT1, typename IterT2> + Filter( IterT1 ffwdbegin, IterT1 ffwdend, // feed-forward coeffs + IterT2 fbackbegin, IterT2 fbackend, // feedback coeffs + double gain = 1. ); +#else + Filter( const double * ffwdbegin, const double * ffwdend, // feed-forward coeffs + const double * fbackbegin, const double * fbackend, // feedback coeffs + double gain = 1. ); +#endif + + + // copy constructor + //! Make a copy of another digital filter. + //! Do not copy the filter state (delay line). + Filter( const Filter & other ); + + // assignment operator + //! Make a copy of another digital filter. + //! Do not copy the filter state (delay line). + Filter & operator=( const Filter & rhs ); + + //! Destructor is virtual to enable subclassing. Subclasses may specialize + //! construction, and may add functionality, but for efficiency, the filtering + //! operation is non-virtual. + ~Filter( void ); + + +// --- filtering --- + + //! Compute a filtered sample from the next input sample. + //! + //! \param input is the next input sample + //! \return the next output sample + double apply( double input ); + + //! Function call operator, same as sample(). + //! + //! \sa apply + double operator() ( double input ) { return apply(input); } + +// --- access/mutation --- + + //! Provide access to the numerator (feed-forward) coefficients + //! of this filter. The coefficients are stored in order of increasing + //! delay (lowest order coefficient first). + + std::vector< double > numerator( void ); + + //! Provide access to the numerator (feed-forward) coefficients + //! of this filter. The coefficients are stored in order of increasing + //! delay (lowest order coefficient first). + + const std::vector< double > numerator( void ) const; + + //! Provide access to the denominator (feedback) coefficients + //! of this filter. The coefficients are stored in order of increasing + //! delay (lowest order coefficient first). + + std::vector< double > denominator( void ); + + //! Provide access to the denominator (feedback) coefficients + //! of this filter. The coefficients are stored in order of increasing + //! delay (lowest order coefficient first). + + const std::vector< double > denominator( void ) const; + + + //! Clear the filter state. + void clear( void ); + + +private: + +// --- implementation --- + + //! single delay line for Direct-Form II implementation + std::deque< double > m_delayline; + + //! feed-forward coefficients + std::vector< double > m_ffwdcoefs; + + //! feedback coefficients + std::vector< double > m_fbackcoefs; + + //! filter gain (applied to output) + double m_gain; + +}; // end of class Filter + + + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Initialize a Filter having the specified coefficients, and +//! order equal to the larger of the two coefficient ranges. +//! Coefficients in the sequences are stored in increasing order +//! (lowest order coefficient first). +//! +//! If template members are allowed, then the coefficients +//! can be stored in any kind of iterator range, otherwise, +//! they must be in an array of doubles. +//! +//! \param ffwdbegin is the beginning of a sequence of feed-forward coefficients +//! \param ffwdend is the end of a sequence of feed-forward coefficients +//! \param fbackbegin is the beginning of a sequence of feedback coefficients +//! \param fbackend is the end of a sequence of feedback coefficients +//! \param gain is an optional gain scale applied to the filtered signal +// +#if !defined(NO_TEMPLATE_MEMBERS) +template<typename IterT1, typename IterT2> +Filter::Filter( IterT1 ffwdbegin, IterT1 ffwdend, // feed-forward coeffs + IterT2 fbackbegin, IterT2 fbackend, // feedback coeffs + double gain ) : +#else +inline +Filter::Filter( const double * ffwdbegin, const double * ffwdend, // feed-forward coeffs + const double * fbackbegin, const double * fbackend, // feedback coeffs + double gain ) : +#endif + m_ffwdcoefs( ffwdbegin, ffwdend ), + m_fbackcoefs( fbackbegin, fbackend ), + m_delayline( std::max( ffwdend-ffwdbegin, fbackend-fbackbegin ) - 1, 0. ), + m_gain( gain ) +{ + if ( *fbackbegin == 0. ) + { + Throw( InvalidObject, + "Tried to create a Filter with feeback coefficient at zero delay equal to 0.0" ); + } + + // normalize the coefficients by 1/a[0], if a[0] is not equal to 1.0 + // (already checked for a[0] == 0 above) + if ( *fbackbegin != 1. ) + { + // scale all filter coefficients by a[0]: + std::transform( m_ffwdcoefs.begin(), m_ffwdcoefs.end(), m_ffwdcoefs.begin(), + std::bind2nd( std::divides<double>(), *fbackbegin ) ); + std::transform( m_fbackcoefs.begin(), m_fbackcoefs.end(), m_fbackcoefs.begin(), + std::bind2nd( std::divides<double>(), *fbackbegin ) ); + m_fbackcoefs[0] = 1.; + } +} + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_FILTER_H */ diff --git a/src/loris/FourierTransform.C b/src/loris/FourierTransform.C new file mode 100644 index 0000000..2a29238 --- /dev/null +++ b/src/loris/FourierTransform.C @@ -0,0 +1,641 @@ +/* + * 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 + * + * + * FourierTransform.C + * + * Implementation of class Loris::FourierTransform, providing a simplified + * uniform interface to the FFTW library (www.fftw.org), version 2.1.3 + * or newer (including version 3), or to the General Purpose FFT package + * by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html if + * FFTW is unavailable. + * + * Kelly Fitz, 2 Jun 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "FourierTransform.h" +#include "LorisExceptions.h" +#include "Notifier.h" + +#include <cmath> +#include <complex> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = std::acos( -1.0 ); +#endif + +#if defined(HAVE_FFTW3_H) && HAVE_FFTW3_H + #include <fftw3.h> +#elif defined(HAVE_FFTW_H) && HAVE_FFTW_H + #include <fftw.h> +#endif + +// --------------------------------------------------------------------------- +// isPO2 - return true if N is a power of two +// --------------------------------------------------------------------------- +// If out_expon is non-zero, return the exponent in that address. +// +static bool isPO2( unsigned int N, int * out_expon = 0 ) +{ + unsigned int M = 1; + int exp = 0; + while ( M < N ) + { + M *= 2; + ++exp; + } + if ( 0 != out_expon && M == N ) + { + *out_expon = exp; + } + return M == N; +} + +// =========================================================================== +// No longer matters that fftw and this class use the same floating point +// data format. The insulating implementation class now does its job of +// really insulating clients completely from FFTW, by copying data between +// buffers of std::complex< double > and fftw_complex, rather than +// relying on those two complex types to have the same memory layout. +// The overhead of copying is probably not significant, compared to +// the expense of computing the transform. +// +// about complex math functions for fftw_complex: +// +// These functions are all defined as templates in <complex>. +// Regrettably, they are all implemented using real() and +// imag() _member_ functions of the template argument, T. +// If they had instead been implemented in terms of the real() +// and imag() (template) free functions, then I could just specialize +// those two for the fftw complex data type, and the other template +// functions would work. Instead, I have to specialize _all_ of +// those functions that I want to use. I hope this was a learning +// experience for someone... In the mean time, the alternative I +// have is to take advantage of the fact that fftw_complex and +// std::complex<double> have the same footprint, so I can just +// cast back and forth between the two types. Its icky, but it +// works, and its a lot faster than converting, and more palatable +// than redefining all those operators. +// +// On the subject of brilliant designs, fftw_complex is defined as +// a typedef of an anonymous struct, as in typedef struct {...} fftw_complex, +// so I cannot forward-declare that type. +// +// In other good news, the planning structure got a slight name change +// in version 3, making it even more important to remove all traces of +// FFTW from the FourierTransform class definition. +// +// =========================================================================== + +// begin namespace +namespace Loris { + +using std::complex; +using std::vector; + +// --- private implementation class --- + +// --------------------------------------------------------------------------- +// FTimpl +// +// Insulating implementation class to insulate clients +// completely from everything about the interaction between +// Loris and FFTW. There is more copying of data between buffers, +// but this is not the expensive part of computing Fourier transforms +// and we don't have to do unsavory things that require std::complex +// and fftw_complex to have the same memory layout (we could even get +// by with single-precision floats in FFTW if necessary). Also got +// rid of lots of shared buffer stuff that just made the implementation +// lots more complicated than necessary. This one is simple, if not +// as memory efficient. +// + +#if defined(HAVE_FFTW3_H) && HAVE_FFTW3_H + +class FTimpl // FFTW version 3 +{ +private: + + fftw_plan plan; + FourierTransform::size_type N; + fftw_complex * ftIn; + fftw_complex * ftOut; + +public: + + // Construct an implementation instance: + // allocate an input buffer, and an output buffer + // and make a plan. + FTimpl( FourierTransform::size_type sz ) : + plan( 0 ), N( sz ), ftIn( 0 ), ftOut( 0 ) + { + // allocate buffers: + ftIn = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N ); + ftOut = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N ); + if ( 0 == ftIn || 0 == ftOut ) + { + fftw_free( ftIn ); + fftw_free( ftOut ); + throw RuntimeError( "cannot allocate Fourier transform buffers" ); + } + + // create a plan: + plan = fftw_plan_dft_1d( N, ftIn, ftOut, FFTW_FORWARD, FFTW_ESTIMATE ); + + // verify: + if ( 0 == plan ) + { + Throw( RuntimeError, "FourierTransform could not make a (fftw) plan." ); + } + } + + // Destroy the implementation instance: + // dump the plan. + ~FTimpl( void ) + { + if ( 0 != plan ) + { + fftw_destroy_plan( plan ); + } + + fftw_free( ftIn ); + fftw_free( ftOut ); + } + + // Copy complex< double >'s from a buffer into ftIn, + // the buffer must be as long as ftIn. + void loadInput( const complex< double > * bufPtr ) + { + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + ftIn[ k ][0] = bufPtr->real(); // real part + ftIn[ k ][1] = bufPtr->imag(); // imaginary part + ++bufPtr; + } + } + + // Copy complex< double >'s from ftOut into a buffer, + // which must be as long as ftOut. + void copyOutput( complex< double > * bufPtr ) const + { + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + *bufPtr = complex< double >( ftOut[ k ][0], ftOut[ k ][1] ); + ++bufPtr; + } + } + + // Compute a forward transform. + void forward( void ) + { + fftw_execute( plan ); + } + +}; // end of class FTimpl for FFTW version 3 + +#elif defined(HAVE_FFTW_H) && HAVE_FFTW_H + +// "die hook" for FFTW, which otherwise try to write to a +// non-existent console. +static void fftw_die_Loris( const char * s ) +{ + using namespace std; // exit might be hidden in there + + notifier << "The FFTW library used by Loris has encountered a fatal error: " << s << endl; + exit(EXIT_FAILURE); +} + +class FTimpl // FFTW version 2 +{ +private: + + fftw_plan plan; + FourierTransform::size_type N; + fftw_complex * ftIn; + fftw_complex * ftOut; + +public: + + // Construct an implementation instance: + // allocate an input buffer, and an output buffer + // and make a plan. + FTimpl( FourierTransform::size_type sz ) : + plan( 0 ), N( sz ), ftIn( 0 ), ftOut( 0 ) + { + // allocate buffers: + ftIn = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N ); + ftOut = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N ); + if ( 0 == ftIn || 0 == ftOut ) + { + fftw_free( ftIn ); + fftw_free( ftOut ); + Throw( RuntimeError, "cannot allocate Fourier transform buffers" ); + } + + // create a plan: + plan = fftw_create_plan_specific( N, FFTW_FORWARD, FFTW_ESTIMATE, + ftIn, 1, ftOut, 1 ); + + // verify: + if ( 0 == plan ) + { + Throw( RuntimeError, "FourierTransform could not make a (fftw) plan." ); + } + + // FFTW calls fprintf a lot, which may be a problem in + // non-console-enabled applications. Catch fftw_die() + // calls by routing the error message to our own Notifier + // and exiting, using the function defined above. + // + // (version 2 only) + fftw_die_hook = fftw_die_Loris; + } + + // Destroy the implementation instance: + // dump the plan. + ~FTimpl( void ) + { + if ( 0 != plan ) + { + fftw_destroy_plan( plan ); + } + + fftw_free( ftIn ); + fftw_free( ftOut ); + } + + // Copy complex< double >'s from a buffer into ftIn, + // the buffer must be as long as ftIn. + void loadInput( const complex< double > * bufPtr ) + { + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + c_re( ftIn[ k ] ) = bufPtr->real(); + c_im( ftIn[ k ] ) = bufPtr->imag(); + ++bufPtr; + } + } + + // Copy complex< double >'s from ftOut into a buffer, + // which must be as long as ftOut. + void copyOutput( complex< double > * bufPtr ) const + { + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + *bufPtr = complex< double >( c_re( ftOut[ k ] ), c_im( ftOut[ k ] ) ); + ++bufPtr; + } + } + + // Compute a forward transform. + void forward( void ) + { + fftw_one( plan, ftIn, ftOut ); + } + +}; // end of class FTimpl for FFTW version 2 + +#else + +#define SORRY_NO_FFTW 1 + +// function prototype, definition in fftsg.c +extern "C" void cdft(int, int, double *, int *, double *); + +// function prototype, definition below +static void slowDFT( double * in, double * out, int N ); + +// Uses General Purpose FFT (Fast Fourier/Cosine/Sine Transform) Package +// by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html defined +// in fftsg.c. +// +// In the event that the size is not a power of two, uses a (very) slow +// direct DFT computation, defined below. In this case, the workspace +// array is not used, and the twiddle factor array is used to store the +// transform result. + +class FTimpl // platform-neutral stand-alone implementation +{ +private: + + double * mTxInOut; // input/output buffer for in-place transform + double * mTwiddle; // storage for twiddle factors + int * mWorkspace; // workspace storage + + FourierTransform::size_type N; + + bool mIsPO2; + +public: + + // Construct an implementation instance: + // allocate buffers and workspace, and + // initialize the twiddle factors. + FTimpl( FourierTransform::size_type sz ) : + mTxInOut( 0 ), mTwiddle( 0 ), mWorkspace( 0 ), N( sz ), mIsPO2( isPO2( sz ) ) + { + mTxInOut = new double[ 2*N ]; + // input/output buffer for in-place transform + + if ( mIsPO2 ) + { + mTwiddle = new double[ N/2 ]; + // storage for twiddle factors + + mWorkspace = new int[ 2*int( std::sqrt((double)N) + 0.5 ) ]; + // workspace + + if ( 0 == mTxInOut || 0 == mTwiddle || 0 == mWorkspace ) + { + delete [] mTxInOut; + delete [] mTwiddle; + delete [] mWorkspace; + Throw( RuntimeError, "FourierTransform: could not initialize tranform" ); + } + + mWorkspace[0] = 0; // first time only, triggers setup + // no need to do it now, it will happen the + // first time a transform is computed + // cdft_double( 2*N, -1, mTxInOut, mWorkspace, mTwiddle ); + } + else + { + mTwiddle = new double[ 2*N ]; + // use for result in slowDFT + + if ( 0 == mTxInOut || 0 == mTwiddle ) + { + delete [] mTxInOut; + delete [] mTwiddle; + Throw( RuntimeError, "FourierTransform: could not initialize tranform" ); + } + } + } + + // Destroy the implementation instance: + ~FTimpl( void ) + { + delete [] mTxInOut; + delete [] mTwiddle; + delete [] mWorkspace; + } + + // Copy complex< double >'s from a buffer into ftIn, + // the buffer must be as long as ftIn. + void loadInput( const complex< double > * bufPtr ) + { + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + mTxInOut[ 2*k ] = bufPtr->real(); + mTxInOut[ 2*k+1 ] = bufPtr->imag(); + ++bufPtr; + } + } + + // Copy complex< double >'s from ftOut into a buffer, + // which must be as long as ftOut. Result might be + // stored in the twiddle factor array if this is not + // power of two length DFT. + void copyOutput( complex< double > * bufPtr ) const + { + double * result = mTxInOut; + if ( !mIsPO2 ) + { + result = mTwiddle; + } + + for ( FourierTransform::size_type k = 0; k < N; ++k ) + { + *bufPtr = complex< double >( result[ 2*k ], result[ 2*k+1 ] ); + ++bufPtr; + } + } + + // Compute a forward transform. + void forward( void ) + { + if ( mIsPO2 ) + { + cdft( 2*N, -1, mTxInOut, mWorkspace, mTwiddle ); + } + else + { + slowDFT( mTxInOut, mTwiddle, N ); + } + } + +}; // end of class platform-neutral stand-alone FTimpl + +#endif + +// --- FourierTransform members --- + +// --------------------------------------------------------------------------- +// FourierTransform constructor +// --------------------------------------------------------------------------- +//! Initialize a new FourierTransform of the specified size. +//! +//! \param len is the length of the transform in samples (the +//! number of samples in the transform) +//! \throw RuntimeError if the necessary buffers cannot be +//! allocated, or there is an error configuring FFTW. +// +FourierTransform::FourierTransform( size_type len ) : + _buffer( len ), + _impl( new FTimpl( len ) ) +{ + // zero: + std::fill( _buffer.begin(), _buffer.end(), 0. ); +} + +// --------------------------------------------------------------------------- +// FourierTransform copy constructor +// --------------------------------------------------------------------------- +//! Initialize a new FourierTransform that is a copy of another, +//! having the same size and the same buffer contents. +//! +//! \param rhs is the instance to copy +//! \throw RuntimeError if the necessary buffers cannot be +//! allocated, or there is an error configuring FFTW. +// +FourierTransform::FourierTransform( const FourierTransform & rhs ) : + _buffer( rhs._buffer ), + _impl( new FTimpl( rhs._buffer.size() ) ) // not copied +{ +} + +// --------------------------------------------------------------------------- +// FourierTransform destructor +// --------------------------------------------------------------------------- +//! Free the resources associated with this FourierTransform. +// +FourierTransform::~FourierTransform( void ) +{ + delete _impl; +} + +// --------------------------------------------------------------------------- +// FourierTransform assignment operator +// --------------------------------------------------------------------------- +//! Make this FourierTransform a copy of another, having +//! the same size and buffer contents. +//! +//! \param rhs is the instance to copy +//! \return a refernce to this instance +//! \throw RuntimeError if the necessary buffers cannot be +//! allocated, or there is an error configuring FFTW. +// +FourierTransform & +FourierTransform::operator=( const FourierTransform & rhs ) +{ + if ( this != &rhs ) + { + _buffer = rhs._buffer; + + // The implementation instance is not assigned, + // but a new one is created. + delete _impl; + _impl = 0; + _impl = new FTimpl( _buffer.size() ); + } + + return *this; +} + +// --------------------------------------------------------------------------- +// size +// --------------------------------------------------------------------------- +//! Return the length of the transform (in samples). +//! +//! \return the length of the transform in samples. +FourierTransform::size_type +FourierTransform::size( void ) const +{ + return _buffer.size(); +} + +// --------------------------------------------------------------------------- +// transform +// --------------------------------------------------------------------------- +//! Compute the Fourier transform of the samples stored in the +//! transform buffer. The samples stored in the transform buffer +//! (accessed by index or by iterator) are replaced by the +//! transformed samples, in-place. +// +void +FourierTransform::transform( void ) +{ + // copy data into the transform input buffer: + _impl->loadInput( &_buffer.front() ); + + // crunch: + _impl->forward(); + + // copy the data out of the transform output buffer: + _impl->copyOutput( &_buffer.front() ); +} + + +// --- slow non-power-of-two DFT implementation --- + +#if defined(SORRY_NO_FFTW) + +// --------------------------------------------------------------------------- +// slowDFT +// --------------------------------------------------------------------------- +// Non-power-of-two DFT implementation. in and out cannot be the same, +// and each is 2*N long, storing interleaved complex numbers. +// This version is only used when FFTW is unavailable. +// +static +void slowDFT( double * in, double * out, int N ) +{ +#if 1 + // slow DFT + // This version of the direct DFT computation is tolerably + // speedy, even for rather long transforms (like 8k). + // There is only one expensive call to std::polar, and + // twiddle factors are updated by multiplying. This + // causes some loss in numerical accuracy, worse for + // longer transforms, but even for 10k long transforms, + // accuracy is within hundredths of a percent in my experiments. + + const std::complex< double > eminj2pioN = + std::polar( 1.0, -2.0 * Pi / N ); + + std::complex< double > Wn = 1; + for ( int n = 0; n < N; ++n ) + { + std::complex< double > Wkn = 1; + std::complex< double > Xkn = 0; + for ( int k = 0; k < N; ++k ) + { + Xkn += std::complex< double >( in[ 2*k ], in[ 2*k+1 ] ) * Wkn; + Wkn *= Wn; + } + + out[ 2*n ] = Xkn.real(); + out[ 2*n+1 ] = Xkn.imag(); + Wn *= eminj2pioN; + } + +#else + // very, very slow + // This version of the direct DFT computation is slightly + // more accurate, increasingly so for longer transforms, + // but it is so much slower (4-5x) that the small improvement + // in accuracy is probably not worth the extra wait. For + // short transforms, the accuracy of both algorithms is + // very high, and for long transforms, this algorithm is + // too slow. + // + // Both algorithms are retained here, so that this + // tradeoff can be re-evaluated if necessary. + + for ( int n = 0; n < N; ++n ) + { + std::complex< double > Xkn = 0; + for ( int k = 0; k < N; ++k ) + { + std::complex< double > Wkn = std::polar( 1.0, -2.0 * Pi * k * n / N ); + Xkn += std::complex< double >( in[ 2*k ], in[ 2*k+1 ] ) * Wkn; + } + + out[ 2*n ] = Xkn.real(); + out[ 2*n+1 ] = Xkn.imag(); + } + +#endif +} + +#endif // defined(SORRY_NO_FFTW) + +} // end of namespace Loris diff --git a/src/loris/FourierTransform.h b/src/loris/FourierTransform.h new file mode 100644 index 0000000..34867ee --- /dev/null +++ b/src/loris/FourierTransform.h @@ -0,0 +1,223 @@ +#ifndef INCLUDE_FOURIERTRANSFORM_H +#define INCLUDE_FOURIERTRANSFORM_H +/* + * 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 + * + * + * FourierTransform.h + * + * Definition of class Loris::FourierTransform, providing a simplified + * uniform interface to the FFTW library (www.fftw.org), version 2.1.3 + * or newer (including version 3), or to the General Purpose FFT package + * by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html if + * FFTW is unavailable. + * + * Kelly Fitz, 2 Jun 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include <complex> +#include <vector> + +// begin namespace +namespace Loris { + +// insulating implementation class, defined in FourierTransform.C +class FTimpl; + +// --------------------------------------------------------------------------- +// class FourierTransform +// +//! FourierTransform provides a simplified interface to the FFTW library +//! (www.fftw.org). Loris uses the FFTW library to perform efficient +//! Fourier transforms of arbitrary length. Clients store and access +//! the in-place transform data as a sequence of std::complex< double >. +//! Samples are stored in the FourierTransform instance using subscript +//! or iterator access, the transform is computed by the transform member, +//! and the transformed samples replace the input samples, and are +//! accessed by subscript or iterator. FourierTransform computes a complex +//! transform, so it can be used to invert a transform of real samples +//! as well. Uses the standard library complex class, which implements +//! arithmetic operations. +//! +//! Supports FFTW versions 2 and 3. +//! Does not make use of FFTW "wisdom" to speed up transform computation. +//! +//! If FFTW is unavailable, uses instead the General Purpose FFT package +//! by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html defined +//! in fftsg.c for power-of-two transforms, and a very slow direct DFT +//! implementation for non-PO2 transforms. +// +class FourierTransform +{ +// -- public interface -- +public: + + //! An unsigned integral type large enough + //! to represent the length of any transform. + typedef std::vector< std::complex< double > >::size_type size_type; + + //! The type of a non-const iterator of (complex) transform samples. + typedef std::vector< std::complex< double > >::iterator iterator; + + //! The type of a const iterator of (complex) transform samples. + typedef std::vector< std::complex< double > >::const_iterator const_iterator; + +// --- lifecycle --- + + //! Initialize a new FourierTransform of the specified size. + //! + //! \param len is the length of the transform in samples (the + //! number of samples in the transform) + //! \throw RuntimeError if the necessary buffers cannot be + //! allocated, or there is an error configuring FFTW. + FourierTransform( size_type len ); + + //! Initialize a new FourierTransform that is a copy of another, + //! having the same size and the same buffer contents. + //! + //! \param rhs is the instance to copy + //! \throw RuntimeError if the necessary buffers cannot be + //! allocated, or there is an error configuring FFTW. + FourierTransform( const FourierTransform & rhs ); + + //! Free the resources associated with this FourierTransform. + ~FourierTransform( void ); + +// --- operators --- + + //! Make this FourierTransform a copy of another, having + //! the same size and buffer contents. + //! + //! \param rhs is the instance to copy + //! \return a refernce to this instance + //! \throw RuntimeError if the necessary buffers cannot be + //! allocated, or there is an error configuring FFTW. + FourierTransform & operator= ( const FourierTransform & rhs ); + + +// --- access/mutation --- + + //! Access (read/write) a transform sample by index. + //! Use this member to fill the transform buffer before + //! computing the transform, and to access the samples + //! after computing the transform. (inlined for speed) + //! + //! \param index is the index or rank of the complex + //! transform sample to access. Zero is the first + //! position in the buffer. + //! \return non-const reference to the std::complex< double > + //! at the specified position in the buffer. + std::complex< double > & operator[] ( size_type index ) + { + return _buffer[ index ]; + } + + //! Access (read-only) a transform sample by index. + //! Use this member to fill the transform buffer before + //! computing the transform, and to access the samples + //! after computing the transform. (inlined for speed) + //! + //! \param index is the index or rank of the complex + //! transform sample to access. Zero is the first + //! position in the buffer. + //! \return const reference to the std::complex< double > + //! at the specified position in the buffer. + const std::complex< double > & operator[] ( size_type index ) const + { + return _buffer[ index ]; + } + + //! Return an iterator refering to the beginning of the sequence of + //! complex samples in the transform buffer. + //! + //! \return a non-const iterator refering to the first position + //! in the transform buffer. + iterator begin( void ) + { + return _buffer.begin(); + } + + //! Return an iterator refering to the end of the sequence of + //! complex samples in the transform buffer. + //! + //! \return a non-const iterator refering to one past the last + //! position in the transform buffer. + iterator end( void ) + { + return _buffer.end(); + } + + //! Return a const iterator refering to the beginning of the sequence of + //! complex samples in the transform buffer. + //! + //! \return a const iterator refering to the first position + //! in the transform buffer. + const_iterator begin( void ) const + { + return _buffer.begin(); + } + + //! Return a const iterator refering to the end of the sequence of + //! complex samples in the transform buffer. + //! + //! \return a const iterator refering to one past the last + //! position in the transform buffer. + const_iterator end( void ) const + { + return _buffer.end(); + } + +// --- operations --- + + //! Compute the Fourier transform of the samples stored in the + //! transform buffer. The samples stored in the transform buffer + //! (accessed by index or by iterator) are replaced by the + //! transformed samples, in-place. + void transform( void ); + +// --- inquiry --- + + //! Return the length of the transform (in samples). + //! + //! \return the length of the transform in samples. + size_type size( void ) const ; + +// -- instance variables -- +private: + + //! buffer containing the complex transform input before + //! computing the transform, and the complex transform output + //! after computing the transform + std::vector< std::complex< double > > _buffer; + + // insulating implementation instance (defined in + // FourierTransform.C), conceals interface to FFTW + FTimpl * _impl; + +}; // end of class FourierTransform + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_FOURIERTRANSFORM_H */ diff --git a/src/loris/FrequencyReference.C b/src/loris/FrequencyReference.C new file mode 100644 index 0000000..3561e15 --- /dev/null +++ b/src/loris/FrequencyReference.C @@ -0,0 +1,293 @@ +/* + * 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 + * + * + * FrequencyReference.C + * + * Implementation of class FrequencyReference. + * + * Kelly Fitz, 3 Dec 2001 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "FrequencyReference.h" + +#include "Breakpoint.h" +#include "Fundamental.h" +#include "LinearEnvelope.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include <algorithm> +#include <cmath> + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// createEstimator (static) +// --------------------------------------------------------------------------- +// This class is now a wrapper providing the legacy interface to an improved +// and more flexible fundamental frequency estimator. This function +// constructs and configures an instance of the new estimator that +// provides the functionality of the older (Loris 1.4 through 1.5.2) +// FrequencyReference class. +// + +static const double Range = 50; +static const double Ceiling = 20000; +static const double Floor = -60; +static const double Precision = 0.1; +static const double Confidence = 0.9; +static const double Interval = 0.01; + +FundamentalFromPartials createEstimator( void ) +{ + FundamentalFromPartials eparts; + + eparts.setAmpFloor( Floor ); + eparts.setAmpRange( Range ); + eparts.setFreqCeiling( Ceiling ); + eparts.setPrecision( Precision ); + + return eparts; +} + +// --------------------------------------------------------------------------- +// construction +// --------------------------------------------------------------------------- +//! Construct a new fundamental FrequencyReference derived from the +//! specified half-open (STL-style) range of Partials that lies +//! within the speficied average frequency range. Construct the +//! reference envelope with approximately numSamps points. +//! +//! \param begin The beginning of a range of Partials from which to +//! construct a frequency refence envelope. +//! \param end The end of a range of Partials from which to +//! construct a frequency refence envelope. +//! \param minFreq The minimum expected fundamental frequency. +//! \param maxFreq The maximum expected fundamental frequency. +//! \param numSamps The approximate number of estimate of the +//! fundamental frequency from which to construct the +//! frequency reference envelope. +// +FrequencyReference::FrequencyReference( PartialList::const_iterator begin, + PartialList::const_iterator end, + double minFreq, double maxFreq, + long numSamps ) : + _env( new LinearEnvelope() ) +{ + if ( numSamps < 1 ) + { + Throw( InvalidArgument, "A frequency reference envelope must have a positive number of samples." ); + } + + // sanity: + if ( maxFreq < minFreq ) + { + std::swap( minFreq, maxFreq ); + } + +#ifdef Loris_Debug + debugger << "Finding frequency reference envelope in range " << + debugger << minFreq << " to " << maxFreq << " Hz, from " << + debugger << std::distance(begin,end) << " Partials" << std::endl; +#endif + + + FundamentalFromPartials est = createEstimator(); + std::pair< double, double > span = PartialUtils::timeSpan( begin, end ); + double dt = ( span.second - span.first ) / ( numSamps + 1 ); + *_env = est.buildEnvelope( begin, end, + span.first, span.second, dt, + minFreq, maxFreq, + Confidence ); +} + +// --------------------------------------------------------------------------- +// construction +// --------------------------------------------------------------------------- +//! Construct a new fundamental FrequencyReference derived from the +//! specified half-open (STL-style) range of Partials that lies +//! within the speficied average frequency range. Construct the +//! reference envelope from fundamental estimates taken every +//! five milliseconds. +//! +//! \param begin The beginning of a range of Partials from which to +//! construct a frequency refence envelope. +//! \param end The end of a range of Partials from which to +//! construct a frequency refence envelope. +//! \param minFreq The minimum expected fundamental frequency. +//! \param maxFreq The maximum expected fundamental frequency. +// +FrequencyReference::FrequencyReference( PartialList::const_iterator begin, + PartialList::const_iterator end, + double minFreq, double maxFreq ) : + _env( new LinearEnvelope() ) +{ + // sanity: + if ( maxFreq < minFreq ) + { + std::swap( minFreq, maxFreq ); + } + +#ifdef Loris_Debug + debugger << "Finding frequency reference envelope in range " << + debugger << minFreq << " to " << maxFreq << " Hz, from " << + debugger << std::distance(begin,end) << " Partials" << std::endl; +#endif + + FundamentalFromPartials est = createEstimator(); + std::pair< double, double > span = PartialUtils::timeSpan( begin, end ); + *_env = est.buildEnvelope( begin, end, + span.first, span.second, Interval, + minFreq, maxFreq, + Confidence ); + +} + +// --------------------------------------------------------------------------- +// copy construction +// --------------------------------------------------------------------------- +// +FrequencyReference::FrequencyReference( const FrequencyReference & other ) : + _env( other._env->clone() ) +{ +} + +// --------------------------------------------------------------------------- +// destruction +// --------------------------------------------------------------------------- +// +FrequencyReference::~FrequencyReference() +{ +} + +// --------------------------------------------------------------------------- +// assignment +// --------------------------------------------------------------------------- +// +FrequencyReference & +FrequencyReference::operator = ( const FrequencyReference & rhs ) +{ + if ( &rhs != this ) + { + _env.reset( rhs._env->clone() ); + } + return *this; +} + +// --------------------------------------------------------------------------- +// clone +// --------------------------------------------------------------------------- +// +FrequencyReference * +FrequencyReference::clone( void ) const +{ + return new FrequencyReference( *this ); +} + +// --------------------------------------------------------------------------- +// valueAt +// --------------------------------------------------------------------------- +// +double +FrequencyReference::valueAt( double x ) const +{ + return _env->valueAt(x); +} + +// --------------------------------------------------------------------------- +// envelope +// --------------------------------------------------------------------------- +// Conversion to LinearEnvelope return a BreakpointEnvelope that +// evaluates indentically to this FrequencyReference at all time. +// +LinearEnvelope +FrequencyReference::envelope( void ) const +{ + return *_env; +} + +// --------------------------------------------------------------------------- +// timeOfPeakEnergy (static helper function) +// --------------------------------------------------------------------------- +// Return the time at which the given Partial attains its +// maximum sinusoidal energy. +// +static double timeOfPeakEnergy( const Partial & p ) +{ + Partial::const_iterator partialIter = p.begin(); + double maxAmp = + partialIter->amplitude() * std::sqrt( 1. - partialIter->bandwidth() ); + double time = partialIter.time(); + + for ( ++partialIter; partialIter != p.end(); ++partialIter ) + { + double a = partialIter->amplitude() * + std::sqrt( 1. - partialIter->bandwidth() ); + if ( a > maxAmp ) + { + maxAmp = a; + time = partialIter.time(); + } + } + + return time; +} +// --------------------------------------------------------------------------- +// IsInFrequencyRange +// --------------------------------------------------------------------------- +// Function object for finding Partials that attain their maximum +// sinusoidal energy at a frequency within a specified range. +// +struct IsInFrequencyRange +{ + double minFreq, maxFreq; + IsInFrequencyRange( double min, double max ) : + minFreq( min ), + maxFreq( max ) + { + // sanity: + if ( maxFreq < minFreq ) + std::swap( minFreq, maxFreq ); + } + + bool operator() ( const Partial & p ) + { + double compareFreq = p.frequencyAt( timeOfPeakEnergy( p ) ); + return compareFreq >= minFreq && compareFreq <= maxFreq; + } +}; + + + +} // end of namespace Loris diff --git a/src/loris/FrequencyReference.h b/src/loris/FrequencyReference.h new file mode 100644 index 0000000..366c57f --- /dev/null +++ b/src/loris/FrequencyReference.h @@ -0,0 +1,132 @@ +#ifndef INCLUDE_FREQUENCYREFERENCE_H +#define INCLUDE_FREQUENCYREFERENCE_H +/* + * 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 + * + * + * FrequencyReference.h + * + * Definition of class FrequencyReference. + * + * Kelly Fitz, 3 Dec 2001 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Envelope.h" +#include "PartialList.h" +#include <memory> + +// begin namespace +namespace Loris { + +class LinearEnvelope; + +// --------------------------------------------------------------------------- +// class FrequencyReference +// +//! Class FrequencyReference represents a reference frequency envelope +//! derived from an estimate of the fundamental frequency of a given range +//! of Partials within in a specified frequency range. This reference envelope +//! can be used for channelizing the Partials in preparation for morphing +//! (see Channelizer.h). +//! +//! FrequencyReference implements the Envelope interface (see +//! Envelope.h). +// +class FrequencyReference : public Envelope +{ +// -- instance variables -- + std::auto_ptr< LinearEnvelope > _env; + +// -- public interface -- +public: +// -- construction -- + + //! Construct a new fundamental FrequencyReference derived from the + //! specified half-open (STL-style) range of Partials that lies + //! within the speficied average frequency range. Construct the + //! reference envelope with approximately numSamps points. + //! + //! \param begin The beginning of a range of Partials from which to + //! construct a frequency refence envelope. + //! \param end The end of a range of Partials from which to + //! construct a frequency refence envelope. + //! \param minFreq The minimum expected fundamental frequency. + //! \param maxFreq The maximum expected fundamental frequency. + //! \param numSamps The approximate number of estimate of the + //! fundamental frequency from which to construct the + //! frequency reference envelope. + FrequencyReference( PartialList::const_iterator begin, + PartialList::const_iterator end, + double minFreq, double maxFreq, long numSamps ); + + //! Construct a new fundamental FrequencyReference derived from the + //! specified half-open (STL-style) range of Partials that lies + //! within the speficied average frequency range. Construct the + //! reference envelope from fundamental estimates taken every + //! five milliseconds. + //! + //! \param begin The beginning of a range of Partials from which to + //! construct a frequency refence envelope. + //! \param end The end of a range of Partials from which to + //! construct a frequency refence envelope. + //! \param minFreq The minimum expected fundamental frequency. + //! \param maxFreq The maximum expected fundamental frequency. + FrequencyReference( PartialList::const_iterator begin, + PartialList::const_iterator end, + double minFreq, double maxFreq ); + + + //! Construct a new FrequencyReference that is an exact copy of the + //! specified FrequencyReference. + FrequencyReference( const FrequencyReference & other ); + + //! Assignment operator: make this FrequencyReference an exact copy + //! of the specified FrequencyReference. + FrequencyReference & operator= ( const FrequencyReference & other ); + + //! Destroy this FrequencyReference. + ~FrequencyReference(); + +// -- conversion to LinearEnvelope -- + + //! Return a LinearEnvelope that evaluates indentically to this + //! FrequencyReference at all time. + LinearEnvelope envelope( void ) const; + +// -- Envelope interface -- + + //! Return an exact copy of this FrequencyReference (following the + //! Prototype pattern). + virtual FrequencyReference * clone( void ) const; + + //! Return the frequency value (in Hz) of this FrequencyReference at the + //! specified time. + virtual double valueAt( double x ) const; + +}; // end of class FrequencyReference + +} // end of namespace Loris + +#endif // ndef INCLUDE_FREQUENCYREFERENCE_H diff --git a/src/loris/Fundamental.C b/src/loris/Fundamental.C new file mode 100644 index 0000000..cddb226 --- /dev/null +++ b/src/loris/Fundamental.C @@ -0,0 +1,712 @@ +/* + * 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 + * + * + * Fundamental.C + * + * Definition of classes for computing an estimate of time-varying + * fundamental frequency from either a sequence of samples or a + * collection of Partials using a frequency domain maximum likelihood + * algorithm adapted from Quatieri's speech signal processing textbook. + * + * Kelly Fitz, 25 March 2008 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Fundamental.h" + +#include "LorisExceptions.h" +#include "KaiserWindow.h" +#include "LinearEnvelope.h" +#include "Notifier.h" +#include "PartialUtils.h" +#include "ReassignedSpectrum.h" +#include "SpectralPeakSelector.h" + +#include "F0Estimate.h" + +#include <algorithm> +#include <cmath> +#include <vector> + +using namespace std; + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + + + +#define VERIFY_ARG(func, test) \ + do { \ + if (!(test)) \ + Throw( Loris::InvalidArgument, #func ": " #test ); \ + } while (false) + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// FundamentalEstimator members +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + + +// -- lifecycle -- + +// --------------------------------------------------------------------------- +// constructor (protected) +// --------------------------------------------------------------------------- +//! Construct a new estimator with specified precision and +//! other parameters given default values. +//! +//! The specified precision is used to terminate the iterative +//! estimation procedure. +//! +//! \param precisionHz is the precision in Hz with which the +//! fundamental estimates will be made. + +FundamentalEstimator::FundamentalEstimator( double precisionHz ) : + m_precision( precisionHz ), + m_ampFloor( DefaultAmpFloor ), + m_ampRange( DefaultAmpRange ), + m_freqCeiling( DefaultFreqCeiling ) +{ + VERIFY_ARG( FundamentalEstimator, precisionHz > 0 ); +} + +// --------------------------------------------------------------------------- +// destructor +// --------------------------------------------------------------------------- + +FundamentalEstimator::~FundamentalEstimator( void ) +{ +} + +// -- spectral analysis parameter access -- + +// --------------------------------------------------------------------------- +// ampFloor +// --------------------------------------------------------------------------- +//! Return the absolute amplitude threshold in (negative) dB, +//! below which spectral peaks will not be considered in the +//! estimation of the fundamental (default is 30 dB). +double +FundamentalEstimator::ampFloor( void ) const +{ + return m_ampFloor; +} + +// --------------------------------------------------------------------------- +// ampRange +// --------------------------------------------------------------------------- +//! Return the amplitude range in dB, +//! relative to strongest peak in a frame, floating +//! amplitude threshold below which spectral +//! peaks will not be considered in the estimation of +//! the fundamental (default is 30 dB). +// +double +FundamentalEstimator::ampRange( void ) const +{ + return m_ampRange; +} + +// --------------------------------------------------------------------------- +// freqCeiling +// --------------------------------------------------------------------------- +//! Return the frequency ceiling in Hz, the +//! frequency threshold above which spectral +//! peaks will not be considered in the estimation of +//! the fundamental (default is 10 kHz). +// +double +FundamentalEstimator::freqCeiling( void ) const +{ + return m_freqCeiling; +} + +// --------------------------------------------------------------------------- +// precision +// --------------------------------------------------------------------------- +//! Return the precision of the estimate in Hz, the +//! fundamental frequency will be estimated to +//! within this range (default is 0.1 Hz). +// +double +FundamentalEstimator::precision( void ) const +{ + return m_precision; +} + + +// -- spectral analysis parameter mutation -- + +// --------------------------------------------------------------------------- +// setAmpFloor +// --------------------------------------------------------------------------- +//! Set the absolute amplitude threshold in (negative) dB, +//! below which spectral peaks will not be considered in the +//! estimation of the fundamental (default is 30 dB). +//! +//! \param x is the new value of this parameter. +void +FundamentalEstimator::setAmpFloor( double x ) +{ + VERIFY_ARG( setAmpFloor, x < 0 ); + m_ampFloor = x; +} + +// --------------------------------------------------------------------------- +// setAmpRange +// --------------------------------------------------------------------------- +//! Set the amplitude range in dB, +//! relative to strongest peak in a frame, floating +//! amplitude threshold (negative) below which spectral +//! peaks will not be considered in the estimation of +//! the fundamental (default is 30 dB). +//! +//! \param x is the new value of this parameter. +// +void +FundamentalEstimator::setAmpRange( double x ) +{ + VERIFY_ARG( setAmpRange, x > 0 ); + m_ampRange = x; +} + +// --------------------------------------------------------------------------- +// setFreqCeiling +// --------------------------------------------------------------------------- +//! Set the frequency ceiling in Hz, the +//! frequency threshold above which spectral +//! peaks will not be considered in the estimation of +//! the fundamental (default is 10 kHz). Must be +//! greater than the lower bound. +//! +//! \param x is the new value of this parameter. +// +void +FundamentalEstimator::setFreqCeiling( double x ) +{ + m_freqCeiling = x; +} + +// --------------------------------------------------------------------------- +// setPrecision +// --------------------------------------------------------------------------- +//! Set the precision of the estimate in Hz, the +//! fundamental frequency will be estimated to +//! within this range (default is 0.1 Hz). +//! +//! \param x is the new value of this parameter. +// +void +FundamentalEstimator::setPrecision( double x ) +{ + VERIFY_ARG( setPrecision, x > 0 ); + m_precision = x; +} + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// FundamentalFromSamples members +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +// -- lifecycle -- + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new estimator configured with the given +//! analysis window width (main lobe, zero-to-zero). All other +//! spectrum analysis parameters are computed from the specified +//! window width. +//! +//! The specified precision is used to terminate the iterative +//! estimation procedure. If unspecified, the default value, +//! DefaultPrecisionOver100 * 100 is used. +//! +//! \param windowWidthHz is the main lobe width of the Kaiser +//! analysis window in Hz. +//! +//! \param precisionHz is the precision in Hz with which the +//! fundamental estimates will be made. + +FundamentalFromSamples::FundamentalFromSamples( double winWidthHz, + double precisionHz ) : + m_cacheSampleRate( 0 ), + m_windowWidth( winWidthHz ), + FundamentalEstimator( precisionHz ) +{ + VERIFY_ARG( FundamentalFromSamples, winWidthHz > 0 ); +} + +// --------------------------------------------------------------------------- +// destructor +// --------------------------------------------------------------------------- + +FundamentalFromSamples::~FundamentalFromSamples( void ) +{ +} + +// -- fundamental frequency estimation -- + + + +// --------------------------------------------------------------------------- +// buildEnvelope +// --------------------------------------------------------------------------- +//! Construct a linear envelope from fundamental frequency +//! estimates taken at the specified interval in seconds. +//! + +LinearEnvelope +FundamentalFromSamples::buildEnvelope( const double * sampsBeg, + const double * sampsEnd, + double sampleRate, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ) +{ + // sanity check + if ( tbeg > tend ) + { + std::swap( tbeg, tend ); + } + + LinearEnvelope env; + + std::vector< double > amplitudes, frequencies; + + double time = tbeg; + while ( time < tend ) + { + collectFreqsAndAmps( sampsBeg, sampsEnd-sampsBeg, sampleRate, + frequencies, amplitudes, time ); + if ( ! amplitudes.empty() ) + { + F0Estimate est( amplitudes, frequencies, lowerFreqBound, upperFreqBound, + m_precision ); + + if ( est.confidence() >= confidenceThreshold ) + { + env.insert( time, est.frequency() ); + } + } + + time += interval; + } + + return env; +} + + +// --------------------------------------------------------------------------- +// estimateAt +// --------------------------------------------------------------------------- +//! Return an estimate of the fundamental frequency computed +//! at the specified time. + +FundamentalFromSamples::value_type +FundamentalFromSamples::estimateAt( const double * sampsBeg, + const double * sampsEnd, + double sampleRate, + double time, + double lowerFreqBound, double upperFreqBound ) +{ + std::vector< double > amplitudes, frequencies; + + collectFreqsAndAmps( sampsBeg, sampsEnd-sampsBeg, sampleRate, + frequencies, amplitudes, time ); + + F0Estimate est( amplitudes, frequencies, lowerFreqBound, upperFreqBound, m_precision ); + + return est; +} + +// -- spectral analysis parameter access/mutation -- + +// --------------------------------------------------------------------------- +// windowWidth +// --------------------------------------------------------------------------- +//! Return the frequency-domain main lobe width (in Hz) (measured between +//! zero-crossings) of the analysis window used in spectral +//! analysis. +double FundamentalFromSamples::windowWidth( void ) const +{ + return m_windowWidth; +} + +// --------------------------------------------------------------------------- +// setWindowWidth +// --------------------------------------------------------------------------- +//! Set the frequency-domain main lobe width (in Hz) (measured between +//! zero-crossings) of the analysis window used in spectral +//! analysis. +//! +//! \param x is the new value of this parameter. +void FundamentalFromSamples::setWindowWidth( double x ) +{ + VERIFY_ARG( setWindowWidth, x > 0 ); + m_windowWidth = x; +} + + + +// -- private auxiliary functions -- + +// --------------------------------------------------------------------------- +// buildSpectrumAnalyzer +// --------------------------------------------------------------------------- +//! Construct the ReassignedSpectrum that will be used to perform +//! spectral analysis from which peak frequencies and amplitudes +//! will be drawn. This construction is performed in a lazy fashion, +//! and needs to be done again when certain of the parameters change. +//! +//! The spectrum analyzer cannot be constructed without knowledge of +//! the sample rate, specified in Hz, which is needed to determine the +//! parameters of the analysis window. (The sample rate is cached in +//! this class in order that it be possible to determine whether the +//! spectrum analyzer can be reused from one estimate to another.) +// +void +FundamentalFromSamples::buildSpectrumAnalyzer( double srate ) +{ + // configure the reassigned spectral analyzer, + // always use odd-length windows: + const double sidelobeLevel = - m_ampFloor; // amp floor is negative + double winshape = KaiserWindow::computeShape( sidelobeLevel ); + long winlen = KaiserWindow::computeLength( m_windowWidth / srate, winshape ); + if ( 1 != (winlen % 2) ) + { + ++winlen; + } + + std::vector< double > window( winlen ); + KaiserWindow::buildWindow( window, winshape ); + + std::vector< double > windowDeriv( winlen ); + KaiserWindow::buildTimeDerivativeWindow( windowDeriv, winshape ); + + m_spectrum.reset( new ReassignedSpectrum( window, windowDeriv ) ); + + // remember the sample rate used to build this spectrum + // analyzer: + m_cacheSampleRate = srate; +} + +// --------------------------------------------------------------------------- +// sort_peaks_greater_amplitude +// --------------------------------------------------------------------------- +// predicate used for sorting peaks in order of decreasing amplitude: +static bool sort_peaks_greater_amplitude( const SpectralPeak & lhs, + const SpectralPeak & rhs ) +{ + return lhs.amplitude() > rhs.amplitude(); +} + +// --------------------------------------------------------------------------- +// collectFreqsAndAmps +// --------------------------------------------------------------------------- +//! Perform spectral analysis on a sequence of samples, using +//! an analysis window centered at the specified time in seconds. +//! Collect the frequencies and amplitudes of the peaks and return +//! them in the vectors provided. +// + +void +FundamentalFromSamples::collectFreqsAndAmps( const double * samps, + unsigned long nsamps, + double sampleRate, + std::vector< double > & frequencies, + std::vector< double > & amplitudes, + double time ) +{ + amplitudes.clear(); + frequencies.clear(); + + // build the spectrum analyzer if necessary: + if ( m_cacheSampleRate != sampleRate || + 0 == m_spectrum.get() ) + { + buildSpectrumAnalyzer( sampleRate ); + } + + + // configure the peak selection and partial formation policies: + unsigned long winlen = m_spectrum->window().size(); + const double maxTimeCorrection = 0.25 * winlen / sampleRate; // one-quarter the window width + SpectralPeakSelector selector( sampleRate, maxTimeCorrection ); + + + + // compute reassigned spectrum: + // sampsBegin is the position of the first sample to be transformed, + // sampsEnd is the position after the last sample to be transformed. + // (these computations work for odd length windows only) + unsigned long winMiddle = (unsigned long)( sampleRate * time ); + unsigned long sampsBegin = std::max( long(winMiddle) - long(winlen / 2), 0L ); + unsigned long sampsEnd = std::min( winMiddle + (winlen / 2) + 1, nsamps ); + + if ( winMiddle < sampsEnd ) + { + m_spectrum->transform( samps + sampsBegin, samps + winMiddle, samps + sampsEnd ); + + // extract peaks from the spectrum, no fading: + Peaks peaks = selector.selectPeaks( *m_spectrum ); + + if ( ! peaks.empty() ) + { + // sort the peaks in order of decreasing amplitude + // + // (HEY is there any reason to do this, other than to find the largest?) + //std::sort( peaks.begin(), peaks.end(), sort_peaks_greater_amplitude ); + Peaks::iterator maxpos = std::max_element( peaks.begin(), peaks.end(), sort_peaks_greater_amplitude ); + + // determine the floating amplitude threshold + const double thresh = + std::max( std::pow( 10.0, - 0.05 * - m_ampFloor ), + std::pow( 10.0, - 0.05 * m_ampRange ) * maxpos->amplitude() ); + + // collect amplitudes and frequencies and try to + // estimate the fundamental + for ( Peaks::const_iterator spkpos = peaks.begin(); spkpos != peaks.end(); ++spkpos ) + { + if ( spkpos->amplitude() > thresh && + spkpos->frequency() < m_freqCeiling ) + { + amplitudes.push_back( spkpos->amplitude() ); + frequencies.push_back( spkpos->frequency() ); + } + } + } + } +} + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// FundamentalFromPartials members +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new estimator configured with the given precision. +//! The specified precision is used to terminate the iterative +//! estimation procedure. If unspecified, the default value, +//! DefaultPrecisionOver100 * 100 is used. +//! +//! \param precisionHz is the precision in Hz with which the +//! fundamental estimates will be made. + +FundamentalFromPartials::FundamentalFromPartials( double precisionHz ) : + FundamentalEstimator( precisionHz ) +{ +} + +// --------------------------------------------------------------------------- +// copy constructor +// --------------------------------------------------------------------------- +//! Construct a copy of an estimator. Nothing much to do since this class +//! has no data members. +// + +FundamentalFromPartials::FundamentalFromPartials( const FundamentalFromPartials & rhs ) : + FundamentalEstimator( rhs ) +{ +} + +// --------------------------------------------------------------------------- +// destructor +// --------------------------------------------------------------------------- + +FundamentalFromPartials::~FundamentalFromPartials( void ) +{ +} + +// --------------------------------------------------------------------------- +// assignment +// --------------------------------------------------------------------------- +//! Pass the assignment opertion up to the base class. +// + +FundamentalFromPartials & +FundamentalFromPartials::operator=( const FundamentalFromPartials & rhs ) +{ + FundamentalEstimator::operator=( rhs ); + + return *this; +} + +// -- fundamental frequency estimation -- + +// --------------------------------------------------------------------------- +// buildEnvelope +// --------------------------------------------------------------------------- +//! Construct a linear envelope from fundamental frequency +//! estimates taken at the specified interval in seconds. +//! + +LinearEnvelope +FundamentalFromPartials::buildEnvelope( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ) +{ + // sanity check + if ( tbeg > tend ) + { + std::swap( tbeg, tend ); + } + + LinearEnvelope env; + + std::vector< double > amplitudes, frequencies; + + double time = tbeg; + while ( time < tend ) + { + collectFreqsAndAmps( begin_partials, end_partials, frequencies, amplitudes, time ); + + if (! amplitudes.empty() ) + { + F0Estimate est( amplitudes, frequencies, lowerFreqBound, upperFreqBound, + m_precision ); + + if ( est.confidence() >= confidenceThreshold ) + { + env.insert( time, est.frequency() ); + } + } + + time += interval; + } + + return env; +} + +// --------------------------------------------------------------------------- +// estimateAt +// --------------------------------------------------------------------------- +//! Return an estimate of the fundamental frequency computed +//! at the specified time. + +FundamentalFromPartials::value_type +FundamentalFromPartials::estimateAt( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double time, + double lowerFreqBound, double upperFreqBound ) +{ + std::vector< double > amplitudes, frequencies; + + collectFreqsAndAmps( begin_partials, end_partials, frequencies, amplitudes, time ); + + F0Estimate est( amplitudes, frequencies, lowerFreqBound, upperFreqBound, m_precision ); + + return est; +} + + +// -- private auxiliary functions -- + +// --------------------------------------------------------------------------- +// collectFreqsAndAmps +// --------------------------------------------------------------------------- +//! Perform spectral analysis on a sequence of samples, using +//! an analysis window centered at the specified time in seconds. +//! Collect the frequencies and amplitudes of the peaks and return +//! them in the vectors provided. +// + +void +FundamentalFromPartials::collectFreqsAndAmps( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + std::vector< double > & frequencies, + std::vector< double > & amplitudes, + double time ) +{ + amplitudes.clear(); + frequencies.clear(); + + if ( begin_partials != end_partials ) + { + // determine the absolute amplitude threshold + double thresh = std::pow( 10.0, - 0.05 * - m_ampFloor ); + + double max_amp = 0; + for ( PartialList::const_iterator it = begin_partials; it != end_partials; ++it ) + { + // compute the sinusoidal amplitude (without bandwidth energy) + double sine_amp = std::sqrt(1 - it->bandwidthAt( time )) * it->amplitudeAt( time ); + double freq = it->frequencyAt( time ); + + if ( sine_amp > thresh && + freq < m_freqCeiling ) + { + amplitudes.push_back( sine_amp ); + frequencies.push_back( freq ); + } + + max_amp = std::max( sine_amp, max_amp ); + } + + // remove quietest ones - this isn't very efficient, + // but it is much faster than making two passes (and + // computing two sequences of sinusoidal amplitudes). + thresh = std::pow( 10.0, - 0.05 * m_ampRange ) * max_amp; + vector< double >::size_type N = amplitudes.size(); + vector< double >::size_type k = 0; + while ( k < N ) + { + if ( amplitudes[k] < thresh ) + { + amplitudes.erase( amplitudes.begin() + k ); + frequencies.erase( frequencies.begin() + k ); + --N; + } + else + { + ++k; + } + } + } + +} + +} // end of namespace Loris diff --git a/src/loris/Fundamental.h b/src/loris/Fundamental.h new file mode 100644 index 0000000..5971408 --- /dev/null +++ b/src/loris/Fundamental.h @@ -0,0 +1,737 @@ +#ifndef INCLUDE_FUNDAMENTAL_H +#define INCLUDE_FUNDAMENTAL_H +/* + * 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 + * + * + * Fundamental.h + * + * Definition of classes for computing an estimate of time-varying + * fundamental frequency from either a sequence of samples or a + * collection of Partials using a frequency domain maximum likelihood + * algorithm adapted from Quatieri's speech signal processing textbook. + * + * Kelly Fitz, 25 March 2008 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "LinearEnvelope.h" +#include "PartialList.h" +#include "F0Estimate.h" + +#include <memory> +#include <vector> + +// begin namespace +namespace Loris { + +class ReassignedSpectrum; + +// --------------------------------------------------------------------------- +// class FundamentalEstimator +// +//! Base class for fundamental estimation, common storage for member +//! variable parameters, type definitions, and constants. + +class FundamentalEstimator +{ +public: + +// -- types -- + + typedef F0Estimate value_type; + +// -- constants -- + + enum { + + DefaultAmpFloor = -60, //! the default absolute amplitude threshold in dB + + DefaultAmpRange = 30, //! the default floating amplitude threshold in dB + + DefaultFreqCeiling = 4000, //! the default frequency threshold in Hz + + DefaultPrecisionOver100 = 10, //! the default frequency precision in 1/100 Hz + + DefaultMinConfidencePct = 90 //! the default required percent confidence to + //! return an estimate (100 is absolute confidence) + }; + + +// -- lifecycle -- + +protected: + + //! Construct a new estimator with specified precision and + //! other parameters given default values. + //! + //! The specified precision is used to terminate the iterative + //! estimation procedure. + //! + //! \param precisionHz is the precision in Hz with which the + //! fundamental estimates will be made. + FundamentalEstimator( double precisionHz ); + +public: + + //! Destructor + virtual ~FundamentalEstimator( void ); + + + // compiler-generated copy and assignment are OK + + +// -- parameter access -- + + //! Return the absolute amplitude threshold in (negative) dB, + //! below which spectral peaks will not be considered in the + //! estimation of the fundamental (default is 30 dB). + double ampFloor( void ) const; + + //! Return the amplitude range in dB, + //! relative to strongest peak in a frame, floating + //! amplitude threshold (negative) below which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is 30 dB). + double ampRange( void ) const; + + //! Return the frequency ceiling in Hz, the + //! frequency threshold above which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is 10 kHz). + double freqCeiling( void ) const; + + //! Return the precision of the estimate in Hz, the + //! fundamental frequency will be estimated to + //! within this range (default is 0.1 Hz). + double precision( void ) const; + +// -- parameter mutation -- + + //! Set the absolute amplitude threshold in (negative) dB, + //! below which spectral peaks will not be considered in the + //! estimation of the fundamental (default is 30 dB). + //! + //! \param x is the new value of this parameter. + void setAmpFloor( double x ); + + //! Set the amplitude range in dB, + //! relative to strongest peak in a frame, floating + //! amplitude threshold (negative) below which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is 30 dB). + //! + //! \param x is the new value of this parameter. + void setAmpRange( double x ); + + //! Set the frequency ceiling in Hz, the + //! frequency threshold above which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is 10 kHz). Must be + //! greater than the lower bound. + //! + //! \param x is the new value of this parameter. + void setFreqCeiling( double x ); + + //! Set the precision of the estimate in Hz, the + //! fundamental frequency will be estimated to + //! within this range (default is 0.1 Hz). + //! + //! \param x is the new value of this parameter. + void setPrecision( double x ); + + +protected: + +// -- parameter member variables -- + + + double m_precision; //! in Hz, fundamental frequency will be estimated to + //! within this range (default is 0.1 Hz) + + double m_ampFloor; //! absolute amplitude threshold below which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is equivalent to 60 dB + //! quieter than a full scale sinusoid) + + double m_ampRange; //! floating amplitude threshold relative to the peak + //! having the largest magnitude below which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is equivalent to 30 dB) + + double m_freqCeiling; //! in Hz, frequency threshold above which spectral + //! peaks will not be considered in the estimation of + //! the fundamental (default is 10 kHz) + + +}; // end of base class FundamentalEstimator + + + +// --------------------------------------------------------------------------- +// class FundamentalFromSamples +// +//! Class FundamentalFromSamples represents an algorithm for +//! time-varying fundamental frequency estimation based on +//! time-frequency reassigned spectral analysis of a sequence +//! of samples. This class is adapted from the Analyzer class +//! (see Analyzer.h), and performs the same spectral analysis +//! and peak extraction, but does not form Partials. +//! +//! For more information about Reassigned Bandwidth-Enhanced +//! Analysis and the Reassigned Bandwidth-Enhanced Additive Sound +//! Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. +// +class FundamentalFromSamples : public FundamentalEstimator +{ +// -- public interface -- + +public: + +// -- lifecycle -- + + //! Construct a new estimator configured with the given + //! analysis window width (main lobe, zero-to-zero). All other + //! spectrum analysis parameters are computed from the specified + //! window width. + //! + //! The specified precision is used to terminate the iterative + //! estimation procedure. If unspecified, the default value, + //! DefaultPrecisionOver100 * 100 is used. + //! + //! \param windowWidthHz is the main lobe width of the Kaiser + //! analysis window in Hz. + //! + //! \param precisionHz is the precision in Hz with which the + //! fundamental estimates will be made. + FundamentalFromSamples( double winWidthHz, + double precisionHz = DefaultPrecisionOver100 * 0.01 ); + + + + //! Destructor + ~FundamentalFromSamples( void ); + +// -- fundamental frequency estimation -- + + // buildEnvelope + // + //! Construct a linear envelope from fundamental frequency + //! estimates taken at the specified interval in seconds + //! starting at tbeg (seconds) and ending before tend (seconds). + //! + //! \param samps is the beginning of a sequence of samples + //! \param sampsEnd is the end of the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param tbeg is the beginning of the time interval (in seconds) + //! \param tend is the end of the time interval (in seconds) + //! \param interval is the time between breakpoints in the + //! fundamental frequency envelope (in seconds) + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param confidenceThreshold is the minimum confidence level + //! resuired for a fundamental frequency estimate to be + //! added to the envelope. Lower confidence estimates are + //! not added, the envelope returned will not contain + //! breakpoints at times associated with low confidence + //! estimates + //! \return a LinearEnvelope composed of breakpoints corresponding to + //! the fundamental frequency estimates at samples of the span + //! tbeg to tend at the specified sampling interval, only estimates + //! having confidence level exceeding the specified confidence + //! threshold are added to the envelope + LinearEnvelope buildEnvelope( const double * sampsBeg, + const double * sampsEnd, + double sampleRate, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ); + + // buildEnvelope + // + //! Construct a linear envelope from fundamental frequency + //! estimates taken at the specified interval in seconds + //! starting at tbeg (seconds) and ending before tend (seconds). + //! + //! \param samps is the beginning of a sequence of samples + //! \param nsamps is the length of the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param tbeg is the beginning of the time interval (in seconds) + //! \param tend is the end of the time interval (in seconds) + //! \param interval is the time between breakpoints in the + //! fundamental frequency envelope (in seconds) + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param confidenceThreshold is the minimum confidence level + //! resuired for a fundamental frequency estimate to be + //! added to the envelope. Lower confidence estimates are + //! not added, the envelope returned will not contain + //! breakpoints at times associated with low confidence + //! estimates + //! \return a LinearEnvelope composed of breakpoints corresponding to + //! the fundamental frequency estimates at samples of the span + //! tbeg to tend at the specified sampling interval, only estimates + //! having confidence level exceeding the specified confidence + //! threshold are added to the envelope + LinearEnvelope buildEnvelope( const double * sampsBeg, + unsigned long nsamps, + double sampleRate, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ) + { + return buildEnvelope( sampsBeg, sampsBeg + nsamps, sampleRate, + tbeg, tend, interval, + lowerFreqBound, upperFreqBound, + confidenceThreshold ); + } + + + // buildEnvelope + // + //! Construct a linear envelope from fundamental frequency + //! estimates taken at the specified interval in seconds + //! starting at tbeg (seconds) and ending before tend (seconds). + //! + //! \param samps is the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param tbeg is the beginning of the time interval (in seconds) + //! \param tend is the end of the time interval (in seconds) + //! \param interval is the time between breakpoints in the + //! fundamental frequency envelope (in seconds) + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param confidenceThreshold is the minimum confidence level + //! resuired for a fundamental frequency estimate to be + //! added to the envelope. Lower confidence estimates are + //! not added, the envelope returned will not contain + //! breakpoints at times associated with low confidence + //! estimates + //! \return a LinearEnvelope composed of breakpoints corresponding to + //! the fundamental frequency estimates at samples of the span + //! tbeg to tend at the specified sampling interval, only estimates + //! having confidence level exceeding the specified confidence + //! threshold are added to the envelope + LinearEnvelope buildEnvelope( const std::vector< double > & samps, + double sampleRate, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ) + { + return buildEnvelope( &samps[0], &samps[0] + samps.size(), sampleRate, + tbeg, tend, interval, + lowerFreqBound, upperFreqBound, + confidenceThreshold ); + } + + + // estimateAt + // + //! Return an estimate of the fundamental frequency computed + //! at the specified time. The F0Estimate returned stores the + //! estimate of the fundamental frequency (in Hz) and the + //! relative confidence (from 0 to 1) associated with that + //! estimate. + //! + //! \param sampsBeg is the beginning of a sequence of samples + //! \param sampsEnd is the end of the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param time is the time in seconds at which to attempt to estimate + //! the fundamental frequency + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \return the estimate of fundamental frequency in Hz and the + //! confidence associated with that estimate (see + //! F0Estimate.h) + + value_type estimateAt( const double * sampsBeg, + const double * sampsEnd, + double sampleRate, + double time, + double lowerFreqBound, double upperFreqBound ); + + // estimateAt + // + //! Return an estimate of the fundamental frequency computed + //! at the specified time. The F0Estimate returned stores the + //! estimate of the fundamental frequency (in Hz) and the + //! relative confidence (from 0 to 1) associated with that + //! estimate. + //! + //! \param samps is the beginning of a sequence of samples + //! \param nsamps is the length of the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param time is the time in seconds at which to attempt to estimate + //! the fundamental frequency + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \return the estimate of fundamental frequency in Hz and the + //! confidence associated with that estimate (see + //! F0Estimate.h) + + value_type estimateAt( const double * sampsBeg, + unsigned long nsamps, + double sampleRate, + double time, + double lowerFreqBound, double upperFreqBound ) + { + return estimateAt( sampsBeg, sampsBeg + nsamps, sampleRate, + time, lowerFreqBound, upperFreqBound ); + } + + // estimateAt + // + //! Return an estimate of the fundamental frequency computed + //! at the specified time. The F0Estimate returned stores the + //! estimate of the fundamental frequency (in Hz) and the + //! relative confidence (from 0 to 1) associated with that + //! estimate. + //! + //! \param samps is the sequence of samples + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param time is the time in seconds at which to attempt to estimate + //! the fundamental frequency + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \return the estimate of fundamental frequency in Hz and the + //! confidence associated with that estimate (see + //! F0Estimate.h) + + value_type estimateAt( const std::vector< double > & samps, + double sampleRate, + double time, + double lowerFreqBound, double upperFreqBound ) + { + return estimateAt( &samps[0], &samps[0] + samps.size(), sampleRate, + time, lowerFreqBound, upperFreqBound ); + } + + + +// -- spectral analysis parameter access/mutation -- + + //! Return the frequency-domain main lobe width (in Hz measured + //! between zero-crossings) of the analysis window used in spectral + //! analysis. + double windowWidth( void ) const; + + //! Set the frequency-domain main lobe width (in Hz measured + //! between zero-crossings) of the analysis window used in spectral + //! analysis. + //! + //! \param w is the new main lobe width in Hz + void setWindowWidth( double w ); + + + +// -- private auxiliary functions -- + +private: + + // buildSpectrumAnalyzer + // + //! Construct the ReassignedSpectrum that will be used to perform + //! spectral analysis from which peak frequencies and amplitudes + //! will be drawn. This construction is performed in a lazy fashion, + //! and needs to be done again when certain of the parameters change. + //! + //! \param srate is the sampling frequency in Hz, needed to compute + //! analysis window parameters + void buildSpectrumAnalyzer( double srate ); + + + // collectFreqsAndAmps + // + //! Perform spectral analysis on a sequence of samples, using + //! an analysis window centered at the specified time in seconds. + //! Collect the frequencies and amplitudes of the peaks and return + //! them in the vectors provided. + //! + //! \param samps is the beginning of a sequence of samples + //! \param nsamps is the length of the sequence of Partials + //! \param sampleRate is the sampling rate (in Hz) associated + //! with the sequence of samples (used to compute frequencies + //! in Hz, and to convert the time from seconds to samples) + //! \param frequencies is a vector in which to store a sequence of + //! frequencies to be used to estimate the most likely + //! fundamental frequency + //! \param amplitudes is a vector in which to store a sequence of + //! amplitudes to be used to estimate the most likely + //! fundamental frequency + //! \param time is the time in seconds at which to collect frequencies + //! and amplitudes of spectral peaks + + void collectFreqsAndAmps( const double * samps, + unsigned long nsamps, + double sampleRate, + std::vector< double > & frequencies, + std::vector< double > & amplitudes, + double time ); + + +// -- private member variables -- + + std::auto_ptr< ReassignedSpectrum > m_spectrum; + //! the spectrum analyzer + + double m_cacheSampleRate; //! the sample rate used to construct the + + double m_windowWidth; //! the width of the main lobe of the window to + //! be used in spectral analysis, in Hz + +// disallow these until they are implemented + + FundamentalFromSamples( const FundamentalFromSamples & ); + FundamentalFromSamples & operator= ( const FundamentalFromSamples & ); + +}; // end of class FundamentalFromSamples + + + +// --------------------------------------------------------------------------- +// class FundamentalFromPartials +// +//! Class FundamentalFromPartials represents an algorithm for +//! time-varying fundamental frequency estimation from instantaneous +//! Partial amplitudes and frequencies based on a likelihood +//! estimator adapted from Quatieri's Speech Signal Processing text + +class FundamentalFromPartials : public FundamentalEstimator +{ +// -- public interface -- + +public: + +// -- lifecycle -- + + //! Construct a new estimator. + //! + //! The specified precision is used to terminate the iterative + //! estimation procedure. If unspecified, the default value, + //! DefaultPrecisionOver100 * 100 is used. + //! + //! \param precisionHz is the precision in Hz with which the + //! fundamental estimates will be made. + FundamentalFromPartials( double precisionHz = DefaultPrecisionOver100 * 0.01 ); + + + //! Destructor + ~FundamentalFromPartials( void ); + + //! Construct a copy of an estimator. Nothing much to do since this class + //! has no data members. + FundamentalFromPartials( const FundamentalFromPartials & ); + + //! Pass the assignment opertion up to the base class. + FundamentalFromPartials & operator= ( const FundamentalFromPartials & ); + +// -- fundamental frequency estimation -- + + // buildEnvelope + // + //! Construct a linear envelope from fundamental frequency + //! estimates taken at the specified interval in seconds + //! starting at tbeg (seconds) and ending before tend (seconds). + //! + //! \param begin_partials is the beginning of a sequence of Partials + //! \param end_partials is the end of a sequence of Partials + //! \param tbeg is the beginning of the time interval (in seconds) + //! \param tend is the end of the time interval (in seconds) + //! \param interval is the time between breakpoints in the + //! fundamental frequency envelope (in seconds) + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param confidenceThreshold is the minimum confidence level + //! resuired for a fundamental frequency estimate to be + //! added to the envelope. Lower confidence estimates are + //! not added, the envelope returned will not contain + //! breakpoints at times associated with low confidence + //! estimates + //! \return a LinearEnvelope composed of breakpoints corresponding to + //! the fundamental frequency estimates at samples of the span + //! tbeg to tend at the specified sampling interval, only estimates + //! having confidence level exceeding the specified confidence + //! threshold are added to the envelope + LinearEnvelope buildEnvelope( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ); + + // buildEnvelope + // + //! Construct a linear envelope from fundamental frequency + //! estimates taken at the specified interval in seconds + //! starting at tbeg (seconds) and ending before tend (seconds). + //! + //! \param partials is the sequence of Partials + //! \param tbeg is the beginning of the time interval (in seconds) + //! \param tend is the end of the time interval (in seconds) + //! \param interval is the time between breakpoints in the + //! fundamental frequency envelope (in seconds) + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param confidenceThreshold is the minimum confidence level + //! resuired for a fundamental frequency estimate to be + //! added to the envelope. Lower confidence estimates are + //! not added, the envelope returned will not contain + //! breakpoints at times associated with low confidence + //! estimates + //! \return a LinearEnvelope composed of breakpoints corresponding to + //! the fundamental frequency estimates at samples of the span + //! tbeg to tend at the specified sampling interval, only estimates + //! having confidence level exceeding the specified confidence + //! threshold are added to the envelope + LinearEnvelope buildEnvelope( const PartialList & partials, + double tbeg, double tend, + double interval, + double lowerFreqBound, double upperFreqBound, + double confidenceThreshold ) + { + return buildEnvelope( partials.begin(), partials.end(), + tbeg, tend, interval, + lowerFreqBound, upperFreqBound, + confidenceThreshold ); + } + + + // estimateAt + // + //! Return an estimate of the fundamental frequency computed + //! at the specified time. The F0Estimate returned stores the + //! estimate of the fundamental frequency (in Hz) and the + //! relative confidence (from 0 to 1) associated with that + //! estimate. + //! + //! \param begin_partials is the beginning of a sequence of Partials + //! \param end_partials is the end of a sequence of Partials + //! \param time is the time in seconds at which to attempt to estimate + //! the fundamental frequency + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \return the estimate of fundamental frequency in Hz and the + //! confidence associated with that estimate (see + //! F0Estimate.h) + value_type estimateAt( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double time, + double lowerFreqBound, double upperFreqBound ); + + // estimateAt + // + //! Return an estimate of the fundamental frequency computed + //! at the specified time. The F0Estimate returned stores the + //! estimate of the fundamental frequency (in Hz) and the + //! relative confidence (from 0 to 1) associated with that + //! estimate. + //! + //! \param partials is the sequence of Partials + //! \param time is the time in seconds at which to attempt to estimate + //! the fundamental frequency + //! \param lowerFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \param upperFreqBound is the lower bound on the fundamental + //! frequency estimate (in Hz) + //! \return the estimate of fundamental frequency in Hz and the + //! confidence associated with that estimate (see + //! F0Estimate.h) + value_type estimateAt( const PartialList & partials, + double time, + double lowerFreqBound, double upperFreqBound ) + { + return estimateAt( partials.begin(), partials.end(), + time, + lowerFreqBound, upperFreqBound ); + } + + + +// -- private auxiliary functions -- + +private: + + // collectFreqsAndAmps + // + //! Collect the frequencies and amplitudes of a range of partials + //! at the specified time and return them in the vectors provided. + //! + //! \param begin_partials is the beginning of a sequence of Partials + //! \param end_partials is the end of a sequence of Partials + //! \param frequencies is a vector in which to store a sequence of + //! frequencies to be used to estimate the most likely + //! fundamental frequency + //! \param amplitudes is a vector in which to store a sequence of + //! amplitudes to be used to estimate the most likely + //! fundamental frequency + //! \param time is the time in seconds at which to collect frequencies + //! and amplitudes of the Partials + void collectFreqsAndAmps( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + std::vector< double > & frequencies, + std::vector< double > & amplitudes, + double time ); + + + +}; // end of class FundamentalFromPartials + + + +} // end of namespace Loris + +#endif // ndef INCLUDE_FUNDAMENTAL_H diff --git a/src/loris/Harmonifier.C b/src/loris/Harmonifier.C new file mode 100644 index 0000000..3a41c58 --- /dev/null +++ b/src/loris/Harmonifier.C @@ -0,0 +1,158 @@ +/* + * 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 + * + * + * Harmonifier.h + * + * Definition of class Harmonifier. + * + * Kelly Fitz, 26 Oct 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "Harmonifier.h" + +#include "LinearEnvelope.h" + +#include <cmath> // for pow + +using namespace Loris; + + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- +//! Construct a new Harmonifier that applies the specified +//! reference Partial to fix the frequencies of Breakpoints +//! whose amplitude is below threshold_dB (0 by default, +//! to apply only to quiet Partials, specify a threshold, +//! like -90). +// +Harmonifier::Harmonifier( const Partial & ref, double threshold_dB ) : + _refPartial( ref ), + _freqFixThresholdDb( threshold_dB ), + _weight( createDefaultEnvelope() ) +{ + if ( 0 == _refPartial.numBreakpoints() ) + { + Throw( InvalidArgument, + "Cannot use an empty reference Partial in Harmonizer" ); + } + if ( 0 == _refPartial.label() ) + { + // if the reference is unlabeled, assume that it is the fundamental + _refPartial.setLabel( 1 ); + } + +} + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- +//! Construct a new Harmonifier that applies the specified +//! reference Partial to fix the frequencies of Breakpoints +//! whose amplitude is below threshold_dB (0 by default, +//! to apply only to quiet Partials, specify a threshold, +//! like -90). The Envelope is a time-varying weighting +//! on the harmonifing process. When 1, harmonic frequencies +//! are used, when 0, breakpoint frequencies are unmodified. +// +Harmonifier::Harmonifier( const Partial & ref, const Envelope & env, + double threshold_dB ) : + _refPartial( ref ), + _freqFixThresholdDb( threshold_dB ), + _weight( env.clone() ) +{ + if ( 0 == _refPartial.numBreakpoints() ) + { + Throw( InvalidArgument, + "Cannot use an empty reference Partial in Harmonizer" ); + } + if ( 0 == _refPartial.label() ) + { + // if the reference is unlabeled, assume that it is the fundamental + _refPartial.setLabel( 1 ); + } + +} + +// --------------------------------------------------------------------------- +// Destructor +// --------------------------------------------------------------------------- +Harmonifier::~Harmonifier( void ) +{ +} + +// --------------------------------------------------------------------------- +// harmonify +// --------------------------------------------------------------------------- +//! Apply the reference envelope to a Partial. +//! +//! \pre The Partial p must be labeled with its harmonic number. +// +void Harmonifier::harmonify( Partial & p ) const +{ + // compute absolute magnitude thresholds: + static const double FadeRangeDB = 10; + const double BeginFade = std::pow( 10., 0.05 * (_freqFixThresholdDb+FadeRangeDB) ); + const double Threshold = std::pow( 10., 0.05 * _freqFixThresholdDb ); + const double OneOverFadeSpan = 1. / ( BeginFade - Threshold ); + + double fscale = (double)p.label() / _refPartial.label(); + + for ( Partial::iterator it = p.begin(); it != p.end(); ++it ) + { + Breakpoint & bp = it.breakpoint(); + + if ( bp.amplitude() < BeginFade ) + { + // alpha is the harmonic frequency weighting: + // when alpha is 1, the harmonic frequency is used, + // when alpha is 0, the breakpoint frequency is + // unmodified. + double alpha = + std::min( ( BeginFade - bp.amplitude() ) * OneOverFadeSpan, 1. ); + + // alpha is scaled by the weigthing envelope + alpha *= _weight->valueAt( it.time() ); + + double fRef = _refPartial.frequencyAt( it.time() ); + + bp.setFrequency( ( alpha * ( fRef * fscale ) ) + + ( (1 - alpha) * bp.frequency() ) ); + } + + } +} + +// --------------------------------------------------------------------------- +// createDefaultEnvelope (STATIC) +// --------------------------------------------------------------------------- +//! Return the default weighing envelope (always 1). +//! Used in template constructors. +// +Envelope * Harmonifier::createDefaultEnvelope( void ) +{ + return new LinearEnvelope( 1 ); +} + diff --git a/src/loris/Harmonifier.h b/src/loris/Harmonifier.h new file mode 100644 index 0000000..b1b04d8 --- /dev/null +++ b/src/loris/Harmonifier.h @@ -0,0 +1,441 @@ +#ifndef INCLUDE_HARMONIFIER_H +#define INCLUDE_HARMONIFIER_H +/* + * 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 + * + * + * Harmonifier.h + * + * Definition of class Harmonifier. + * + * Kelly Fitz, 26 Oct 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "Envelope.h" +#include "LorisExceptions.h" +#include "Partial.h" +#include "PartialUtils.h" + +#include <algorithm> // for find +#include <memory> // for auto_ptr + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// Class Harmonifier +// +//! A Harmonifier uses a reference frequency envelope to make the +//! frequencies of labeled Partials harmonic. The amount of frequency +//! adjustment can be controlled by a time-varying envelope, and a +//! threshold can be supplied so that only quiet Partials are affected. +// +class Harmonifier +{ +// -- instance variables -- + + Partial _refPartial; //! the Partial whose frequency supplies the + //! reference frequency envelope. + + double _freqFixThresholdDb; //! amplitude threshold below which Partial + //! frequencies are corrected according to + //! a reference Partial, if specified. + + std::auto_ptr< Envelope > _weight; //! weighting function, when 1 harmonic + //! frequencies are used, when 0 breakpoint + //! frequencies are unmodified. + +// -- public interface -- +public: + +// -- lifecycle -- + + //! Construct a new Harmonifier that applies the specified + //! reference Partial to fix the frequencies of Breakpoints + //! whose amplitude is below threshold_dB (0 by default, + //! to apply only to quiet Partials, specify a threshold, + //! like -90). + Harmonifier( const Partial & ref, double threshold_dB = 0 ); + + //! Construct a new Harmonifier that applies the specified + //! reference Partial to fix the frequencies of Breakpoints + //! whose amplitude is below threshold_dB (0 by default, + //! to apply only to quiet Partials, specify a threshold, + //! like -90). The Envelope is a time-varying weighting + //! on the harmonifing process. When 1, harmonic frequencies + //! are used, when 0, breakpoint frequencies are unmodified. + Harmonifier( const Partial & ref, const Envelope & env, + double threshold_dB = 0 ); + + //! Construct a new Harmonifier that applies the specified + //! reference Partial to fix the frequencies of Breakpoints + //! whose amplitude is below threshold_dB (0 by default, + //! to apply only to quiet Partials, specify a threshold, + //! like -90). The reference Partial is the first Partial + //! in the range [b,e) having the specified label. + // + //! \throw InvalidArgument if no Partial in the range [b,e) + //! has the specified label. + // +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + Harmonifier( Iter b, Iter e, Partial::label_type refLabel, + double threshold_dB = 0 ); +#else + inline + Harmonifier( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, double threshold_dB = 0 ); +#endif + + //! Construct a new Harmonifier that applies the specified + //! reference Partial to fix the frequencies of Breakpoints + //! whose amplitude is below threshold_dB (0 by default, + //! to apply only to quiet Partials, specify a threshold, + //! like -90). The reference Partial is the first Partial + //! in the range [b,e) having the specified label. + //! + //! The Envelope is a time-varying weighting + //! on the harmonifing process. When 1, harmonic frequencies + //! are used, when 0, breakpoint frequencies are unmodified. + // + //! \throw InvalidArgument if no Partial in the range [b,e) + //! has the specified label. + // +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + Harmonifier( Iter b, Iter e, Partial::label_type refLabel, + const Envelope & env, double threshold_dB = 0 ); +#else + inline + Harmonifier( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, const Envelope & env, + double threshold_dB = 0 ); +#endif + + //! Destructor. + ~Harmonifier( void ); + + // use compiler-generated copy and assign. + +// -- operation -- + + //! Apply the reference envelope to a Partial. + void harmonify( Partial & p ) const; + + //! Apply the reference envelope to all Partials in a range. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void harmonify( Iter b, Iter e ); +#else + inline + void harmonify( PartialList::iterator b, PartialList::iterator e ); +#endif + +// -- static members -- + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Harmonifier using as reference the Partial in + //! the specified range labeled refLabel, then apply + //! the instance to all Partials in the range. + //! + //! \param b is the beginning of the range of Partials to harmonify + //! \param e is (one-past) the end of the range of Partials to harmonify + //! \param refLabel is the label of the Partial in [b,e) to + //! use as reference Partial. The reference Partial is the first + //! Partial in the range [b,e) having the specified label. + //! \param threshold_dB is the amplitude below which breakpoint + //! frequencies are harmonified (0 by default, to apply + //! only to quiet Partials, specify a threshold, like -90). + //! + //! \throw InvalidArgument if no Partial in the range [b,e) + //! has the specified label. + //! \throw InvalidArgument if refLabel is non-positive. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + static + void harmonify( Iter b, Iter e, + Partial::label_type refLabel, + double threshold_dB = 0 ); +#else + static inline + void harmonify( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, + double threshold_dB = 0 ); +#endif + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Harmonifier using as reference the Partial in + //! the specified range labeled refLabel, then apply + //! the instance to all Partials in the range. + //! + //! \param b is the beginning of the range of Partials to harmonify + //! \param e is (one-past) the end of the range of Partials to harmonify + //! \param refLabel is the label of the Partial in [b,e) to + //! use as reference Partial. The reference Partial is the first + //! Partial in the range [b,e) having the specified label. + //! \param env is a weighting envelope to apply to the harmonification + //! process: when env is 1, use harmonic frequencies, when env + //! is 0, breakpoint frequencies are unmodified. + //! \param threshold_dB is the amplitude below which breakpoint + //! frequencies are harmonified (0 by default, to apply + //! only to quiet Partials, specify a threshold, like -90). + //! + //! \throw InvalidArgument if no Partial in the range [b,e) + //! has the specified label. + //! \throw InvalidArgument if refLabel is non-positive. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + static + void harmonify( Iter b, Iter e, + Partial::label_type refLabel, + const Envelope & env, double threshold_dB = 0 ); +#else + static inline + void harmonify( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, + const Envelope & env, double threshold_dB = 0 ); +#endif + +private: + +// -- helpers -- + + //! Return the default weighing envelope (always 1). + //! Used in template constructors. + static Envelope * createDefaultEnvelope( void ); + +}; + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new Harmonifier that applies the specified +//! reference Partial to fix the frequencies of Breakpoints +//! whose amplitude is below threshold_dB (0 by default, +//! to apply only to quiet Partials, specify a threshold, +//! like -90). The reference Partial is the first Partial +//! in the range [b,e) having the specified label. +//! \throw InvalidArgument if no Partial in the range [b,e) +//! has the specified label. +//! \throw InvalidArgument if refLabel is non-positive. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +Harmonifier::Harmonifier( Iter b, Iter e, Partial::label_type refLabel, + double threshold_dB ) : +#else +inline +Harmonifier::Harmonifier( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, double threshold_dB ) : +#endif + _freqFixThresholdDb( threshold_dB ), + _weight( createDefaultEnvelope() ) +{ + if ( 1 > refLabel ) + { + Throw( InvalidArgument, "The reference label must be positive." ); + } + + b = std::find_if( b, e, PartialUtils::isLabelEqual( refLabel ) ); + if ( b == e ) + { + Throw( InvalidArgument, "no Partial has the specified reference label" ); + } + + if ( 0 == b->numBreakpoints() ) + { + Throw( InvalidArgument, + "Cannot use an empty reference Partial in Harmonizer" ); + } + _refPartial = *b; +} + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new Harmonifier that applies the specified +//! reference Partial to fix the frequencies of Breakpoints +//! whose amplitude is below threshold_dB (0 by default, +//! to apply only to quiet Partials, specify a threshold, +//! like -90). The reference Partial is the first Partial +//! in the range [b,e) having the specified label. +//! +//! The Envelope is a time-varying weighting +//! on the harmonifing process. When 1, harmonic frequencies +//! are used, when 0, breakpoint frequencies are unmodified. +//! +//! \throw InvalidArgument if no Partial in the range [b,e) +//! has the specified label. +//! \throw InvalidArgument if refLabel is non-positive. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +Harmonifier::Harmonifier( Iter b, Iter e, Partial::label_type refLabel, + const Envelope & env, double threshold_dB ) : +#else +inline +Harmonifier::Harmonifier( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, const Envelope & env, + double threshold_dB ) : +#endif + _freqFixThresholdDb( threshold_dB ), + _weight( env.clone() ) +{ + if ( 1 > refLabel ) + { + Throw( InvalidArgument, "The reference label must be positive." ); + } + + b = std::find_if( b, e, PartialUtils::isLabelEqual( refLabel ) ); + if ( b == e ) + { + Throw( InvalidArgument, "no Partial has the specified reference label" ); + } + + if ( 0 == b->numBreakpoints() ) + { + Throw( InvalidArgument, + "Cannot use an empty reference Partial in Harmonizer" ); + } + _refPartial = *b; +} + +// --------------------------------------------------------------------------- +// harmonify +// --------------------------------------------------------------------------- +//! Apply the reference envelope to all Partials in a range. +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Harmonifier::harmonify( Iter b, Iter e ) +#else +inline +void Harmonifier::harmonify( PartialList::iterator b, PartialList::iterator e ) +#endif +{ + while ( b != e ) + { + harmonify( *b ); + ++b; + } +} + +// --------------------------------------------------------------------------- +// harmonify (STATIC) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! Construct a Harmonifier using as reference the Partial in +//! the specified range labeled refLabel, then apply +//! the instance to all Partials in the range. +//! +//! \param b is the beginning of the range of Partials to harmonify +//! \param e is (one-past) the end of the range of Partials to harmonify +//! \param refLabel is the label of the Partial in [b,e) to +//! use as reference Partial. The reference Partial is the first +//! Partial in the range [b,e) having the specified label. +//! \param threshold_dB is the amplitude below which breakpoint +//! frequencies are harmonified (0 by default, to apply +//! only to quiet Partials, specify a threshold, like -90). +//! +//! \throw InvalidArgument if no Partial in the range [b,e) +//! has the specified label. +//! \throw InvalidArgument if refLabel is non-positive. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Harmonifier::harmonify( Iter b, Iter e, + Partial::label_type refLabel, + double threshold_dB ) +#else +inline +void Harmonifier::harmonify( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, + double threshold_dB ) +#endif +{ + Harmonifier instance( b, e, refLabel, threshold_dB ); + instance.harmonify( b, e ); +} + +// --------------------------------------------------------------------------- +// harmonify (STATIC) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! Construct a Harmonifier using as reference the Partial in +//! the specified range labeled refLabel, then apply +//! the instance to all Partials in the range. +//! +//! \param b is the beginning of the range of Partials to harmonify +//! \param e is (one-past) the end of the range of Partials to harmonify +//! \param refLabel is the label of the Partial in [b,e) to +//! use as reference Partial. The reference Partial is the first +//! Partial in the range [b,e) having the specified label. +//! \param env is a weighting envelope to apply to the harmonification +//! process: when env is 1, use harmonic frequencies, when env +//! is 0, breakpoint frequencies are unmodified. +//! \param threshold_dB is the amplitude below which breakpoint +//! frequencies are harmonified (0 by default, to apply +//! only to quiet Partials, specify a threshold, like -90). +//! +//! \throw InvalidArgument if no Partial in the range [b,e) +//! has the specified label. +//! \throw InvalidArgument if refLabel is non-positive. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Harmonifier::harmonify( Iter b, Iter e, + Partial::label_type refLabel, + const Envelope & env, double threshold_dB ) +#else +inline +void Harmonifier::harmonify( PartialList::iterator b, PartialList::iterator e, + Partial::label_type refLabel, + const Envelope & env, double threshold_dB ) +#endif +{ + Harmonifier instance( b, e, refLabel, env, threshold_dB ); + instance.harmonify( b, e ); +} + +} // namespace Loris + +#endif /* ndef INCLUDE_HARMONIFIER_H */ diff --git a/src/loris/ImportLemur.C b/src/loris/ImportLemur.C new file mode 100644 index 0000000..f14dc4b --- /dev/null +++ b/src/loris/ImportLemur.C @@ -0,0 +1,519 @@ +/* + * 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 + * + * + * ImportLemur.C + * + * Implementation of class Loris::ImportLemur for importing Partials stored + * in Lemur 5 alpha files. + * + * Kelly Fitz, 10 Sept 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "ImportLemur.h" +#include "BigEndian.h" +#include "LorisExceptions.h" +#include "Partial.h" +#include "PartialList.h" +#include "Breakpoint.h" +#include "Notifier.h" +#include <algorithm> +#include <cmath> +#include <fstream> +#include <string> + +// in case configure wasn't run (no config.h), +// pick some (hopefully-) reasonable values for +// these things and hope for the best... +#if ! defined( SIZEOF_SHORT ) +#define SIZEOF_SHORT 2 +#endif + +#if ! defined( SIZEOF_INT ) +#define SIZEOF_INT 4 +#endif + +#if ! defined( SIZEOF_LONG ) +#define SIZEOF_LONG 4 // not for DEC Alpha! +#endif + +#if SIZEOF_SHORT == 2 +typedef short Int_16; +typedef unsigned short Uint_16; +#elif SIZEOF_INT == 2 +typedef int Int_16; +typedef unsigned int Uint_16; +#else +#error "cannot find an appropriate type for 16-bit integers" +#endif + +#if SIZEOF_INT == 4 +typedef int Int_32; +typedef unsigned int Uint_32; +#elif SIZEOF_LONG == 4 +typedef long Int_32; +typedef unsigned long Uint_32; +#else +#error "cannot find an appropriate type for 32-bit integers" +#endif + + +#if ! defined( SIZEOF_FLOAT ) || SIZEOF_FLOAT == 4 +typedef float Float_32; +#else +#error "cannot find an appropriate type for 32-bit floats" +#endif + +#if ! defined( SIZEOF_DOUBLE ) || SIZEOF_DOUBLE == 8 +typedef double Double_64; +#else +#error "cannot find an appropriate type for 64-bit double-precision floats" +#endif + + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + +// -- types and ids -- +enum { + ContainerId = 0x464f524d, // 'FORM' + LEMR_ID = 0x4c454d52, // 'LEMR' + AnalysisParamsID = 0x4c4d414e, // 'LMAN' + TrackDataID = 0x54524b53, // 'TRKS' + FormatNumber = 4962 }; + +// for reading and writing files, the exact sizes and +// alignments are critical. +typedef Int_32 ID; +struct CkHeader +{ + Int_32 id; + Int_32 size; +}; + +struct ContainerCk +{ + CkHeader header; + ID formType; +}; + +struct AnalysisParamsCk +{ + //Int_32 ckID; + //Int_32 ckSize; + CkHeader header; + + Int_32 formatNumber; + Int_32 originalFormatNumber; + + Int_32 ftLength; // samples, transform length + Float_32 winWidth; // Hz, main lobe width + Float_32 winAtten; // dB, sidelobe attenuation + Int_32 hopSize; // samples, frame length + Float_32 sampleRate; // Hz, from analyzed sample + + Float_32 noiseFloor; // dB (negative) + Float_32 peakAmpRange; // dB, floating relative amplitde threshold + Float_32 maskingRolloff; // dB/Hz, peak masking curve + Float_32 peakSeparation; // Hz, minimum separation between peaks + Float_32 freqDrift; // Hz, maximum track freq drift over a frame +}; + +struct TrackDataCk +{ + CkHeader header; + Uint_32 numberOfTracks; + Int_32 trackOrder; // enumerated type + // track data follows +}; + +struct TrackOnDisk +{ + Double_64 startTime; // in milliseconds + Float_32 initialPhase; + Uint_32 numPeaks; + Int_32 label; +}; + +struct PeakOnDisk +{ + Float_32 magnitude; + Float_32 frequency; + Float_32 interpolatedFrequency; + Float_32 bandwidth; + Double_64 ttn; +}; + +// prototypes for import helpers: +static void importPartials( std::istream & s, PartialList & partials, double bweCutoff ); +static void getPartial( std::istream & s, PartialList & partials, double bweCutoff ); +static void readChunkHeader( std::istream & s, CkHeader & ck ); +static void readContainer( std::istream & s ); +static void readParamsChunk( std::istream & s ); +static long readTracksChunk( std::istream & s ); +static void readTrackHeader( std::istream & s, TrackOnDisk & t ); +static void readPeakData( std::istream & s, PeakOnDisk & p ); + +// --------------------------------------------------------------------------- +// ImportLemur constructor +// --------------------------------------------------------------------------- +// Imports immediately. +// bweCutoff defaults to 1kHz, the default cutoff in Lemur. +// Clients should be prepared to catch ImportErrors. +// +ImportLemur::ImportLemur( const std::string & fname, double bweCutoff /* = 1000 Hz */) +{ + std::fstream fs; + try + { + fs.open( fname.c_str(), std::ios::in | std::ios::binary ); + } + catch( std::exception & ex ) + { + Throw( ImportException, ex.what() ); + } + importPartials( fs, _partials, bweCutoff ); +} + +// --------------------------------------------------------------------------- +// importPartials +// --------------------------------------------------------------------------- +// Clients should be prepared to catch ImportExceptions. +// +// THIS WON'T WORK IF CHUNKS ARE IN A DIFFERENT ORDER!!! +// Fortunately, they never will be, since only the research +// version of Lemur ever wrote these files anyway. +// +static void +importPartials( std::istream & s, PartialList & partials, double bweCutoff ) +{ + try + { + // the Container chunk must be first, read it: + readContainer( s ); + + // read other chunks: + bool foundParams = false, foundTracks = false; + while ( ! foundParams || ! foundTracks ) + { + // read a chunk header, if it isn't the one we want, skip over it. + CkHeader h; + readChunkHeader( s, h ); + + if ( h.id == AnalysisParamsID ) + { + readParamsChunk( s ); + foundParams = true; + } + else if ( h.id == TrackDataID ) + { + if (! foundParams) // I hope this doesn't happen + { + Throw( ImportException, + "Mia culpa! I am not smart enough to read the Track data before the Analysis Parameters data." ); + } + // read Partials: + for ( long counter = readTracksChunk( s ); counter > 0; --counter ) + { + getPartial(s, partials, bweCutoff); + } + foundTracks = true; + } + else + { + s.ignore( h.size ); + } + } + + } + catch ( Exception & ex ) + { + if ( s.eof() ) + { + ex.append("Reached end of file before finding both a Tracks chunk and a Parameters chunk."); + } + ex.append( "Import failed." ); + Throw( ImportException, ex.str() ); + } + +} + +// --------------------------------------------------------------------------- +// readContainer +// --------------------------------------------------------------------------- +// +static void +readContainer( std::istream & s ) +{ + ContainerCk ck; + try + { + // read chunk header: + readChunkHeader( s, ck.header ); + if( ck.header.id != ContainerId ) + Throw( FileIOException, "Found no Container chunk." ); + + // read FORM type + BigEndian::read( s, 1, sizeof(ID), (char *)&ck.formType ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to read badly-formatted Lemur file (bad Container chunk)." ); + throw; + } + + // make sure its really a Dr. Lemur file: + if ( ck.formType != LEMR_ID ) + { + Throw( ImportException, "File is not formatted correctly for Lemur 5 import." ); + } +} + +// --------------------------------------------------------------------------- +// getPartial +// --------------------------------------------------------------------------- +// Convert any FileIOExceptions into ImportExceptions, so that clients can +// reasonably expect to catch only ImportExceptions. +// +static void +getPartial( std::istream & s, PartialList & partials, double bweCutoff ) +{ + try + { + // read the Track header: + TrackOnDisk tkHeader; + readTrackHeader( s, tkHeader ); + + // create a Partial: + Partial p; + p.setLabel( tkHeader.label ); + + // keep running phase and time for Breakpoint construction: + double phase = tkHeader.initialPhase; + + // convert time to seconds and offset by a millisecond to + // allow for implied onset (Lemur analysis data was shifted + // such that the earliest Partial starts at 0). + double time = tkHeader.startTime * 0.001; + + // use this to compute phases: + double prevTtnSec = 0.; + + // loop: read Peak, create Breakpoint, add to Partial: + for ( int i = 0; i < tkHeader.numPeaks; ++i ) { + // read Peak: + PeakOnDisk pkData; + readPeakData( s, pkData ); + + double frequency = pkData.frequency; + double amplitude = pkData.magnitude; + double bandwidth = std::min( pkData.bandwidth, 1.f ); + + // fix bandwidth: + // Lemur used a cutoff frequency, below which + // bandwidth was ignored; Loris does not, so + // toss out that bogus bandwidth. + if ( frequency < bweCutoff ) { + amplitude *= std::sqrt(1. - bandwidth); + bandwidth = 0.; + } + // otherwise, adjust the bandwidth value + // to account for the difference in noise + // scaling between Lemur and Loris; this + // mess doubles the noise modulation index + // without changing the sine modulation index, + // see Oscillator::modulate(). + else { + amplitude *= std::sqrt( 1. + (3. * bandwidth) ); + bandwidth = (4. * bandwidth) / ( 1. + (3. * bandwidth) ); + } + + // update phase based on _this_ pkData's interpolated freq: + phase +=2. * Pi * prevTtnSec * pkData.interpolatedFrequency; + phase = std::fmod( phase, 2. * Pi ); + + // create Breakpoint: + Breakpoint bp( frequency, amplitude, bandwidth, phase ); + + // insert in Partial: + p.insert( time, bp ); + + // update time: + prevTtnSec = pkData.ttn * 0.001; + time += prevTtnSec; + } + + if ( p.duration() > 0. ) { + partials.push_back( p ); + } + /* + else { + debugger << "import rejecting a Partial of zero duration (" + << tkHeader.numPeaks << " peaks read)" << endl; + } + */ + } + catch( FileIOException & ex ) { + ex.append( "Failed to import a partial from a Lemur file." ); + throw; + } + +} + +// --------------------------------------------------------------------------- +// readChunkHeader +// --------------------------------------------------------------------------- +// Read the id and chunk size from the current file position. +// +static void +readChunkHeader( std::istream & s, CkHeader & h ) +{ + BigEndian::read( s, 1, sizeof(ID), (char *)&h.id ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&h.size ); +} + +// --------------------------------------------------------------------------- +// readTracksChunk +// --------------------------------------------------------------------------- +// Leave file positioned at end of chunk header data and at the beginning +// of the first track. +// Assumes that the stream is correctly positioned and that the chunk +// header has been read. +// Returns the number of tracks to read. +// +static long +readTracksChunk( std::istream & s ) +{ + TrackDataCk ck; + try + { + // found it, read it one field at a time: + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&ck.numberOfTracks ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.trackOrder ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to read badly-formatted Lemur file (bad Track Data chunk)." ); + throw; + } + + return ck.numberOfTracks; +} + +// --------------------------------------------------------------------------- +// readParamsChunk +// --------------------------------------------------------------------------- +// Verify that the file has the correct format and is available for reading. +// Assumes that the stream is correctly positioned and that the chunk +// header has been read. +// +static void +readParamsChunk( std::istream & s ) +{ + AnalysisParamsCk ck; + try + { + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.formatNumber ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.originalFormatNumber ); + + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.ftLength ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.winWidth ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.winAtten ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&ck.hopSize ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.sampleRate ); + + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.noiseFloor ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.peakAmpRange ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.maskingRolloff ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.peakSeparation ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&ck.freqDrift ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to read badly-formatted Lemur file (bad Parameters chunk)." ); + throw; + } + + if ( ck.formatNumber != FormatNumber ) + { + Throw( FileIOException, "File has wrong Lemur format for Lemur 5 import." ); + } +} + +// --------------------------------------------------------------------------- +// readTrackHeader +// --------------------------------------------------------------------------- +// Read from current position. +// +static void +readTrackHeader( std::istream & s, TrackOnDisk & t ) +{ + try + { + BigEndian::read( s, 1, sizeof(Double_64), (char *)&t.startTime ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&t.initialPhase ); + BigEndian::read( s, 1, sizeof(Uint_32), (char *)&t.numPeaks ); + BigEndian::read( s, 1, sizeof(Int_32), (char *)&t.label ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to read track data in Lemur 5 import." ); + throw; + } +} + +// --------------------------------------------------------------------------- +// readPeakData +// --------------------------------------------------------------------------- +// Read from current position. +// +static void +readPeakData( std::istream & s, PeakOnDisk & p ) +{ + try + { + BigEndian::read( s, 1, sizeof(Float_32), (char *)&p.magnitude ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&p.frequency ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&p.interpolatedFrequency ); + BigEndian::read( s, 1, sizeof(Float_32), (char *)&p.bandwidth ); + BigEndian::read( s, 1, sizeof(Double_64), (char *)&p.ttn ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to read peak data in Lemur 5 import." ); + throw; + } +} +} // end of namespace Loris diff --git a/src/loris/ImportLemur.h b/src/loris/ImportLemur.h new file mode 100644 index 0000000..e887e64 --- /dev/null +++ b/src/loris/ImportLemur.h @@ -0,0 +1,84 @@ +#ifndef INCLUDE_IMPORTLEMUR_H +#define INCLUDE_IMPORTLEMUR_H +/* + * 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 + * + * + * ImportLemur.h + * + * Definition of class Loris::ImportLemur for importing Partials stored + * in Lemur 5 alpha files. + * + * Kelly Fitz, 10 Sept 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "PartialList.h" +#include "LorisExceptions.h" +#include <string> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class ImportLemur +// +class ImportLemur +{ +// -- instance variables -- + PartialList _partials; // collect Partials here + +// -- public interface -- +public: +// construction: +// (compiler can generate destructor) + ImportLemur( const std::string & fname, double bweCutoff = 1000 ); + +// PartialList access: + PartialList & partials( void ) { return _partials; } + const PartialList & partials( void ) const { return _partials; } + +// -- unimplemented -- +private: + ImportLemur( const ImportLemur & other ); + ImportLemur & operator = ( const ImportLemur & rhs ); + +}; // end of class ImportLemur + +// --------------------------------------------------------------------------- +// class ImportException +// +// Class of exceptions thrown when there is an error importing +// Partials. +// +class ImportException : public Exception +{ +public: + ImportException( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Import Error -- ").append( str ), where ) {} +}; + +} // end of namespace Loris + +#endif /* ndef INCLUDE_IMPORTLEMUR_H */ diff --git a/src/loris/KaiserWindow.C b/src/loris/KaiserWindow.C new file mode 100644 index 0000000..c7f3952 --- /dev/null +++ b/src/loris/KaiserWindow.C @@ -0,0 +1,247 @@ +/* + * 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 + * + * + * KaiserWindow.C + * + * Implementation of class Loris::KaiserWindow. + * + * Kelly Fitz, 14 Dec 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "KaiserWindow.h" +#include "LorisExceptions.h" +#include <cmath> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +using namespace std; + +// begin namespace +namespace Loris { + +// prototypes for static helpers, defined below +static double zeroethOrderBessel( double x ); +static double firstOrderBessel( double x ); + +// --------------------------------------------------------------------------- +// buildWindow +// --------------------------------------------------------------------------- +//! Build a new Kaiser analysis window having the specified shaping +//! parameter. See Oppenheim and Schafer: "Digital Signal Processing" +//! (1975), p. 452 for further explanation of the Kaiser window. Also, +//! see Kaiser and Schafer, 1980. +//! +//! \param win is the vector that will store the window +//! samples. The number of samples computed will be +//! equal to the length of this vector. Any previous +//! contents will be overwritten. +//! \param shape is the Kaiser shaping parameter, controlling +//! the sidelobe rejection level. +// +void +KaiserWindow::buildWindow( vector< double > & win, double shape ) +{ + // Pre-compute the shared denominator in the Kaiser equation. + const double oneOverDenom = 1.0 / zeroethOrderBessel( shape ); + + const unsigned int N = win.size() - 1; + const double oneOverN = 1.0 / N; + + for ( unsigned int n = 0; n <= N; ++n ) + { + const double K = (2.0 * n * oneOverN) - 1.0; + const double arg = sqrt( 1.0 - (K * K) ); + + win[n] = zeroethOrderBessel( shape * arg ) * oneOverDenom; + } +} + +// --------------------------------------------------------------------------- +// createDerivativeWindow +// --------------------------------------------------------------------------- +//! Build a new time-derivative Kaiser analysis window having the +//! specified shaping parameter, for computing frequency reassignment. +//! The closed form for the time derivative can be obtained from the +//! property of modified Bessel functions that the derivative of the +//! zeroeth order function is equal to the first order function. +//! +//! \param win is the vector that will store the window +//! samples. The number of samples computed will be +//! equal to the length of this vector. Any previous +//! contents will be overwritten. +//! \param shape is the Kaiser shaping parameter, controlling +//! the sidelobe rejection level. +// +void +KaiserWindow::buildTimeDerivativeWindow( vector< double > & win, double shape ) +{ + // Pre-compute the common factor that does not depend on n. + const unsigned int N = win.size() - 1; + const double oneOverN = 1.0 / N; + + const double commonFac = - 2.0 * shape / (N * zeroethOrderBessel( shape ) ); + + // w'[0] = w'[N] = 0 + win[0] = win[N] = 0.0; + + for ( unsigned int n = 1; n < N; ++n ) + { + const double K = (2.0 * n * oneOverN) - 1.0; + const double arg = sqrt( 1.0 - (K * K) ); + + win[n] = commonFac * firstOrderBessel( shape * arg ) * K / arg; + } +} + + +// --------------------------------------------------------------------------- +// zeroethOrderBessel +// --------------------------------------------------------------------------- +// Compute the zeroeth order modified Bessel function of the first kind +// at x using the series expansion, used to compute the Kasier window +// function. +// +static double zeroethOrderBessel( double x ) +{ + const double eps = 0.000001; + + // initialize the series term for m=0 and the result + double besselValue = 0; + double term = 1; + double m = 0; + + // accumulate terms as long as they are significant + while(term > eps * besselValue) + { + besselValue += term; + + // update the term + ++m; + term *= (x*x) / (4*m*m); + } + + return besselValue; +} + +// --------------------------------------------------------------------------- +// firstOrderBessel +// --------------------------------------------------------------------------- +// Compute the first order modified Bessel function of the first kind +// at x using the series expansion, used to compute the time derivative +// of the Kasier window function for computing frequency reassignment. +// +static double firstOrderBessel( double x ) +{ + const double eps = 0.000001; + + // initialize the series term for m=0 and the result + double besselValue = 0; + double term = .5*x; + double m = 0; + + // accumulate terms as long as they are significant + while(term > eps * besselValue) + { + besselValue += term; + + // update the term + ++m; + term *= (x*x) / (4*m*(m+1)); + } + + return besselValue; +} + +// --------------------------------------------------------------------------- +// computeShape +// --------------------------------------------------------------------------- +// Compute the Kaiser window shaping parameter from the specified attenuation +// of side lobes. This algorithm is given in Kaiser an Schafer,1980 and is +// supposed to give better than 0.36% accuracy (Kaiser and Schafer 1980). +// +double +KaiserWindow::computeShape( double atten ) +{ + if ( atten < 0. ) + { + Throw( InvalidArgument, + "Kaiser window shape must be computed from positive (> 0dB)" + " sidelobe attenuation. (received attenuation < 0)" ); + } + + double alpha; + + if ( atten > 60.0 ) + { + alpha = 0.12438 * (atten + 6.3); + } + else if ( atten > 13.26 ) + { + alpha = 0.76609L * ( pow((atten - 13.26), 0.4) ) + + 0.09834L * (atten - 13.26L); + } + else + { + // can't have less than 13dB. + alpha = 0.0; + } + + return alpha; +} +// --------------------------------------------------------------------------- +// computeLength +// --------------------------------------------------------------------------- +// Compute the length (in samples) of the Kaiser window from the desired +// (approximate) main lobe width and the control parameter. Of course, since +// the window must be an integer number of samples in length, your actual +// lobal mileage may vary. This equation appears in Kaiser and Schafer 1980 +// (on the use of the I0 window class for spectral analysis) as Equation 9. +// +// The main width of the main lobe must be normalized by the sample rate, +// that is, it is a fraction of the sample rate. +// +unsigned long +KaiserWindow::computeLength( double width, double alpha ) +{ + //double alpha = computeShape( atten ); + + // The last 0.5 is cheap rounding. + // But I think I don't need cheap rounding because the equation + // from Kaiser and Schafer has a +1 that appears to be a cheap + // ceiling function. + return long(1.0 + (2. * sqrt((Pi*Pi) + (alpha*alpha)) / (Pi * width)) /* + 0.5 */); +} + +} // end of namespace Loris + diff --git a/src/loris/KaiserWindow.h b/src/loris/KaiserWindow.h new file mode 100644 index 0000000..d0ba494 --- /dev/null +++ b/src/loris/KaiserWindow.h @@ -0,0 +1,112 @@ +#ifndef INCLUDE_KAISERWINDOW_H +#define INCLUDE_KAISERWINDOW_H +/* + * 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 + * + * + * KaiserWindow.h + * + * Definition of class Loris::KaiserWindow. + * + * Kelly Fitz, 14 Dec 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class KaiserWindow +// +//! Computes samples of a Kaiser window +//! function (see Kaiser and Schafer, 1980) for windowing FFT data. +// +class KaiserWindow +{ +// -- public interface -- +public: + + // -- window building -- + + //! Build a new Kaiser analysis window having the specified shaping + //! parameter. See Oppenheim and Schafer: "Digital Signal Processing" + //! (1975), p. 452 for further explanation of the Kaiser window. Also, + //! see Kaiser and Schafer, 1980. + //! + //! \param win is the vector that will store the window + //! samples. The number of samples computed will be + //! equal to the length of this vector. Any previous + //! contents will be overwritten. + //! \param shape is the Kaiser shaping parameter, controlling + //! the sidelobe rejection level. + static void buildWindow( std::vector< double > & win, double shape ); + + //! Build a new time-derivative Kaiser analysis window having the + //! specified shaping parameter, for computing frequency reassignment. + //! The closed form for the time derivative can be obtained from the + //! property of modified Bessel functions that the derivative of the + //! zeroeth order function is equal to the first order function. + //! + //! \param win is the vector that will store the window + //! samples. The number of samples computed will be + //! equal to the length of this vector. Any previous + //! contents will be overwritten. + //! \param shape is the Kaiser shaping parameter, controlling + //! the sidelobe rejection level. + static void buildTimeDerivativeWindow( std::vector< double > & win, double shape ); + + + // -- window parameter estimation -- + + //! Compute a shaping parameter that will achieve the specified + //! level of sidelobe rejection. + //! + //! \param atten is the desired sidelobe attenuation in + //! positive decibels (e.g. 65 dB) + //! \returns the Kaiser shaping paramater + static double computeShape( double atten ); + + //! Compute the necessary length in samples of a Kaiser window + //! having the specified shaping parameter that has the + //! desired main lobe width. + //! + //! \param width is the desired main lobe width expressed + //! as a fraction of the sample rate. + //! \param alpha is the Kaiser shaping parameter (the + //! main lobe width is influenced primarily by the + //! window length,but also by the shape). + //! \returns the window length in samples + static unsigned long computeLength( double width, double alpha ); + +// construction is not allowed: +private: + KaiserWindow( void ); + +}; // end of class KaiserWindow + +} // end of namespace Loris + +#endif /* ndef INCLUDE_KAISERWINDOW_H */ diff --git a/src/loris/LinearEnvelope.C b/src/loris/LinearEnvelope.C new file mode 100644 index 0000000..13d59ec --- /dev/null +++ b/src/loris/LinearEnvelope.C @@ -0,0 +1,190 @@ +/* + * 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 + * + * + * LinearEnvelope.C + * + * Implementation of class LinearEnvelope. + * + * Kelly Fitz, 23 April 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "LinearEnvelope.h" + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Construct a new LinearEnvelope having no +//! breakpoints (and an implicit value of 0 everywhere). +// +LinearEnvelope::LinearEnvelope( void ) +{ +} + +// --------------------------------------------------------------------------- +// constructor with initial (or constant) value +// --------------------------------------------------------------------------- +//! Construct and return a new LinearEnvelope having a +//! single breakpoint at 0 (and an implicit value everywhere) +//! of initialValue. +//! +//! \param initialValue is the value of this LinearEnvelope +//! at time 0. +// +LinearEnvelope::LinearEnvelope( double initialValue ) +{ + insertBreakpoint( 0., initialValue ); +} + +// --------------------------------------------------------------------------- +// clone +// --------------------------------------------------------------------------- +//! Return an exact copy of this LinearEnvelope +//! (polymorphic copy, following the Prototype pattern). +// +LinearEnvelope * +LinearEnvelope::clone( void ) const +{ + return new LinearEnvelope( *this ); +} + +// --------------------------------------------------------------------------- +// insert +// --------------------------------------------------------------------------- +//! Insert a breakpoint representing the specified (time, value) +//! pair into this LinearEnvelope. If there is already a +//! breakpoint at the specified time, it will be replaced with +//! the new breakpoint. +//! +//! \param time is the time at which to insert a new breakpoint +//! \param value is the value of the new breakpoint +// +void +LinearEnvelope::insert( double time, double value ) +{ + (*this)[time] = value; +} + +// --------------------------------------------------------------------------- +// operator+= +// --------------------------------------------------------------------------- +//! Add a constant value to this LinearEnvelope and return a reference +//! to self. +//! +//! \param offset is the value to add to all points in the envelope +LinearEnvelope & LinearEnvelope::operator+=( double offset ) +{ + for ( iterator it = begin(); it != end(); ++it ) + { + it->second += offset; + } + return *this; +} + +// --------------------------------------------------------------------------- +// operator*= +// --------------------------------------------------------------------------- +//! Scale this LinearEnvelope by a constant value and return a reference +//! to self. +//! +//! \param scale is the value by which to multiply to all points in +//! the envelope +LinearEnvelope & LinearEnvelope::operator*=( double scale ) +{ + for ( iterator it = begin(); it != end(); ++it ) + { + it->second *= scale; + } + return *this; +} + +// --------------------------------------------------------------------------- +// operator/ (non-member binary operator) +// --------------------------------------------------------------------------- +//! Divide constant value by a LinearEnvelope and return a new +//! LinearEnvelope. No shortcut implementation for this one, +//! don't inline. +LinearEnvelope operator/( double num, LinearEnvelope env ) +{ + for ( LinearEnvelope::iterator it = env.begin(); it != env.end(); ++it ) + { + it->second = num / it->second; + } + + return env; +} + +// --------------------------------------------------------------------------- +// valueAt +// --------------------------------------------------------------------------- +//! Return the linearly-interpolated value of this LinearEnvelope at +//! the specified time. +//! +//! \param t is the time at which to evaluate this LinearEnvelope. +// +double +LinearEnvelope::valueAt( double t ) const +{ + // return zero if no breakpoints have been specified: + if ( size() == 0 ) + { + return 0.; + } + + const_iterator it = lower_bound( t ); + + if ( it == begin() ) + { + // t is less than the first breakpoint, extend: + return it->second; + } + else if ( it == end() ) + { + // t is greater than the last breakpoint, extend: + // (no direct way to access the last element of a map) + return (--it)->second; + } + else + { + // linear interpolation between consecutive breakpoints: + double xgreater = it->first; + double ygreater = it->second; + --it; + double xless = it->first; + double yless = it->second; + + double alpha = (t - xless) / (xgreater - xless); + return ( alpha * ygreater ) + ( (1. - alpha) * yless ); + } +} + +} // end of namespace Loris diff --git a/src/loris/LinearEnvelope.h b/src/loris/LinearEnvelope.h new file mode 100644 index 0000000..be06f69 --- /dev/null +++ b/src/loris/LinearEnvelope.h @@ -0,0 +1,303 @@ +#ifndef INCLUDE_LINEARENVELOPE_H +#define INCLUDE_LINEARENVELOPE_H +/* + * 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 + * + * + * LinearEnvelope.h + * + * Definition of class LinearEnvelope. + * + * Kelly Fitz, 23 April 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Envelope.h" +#include <map> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class LinearEnvelope +// +//! A LinearEnvelope represents a linear segment breakpoint function +//! with infinite extension at each end (that is, evalutaing the +//! envelope past either end of the breakpoint function yields the +//! value at the nearest end point). +//! +//! LinearEnvelope implements the Envelope interface, described +//! by the abstract class Envelope. +//! +//! LinearEnvelope inherits the types +//! \li \c size_type +//! \li \c value_type +//! \li \c iterator +//! \li \c const_iterator +//! +//! and the member functions +//! \li size_type size( void ) const +//! \li bool empty( void ) const +//! \li iterator begin( void ) +//! \li const_iterator begin( void ) const +//! \li iterator end( void ) +//! \li const_iterator end( void ) const +//! +//! from std::map< double, double >. +// +class LinearEnvelope : public Envelope, private std::map< double, double > +{ +// -- public interface -- +public: +// -- construction -- + + //! Construct a new LinearEnvelope having no + //! breakpoints (and an implicit value of 0 everywhere). + LinearEnvelope( void ); + + //! Construct and return a new LinearEnvelope having a + //! single breakpoint at 0 (and an implicit value everywhere) + //! of initialValue. + //! + //! \param initialValue is the value of this LinearEnvelope + //! at time 0. + explicit LinearEnvelope( double initialValue ); + + // compiler-generated copy, assignment, and destruction are OK. + +// -- Envelope interface -- + + //! Return an exact copy of this LinearEnvelope + //! (polymorphic copy, following the Prototype pattern). + virtual LinearEnvelope * clone( void ) const; + + //! Return the linearly-interpolated value of this LinearEnvelope at + //! the specified time. + //! + //! \param t is the time at which to evaluate this + //! LinearEnvelope. + virtual double valueAt( double t ) const; + + +// -- envelope composition -- + + //! Insert a breakpoint representing the specified (time, value) + //! pair into this LinearEnvelope. If there is already a + //! breakpoint at the specified time, it will be replaced with + //! the new breakpoint. + //! + //! \param time is the time at which to insert a new breakpoint + //! \param value is the value of the new breakpoint + void insert( double time, double value ); + + //! Insert a breakpoint representing the specified (time, value) + //! pair into this LinearEnvelope. Same as insert, retained + //! for backwards-compatibility. + //! + //! \param time is the time at which to insert a new breakpoint + //! \param value is the value of the new breakpoint + void insertBreakpoint( double time, double value ) + { insert( time, value ); } + + + //! Add a constant value to this LinearEnvelope and return a reference + //! to self. + //! + //! \param offset is the value to add to all points in the envelope + LinearEnvelope & operator+=( double offset ); + + //! Subtract a constant value from this LinearEnvelope and return a reference + //! to self. + //! + //! \param offset is the value to subtract from all points in the envelope + LinearEnvelope & operator-=( double offset ) + { + return operator+=( -offset ); + } + + //! Scale this LinearEnvelope by a constant value and return a reference + //! to self. + //! + //! \param scale is the value by which to multiply to all points in + //! the envelope + LinearEnvelope & operator*=( double scale ); + + //! Divide this LinearEnvelope by a constant value and return a reference + //! to self. + //! + //! \param div is the value by which to divide to all points in + //! the envelope + LinearEnvelope & operator/=( double div ) + { + return operator*=( 1.0 / div ); + } + +// -- interface inherited from std::map -- + + using std::map< double, double >::size; + using std::map< double, double >::empty; + using std::map< double, double >::clear; + using std::map< double, double >::begin; + using std::map< double, double >::end; + using std::map< double, double >::size_type; + using std::map< double, double >::value_type; + using std::map< double, double >::iterator; + using std::map< double, double >::const_iterator; + +}; // end of class LinearEnvelope + + +// -- binary operators (inline nonmembers) -- + +//! Add a constant value to a LinearEnvelope and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator+( LinearEnvelope env, double offset ) +{ + env += offset; + return env; +} + +//! Add a constant value to a LinearEnvelope and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator+( double offset, LinearEnvelope env ) +{ + env += offset; + return env; +} + +//! Add two LinearEnvelopes and return a new LinearEnvelope. +inline +LinearEnvelope operator+( const LinearEnvelope & e1, const LinearEnvelope & e2 ) +{ + LinearEnvelope ret; + + // For each breakpoint in e1, insert a breakpoint having a value + // equal to the sum of the two envelopes at that time. + for ( LinearEnvelope::const_iterator it = e1.begin(); it != e1.end(); ++it ) + { + double t = it->first; + double v = it->second; + + ret.insert( t, v + e2.valueAt( t ) ); + } + + // For each breakpoint in e2, insert a breakpoint having a value + // equal to the sum of the two envelopes at that time. + for ( LinearEnvelope::const_iterator it = e2.begin(); it != e2.end(); ++it ) + { + double t = it->first; + double v = it->second; + + ret.insert( t, v + e1.valueAt( t ) ); + } + + return ret; +} + +//! Subtract a constant value from a LinearEnvelope and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator-( LinearEnvelope env, double offset ) +{ + env -= offset; + return env; +} + +//! Subtract a LinearEnvelope from a constant value and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator-( double offset, LinearEnvelope env ) +{ + env *= -1.0; + env += offset; + return env; +} + +//! Subtract two LinearEnvelopes and return a new LinearEnvelope. +inline +LinearEnvelope operator-( const LinearEnvelope & e1, const LinearEnvelope & e2 ) +{ + LinearEnvelope ret; + + // For each breakpoint in e1, insert a breakpoint having a value + // equal to the difference between the two envelopes at that time. + for ( LinearEnvelope::const_iterator it = e1.begin(); it != e1.end(); ++it ) + { + double t = it->first; + double v = it->second; + + ret.insert( t, v - e2.valueAt( t ) ); + } + + // For each breakpoint in e2, insert a breakpoint having a value + // equal to the difference between the two envelopes at that time. + for ( LinearEnvelope::const_iterator it = e2.begin(); it != e2.end(); ++it ) + { + double t = it->first; + double v = it->second; + + ret.insert( t, e1.valueAt( t ) - v ); + } + + return ret; +} + +//! Scale a LinearEnvelope by a constant value and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator*( LinearEnvelope env, double scale ) +{ + env *= scale; + return env; +} + +//! Scale a LinearEnvelope by a constant value and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator*( double scale, LinearEnvelope env ) +{ + env *= scale; + return env; +} + +//! Divide a LinearEnvelope by a constant value and return a new +//! LinearEnvelope. +inline +LinearEnvelope operator/( LinearEnvelope env, double div ) +{ + env /= div; + return env; +} + +//! Divide constant value by a LinearEnvelope and return a new +//! LinearEnvelope. No shortcut implementation for this one, +//! don't inline. +LinearEnvelope operator/( double scale, LinearEnvelope env ); + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_LINEARENVELOPE_H */ diff --git a/src/loris/LorisExceptions.C b/src/loris/LorisExceptions.C new file mode 100644 index 0000000..ccccba4 --- /dev/null +++ b/src/loris/LorisExceptions.C @@ -0,0 +1,86 @@ +/* + * 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 + * + * + * LorisExceptions.C + * + * Implementation of class Exception, a generic exception class. + * + * This file was formerly called Exception.C, and had a corresponding header + * called Exception.h but that filename caused build problems on case-insensitive + * systems that sometimes had system headers called exception.h. So the header + * name was changed to LorisExceptions.h, and this source files name was + * changed to match. + * + * Kelly Fitz, 17 Oct 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "LorisExceptions.h" +#include <string> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// Exception constructor +// --------------------------------------------------------------------------- +//! Construct a new instance with the specified description and, optionally +//! a string identifying the location at which the exception as thrown. The +//! Throw( Exception_Class, description_string ) macro generates a location +//! string automatically using __FILE__ and __LINE__. +//! +//! \param str is a string describing the exceptional condition +//! \param where is an option string describing the location in +//! the source code from which the exception was thrown +//! (generated automatically byt he Throw macro). +// +Exception::Exception( const std::string & str, const std::string & where ) : + _sbuf( str ) +{ + _sbuf.append( where ); + _sbuf.append(" "); +} + +// --------------------------------------------------------------------------- +// append +// --------------------------------------------------------------------------- +//! Append the specified string to this Exception's description, +//! and return a reference to this Exception. +//! +//! \param str is text to append to the exception description +//! \return a reference to this Exception. +// +Exception & +Exception::append( const std::string & str ) +{ + _sbuf.append(str); + return *this; +} + +} // end of namespace Loris diff --git a/src/loris/LorisExceptions.h b/src/loris/LorisExceptions.h new file mode 100644 index 0000000..d0537e0 --- /dev/null +++ b/src/loris/LorisExceptions.h @@ -0,0 +1,308 @@ +#ifndef INCLUDE_EXCEPTIONS_H +#define INCLUDE_EXCEPTIONS_H +/* + * 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 + * + * + * LorisExceptions.h + * + * Definition of class Exception, a generic exception class, and + * commonly-used derived exception classes. + * + * This file was formerly called Exception.h, but that filename caused build + * problems on case-insensitive systems that sometimes had system headers + * called exception.h. + * + * Kelly Fitz, 17 Oct 2006 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include <stdexcept> +#include <string> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Exception +// +//! Exception is a generic exception class for reporting exceptional +//! circumstances in Loris. Exception is derived from std:exception, +//! and is the base for a hierarchy of derived exception classes +//! in Loris. +//! +// +class Exception : public std::exception +{ +// -- public interface -- +public: +// --- lifecycle --- + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + Exception( const std::string & str, const std::string & where = "" ); + + //! Destroy this Exception. + virtual ~Exception( void ) throw() + { + } + +// --- access/mutation --- + + //! Return a description of this Exception in the form of a + //! C-style string (char pointer). Overrides std::exception::what. + //! + //! \return a C-style string describing the exceptional condition. + const char * what( void ) const throw() { return _sbuf.c_str(); } + + //! Append the specified string to this Exception's description, + //! and return a reference to this Exception. + //! + //! \param str is text to append to the exception description + //! \return a reference to this Exception. + Exception & append( const std::string & str ); + + //! Return a read-only refernce to this Exception's + //! description string. + //! + //! \return a string describing the exceptional condition + const std::string & str( void ) const + { + return _sbuf; + } + +// -- instance variables -- +protected: + + //! string for storing the exception description + std::string _sbuf; + +}; // end of class Exception + +// --------------------------------------------------------------------------- +// class AssertionFailure +// +//! Class of exceptions thrown when an assertion (usually representing an +//! invariant condition, and usually detected by the Assert macro) is +//! violated. +// +class AssertionFailure : public Exception +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + AssertionFailure( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Assertion failed -- ").append( str ), where ) + { + } + +}; // end of class AssertionFailure + +// --------------------------------------------------------------------------- +// class IndexOutOfBounds +// +//! Class of exceptions thrown when a subscriptable object is accessed +//! with an index that is out of range. +// +class IndexOutOfBounds : public Exception +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + IndexOutOfBounds( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Index out of bounds -- ").append( str ), where ) {} + +}; // end of class IndexOutOfBounds + + +// --------------------------------------------------------------------------- +// class InvalidObject +// +//! Class of exceptions thrown when an object is found to be badly configured +//! or otherwise invalid. +// +class InvalidObject : public Exception +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + InvalidObject( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Invalid configuration or object -- ").append( str ), where ) + { + } + +}; // end of class InvalidObject + +// --------------------------------------------------------------------------- +// class InvalidIterator +// +//! Class of exceptions thrown when an Iterator is found to be badly configured +//! or otherwise invalid. +// +class InvalidIterator : public InvalidObject +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + InvalidIterator( const std::string & str, const std::string & where = "" ) : + InvalidObject( std::string("Invalid Iterator -- ").append( str ), where ) + { + } + +}; // end of class InvalidIterator + +// --------------------------------------------------------------------------- +// class InvalidArgument +// +//! Class of exceptions thrown when a function argument is found to be invalid. +// +class InvalidArgument : public Exception +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + InvalidArgument( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Invalid Argument -- ").append( str ), where ) + { + } + +}; // end of class InvalidArgument + +// --------------------------------------------------------------------------- +// class RuntimeError +// +//! Class of exceptions thrown when an unanticipated runtime error is +//! encountered. +// +class RuntimeError : public Exception +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + RuntimeError( const std::string & str, const std::string & where = "" ) : + Exception( std::string("Runtime Error -- ").append( str ), where ) + { + } + +}; // end of class RuntimeError + +// --------------------------------------------------------------------------- +// class FileIOException +// +//! Class of exceptions thrown when file input or output fails. +// +class FileIOException : public RuntimeError +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically by the Throw macro). + FileIOException( const std::string & str, const std::string & where = "" ) : + RuntimeError( std::string("File i/o error -- ").append( str ), where ) + { + } + +}; // end of class FileIOException + +// --------------------------------------------------------------------------- +// macros for throwing exceptions +// +// The compelling reason for using macros instead of inlines for all these +// things is that the __FILE__ and __LINE__ macros will be useful. +// +#define __STR(x) __VAL(x) +#define __VAL(x) #x +#define Throw( exType, report ) \ + throw exType( report, " ( " __FILE__ " line: " __STR(__LINE__) " )" ) + +#define Assert(test) \ + do { \ + if (!(test)) Throw( Loris::AssertionFailure, #test ); \ + } while (false) + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_EXCEPTIONS_H */ diff --git a/src/loris/Makefile.am b/src/loris/Makefile.am new file mode 100644 index 0000000..1dd3986 --- /dev/null +++ b/src/loris/Makefile.am @@ -0,0 +1,163 @@ +# Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken +# <loris@cerlsoundgroup.org> +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# source code for Loris classes +CPP_SRC = AiffData.C \ + AiffData.h \ + AiffFile.C \ + AiffFile.h \ + Analyzer.C \ + Analyzer.h \ + AssociateBandwidth.C \ + AssociateBandwidth.h \ + BigEndian.C \ + BigEndian.h \ + Breakpoint.C \ + Breakpoint.h \ + BreakpointEnvelope.h \ + BreakpointUtils.C \ + BreakpointUtils.h \ + Channelizer.C \ + Channelizer.h \ + Collator.C \ + Collator.h \ + Dilator.C \ + Dilator.h \ + Distiller.C \ + Distiller.h \ + Envelope.C \ + Envelope.h \ + F0Estimate.C \ + F0Estimate.h \ + LorisExceptions.C \ + LorisExceptions.h \ + Filter.C \ + Filter.h \ + FourierTransform.C \ + FourierTransform.h \ + FrequencyReference.C \ + FrequencyReference.h \ + Fundamental.C \ + Fundamental.h \ + Harmonifier.C \ + Harmonifier.h \ + ImportLemur.C \ + ImportLemur.h \ + KaiserWindow.C \ + KaiserWindow.h \ + LinearEnvelope.C \ + LinearEnvelope.h \ + Marker.C \ + Marker.h \ + Morpher.C \ + Morpher.h \ + NoiseGenerator.C \ + NoiseGenerator.h \ + Notifier.C \ + Notifier.h \ + Oscillator.C \ + Oscillator.h \ + Partial.C \ + Partial.h \ + PartialBuilder.C \ + PartialBuilder.h \ + PartialList.h \ + PartialPtrs.h \ + PartialUtils.C \ + PartialUtils.h \ + phasefix.C \ + phasefix.h \ + ReassignedSpectrum.C \ + ReassignedSpectrum.h \ + Resampler.C \ + Resampler.h \ + SdifFile.h \ + SdifFile.C \ + Sieve.h \ + Sieve.C \ + SpcFile.C \ + SpcFile.h \ + SpectralPeaks.h \ + SpectralPeakSelector.C \ + SpectralPeakSelector.h \ + SpectralSurface.C \ + SpectralSurface.h \ + Synthesizer.C \ + Synthesizer.h \ + fftsg.c + + +# source code for the procedural (C) interface +PI_SRC = loris.h lorisAnalyzer_pi.C lorisBpEnvelope_pi.C \ + lorisException_pi.C lorisException_pi.h lorisNonObj_pi.C \ + lorisPartialList_pi.C lorisUtilities_pi.C + + +# convenience library containing Csound opcodes +if HAVE_CSOUND +CSOUND_LIB = $(top_builddir)/csound/liblorisops.la +endif + + +lib_LTLIBRARIES = libloris.la +libloris_la_SOURCES = $(CPP_SRC) $(PI_SRC) +libloris_la_CPPFLAGS = $(INCLUDE_FFTW) $(default_includes) +libloris_la_LIBADD = $(LINK_FFTW) $(CSOUND_LIB) + +# the library version for Loris 1.8 is 13:0:0 +libloris_la_LDFLAGS = -version-info 13:0:0 $(EXTRA_LD_FLAGS) + +# loris.h is generated automatically from loris.h.in +nodist_include_HEADERS = loris.h +EXTRA_DIST = loris.h.in + +# installed Loris header files +pkginclude_HEADERS = \ + AiffFile.h \ + Analyzer.h \ + BreakpointEnvelope.h \ + Breakpoint.h \ + BreakpointUtils.h \ + Channelizer.h \ + Collator.h \ + Dilator.h \ + Distiller.h \ + Envelope.h \ + Exception.h \ + F0Estimate.h \ + Filter.h \ + FourierTransform.h \ + FrequencyReference.h \ + Fundamental.h \ + Harmonifier.h \ + ImportLemur.h \ + KaiserWindow.h \ + LinearEnvelope.h \ + LorisExceptions.h \ + Marker.h \ + Morpher.h \ + NoiseGenerator.h \ + Notifier.h \ + Oscillator.h \ + Partial.h \ + PartialList.h \ + PartialPtrs.h \ + PartialUtils.h \ + ReassignedSpectrum.h \ + Resampler.h \ + SdifFile.h \ + Sieve.h \ + SpcFile.h \ + SpectralSurface.h \ + Synthesizer.h + +MAINTAINERCLEANFILES = Makefile.in + diff --git a/src/loris/Makefile.in b/src/loris/Makefile.in new file mode 100644 index 0000000..c66a11d --- /dev/null +++ b/src/loris/Makefile.in @@ -0,0 +1,1168 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken +# <loris@cerlsoundgroup.org> +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src +DIST_COMMON = README $(pkginclude_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in $(srcdir)/loris.h.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = loris.h +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)" \ + "$(DESTDIR)$(pkgincludedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libloris_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(CSOUND_LIB) +am__objects_1 = libloris_la-AiffData.lo libloris_la-AiffFile.lo \ + libloris_la-Analyzer.lo libloris_la-AssociateBandwidth.lo \ + libloris_la-BigEndian.lo libloris_la-Breakpoint.lo \ + libloris_la-BreakpointUtils.lo libloris_la-Channelizer.lo \ + libloris_la-Collator.lo libloris_la-Dilator.lo \ + libloris_la-Distiller.lo libloris_la-Envelope.lo \ + libloris_la-F0Estimate.lo libloris_la-LorisExceptions.lo \ + libloris_la-Filter.lo libloris_la-FourierTransform.lo \ + libloris_la-FrequencyReference.lo libloris_la-Fundamental.lo \ + libloris_la-Harmonifier.lo libloris_la-ImportLemur.lo \ + libloris_la-KaiserWindow.lo libloris_la-LinearEnvelope.lo \ + libloris_la-Marker.lo libloris_la-Morpher.lo \ + libloris_la-NoiseGenerator.lo libloris_la-Notifier.lo \ + libloris_la-Oscillator.lo libloris_la-Partial.lo \ + libloris_la-PartialBuilder.lo libloris_la-PartialUtils.lo \ + libloris_la-phasefix.lo libloris_la-ReassignedSpectrum.lo \ + libloris_la-Resampler.lo libloris_la-SdifFile.lo \ + libloris_la-Sieve.lo libloris_la-SpcFile.lo \ + libloris_la-SpectralPeakSelector.lo \ + libloris_la-SpectralSurface.lo libloris_la-Synthesizer.lo \ + libloris_la-fftsg.lo +am__objects_2 = libloris_la-lorisAnalyzer_pi.lo \ + libloris_la-lorisBpEnvelope_pi.lo \ + libloris_la-lorisException_pi.lo libloris_la-lorisNonObj_pi.lo \ + libloris_la-lorisPartialList_pi.lo \ + libloris_la-lorisUtilities_pi.lo +am_libloris_la_OBJECTS = $(am__objects_1) $(am__objects_2) +libloris_la_OBJECTS = $(am_libloris_la_OBJECTS) +libloris_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(libloris_la_LDFLAGS) $(LDFLAGS) -o $@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/config/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(libloris_la_SOURCES) +DIST_SOURCES = $(libloris_la_SOURCES) +HEADERS = $(nodist_include_HEADERS) $(pkginclude_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CSOUND = @CSOUND@ +CSOUND_CONFIG = @CSOUND_CONFIG@ +CSOUND_CXXFLAGS = @CSOUND_CXXFLAGS@ +CSOUND_PREFIX = @CSOUND_PREFIX@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +LINK_FFTW = @LINK_FFTW@ +LN_S = @LN_S@ +LORIS_MAJOR_VERSION = @LORIS_MAJOR_VERSION@ +LORIS_MINOR_VERSION = @LORIS_MINOR_VERSION@ +LORIS_SUBMINOR_VERSION = @LORIS_SUBMINOR_VERSION@ +LORIS_VERSION_STR = @LORIS_VERSION_STR@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NMEDIT = @NMEDIT@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PYFLAGS = @PYFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SWIG = @SWIG@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_aux_dir = @ac_aux_dir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# source code for Loris classes +CPP_SRC = AiffData.C \ + AiffData.h \ + AiffFile.C \ + AiffFile.h \ + Analyzer.C \ + Analyzer.h \ + AssociateBandwidth.C \ + AssociateBandwidth.h \ + BigEndian.C \ + BigEndian.h \ + Breakpoint.C \ + Breakpoint.h \ + BreakpointEnvelope.h \ + BreakpointUtils.C \ + BreakpointUtils.h \ + Channelizer.C \ + Channelizer.h \ + Collator.C \ + Collator.h \ + Dilator.C \ + Dilator.h \ + Distiller.C \ + Distiller.h \ + Envelope.C \ + Envelope.h \ + F0Estimate.C \ + F0Estimate.h \ + LorisExceptions.C \ + LorisExceptions.h \ + Filter.C \ + Filter.h \ + FourierTransform.C \ + FourierTransform.h \ + FrequencyReference.C \ + FrequencyReference.h \ + Fundamental.C \ + Fundamental.h \ + Harmonifier.C \ + Harmonifier.h \ + ImportLemur.C \ + ImportLemur.h \ + KaiserWindow.C \ + KaiserWindow.h \ + LinearEnvelope.C \ + LinearEnvelope.h \ + Marker.C \ + Marker.h \ + Morpher.C \ + Morpher.h \ + NoiseGenerator.C \ + NoiseGenerator.h \ + Notifier.C \ + Notifier.h \ + Oscillator.C \ + Oscillator.h \ + Partial.C \ + Partial.h \ + PartialBuilder.C \ + PartialBuilder.h \ + PartialList.h \ + PartialPtrs.h \ + PartialUtils.C \ + PartialUtils.h \ + phasefix.C \ + phasefix.h \ + ReassignedSpectrum.C \ + ReassignedSpectrum.h \ + Resampler.C \ + Resampler.h \ + SdifFile.h \ + SdifFile.C \ + Sieve.h \ + Sieve.C \ + SpcFile.C \ + SpcFile.h \ + SpectralPeaks.h \ + SpectralPeakSelector.C \ + SpectralPeakSelector.h \ + SpectralSurface.C \ + SpectralSurface.h \ + Synthesizer.C \ + Synthesizer.h \ + fftsg.c + + +# source code for the procedural (C) interface +PI_SRC = loris.h lorisAnalyzer_pi.C lorisBpEnvelope_pi.C \ + lorisException_pi.C lorisException_pi.h lorisNonObj_pi.C \ + lorisPartialList_pi.C lorisUtilities_pi.C + + +# convenience library containing Csound opcodes +@HAVE_CSOUND_TRUE@CSOUND_LIB = $(top_builddir)/csound/liblorisops.la +lib_LTLIBRARIES = libloris.la +libloris_la_SOURCES = $(CPP_SRC) $(PI_SRC) +libloris_la_CPPFLAGS = $(INCLUDE_FFTW) $(default_includes) +libloris_la_LIBADD = $(LINK_FFTW) $(CSOUND_LIB) + +# the library version for Loris 1.8 is 13:0:0 +libloris_la_LDFLAGS = -version-info 13:0:0 $(EXTRA_LD_FLAGS) + +# loris.h is generated automatically from loris.h.in +nodist_include_HEADERS = loris.h +EXTRA_DIST = loris.h.in + +# installed Loris header files +pkginclude_HEADERS = \ + AiffFile.h \ + Analyzer.h \ + BreakpointEnvelope.h \ + Breakpoint.h \ + BreakpointUtils.h \ + Channelizer.h \ + Collator.h \ + Dilator.h \ + Distiller.h \ + Envelope.h \ + Exception.h \ + F0Estimate.h \ + Filter.h \ + FourierTransform.h \ + FrequencyReference.h \ + Fundamental.h \ + Harmonifier.h \ + ImportLemur.h \ + KaiserWindow.h \ + LinearEnvelope.h \ + LorisExceptions.h \ + Marker.h \ + Morpher.h \ + NoiseGenerator.h \ + Notifier.h \ + Oscillator.h \ + Partial.h \ + PartialList.h \ + PartialPtrs.h \ + PartialUtils.h \ + ReassignedSpectrum.h \ + Resampler.h \ + SdifFile.h \ + Sieve.h \ + SpcFile.h \ + SpectralSurface.h \ + Synthesizer.h + +MAINTAINERCLEANFILES = Makefile.in +all: all-am + +.SUFFIXES: +.SUFFIXES: .C .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +loris.h: $(top_builddir)/config.status $(srcdir)/loris.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libloris.la: $(libloris_la_OBJECTS) $(libloris_la_DEPENDENCIES) + $(libloris_la_LINK) -rpath $(libdir) $(libloris_la_OBJECTS) $(libloris_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-AiffData.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-AiffFile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Analyzer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-AssociateBandwidth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-BigEndian.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Breakpoint.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-BreakpointUtils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Channelizer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Collator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Dilator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Distiller.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Envelope.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-F0Estimate.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Filter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-FourierTransform.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-FrequencyReference.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Fundamental.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Harmonifier.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-ImportLemur.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-KaiserWindow.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-LinearEnvelope.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-LorisExceptions.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Marker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Morpher.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-NoiseGenerator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Notifier.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Oscillator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Partial.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-PartialBuilder.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-PartialUtils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-ReassignedSpectrum.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Resampler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-SdifFile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Sieve.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-SpcFile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-SpectralPeakSelector.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-SpectralSurface.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-Synthesizer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-fftsg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisAnalyzer_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisBpEnvelope_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisException_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisNonObj_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisPartialList_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-lorisUtilities_pi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloris_la-phasefix.Plo@am__quote@ + +.C.o: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.C.obj: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.C.lo: +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $< + +libloris_la-AiffData.lo: AiffData.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-AiffData.lo -MD -MP -MF $(DEPDIR)/libloris_la-AiffData.Tpo -c -o libloris_la-AiffData.lo `test -f 'AiffData.C' || echo '$(srcdir)/'`AiffData.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-AiffData.Tpo $(DEPDIR)/libloris_la-AiffData.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='AiffData.C' object='libloris_la-AiffData.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-AiffData.lo `test -f 'AiffData.C' || echo '$(srcdir)/'`AiffData.C + +libloris_la-AiffFile.lo: AiffFile.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-AiffFile.lo -MD -MP -MF $(DEPDIR)/libloris_la-AiffFile.Tpo -c -o libloris_la-AiffFile.lo `test -f 'AiffFile.C' || echo '$(srcdir)/'`AiffFile.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-AiffFile.Tpo $(DEPDIR)/libloris_la-AiffFile.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='AiffFile.C' object='libloris_la-AiffFile.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-AiffFile.lo `test -f 'AiffFile.C' || echo '$(srcdir)/'`AiffFile.C + +libloris_la-Analyzer.lo: Analyzer.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Analyzer.lo -MD -MP -MF $(DEPDIR)/libloris_la-Analyzer.Tpo -c -o libloris_la-Analyzer.lo `test -f 'Analyzer.C' || echo '$(srcdir)/'`Analyzer.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Analyzer.Tpo $(DEPDIR)/libloris_la-Analyzer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Analyzer.C' object='libloris_la-Analyzer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Analyzer.lo `test -f 'Analyzer.C' || echo '$(srcdir)/'`Analyzer.C + +libloris_la-AssociateBandwidth.lo: AssociateBandwidth.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-AssociateBandwidth.lo -MD -MP -MF $(DEPDIR)/libloris_la-AssociateBandwidth.Tpo -c -o libloris_la-AssociateBandwidth.lo `test -f 'AssociateBandwidth.C' || echo '$(srcdir)/'`AssociateBandwidth.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-AssociateBandwidth.Tpo $(DEPDIR)/libloris_la-AssociateBandwidth.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='AssociateBandwidth.C' object='libloris_la-AssociateBandwidth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-AssociateBandwidth.lo `test -f 'AssociateBandwidth.C' || echo '$(srcdir)/'`AssociateBandwidth.C + +libloris_la-BigEndian.lo: BigEndian.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-BigEndian.lo -MD -MP -MF $(DEPDIR)/libloris_la-BigEndian.Tpo -c -o libloris_la-BigEndian.lo `test -f 'BigEndian.C' || echo '$(srcdir)/'`BigEndian.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-BigEndian.Tpo $(DEPDIR)/libloris_la-BigEndian.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='BigEndian.C' object='libloris_la-BigEndian.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-BigEndian.lo `test -f 'BigEndian.C' || echo '$(srcdir)/'`BigEndian.C + +libloris_la-Breakpoint.lo: Breakpoint.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Breakpoint.lo -MD -MP -MF $(DEPDIR)/libloris_la-Breakpoint.Tpo -c -o libloris_la-Breakpoint.lo `test -f 'Breakpoint.C' || echo '$(srcdir)/'`Breakpoint.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Breakpoint.Tpo $(DEPDIR)/libloris_la-Breakpoint.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Breakpoint.C' object='libloris_la-Breakpoint.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Breakpoint.lo `test -f 'Breakpoint.C' || echo '$(srcdir)/'`Breakpoint.C + +libloris_la-BreakpointUtils.lo: BreakpointUtils.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-BreakpointUtils.lo -MD -MP -MF $(DEPDIR)/libloris_la-BreakpointUtils.Tpo -c -o libloris_la-BreakpointUtils.lo `test -f 'BreakpointUtils.C' || echo '$(srcdir)/'`BreakpointUtils.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-BreakpointUtils.Tpo $(DEPDIR)/libloris_la-BreakpointUtils.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='BreakpointUtils.C' object='libloris_la-BreakpointUtils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-BreakpointUtils.lo `test -f 'BreakpointUtils.C' || echo '$(srcdir)/'`BreakpointUtils.C + +libloris_la-Channelizer.lo: Channelizer.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Channelizer.lo -MD -MP -MF $(DEPDIR)/libloris_la-Channelizer.Tpo -c -o libloris_la-Channelizer.lo `test -f 'Channelizer.C' || echo '$(srcdir)/'`Channelizer.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Channelizer.Tpo $(DEPDIR)/libloris_la-Channelizer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Channelizer.C' object='libloris_la-Channelizer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Channelizer.lo `test -f 'Channelizer.C' || echo '$(srcdir)/'`Channelizer.C + +libloris_la-Collator.lo: Collator.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Collator.lo -MD -MP -MF $(DEPDIR)/libloris_la-Collator.Tpo -c -o libloris_la-Collator.lo `test -f 'Collator.C' || echo '$(srcdir)/'`Collator.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Collator.Tpo $(DEPDIR)/libloris_la-Collator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Collator.C' object='libloris_la-Collator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Collator.lo `test -f 'Collator.C' || echo '$(srcdir)/'`Collator.C + +libloris_la-Dilator.lo: Dilator.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Dilator.lo -MD -MP -MF $(DEPDIR)/libloris_la-Dilator.Tpo -c -o libloris_la-Dilator.lo `test -f 'Dilator.C' || echo '$(srcdir)/'`Dilator.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Dilator.Tpo $(DEPDIR)/libloris_la-Dilator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Dilator.C' object='libloris_la-Dilator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Dilator.lo `test -f 'Dilator.C' || echo '$(srcdir)/'`Dilator.C + +libloris_la-Distiller.lo: Distiller.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Distiller.lo -MD -MP -MF $(DEPDIR)/libloris_la-Distiller.Tpo -c -o libloris_la-Distiller.lo `test -f 'Distiller.C' || echo '$(srcdir)/'`Distiller.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Distiller.Tpo $(DEPDIR)/libloris_la-Distiller.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Distiller.C' object='libloris_la-Distiller.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Distiller.lo `test -f 'Distiller.C' || echo '$(srcdir)/'`Distiller.C + +libloris_la-Envelope.lo: Envelope.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Envelope.lo -MD -MP -MF $(DEPDIR)/libloris_la-Envelope.Tpo -c -o libloris_la-Envelope.lo `test -f 'Envelope.C' || echo '$(srcdir)/'`Envelope.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Envelope.Tpo $(DEPDIR)/libloris_la-Envelope.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Envelope.C' object='libloris_la-Envelope.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Envelope.lo `test -f 'Envelope.C' || echo '$(srcdir)/'`Envelope.C + +libloris_la-F0Estimate.lo: F0Estimate.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-F0Estimate.lo -MD -MP -MF $(DEPDIR)/libloris_la-F0Estimate.Tpo -c -o libloris_la-F0Estimate.lo `test -f 'F0Estimate.C' || echo '$(srcdir)/'`F0Estimate.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-F0Estimate.Tpo $(DEPDIR)/libloris_la-F0Estimate.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='F0Estimate.C' object='libloris_la-F0Estimate.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-F0Estimate.lo `test -f 'F0Estimate.C' || echo '$(srcdir)/'`F0Estimate.C + +libloris_la-LorisExceptions.lo: LorisExceptions.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-LorisExceptions.lo -MD -MP -MF $(DEPDIR)/libloris_la-LorisExceptions.Tpo -c -o libloris_la-LorisExceptions.lo `test -f 'LorisExceptions.C' || echo '$(srcdir)/'`LorisExceptions.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-LorisExceptions.Tpo $(DEPDIR)/libloris_la-LorisExceptions.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='LorisExceptions.C' object='libloris_la-LorisExceptions.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-LorisExceptions.lo `test -f 'LorisExceptions.C' || echo '$(srcdir)/'`LorisExceptions.C + +libloris_la-Filter.lo: Filter.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Filter.lo -MD -MP -MF $(DEPDIR)/libloris_la-Filter.Tpo -c -o libloris_la-Filter.lo `test -f 'Filter.C' || echo '$(srcdir)/'`Filter.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Filter.Tpo $(DEPDIR)/libloris_la-Filter.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Filter.C' object='libloris_la-Filter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Filter.lo `test -f 'Filter.C' || echo '$(srcdir)/'`Filter.C + +libloris_la-FourierTransform.lo: FourierTransform.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-FourierTransform.lo -MD -MP -MF $(DEPDIR)/libloris_la-FourierTransform.Tpo -c -o libloris_la-FourierTransform.lo `test -f 'FourierTransform.C' || echo '$(srcdir)/'`FourierTransform.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-FourierTransform.Tpo $(DEPDIR)/libloris_la-FourierTransform.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FourierTransform.C' object='libloris_la-FourierTransform.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-FourierTransform.lo `test -f 'FourierTransform.C' || echo '$(srcdir)/'`FourierTransform.C + +libloris_la-FrequencyReference.lo: FrequencyReference.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-FrequencyReference.lo -MD -MP -MF $(DEPDIR)/libloris_la-FrequencyReference.Tpo -c -o libloris_la-FrequencyReference.lo `test -f 'FrequencyReference.C' || echo '$(srcdir)/'`FrequencyReference.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-FrequencyReference.Tpo $(DEPDIR)/libloris_la-FrequencyReference.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FrequencyReference.C' object='libloris_la-FrequencyReference.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-FrequencyReference.lo `test -f 'FrequencyReference.C' || echo '$(srcdir)/'`FrequencyReference.C + +libloris_la-Fundamental.lo: Fundamental.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Fundamental.lo -MD -MP -MF $(DEPDIR)/libloris_la-Fundamental.Tpo -c -o libloris_la-Fundamental.lo `test -f 'Fundamental.C' || echo '$(srcdir)/'`Fundamental.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Fundamental.Tpo $(DEPDIR)/libloris_la-Fundamental.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Fundamental.C' object='libloris_la-Fundamental.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Fundamental.lo `test -f 'Fundamental.C' || echo '$(srcdir)/'`Fundamental.C + +libloris_la-Harmonifier.lo: Harmonifier.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Harmonifier.lo -MD -MP -MF $(DEPDIR)/libloris_la-Harmonifier.Tpo -c -o libloris_la-Harmonifier.lo `test -f 'Harmonifier.C' || echo '$(srcdir)/'`Harmonifier.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Harmonifier.Tpo $(DEPDIR)/libloris_la-Harmonifier.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Harmonifier.C' object='libloris_la-Harmonifier.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Harmonifier.lo `test -f 'Harmonifier.C' || echo '$(srcdir)/'`Harmonifier.C + +libloris_la-ImportLemur.lo: ImportLemur.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-ImportLemur.lo -MD -MP -MF $(DEPDIR)/libloris_la-ImportLemur.Tpo -c -o libloris_la-ImportLemur.lo `test -f 'ImportLemur.C' || echo '$(srcdir)/'`ImportLemur.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-ImportLemur.Tpo $(DEPDIR)/libloris_la-ImportLemur.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='ImportLemur.C' object='libloris_la-ImportLemur.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-ImportLemur.lo `test -f 'ImportLemur.C' || echo '$(srcdir)/'`ImportLemur.C + +libloris_la-KaiserWindow.lo: KaiserWindow.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-KaiserWindow.lo -MD -MP -MF $(DEPDIR)/libloris_la-KaiserWindow.Tpo -c -o libloris_la-KaiserWindow.lo `test -f 'KaiserWindow.C' || echo '$(srcdir)/'`KaiserWindow.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-KaiserWindow.Tpo $(DEPDIR)/libloris_la-KaiserWindow.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='KaiserWindow.C' object='libloris_la-KaiserWindow.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-KaiserWindow.lo `test -f 'KaiserWindow.C' || echo '$(srcdir)/'`KaiserWindow.C + +libloris_la-LinearEnvelope.lo: LinearEnvelope.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-LinearEnvelope.lo -MD -MP -MF $(DEPDIR)/libloris_la-LinearEnvelope.Tpo -c -o libloris_la-LinearEnvelope.lo `test -f 'LinearEnvelope.C' || echo '$(srcdir)/'`LinearEnvelope.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-LinearEnvelope.Tpo $(DEPDIR)/libloris_la-LinearEnvelope.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='LinearEnvelope.C' object='libloris_la-LinearEnvelope.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-LinearEnvelope.lo `test -f 'LinearEnvelope.C' || echo '$(srcdir)/'`LinearEnvelope.C + +libloris_la-Marker.lo: Marker.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Marker.lo -MD -MP -MF $(DEPDIR)/libloris_la-Marker.Tpo -c -o libloris_la-Marker.lo `test -f 'Marker.C' || echo '$(srcdir)/'`Marker.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Marker.Tpo $(DEPDIR)/libloris_la-Marker.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Marker.C' object='libloris_la-Marker.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Marker.lo `test -f 'Marker.C' || echo '$(srcdir)/'`Marker.C + +libloris_la-Morpher.lo: Morpher.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Morpher.lo -MD -MP -MF $(DEPDIR)/libloris_la-Morpher.Tpo -c -o libloris_la-Morpher.lo `test -f 'Morpher.C' || echo '$(srcdir)/'`Morpher.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Morpher.Tpo $(DEPDIR)/libloris_la-Morpher.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Morpher.C' object='libloris_la-Morpher.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Morpher.lo `test -f 'Morpher.C' || echo '$(srcdir)/'`Morpher.C + +libloris_la-NoiseGenerator.lo: NoiseGenerator.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-NoiseGenerator.lo -MD -MP -MF $(DEPDIR)/libloris_la-NoiseGenerator.Tpo -c -o libloris_la-NoiseGenerator.lo `test -f 'NoiseGenerator.C' || echo '$(srcdir)/'`NoiseGenerator.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-NoiseGenerator.Tpo $(DEPDIR)/libloris_la-NoiseGenerator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='NoiseGenerator.C' object='libloris_la-NoiseGenerator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-NoiseGenerator.lo `test -f 'NoiseGenerator.C' || echo '$(srcdir)/'`NoiseGenerator.C + +libloris_la-Notifier.lo: Notifier.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Notifier.lo -MD -MP -MF $(DEPDIR)/libloris_la-Notifier.Tpo -c -o libloris_la-Notifier.lo `test -f 'Notifier.C' || echo '$(srcdir)/'`Notifier.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Notifier.Tpo $(DEPDIR)/libloris_la-Notifier.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Notifier.C' object='libloris_la-Notifier.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Notifier.lo `test -f 'Notifier.C' || echo '$(srcdir)/'`Notifier.C + +libloris_la-Oscillator.lo: Oscillator.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Oscillator.lo -MD -MP -MF $(DEPDIR)/libloris_la-Oscillator.Tpo -c -o libloris_la-Oscillator.lo `test -f 'Oscillator.C' || echo '$(srcdir)/'`Oscillator.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Oscillator.Tpo $(DEPDIR)/libloris_la-Oscillator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Oscillator.C' object='libloris_la-Oscillator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Oscillator.lo `test -f 'Oscillator.C' || echo '$(srcdir)/'`Oscillator.C + +libloris_la-Partial.lo: Partial.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Partial.lo -MD -MP -MF $(DEPDIR)/libloris_la-Partial.Tpo -c -o libloris_la-Partial.lo `test -f 'Partial.C' || echo '$(srcdir)/'`Partial.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Partial.Tpo $(DEPDIR)/libloris_la-Partial.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Partial.C' object='libloris_la-Partial.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Partial.lo `test -f 'Partial.C' || echo '$(srcdir)/'`Partial.C + +libloris_la-PartialBuilder.lo: PartialBuilder.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-PartialBuilder.lo -MD -MP -MF $(DEPDIR)/libloris_la-PartialBuilder.Tpo -c -o libloris_la-PartialBuilder.lo `test -f 'PartialBuilder.C' || echo '$(srcdir)/'`PartialBuilder.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-PartialBuilder.Tpo $(DEPDIR)/libloris_la-PartialBuilder.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='PartialBuilder.C' object='libloris_la-PartialBuilder.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-PartialBuilder.lo `test -f 'PartialBuilder.C' || echo '$(srcdir)/'`PartialBuilder.C + +libloris_la-PartialUtils.lo: PartialUtils.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-PartialUtils.lo -MD -MP -MF $(DEPDIR)/libloris_la-PartialUtils.Tpo -c -o libloris_la-PartialUtils.lo `test -f 'PartialUtils.C' || echo '$(srcdir)/'`PartialUtils.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-PartialUtils.Tpo $(DEPDIR)/libloris_la-PartialUtils.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='PartialUtils.C' object='libloris_la-PartialUtils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-PartialUtils.lo `test -f 'PartialUtils.C' || echo '$(srcdir)/'`PartialUtils.C + +libloris_la-phasefix.lo: phasefix.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-phasefix.lo -MD -MP -MF $(DEPDIR)/libloris_la-phasefix.Tpo -c -o libloris_la-phasefix.lo `test -f 'phasefix.C' || echo '$(srcdir)/'`phasefix.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-phasefix.Tpo $(DEPDIR)/libloris_la-phasefix.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='phasefix.C' object='libloris_la-phasefix.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-phasefix.lo `test -f 'phasefix.C' || echo '$(srcdir)/'`phasefix.C + +libloris_la-ReassignedSpectrum.lo: ReassignedSpectrum.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-ReassignedSpectrum.lo -MD -MP -MF $(DEPDIR)/libloris_la-ReassignedSpectrum.Tpo -c -o libloris_la-ReassignedSpectrum.lo `test -f 'ReassignedSpectrum.C' || echo '$(srcdir)/'`ReassignedSpectrum.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-ReassignedSpectrum.Tpo $(DEPDIR)/libloris_la-ReassignedSpectrum.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='ReassignedSpectrum.C' object='libloris_la-ReassignedSpectrum.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-ReassignedSpectrum.lo `test -f 'ReassignedSpectrum.C' || echo '$(srcdir)/'`ReassignedSpectrum.C + +libloris_la-Resampler.lo: Resampler.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Resampler.lo -MD -MP -MF $(DEPDIR)/libloris_la-Resampler.Tpo -c -o libloris_la-Resampler.lo `test -f 'Resampler.C' || echo '$(srcdir)/'`Resampler.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Resampler.Tpo $(DEPDIR)/libloris_la-Resampler.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Resampler.C' object='libloris_la-Resampler.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Resampler.lo `test -f 'Resampler.C' || echo '$(srcdir)/'`Resampler.C + +libloris_la-SdifFile.lo: SdifFile.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-SdifFile.lo -MD -MP -MF $(DEPDIR)/libloris_la-SdifFile.Tpo -c -o libloris_la-SdifFile.lo `test -f 'SdifFile.C' || echo '$(srcdir)/'`SdifFile.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-SdifFile.Tpo $(DEPDIR)/libloris_la-SdifFile.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='SdifFile.C' object='libloris_la-SdifFile.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-SdifFile.lo `test -f 'SdifFile.C' || echo '$(srcdir)/'`SdifFile.C + +libloris_la-Sieve.lo: Sieve.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Sieve.lo -MD -MP -MF $(DEPDIR)/libloris_la-Sieve.Tpo -c -o libloris_la-Sieve.lo `test -f 'Sieve.C' || echo '$(srcdir)/'`Sieve.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Sieve.Tpo $(DEPDIR)/libloris_la-Sieve.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Sieve.C' object='libloris_la-Sieve.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Sieve.lo `test -f 'Sieve.C' || echo '$(srcdir)/'`Sieve.C + +libloris_la-SpcFile.lo: SpcFile.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-SpcFile.lo -MD -MP -MF $(DEPDIR)/libloris_la-SpcFile.Tpo -c -o libloris_la-SpcFile.lo `test -f 'SpcFile.C' || echo '$(srcdir)/'`SpcFile.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-SpcFile.Tpo $(DEPDIR)/libloris_la-SpcFile.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='SpcFile.C' object='libloris_la-SpcFile.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-SpcFile.lo `test -f 'SpcFile.C' || echo '$(srcdir)/'`SpcFile.C + +libloris_la-SpectralPeakSelector.lo: SpectralPeakSelector.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-SpectralPeakSelector.lo -MD -MP -MF $(DEPDIR)/libloris_la-SpectralPeakSelector.Tpo -c -o libloris_la-SpectralPeakSelector.lo `test -f 'SpectralPeakSelector.C' || echo '$(srcdir)/'`SpectralPeakSelector.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-SpectralPeakSelector.Tpo $(DEPDIR)/libloris_la-SpectralPeakSelector.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='SpectralPeakSelector.C' object='libloris_la-SpectralPeakSelector.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-SpectralPeakSelector.lo `test -f 'SpectralPeakSelector.C' || echo '$(srcdir)/'`SpectralPeakSelector.C + +libloris_la-SpectralSurface.lo: SpectralSurface.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-SpectralSurface.lo -MD -MP -MF $(DEPDIR)/libloris_la-SpectralSurface.Tpo -c -o libloris_la-SpectralSurface.lo `test -f 'SpectralSurface.C' || echo '$(srcdir)/'`SpectralSurface.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-SpectralSurface.Tpo $(DEPDIR)/libloris_la-SpectralSurface.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='SpectralSurface.C' object='libloris_la-SpectralSurface.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-SpectralSurface.lo `test -f 'SpectralSurface.C' || echo '$(srcdir)/'`SpectralSurface.C + +libloris_la-Synthesizer.lo: Synthesizer.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-Synthesizer.lo -MD -MP -MF $(DEPDIR)/libloris_la-Synthesizer.Tpo -c -o libloris_la-Synthesizer.lo `test -f 'Synthesizer.C' || echo '$(srcdir)/'`Synthesizer.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-Synthesizer.Tpo $(DEPDIR)/libloris_la-Synthesizer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='Synthesizer.C' object='libloris_la-Synthesizer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-Synthesizer.lo `test -f 'Synthesizer.C' || echo '$(srcdir)/'`Synthesizer.C + +libloris_la-lorisAnalyzer_pi.lo: lorisAnalyzer_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisAnalyzer_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisAnalyzer_pi.Tpo -c -o libloris_la-lorisAnalyzer_pi.lo `test -f 'lorisAnalyzer_pi.C' || echo '$(srcdir)/'`lorisAnalyzer_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisAnalyzer_pi.Tpo $(DEPDIR)/libloris_la-lorisAnalyzer_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisAnalyzer_pi.C' object='libloris_la-lorisAnalyzer_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisAnalyzer_pi.lo `test -f 'lorisAnalyzer_pi.C' || echo '$(srcdir)/'`lorisAnalyzer_pi.C + +libloris_la-lorisBpEnvelope_pi.lo: lorisBpEnvelope_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisBpEnvelope_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisBpEnvelope_pi.Tpo -c -o libloris_la-lorisBpEnvelope_pi.lo `test -f 'lorisBpEnvelope_pi.C' || echo '$(srcdir)/'`lorisBpEnvelope_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisBpEnvelope_pi.Tpo $(DEPDIR)/libloris_la-lorisBpEnvelope_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisBpEnvelope_pi.C' object='libloris_la-lorisBpEnvelope_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisBpEnvelope_pi.lo `test -f 'lorisBpEnvelope_pi.C' || echo '$(srcdir)/'`lorisBpEnvelope_pi.C + +libloris_la-lorisException_pi.lo: lorisException_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisException_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisException_pi.Tpo -c -o libloris_la-lorisException_pi.lo `test -f 'lorisException_pi.C' || echo '$(srcdir)/'`lorisException_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisException_pi.Tpo $(DEPDIR)/libloris_la-lorisException_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisException_pi.C' object='libloris_la-lorisException_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisException_pi.lo `test -f 'lorisException_pi.C' || echo '$(srcdir)/'`lorisException_pi.C + +libloris_la-lorisNonObj_pi.lo: lorisNonObj_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisNonObj_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisNonObj_pi.Tpo -c -o libloris_la-lorisNonObj_pi.lo `test -f 'lorisNonObj_pi.C' || echo '$(srcdir)/'`lorisNonObj_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisNonObj_pi.Tpo $(DEPDIR)/libloris_la-lorisNonObj_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisNonObj_pi.C' object='libloris_la-lorisNonObj_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisNonObj_pi.lo `test -f 'lorisNonObj_pi.C' || echo '$(srcdir)/'`lorisNonObj_pi.C + +libloris_la-lorisPartialList_pi.lo: lorisPartialList_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisPartialList_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisPartialList_pi.Tpo -c -o libloris_la-lorisPartialList_pi.lo `test -f 'lorisPartialList_pi.C' || echo '$(srcdir)/'`lorisPartialList_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisPartialList_pi.Tpo $(DEPDIR)/libloris_la-lorisPartialList_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisPartialList_pi.C' object='libloris_la-lorisPartialList_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisPartialList_pi.lo `test -f 'lorisPartialList_pi.C' || echo '$(srcdir)/'`lorisPartialList_pi.C + +libloris_la-lorisUtilities_pi.lo: lorisUtilities_pi.C +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libloris_la-lorisUtilities_pi.lo -MD -MP -MF $(DEPDIR)/libloris_la-lorisUtilities_pi.Tpo -c -o libloris_la-lorisUtilities_pi.lo `test -f 'lorisUtilities_pi.C' || echo '$(srcdir)/'`lorisUtilities_pi.C +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-lorisUtilities_pi.Tpo $(DEPDIR)/libloris_la-lorisUtilities_pi.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lorisUtilities_pi.C' object='libloris_la-lorisUtilities_pi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libloris_la-lorisUtilities_pi.lo `test -f 'lorisUtilities_pi.C' || echo '$(srcdir)/'`lorisUtilities_pi.C + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libloris_la-fftsg.lo: fftsg.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libloris_la-fftsg.lo -MD -MP -MF $(DEPDIR)/libloris_la-fftsg.Tpo -c -o libloris_la-fftsg.lo `test -f 'fftsg.c' || echo '$(srcdir)/'`fftsg.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libloris_la-fftsg.Tpo $(DEPDIR)/libloris_la-fftsg.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fftsg.c' object='libloris_la-fftsg.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloris_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libloris_la-fftsg.lo `test -f 'fftsg.c' || echo '$(srcdir)/'`fftsg.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-nodist_includeHEADERS: $(nodist_include_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(includedir)" || $(MKDIR_P) "$(DESTDIR)$(includedir)" + @list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \ + done + +uninstall-nodist_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(includedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(includedir)" && rm -f $$files +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkgincludedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkgincludedir)" && rm -f $$files + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)" "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-nodist_includeHEADERS \ + install-pkgincludeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES uninstall-nodist_includeHEADERS \ + uninstall-pkgincludeHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libLTLIBRARIES install-man \ + install-nodist_includeHEADERS install-pdf install-pdf-am \ + install-pkgincludeHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-libLTLIBRARIES uninstall-nodist_includeHEADERS \ + uninstall-pkgincludeHEADERS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/loris/Marker.C b/src/loris/Marker.C new file mode 100644 index 0000000..83ed3e7 --- /dev/null +++ b/src/loris/Marker.C @@ -0,0 +1,176 @@ +/* + * 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 + * + * + * Marker.cc + * + * Definition of members for Marker and MarkerContainer representing labeled + * time points or temporal features in imported and exported data. Used by + * file I/O classes AiffFile, SdifFile, and SpcFile. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Marker.h" + +// begin namespace +namespace Loris { + +// -- construction -- + +// --------------------------------------------------------------------------- +// Marker - default constructor +// --------------------------------------------------------------------------- +// Default constructor - initialize a Marker at time zero with no label. +// +Marker::Marker( void ) : + m_time( 0. ), + m_name("Untitled Marker") +{ +} + +// --------------------------------------------------------------------------- +// Marker - constructor from time and name +// --------------------------------------------------------------------------- +// Initialize a Marker with the specified time (in seconds) and name. +// +Marker::Marker( double t, const std::string & s ) : + m_time( t ), + m_name( s ) +{ +} + +// --------------------------------------------------------------------------- +// Marker - copy constructor +// --------------------------------------------------------------------------- +// Initialize a Marker that is an exact copy of another Marker, that is, +// having the same time and name. +// +Marker::Marker( const Marker & other ) : + m_time( other.m_time ), + m_name( other.m_name ) +{ +} + +// --------------------------------------------------------------------------- +// assignment operator (operator =) +// --------------------------------------------------------------------------- +// Make this Marker an exact copy, having the same time and name, +// as the Marker rhs. +// +Marker & +Marker::operator=( const Marker & rhs ) +{ + if ( this != &rhs ) + { + // the only imaginable exception that could be generated + // would be an out-of-memory exception at the time of this + // string assignment, reserve memory first, so that if this + // does except, the Marker is unchanged: + m_name.reserve( rhs.m_name.size() ); + m_name = rhs.m_name; + m_time = rhs.m_time; + + } + return *this; +} + +// -- comparison -- + +// --------------------------------------------------------------------------- +// less-than operator (operator <) +// --------------------------------------------------------------------------- +// Return true if this Marker must appear earlier than rhs in a sorted +// collection of Markers, and false otherwise. (Markers are sorted by time.) +// +bool +Marker::operator< ( const Marker & rhs ) const +{ + return m_time < rhs.m_time; +} + +// -- access -- + +// --------------------------------------------------------------------------- +// name +// --------------------------------------------------------------------------- +// Return a reference to the name string for this Marker. +// +std::string & +Marker::name( void ) +{ + return m_name; +} + +// --------------------------------------------------------------------------- +// name (const) +// --------------------------------------------------------------------------- +// Return a const reference to the name string for this Marker. +// +const std::string & +Marker::name( void ) const +{ + return m_name; +} + +// --------------------------------------------------------------------------- +// time +// --------------------------------------------------------------------------- +// Return the time (in seconds) associated with this Marker. +// +double +Marker::time( void ) const +{ + return m_time; +} + +// -- mutation -- + +// --------------------------------------------------------------------------- +// setName +// --------------------------------------------------------------------------- +// Set the name of the Marker. +// +void +Marker::setName( const std::string & s ) +{ + m_name = s; +} + +// --------------------------------------------------------------------------- +// setName +// --------------------------------------------------------------------------- +// Set the time (in seconds) associated with this Marker. +// +void +Marker::setTime( double t ) +{ + m_time = t; +} + +} // end of namespace Loris diff --git a/src/loris/Marker.h b/src/loris/Marker.h new file mode 100644 index 0000000..6c51a23 --- /dev/null +++ b/src/loris/Marker.h @@ -0,0 +1,178 @@ +#ifndef INCLUDE_MARKER_H +#define INCLUDE_MARKER_H +/* + * 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 + * + * + * Marker.h + * + * Definition of classes Marker and MarkerContainer representing labeled + * time points or temporal features in imported and exported data. Used by + * file I/O classes AiffFile, SdifFile, and SpcFile. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include <functional> +#include <string> +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Marker +// +//! Class Marker represents a labeled time point in a set of Partials +//! or a vector of samples. Collections of Markers (see the MarkerContainer +//! definition below) are held by the File I/O classes in Loris (AiffFile, +//! SdifFile, and SpcFile) to identify temporal features in imported +//! and exported data. +// +class Marker +{ +// -- public interface -- +public: +// -- construction -- + + //! Default constructor - initialize a Marker at time zero with no label. + Marker( void ); + + //! Initialize a Marker with the specified time (in seconds) and name. + //! + //! \param t is the time associated with the new Marker + //! \param s is the name associated with the new Marker + Marker( double t, const std::string & s ); + + //! Initialize a Marker that is an exact copy of another Marker, that is, + //! having the same time and name. + //! + //! \param other is the Marker to copy from + Marker( const Marker & other ); + + //! Make this Marker an exact copy, having the same time and name, + //! as the Marker rhs. + //! + //! \param rhs is the Marker to assign from + //! \return reference to self + Marker & operator=( const Marker & rhs ); + +// -- comparison -- + + //! Return true if this Marker must appear earlier than rhs in a sorted + //! collection of Markers, and false otherwise. + //! (Markers are sorted by time.) + //! + //! \param rhs is the Marker to compare with this Marker + //! \return true if this Marker's time is earlier than that of + //! rhs, otherwise false + bool operator< ( const Marker & rhs ) const; + +// -- access -- + + //! Return a reference to the name string + //! for this Marker. + std::string & name( void ); + + //! Return a const reference to the name string + //! for this Marker. + const std::string & name( void ) const; + + //! Return the time (in seconds) associated with this Marker. + double time( void ) const; + + +// -- mutation -- + //! Set the name of the Marker. + void setName( const std::string & s ); + + //! Set the time (in seconds) associated with this Marker. + void setTime( double t ); + +// -- comparitors -- + + //! Comparitor (binary) functor returning true if its first Marker + //! argument should appear before the second in a range sorted + //! by Marker name. + struct compareNameLess : + public std::binary_function< const Marker, const Marker, bool > + { + //! Function call operator, return true if the first Marker + //! argument should appear before the second in a range sorted + //! by Marker name. + bool operator()( const Marker & lhs, const Marker & rhs ) const + { return lhs.name() < rhs.name(); } + }; + + //! old name for compareNameLess, legacy support + //! \deprecated Use compareNameLess instead. + typedef compareNameLess sortByName; + + + //! Comparitor (binary) functor returning true if its first Marker + //! argument should appear before the second in a range sorted + //! by Marker time. + struct compareTimeLess : + public std::binary_function< const Marker, const Marker, bool > + { + //! Function call operator, return true if the first Marker + //! argument should appear before the second in a range sorted + //! by Marker time. + bool operator()( const Marker & lhs, const Marker & rhs ) const + { return lhs.time() < rhs.time(); } + }; + + //! old name for compareTimeLess, legacy support + //! \deprecated Use compareTimeLess instead + typedef compareTimeLess sortByTime; + + //! Predicate functor returning true if the name of a Marker + //! equal to the specified string, and false otherwise. + class isNameEqual : public std::unary_function< const Marker, bool > + { + public: + //! Initialize a new instance with the specified name. + isNameEqual( const std::string & s ) : name(s) {} + + //! Function call operator: evaluate a Marker. + bool operator()( const Marker & m ) const + { return m.name() == name; } + + private: + std::string name; //! the name to compare against + }; + +private: + +// -- implementation -- + + double m_time; //! the time in seconds associated with the Marker + std::string m_name; //! the name of the Marker + +}; // end of class Marker + +} // end of namespace Loris + +#endif /* ndef INCLUDE_MARKER_H */ diff --git a/src/loris/Morpher.C b/src/loris/Morpher.C new file mode 100644 index 0000000..963ea25 --- /dev/null +++ b/src/loris/Morpher.C @@ -0,0 +1,1597 @@ +/* + * 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 + * + * + * Morpher.C + * + * Implementation of class Morpher. + * + * Kelly Fitz, 15 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Morpher.h" +#include "Breakpoint.h" +#include "Envelope.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include "phasefix.h" + +#include <algorithm> +#include <memory> +#include <cmath> +#include <vector> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + + +const double Morpher::DefaultFixThreshold = -90; // dB, very low by default + +// shaping parameter, see interpolateAmplitude: +const double Morpher::DefaultAmpShape = 1E-5; + +const double Morpher::DefaultBreakpointGap = 1E-4; // minimum time (sec) between Breakpoints in + // morphed Partials + +// helper declarations +static inline bool partial_is_nonnull( const Partial & p ); + + + +// -- construction -- + +// --------------------------------------------------------------------------- +// Morpher constructor (single morph function) +// --------------------------------------------------------------------------- +// Construct a new Morpher using the same morphing envelope for +// frequency, amplitude, and bandwidth (noisiness). +// +Morpher::Morpher( const Envelope & f ) : + _freqFunction( f.clone() ), + _ampFunction( f.clone() ), + _bwFunction( f.clone() ), + _freqFixThresholdDb( DefaultFixThreshold ), + _logMorphShape( DefaultAmpShape ), + _minBreakpointGapSec( DefaultBreakpointGap ), + _doLogAmpMorphing( true ), + _doLogFreqMorphing( false ) +{ +} + +// --------------------------------------------------------------------------- +// Morpher constructor (distinct morph functions) +// --------------------------------------------------------------------------- +// Construct a new Morpher using the specified morphing envelopes for +// frequency, amplitude, and bandwidth (noisiness). +// +Morpher::Morpher( const Envelope & ff, const Envelope & af, const Envelope & bwf ) : + _freqFunction( ff.clone() ), + _ampFunction( af.clone() ), + _bwFunction( bwf.clone() ), + _freqFixThresholdDb( DefaultFixThreshold ), + _logMorphShape( DefaultAmpShape ), + _minBreakpointGapSec( DefaultBreakpointGap ), + _doLogAmpMorphing( true ), + _doLogFreqMorphing( false ) +{ +} + +// --------------------------------------------------------------------------- +// Morpher copy constructor +// --------------------------------------------------------------------------- +//! Construct a new Morpher that is a duplicate of rhs. +//! +//! \param rhs is the Morpher to duplicate +Morpher::Morpher( const Morpher & rhs ) : + _freqFunction( rhs._freqFunction->clone() ), + _ampFunction( rhs._ampFunction->clone() ), + _bwFunction( rhs._bwFunction->clone() ), + _srcRefPartial( rhs._srcRefPartial ), + _tgtRefPartial( rhs._tgtRefPartial ), + _freqFixThresholdDb( rhs._freqFixThresholdDb ), + _logMorphShape( rhs._logMorphShape ), + _minBreakpointGapSec( rhs._minBreakpointGapSec ), + _doLogAmpMorphing( rhs._doLogAmpMorphing ), + _doLogFreqMorphing( rhs._doLogFreqMorphing ) +{ +} + +// --------------------------------------------------------------------------- +// Morpher destructor +// --------------------------------------------------------------------------- +// Destroy this Morpher. +// +Morpher::~Morpher( void ) +{ +} + +// --------------------------------------------------------------------------- +// Morpher assignment operator +// --------------------------------------------------------------------------- +//! Make this Morpher a duplicate of rhs. +//! +//! \param rhs is the Morpher to duplicate +Morpher & +Morpher::operator= ( const Morpher & rhs ) +{ + if ( &rhs != this ) + { + _freqFunction.reset( rhs._freqFunction->clone() ); + _ampFunction.reset( rhs._ampFunction->clone() ); + _bwFunction.reset( rhs._bwFunction->clone() ); + + _srcRefPartial = rhs._srcRefPartial; + _tgtRefPartial = rhs._tgtRefPartial; + + _freqFixThresholdDb = rhs._freqFixThresholdDb; + _logMorphShape = rhs._logMorphShape; + _minBreakpointGapSec = rhs._minBreakpointGapSec; + + _doLogAmpMorphing = rhs._doLogAmpMorphing; + _doLogFreqMorphing = rhs._doLogFreqMorphing; + + } + return *this; +} + + + +// -- Partial morphing -- + +// --------------------------------------------------------------------------- +// morphPartials +// --------------------------------------------------------------------------- +//! Morph a pair of Partials to yield a new morphed Partial. +//! Dummy Partials (having no Breakpoints) don't contribute to the +//! morph, except to cause their opposite to fade out. +//! Either (or neither) the source or target Partial may be a dummy +//! Partial (no Breakpoints), but not both. The morphed +//! Partial has Breakpoints at times corresponding to every Breakpoint +//! in both source Partials, omitting Breakpoints that would be +//! closer than the minBreakpointGap to their predecessor. +//! The new morphed Partial is assigned the specified label and returned. +//! +//! \param src is the Partial corresponding to a morph function +//! value of 0, evaluated at the specified time. +//! \param tgt is the Partial corresponding to a morph function +//! value of 1, evaluated at the specified time. +//! \param assignLabel is the label assigned to the morphed Partial +//! \return the morphed Partial +// +Partial +Morpher::morphPartials( Partial src, Partial tgt, int assignLabel ) +{ + if ( (src.numBreakpoints() == 0) && (tgt.numBreakpoints() == 0) ) + { + Throw( InvalidArgument, "Cannot morph two empty Partials," ); + } + + Partial::const_iterator src_iter = src.begin(); + Partial::const_iterator tgt_iter = tgt.begin(); + + // find the earliest time that a Breakpoint + // could be added to the morph: + double dontAddBefore = 0; + if ( 0 < src.numBreakpoints() ) + { + dontAddBefore = std::min( dontAddBefore, src_iter.time() ); + } + if ( 0 < tgt.numBreakpoints() ) + { + dontAddBefore = std::min( dontAddBefore, tgt_iter.time() ); + } + + + // make a new Partial: + Partial newp; + newp.setLabel( assignLabel ); + + + // Merge Breakpoints from the two Partials, + // loop until there are no more Breakpoints to + // consider in either Partial. + while ( src_iter != src.end() || tgt_iter != tgt.end() ) + { + if ( ( tgt_iter == tgt.end() ) || + ( src_iter != src.end() && src_iter.time() < tgt_iter.time() ) ) + { + // Ran out of tgt Breakpoints, or + // src Breakpoint is earlier, add it. + // + // Don't insert Breakpoints arbitrarily close together, + // only insert a new Breakpoint if it is later than + // the end of the new Partial by more than the gap time. + if ( dontAddBefore <= src_iter.time() ) + { + appendMorphedSrc( src_iter.breakpoint(), tgt, src_iter.time(), newp ); + } + + ++src_iter; + } + else + { + // Ran out of src Breakpoints, or + // tgt Breakpoint is earlier add it. + // + // Don't insert Breakpoints arbitrarily close together, + // only insert a new Breakpoint if it is later than + // the end of the new Partial by more than the gap time. + if ( dontAddBefore <= tgt_iter.time() ) + { + appendMorphedTgt( tgt_iter.breakpoint(), src, tgt_iter.time(), newp ); + } + + ++tgt_iter; + } + + if ( 0 != newp.numBreakpoints() ) + { + // update the earliest time the next Breakpoint + // could be added to the morph: + dontAddBefore = newp.endTime() + _minBreakpointGapSec; + } + } + + // Recompute the phases to match the sources when the frequency + // morphing function is 0 or 1. + fixMorphedPhases( newp ); + + return newp; +} + + +// --------------------------------------------------------------------------- +// helper - GetMorphState +// --------------------------------------------------------------------------- + +typedef enum { SRC = 0, TGT, INTERP } MorphState; + +static inline MorphState GetMorphState( double fweight ) +{ + if ( fweight <= 0 ) + { + return SRC; + } + else if ( fweight >= 1 ) + { + return TGT; + } + else + { + return INTERP; + } +} + +// --------------------------------------------------------------------------- +// fixMorphedPhases (helper) +// --------------------------------------------------------------------------- +// Recompute phases for a morphed Partial, so that the synthesized phases +// match the source phases as closesly as possible at times when the +// frequency morphing function is equal to 0 or 1. + +void +Morpher::fixMorphedPhases( Partial & newp ) const +{ + if ( 0 != newp.numBreakpoints() ) + { + // set the initial morph state according to the value of the + // frequency function at the time of the first Breakpoint in + // the morphed partial + Partial::iterator bppos = newp.begin(); + Partial::iterator lastPosCorrect = bppos; + MorphState curstate = GetMorphState( _freqFunction->valueAt( bppos.time() ) ); + + // consider each Breakpoint, look for a change in the + // morph state at the time of each Breakpoint + while( ++bppos != newp.end() ) + { + MorphState nxtstate = GetMorphState( _freqFunction->valueAt( bppos.time() ) ); + if ( nxtstate != curstate ) + { + // switch! + if ( INTERP != curstate ) + { + // switch to INTERP + fixPhaseForward( lastPosCorrect, bppos ); + } + else + { + // switch to SRC or TGT + if ( newp.begin() == lastPosCorrect ) + { + // first transition + fixPhaseBackward( lastPosCorrect, bppos ); + } + else + { + // not first transition + fixPhaseBetween( lastPosCorrect, bppos ); + } + } + lastPosCorrect = bppos; + + curstate = nxtstate; + + } + } + + // fix the remaining phases + fixPhaseForward( lastPosCorrect, --bppos ); + } +} + + +// --------------------------------------------------------------------------- +// crossfade +// --------------------------------------------------------------------------- +// Crossfade Partials with no correspondences. +// +// Unlabeled Partials (having label 0) are considered to +// have no correspondences, so they are just faded out, and not +// actually morphed. This is the same as morphing each with an +// empty dummy Partial (having no Breakpoints). +// +// The Partials in the first range are treated as components of the +// source sound, corresponding to a morph function value of 0, and +// those in the second are treated as components of the target sound, +// corresponding to a morph function value of 1. +// +// The crossfaded Partials are stored in the Morpher's PartialList. +// +void +Morpher::crossfade( PartialList::const_iterator beginSrc, + PartialList::const_iterator endSrc, + PartialList::const_iterator beginTgt, + PartialList::const_iterator endTgt, + Partial::label_type label /* default 0 */ ) +{ + Partial nullPartial; + debugger << "crossfading unlabeled (labeled 0) Partials" << endl; + + long debugCounter; + + // crossfade Partials corresponding to a morph weight of 0: + PartialList::const_iterator it; + debugCounter = 0; + for ( it = beginSrc; it != endSrc; ++it ) + { + if ( it->label() == label && 0 != it->numBreakpoints() ) + { + Partial newp; + newp.setLabel( label ); + double dontAddBefore = it->startTime(); + + for ( Partial::const_iterator bpPos = it->begin(); + bpPos != it->end(); + ++bpPos ) + { + // Don't insert Breakpoints arbitrarily close together, + // only insert a new Breakpoint if it is later than + // the end of the new Partial by more than the gap time. + if ( dontAddBefore <= bpPos.time() ) + { + newp.insert( bpPos.time(), + fadeSrcBreakpoint( bpPos.breakpoint(), bpPos.time() ) ); + dontAddBefore = bpPos.time() + _minBreakpointGapSec; + } + } + + if ( newp.numBreakpoints() > 0 ) + { + ++debugCounter; + _partials.push_back( newp ); + } + } + } + debugger << "kept " << debugCounter << " from sound 1" << endl; + + // crossfade Partials corresponding to a morph weight of 1: + debugCounter = 0; + for ( it = beginTgt; it != endTgt; ++it ) + { + if ( it->label() == label && 0 != it->numBreakpoints() ) + { + Partial newp; + newp.setLabel( label ); + double dontAddBefore = it->startTime(); + + for ( Partial::const_iterator bpPos = it->begin(); + bpPos != it->end(); + ++bpPos ) + { + // Don't insert Breakpoints arbitrarily close together, + // only insert a new Breakpoint if it is later than + // the end of the new Partial by more than the gap time. + if ( dontAddBefore <= bpPos.time() ) + { + newp.insert( bpPos.time(), + fadeTgtBreakpoint( bpPos.breakpoint(), bpPos.time() ) ); + dontAddBefore = bpPos.time() + _minBreakpointGapSec; + } + } + + if ( newp.numBreakpoints() > 0 ) + { + ++debugCounter; + _partials.push_back( newp ); + } + } + } + debugger << "kept " << debugCounter << " from sound 2" << endl; +} + +// --------------------------------------------------------------------------- +// morph +// --------------------------------------------------------------------------- +// Morph two sounds (collections of Partials labeled to indicate +// correspondences) into a single labeled collection of Partials. +// Unlabeled Partials (having label 0) are crossfaded. The morphed +// and crossfaded Partials are stored in the Morpher's PartialList. +// +// The Partials in the first range are treated as components of the +// source sound, corresponding to a morph function value of 0, and +// those in the second are treated as components of the target sound, +// corresponding to a morph function value of 1. +// +// Throws InvalidArgument if either the source or target +// sequence is not distilled (contains more than one Partial having +// the same non-zero label). +// +// Ugh! This ought to be a template function! +// Ugh! But then crossfade needs to be a template function. +// Maybe need to do something different with crossfade first. +// +void +Morpher::morph( PartialList::const_iterator beginSrc, + PartialList::const_iterator endSrc, + PartialList::const_iterator beginTgt, + PartialList::const_iterator endTgt ) +{ + // build a PartialCorrespondence, a map of labels + // to pairs of pointers to Partials, by making every + // Partial in the source the first element of the + // pair at the corresponding label, and every Partial + // in the target the second element of the pair at + // the corresponding label. Pointers not assigned to + // point to a Partial in the source or target are + // initialized to 0 in the correspondence map. + PartialCorrespondence correspondence; + + // add source Partials to the correspondence map: + for ( PartialList::const_iterator it = beginSrc; it != endSrc; ++it ) + { + // don't add the crossfade label to the set: + if ( it->label() != 0 ) + { + MorphingPair & match = correspondence[ it->label() ]; + if ( match.src.numBreakpoints() != 0 ) + { + Throw( InvalidArgument, "Source Partials must be distilled before morphing." ); + } + match.src = *it; + } + } + + // add target Partials to the correspondence map: + for ( PartialList::const_iterator it = beginTgt; it != endTgt; ++it ) + { + // don't add the crossfade label to the set: + if ( it->label() != 0 ) + { + MorphingPair & match = correspondence[ it->label() ]; + if ( match.tgt.numBreakpoints() != 0 ) + { + Throw( InvalidArgument, "Target Partials must be distilled before morphing." ); + } + match.tgt = *it; + } + } + + // morph corresponding labeled Partials: + morph_aux( correspondence ); + + // crossfade the remaining unlabeled Partials: + crossfade( beginSrc, endSrc, beginTgt, endTgt ); +} + +// --------------------------------------------------------------------------- +// morphBreakpoints +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the source and target Breakpoints (assumed to correspond exactly +//! to the specified time). +//! +//! \param srcBkpt is the Breakpoint corresponding to a morph function +//! value of 0. +//! \param tgtBkpt is the Breakpoint corresponding to a morph function +//! value of 1. +//! \param time is the time corresponding to srcBkpt (used +//! to evaluate the morphing functions and tgtPartial). +//! \return the morphed Breakpoint +// +Breakpoint +Morpher::morphBreakpoints( Breakpoint srcBkpt, Breakpoint tgtBkpt, + double time ) const +{ + double fweight = _freqFunction->valueAt( time ); + double aweight = _ampFunction->valueAt( time ); + double bweight = _bwFunction->valueAt( time ); + + // compute interpolated Breakpoint parameters: + return interpolateParameters( srcBkpt, tgtBkpt, fweight, + aweight, bweight ); +} + +// --------------------------------------------------------------------------- +// morphSrcBreakpoint +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the source Breakpoint (assumed to correspond exactly to the +//! specified time) and the target Partial (whose parameters are +//! examined at the specified time). +//! +//! \pre the target Partial may not be a dummy Partial (no Breakpoints). +//! +//! \param srcBkpt is the Breakpoint corresponding to a morph function +//! value of 0. +//! \param tgtPartial is the Partial corresponding to a morph function +//! value of 1, evaluated at the specified time. +//! \param time is the time corresponding to srcBkpt (used +//! to evaluate the morphing functions and tgtPartial). +//! \return the morphed Breakpoint +// +Breakpoint +Morpher::morphSrcBreakpoint( const Breakpoint & srcBkpt, const Partial & tgtPartial, + double time ) const +{ + if ( 0 == tgtPartial.numBreakpoints() ) + { + Throw( InvalidArgument, "morphSrcBreakpoint cannot morph with empty Partial" ); + } + + Breakpoint tgtBkpt = tgtPartial.parametersAt( time ); + + return morphBreakpoints( srcBkpt, tgtBkpt, time ); +} + +// --------------------------------------------------------------------------- +// morphTgtBreakpoint +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the target Breakpoint (assumed to correspond exactly to the +//! specified time) and the source Partial (whose parameters are +//! examined at the specified time). +//! +//! \pre the source Partial may not be a dummy Partial (no Breakpoints). +//! +//! \param tgtBkpt is the Breakpoint corresponding to a morph function +//! value of 1. +//! \param srcPartial is the Partial corresponding to a morph function +//! value of 0, evaluated at the specified time. +//! \param time is the time corresponding to srcBkpt (used +//! to evaluate the morphing functions and srcPartial). +//! \return the morphed Breakpoint +// +Breakpoint +Morpher::morphTgtBreakpoint( const Breakpoint & tgtBkpt, const Partial & srcPartial, + double time ) const +{ + if ( 0 == srcPartial.numBreakpoints() ) + { + Throw( InvalidArgument, "morphTgtBreakpoint cannot morph with empty Partial" ); + } + + Breakpoint srcBkpt = srcPartial.parametersAt( time ); + + return morphBreakpoints( srcBkpt, tgtBkpt, time ); +} + +// --------------------------------------------------------------------------- +// fadeSrcBreakpoint +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the source Breakpoint, assumed to correspond exactly to the +//! specified time, and assuming that there is no corresponding +//! target Partial, so the source Breakpoint should be simply faded. +//! +//! \param bp is the Breakpoint corresponding to a morph function +//! value of 0. +//! \param time is the time corresponding to bp (used +//! to evaluate the morphing functions). +//! \return the faded Breakpoint +// +Breakpoint +Morpher::fadeSrcBreakpoint( Breakpoint bp, double time ) const +{ + double alpha = _ampFunction->valueAt( time ); + bp.setAmplitude( interpolateAmplitude( bp.amplitude(), 0, + alpha ) ); + return bp; +} + +// --------------------------------------------------------------------------- +// fadeTgtBreakpoint +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the target Breakpoint, assumed to correspond exactly to the +//! specified time, and assuming that there is not corresponding +//! source Partial, so the target Breakpoint should be simply faded. +//! +//! \param bp is the Breakpoint corresponding to a morph function +//! value of 1. +//! \param time is the time corresponding to bp (used +//! to evaluate the morphing functions). +//! \return the faded Breakpoint +// +Breakpoint +Morpher::fadeTgtBreakpoint( Breakpoint bp, double time ) const +{ + double alpha = _ampFunction->valueAt( time ); + bp.setAmplitude( interpolateAmplitude( 0, bp.amplitude(), + alpha ) ); + return bp; +} + +// -- morphing function access/mutation -- + +// --------------------------------------------------------------------------- +// setFrequencyFunction +// --------------------------------------------------------------------------- +// Assign a new frequency morphing envelope to this Morpher. +// +void +Morpher::setFrequencyFunction( const Envelope & f ) +{ + _freqFunction.reset( f.clone() ); +} + +// --------------------------------------------------------------------------- +// setAmplitudeFunction +// --------------------------------------------------------------------------- +// Assign a new amplitude morphing envelope to this Morpher. +// +void +Morpher::setAmplitudeFunction( const Envelope & f ) +{ + _ampFunction.reset( f.clone() ); +} + +// --------------------------------------------------------------------------- +// setBandwidthFunction +// --------------------------------------------------------------------------- +// Assign a new bandwidth morphing envelope to this Morpher. +// +void +Morpher::setBandwidthFunction( const Envelope & f ) +{ + _bwFunction.reset( f.clone() ); +} + +// --------------------------------------------------------------------------- +// frequencyFunction +// --------------------------------------------------------------------------- +// Return a reference to this Morpher's frequency morphing envelope. +// +const Envelope & +Morpher::frequencyFunction( void ) const +{ + return * _freqFunction; +} + +// --------------------------------------------------------------------------- +// amplitudeFunction +// --------------------------------------------------------------------------- +// Return a reference to this Morpher's amplitude morphing envelope. +// +const Envelope & +Morpher::amplitudeFunction( void ) const +{ + return * _ampFunction; +} + +// --------------------------------------------------------------------------- +// bandwidthFunction +// --------------------------------------------------------------------------- +// Return a reference to this Morpher's bandwidth morphing envelope. +// +const Envelope & +Morpher::bandwidthFunction( void ) const +{ + return * _bwFunction; +} + + +// -- reference Partial label access/mutation -- + +// --------------------------------------------------------------------------- +// sourceReferencePartial +// --------------------------------------------------------------------------- +//! Return the Partial to be used as a reference +//! Partial for the source sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference Partial +//! should be used for the source sequence. +// +const Partial & +Morpher::sourceReferencePartial( void ) const +{ + return _srcRefPartial; +} + +// --------------------------------------------------------------------------- +// sourceReferencePartial +// --------------------------------------------------------------------------- +//! Return the Partial to be used as a reference +//! Partial for the source sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference Partial +//! should be used for the source sequence. +// +Partial & +Morpher::sourceReferencePartial( void ) +{ + return _srcRefPartial; +} + +// --------------------------------------------------------------------------- +// targetReferenceLabel +// --------------------------------------------------------------------------- +//! Return the Partial to be used as a reference +//! Partial for the target sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference Partial +//! should be used for the target sequence. +// +const Partial & +Morpher::targetReferencePartial( void ) const +{ + return _tgtRefPartial; +} + +// --------------------------------------------------------------------------- +// targetReferenceLabel +// --------------------------------------------------------------------------- +//! Return the Partial to be used as a reference +//! Partial for the target sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference Partial +//! should be used for the target sequence. +// +Partial & +Morpher::targetReferencePartial( void ) +{ + return _tgtRefPartial; +} + +// --------------------------------------------------------------------------- +// setSourceReferencePartial +// --------------------------------------------------------------------------- +//! Specify the Partial to be used as a reference +//! Partial for the source sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! The specified Partial must be labeled with its harmonic number. +//! A default (empty) Partial indicates that no reference +//! Partial should be used for the source sequence. +// +void +Morpher::setSourceReferencePartial( const Partial & p ) +{ + if ( p.label() == 0 ) + { + Throw( InvalidArgument, + "the morphing source reference Partial must be " + "labeled with its harmonic number" ); + } + _srcRefPartial = p; +} + +// --------------------------------------------------------------------------- +// setSourceReferencePartial +// --------------------------------------------------------------------------- +//! Specify the Partial to be used as a reference +//! Partial for the source sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference +//! Partial should be used for the source sequence. +//! +//! \param partials a sequence of Partials to search +//! for the reference Partial +//! \param refLabel the label of the Partial in partials +//! that should be selected as the reference +// +void +Morpher::setSourceReferencePartial( const PartialList & partials, + Partial::label_type refLabel ) +{ + if ( refLabel != 0 ) + { + PartialList::const_iterator pos = + std::find_if( partials.begin(), partials.end(), + PartialUtils::isLabelEqual( refLabel ) ); + if ( pos == partials.end() ) + { + Throw( InvalidArgument, "no Partial has the specified reference label" ); + } + _srcRefPartial = *pos; + } + else + { + _srcRefPartial = Partial(); + } +} + +// --------------------------------------------------------------------------- +// setTargetReferencePartial +// --------------------------------------------------------------------------- +//! Specify the Partial to be used as a reference +//! Partial for the target sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! The specified Partial must be labeled with its harmonic number. +//! A default (empty) Partial indicates that no reference +//! Partial should be used for the target sequence. +// +void +Morpher::setTargetReferencePartial( const Partial & p ) +{ + if ( p.label() == 0 ) + { + Throw( InvalidArgument, + "the morphing target reference Partial must be " + "labeled with its harmonic number" ); + } + _tgtRefPartial = p; +} + +// --------------------------------------------------------------------------- +// setTargetReferencePartial +// --------------------------------------------------------------------------- +//! Specify the Partial to be used as a reference +//! Partial for the target sequence in a morph of two Partial +//! sequences. The reference partial is used to compute +//! frequencies for very low-amplitude Partials whose frequency +//! estimates are not considered reliable. The reference Partial +//! is considered to have good frequency estimates throughout. +//! A default (empty) Partial indicates that no reference +//! Partial should be used for the target sequence. +//! +//! \param partials a sequence of Partials to search +//! for the reference Partial +//! \param refLabel the label of the Partial in partials +//! that should be selected as the reference +// +void +Morpher::setTargetReferencePartial( const PartialList & partials, + Partial::label_type refLabel ) +{ + if ( refLabel != 0 ) + { + PartialList::const_iterator pos = + std::find_if( partials.begin(), partials.end(), + PartialUtils::isLabelEqual( refLabel ) ); + if ( pos == partials.end() ) + { + Throw( InvalidArgument, "no Partial has the specified reference label" ); + } + _tgtRefPartial = *pos; + } + else + { + _tgtRefPartial = Partial(); + } +} + + +// --------------------------------------------------------------------------- +// amplitudeShape +// --------------------------------------------------------------------------- +// Return the shaping parameter for the amplitude moprhing +// function (only used in new log-amplitude morphing). +// This shaping parameter controls the +// slope of the amplitude morphing function, +// for values greater than 1, this function +// gets nearly linear (like the old amplitude +// morphing function), for values much less +// than 1 (e.g. 1E-5) the slope is gently +// curved and sounds pretty "linear", for +// very small values (e.g. 1E-12) the curve +// is very steep and sounds un-natural because +// of the huge jump from zero amplitude to +// very small amplitude. +double Morpher::amplitudeShape( void ) const +{ + return _logMorphShape; +} + +// --------------------------------------------------------------------------- +// setAmplitudeShape +// --------------------------------------------------------------------------- +// Set the shaping parameter for the amplitude moprhing +// function. This shaping parameter controls the +// slope of the amplitude morphing function, +// for values greater than 1, this function +// gets nearly linear (like the old amplitude +// morphing function), for values much less +// than 1 (e.g. 1E-5) the slope is gently +// curved and sounds pretty "linear", for +// very small values (e.g. 1E-12) the curve +// is very steep and sounds un-natural because +// of the huge jump from zero amplitude to +// very small amplitude. +// +// x is the new shaping parameter, it must be positive. +void Morpher::setAmplitudeShape( double x ) +{ + if ( x <= 0. ) + { + Throw( InvalidArgument, "the amplitude morph shaping parameter must be positive"); + } + _logMorphShape = x; +} + +// --------------------------------------------------------------------------- +// minBreakpointGap +// --------------------------------------------------------------------------- +// Return the minimum time gap (secs) between two Breakpoints +// in the morphed Partials. Morphing two +// Partials can generate a third Partial having +// Breakpoints arbitrarily close together in time, +// and this makes morphs huge. Raising this +// threshold limits the Breakpoint density in +// the morphed Partials. Default is 1/10 ms. +double Morpher::minBreakpointGap( void ) const +{ + return _minBreakpointGapSec; +} + +// --------------------------------------------------------------------------- +// setMinBreakpointGap +// --------------------------------------------------------------------------- +// Set the minimum time gap (secs) between two Breakpoints +// in the morphed Partials. Morphing two +// Partials can generate a third Partial having +// Breakpoints arbitrarily close together in time, +// and this makes morphs huge. Raising this +// threshold limits the Breakpoint density in +// the morphed Partials. Default is 1/10 ms. +// +// x is the new minimum gap in seconds, it must be positive +// +void Morpher::setMinBreakpointGap( double x ) +{ + if ( x <= 0. ) + { + Throw( InvalidArgument, "the minimum Breakpoint gap must be positive"); + } + _minBreakpointGapSec = x; +} + +// -- PartialList access -- + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +// Return a reference to this Morpher's list of morphed Partials. +// +PartialList & +Morpher::partials( void ) +{ + return _partials; +} + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +// Return a const reference to this Morpher's list of morphed Partials. +// +const PartialList & +Morpher::partials( void ) const +{ + return _partials; +} + +// -- helpers: morphed parameter computation -- + +// --------------------------------------------------------------------------- +// morph_aux +// --------------------------------------------------------------------------- +// Helper function that performs the morph between corresponding pairs +// of Partials identified in a PartialCorrespondence. Called by the +// morph() implementation accepting two sequences of Partials. +// +// PartialCorrespondence represents a map from non-zero Partial +// labels to pairs of Partials (MorphingPair) that should be morphed +// into a single Partial that is assigned that label. +// +void Morpher::morph_aux( PartialCorrespondence & correspondence ) +{ + PartialCorrespondence::const_iterator it; + for ( it = correspondence.begin(); it != correspondence.end(); ++it ) + { + Partial::label_type label = it->first; + MorphingPair match = it->second; + Partial & src = match.src; + Partial & tgt = match.tgt; + + // sanity check: + // one of those Partials must have some Breakpoints + Assert( src.numBreakpoints() != 0 || tgt.numBreakpoints() != 0 ); + + debugger << "morphing " << ( ( 0 < src.numBreakpoints() )?( 1 ):( 0 ) ) + << " and " << ( ( 0 < tgt.numBreakpoints() )?( 1 ):( 0 ) ) + << " partials with label " << label << endl; + + // &^) HEY LOOKIE HERE!!!!!!!!!!!!! + + // ensure that Partials begin and end at zero + // amplitude to solve the problem of Nulls + // getting left out of morphed Partials leading to + // erroneous non-zero amplitude segments: + if ( src.numBreakpoints() != 0 ) + { + if ( src.first().amplitude() != 0.0 && src.startTime() > _minBreakpointGapSec ) + { + double t = src.startTime() - _minBreakpointGapSec; + Breakpoint null = src.parametersAt( t ); + src.insert( t, null ); + } + if ( src.last().amplitude() != 0.0 ) + { + double t = src.endTime() + _minBreakpointGapSec; + Breakpoint null = src.parametersAt( t ); + src.insert( t, null ); + } + } + + if ( tgt.numBreakpoints() != 0 ) + { + if ( tgt.first().amplitude() != 0.0 && tgt.startTime() > _minBreakpointGapSec ) + { + double t = tgt.startTime() - _minBreakpointGapSec; + Breakpoint null = tgt.parametersAt( t ); + tgt.insert( t, null ); + } + if ( tgt.last().amplitude() != 0.0 ) + { + double t = tgt.endTime() + _minBreakpointGapSec; + Breakpoint null = tgt.parametersAt( t ); + tgt.insert( t, null ); + } + } + // &^) HEY LOOKIE HERE!!!!!!!!!!!!! + // the question is: after sticking nulls on the ends, + // should be strip nulls OFF the ends of the morphed + // partial? If so, how many? (ans to second is one, + // cannot have both nulls appear at end of morphed, + // because of min gap). If we unconditionally add + // nulls to ends (regardless of starting and ending + // amps), then we can (I think) be sure that taking + // off one null from each end leaves the Partial in + // an unmolested state.... maybe. No, its possible that + // the morphing function would skip over both artificial + // nulls, so we cannot be sure. Hmmmmm.... + // For now, just leave the nulls on the ends, + // the are relatively harmless. + // + // Actually, a (klugey) solution is to remember the times + // of those artificial nulls, and then see if the + // Partial begins or ends at one of those times. + // No, cannot guarantee that one Partial doesn't + // have a null at the time we put an artificial null + // in the other one. Hmmmmm..... + + + // perform the morph between the two Partials, + // save the result if it has any Breakpoints + // (it may not depending on the morphing functions): + Partial newp = morphPartials( src, tgt, label ); + if ( partial_is_nonnull( newp ) ) + { + _partials.push_back( newp ); + } + } +} + + +// --------------------------------------------------------------------------- +// adjustFrequency +// --------------------------------------------------------------------------- +// Adjust frequency of low-amplitude Breakpoints to be harmonics of the +// reference Partial, if one has been specified. +// +// Leave the phase alone, because I don't know what we can do with it. +// +static void adjustFrequency( Breakpoint & bp, const Partial & ref, + Partial::label_type harmonicNum, + double thresholdDb, + double time ) +{ + if ( ref.numBreakpoints() != 0 ) + { + // compute absolute magnitude thresholds: + static const double FadeRangeDB = 10; + const double BeginFade = std::pow( 10., 0.05 * (thresholdDb+FadeRangeDB) ); + + if ( bp.amplitude() < BeginFade ) + { + const double Threshold = std::pow( 10., 0.05 * thresholdDb ); + const double OneOverFadeSpan = 1. / ( BeginFade - Threshold ); + + double fscale = (double)harmonicNum / ref.label(); + + double alpha = std::min( ( BeginFade - bp.amplitude() ) * OneOverFadeSpan, 1. ); + double fRef = ref.frequencyAt( time ); + bp.setFrequency( ( alpha * ( fRef * fscale ) ) + + ( (1 - alpha) * bp.frequency() ) ); + } + } +} + +// --------------------------------------------------------------------------- +// partial_is_nonnull +// --------------------------------------------------------------------------- +// Helper function to examine a morphed Partial and determine whether +// it has any non-null Breakpoints. If not, there's no point in saving it. +// +static inline bool partial_is_nonnull( const Partial & p ) +{ + for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) + { + if ( it.breakpoint().amplitude() != 0.0 ) + { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- +// Helper function for performing log-domain interpolation +// (originally was for amplitude only). +// +// alpha == 0 returns x, alpha == 1 returns y +// +// It is essential to add in a small offset, so that +// occasional zero amplitudes do not introduce artifacts +// (if amp is zero, then even if alpha is very small +// the effect is to multiply by zero, because 0^x = 0, +// or note that log(0) is -infinity). +// +// This shaping parameter affects the shape of the morph +// curve only when it is of the same order of magnitude as +// one of the sources (x or y) and the other is much larger. +// +// When shape is very small, the curve representing the +// morphed amplitude is very steep, such that there is a +// huge difference between zero amplitude and very small +// amplitude, and this causes audible artifacts. So instead +// use a larger value that shapes the curve more nicely. +// Just have to subtract this value from the morphed +// amplitude to avoid raising the noise floor a whole lot. +// +static inline double +interpolateLog( double x, double y, double alpha, double shape ) +{ + using std::pow; + + double s = x + shape; + double t = y + shape; + + double v = ( s * pow( t / s, alpha ) ) - shape; + + return v; +} + +// --------------------------------------------------------------------------- +// Helper function for performing linear interpolation +// (used to be the only kind we supported). +// +// alpha == 0 returns x, alpha == 1 returns y +// +static inline double +interpolateLinear( double x, double y, double alpha ) +{ + double v = (x * (1-alpha)) + (y * alpha); + + return v; +} + + +// --------------------------------------------------------------------------- +// Helper function for computing individual morphed amplitude values. +// +inline double +Morpher::interpolateAmplitude( double srcAmp, double tgtAmp, double alpha ) const +{ + double morphedAmp = 0; + + if ( _doLogAmpMorphing ) + { + // if both are small, just return 0 + // HEY, is this really what we want? + static const double Epsilon = 1E-12; + if ( ( srcAmp > Epsilon ) || ( tgtAmp > Epsilon ) ) + { + morphedAmp = interpolateLog( srcAmp, tgtAmp, alpha, _logMorphShape ); + } + } + else + { + morphedAmp = interpolateLinear( srcAmp, tgtAmp, alpha ); + } + + // Partial amplitudes should never be negative + double res = std::max( 0.0, morphedAmp ); + + return res; +} + +// --------------------------------------------------------------------------- +// Helper function for computing individual morphed bandwidth values. +// +inline double +Morpher::interpolateBandwidth( double srcBw, double tgtBw, double alpha ) const +{ + double morphedBw = 0; + + if ( _doLogAmpMorphing ) + { + // if both are small, just return 0 + // HEY, is this really what we want? + static const double Epsilon = 1E-12; + if ( ( srcBw > Epsilon ) || ( tgtBw > Epsilon ) ) + { + morphedBw = interpolateLog( srcBw, tgtBw, alpha, _logMorphShape ); + } + } + else + { + morphedBw = interpolateLinear( srcBw, tgtBw, alpha ); + } + + // Partial bandwidths should never be negative + double res = std::max( 0.0, morphedBw ); + + + return res; +} + +// --------------------------------------------------------------------------- +// Helper function for computing individual morphed frequency values. +// +inline double +Morpher::interpolateFrequency( double srcFreq, double tgtFreq, double alpha ) const +{ + double morphedFreq = 1; + + if ( _doLogFreqMorphing ) + { + // guard against the extremely unlikely possibility that + // one of the frequencies is zero + double shape = 0; + if ( 0 == srcFreq || 0 == tgtFreq ) + { + shape = Morpher::DefaultAmpShape; + } + morphedFreq = interpolateLog( srcFreq, tgtFreq, alpha, shape ); + } + else + { + morphedFreq = interpolateLinear( srcFreq, tgtFreq, alpha ); + } + + return morphedFreq; +} + +// --------------------------------------------------------------------------- +// Helper function for computing individual morphed phase values. +// +inline double +Morpher::interpolatePhase( double srcphase, double tgtphase, double alpha ) const +{ + // Interpolate raw absolute phase values. If the interpolated + // phase matters at all (near the morphing function boudaries 0 + // and 1) then that will give a good target phase value, and the + // frequency will be adjusted to match the phase. Otherwise, + // the phase will just be recomputed to match the interpolated + // frequency. + // + // Wrap the computed phase onto an appropriate range. + // wrap the phases so that they are as similar as possible, + // so that phase interpolation is shift-invariant. + while ( ( srcphase - tgtphase ) > Pi ) + { + srcphase -= 2 * Pi; + } + while ( ( tgtphase - srcphase ) > Pi ) + { + srcphase += 2 * Pi; + } + + double morphedPhase = interpolateLinear( srcphase, tgtphase, alpha ); + + return std::fmod( morphedPhase, 2 * Pi ); +} + +// --------------------------------------------------------------------------- +// Helper function for interpolating Breakpoint parameters +// +inline Breakpoint +Morpher::interpolateParameters( const Breakpoint & srcBkpt, const Breakpoint & tgtBkpt, + double fweight, double aweight, double bweight ) const +{ + Breakpoint morphed; + + // interpolate frequencies: + morphed.setFrequency( + interpolateFrequency( srcBkpt.frequency(), tgtBkpt.frequency(), + fweight ) ); + + // interpolate LOG amplitudes: + morphed.setAmplitude( + interpolateAmplitude( srcBkpt.amplitude(), tgtBkpt.amplitude(), + aweight ) ); + + // interpolate bandwidth: + morphed.setBandwidth( + interpolateBandwidth( srcBkpt.bandwidth(), tgtBkpt.bandwidth(), + bweight ) ); + + // interpolate phase: + morphed.setPhase( + interpolatePhase( srcBkpt.phase(), tgtBkpt.phase(), fweight ) ); + + return morphed; +} + +// --------------------------------------------------------------------------- +// appendMorphedSrc +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the source Breakpoint (assumed to correspond exactly to the +//! specified time) and the target Partial (whose parameters are +//! examined at the specified time). Append the morphed Breakpoint +//! to newp only if the source should contribute to the morph at +//! the specified time. +//! +//! If the target Partial is a dummy Partial (no Breakpoints), fade the +//! source instead of morphing. +//! +//! \param srcBkpt is the Breakpoint corresponding to a morph function +//! value of 0. +//! \param tgtPartial is the Partial corresponding to a morph function +//! value of 1, evaluated at the specified time. +//! \param time is the time corresponding to srcBkpt (used +//! to evaluate the morphing functions and tgtPartial). +//! \param newp is the morphed Partial under construction, the morphed +//! Breakpoint is added to this Partial. +// +void +Morpher::appendMorphedSrc( Breakpoint srcBkpt, const Partial & tgtPartial, + double time, Partial & newp ) +{ + double fweight = _freqFunction->valueAt( time ); + double aweight = _ampFunction->valueAt( time ); + double bweight = _bwFunction->valueAt( time ); + + // Need to insert a null (0 amplitude) Breakpoint + // if src and tgt are 0 amplitude but the morphed + // Partial is not. In rare cases, it is possible + // to miss a needed null if we don't check for it + // explicitly. + bool needNull = ( newp.numBreakpoints() != 0 ) && + ( newp.last().amplitude() != 0 ) && + ( srcBkpt.amplitude() == 0) && + ( tgtPartial.numBreakpoints() != 0 ) && + ( tgtPartial.amplitudeAt( time ) == 0 ); + + // Don't insert Breakpoints at src times if all + // morph functions equal 1 (or > MaxMorphParam), + // and a null is not needed. + const double MaxMorphParam = .9; + if ( fweight < MaxMorphParam || + aweight < MaxMorphParam || + bweight < MaxMorphParam || + needNull ) + { + + // adjust source Breakpoint frequencies according to the reference + // Partial (if a reference has been specified): + adjustFrequency( srcBkpt, _srcRefPartial, newp.label(), _freqFixThresholdDb, time ); + + if ( 0 == tgtPartial.numBreakpoints() ) + { + // no corresponding target Partial exists: + if ( 0 == _tgtRefPartial.numBreakpoints() ) + { + // no reference Partial specified for tgt, + // fade src instead: + newp.insert( time, fadeSrcBreakpoint( srcBkpt, time ) ); + } + else + { + // reference Partial has been provided for tgt, + // use it to construct a fake Breakpoint to morph + // with the src: + Breakpoint tgtBkpt = _tgtRefPartial.parametersAt( time ); + double fscale = (double) newp.label() / _tgtRefPartial.label(); + tgtBkpt.setFrequency( fscale * tgtBkpt.frequency() ); + tgtBkpt.setPhase( fscale * tgtBkpt.phase() ); + tgtBkpt.setAmplitude( 0 ); + tgtBkpt.setBandwidth( 0 ); + + // compute interpolated Breakpoint parameters: + newp.insert( time, interpolateParameters( srcBkpt, tgtBkpt, fweight, + aweight, bweight ) ); + } + } + else + { + Breakpoint tgtBkpt = tgtPartial.parametersAt( time ); + + // adjust target Breakpoint frequencies according to the reference + // Partial (if a reference has been specified): + adjustFrequency( tgtBkpt, _tgtRefPartial, newp.label(), _freqFixThresholdDb, time ); + + // compute interpolated Breakpoint parameters: + Breakpoint morphed = interpolateParameters( srcBkpt, tgtBkpt, fweight, + aweight, bweight ); + newp.insert( time, morphed ); + } + } +} + +// --------------------------------------------------------------------------- +// appendMorphedTgt +// --------------------------------------------------------------------------- +//! Compute morphed parameter values at the specified time, using +//! the target Breakpoint (assumed to correspond exactly to the +//! specified time) and the source Partial (whose parameters are +//! examined at the specified time). Append the morphed Breakpoint +//! to newp only if the target should contribute to the morph at +//! the specified time. +//! +//! If the source Partial is a dummy Partial (no Breakpoints), fade the +//! target instead of morphing. +//! +//! \param tgtBkpt is the Breakpoint corresponding to a morph function +//! value of 1. +//! \param srcPartial is the Partial corresponding to a morph function +//! value of 0, evaluated at the specified time. +//! \param time is the time corresponding to srcBkpt (used +//! to evaluate the morphing functions and srcPartial). +//! \param newp is the morphed Partial under construction, the morphed +//! Breakpoint is added to this Partial. +// +void +Morpher::appendMorphedTgt( Breakpoint tgtBkpt, const Partial & srcPartial, + double time, Partial & newp ) +{ + double fweight = _freqFunction->valueAt( time ); + double aweight = _ampFunction->valueAt( time ); + double bweight = _bwFunction->valueAt( time ); + + // Need to insert a null (0 amplitude) Breakpoint + // if src and tgt are 0 amplitude but the morphed + // Partial is not. In rare cases, it is possible + // to miss a needed null if we don't check for it + // explicitly. + bool needNull = ( newp.numBreakpoints() != 0 ) && + ( newp.last().amplitude() != 0 ) && + ( tgtBkpt.amplitude() == 0) && + ( srcPartial.numBreakpoints() != 0 ) && + ( srcPartial.amplitudeAt( time ) == 0 ); + + // Don't insert Breakpoints at src times if all + // morph functions equal 0 (or < MinMorphParam), + // and a null is not needed. + const double MinMorphParam = .1; + if ( fweight > MinMorphParam || + aweight > MinMorphParam || + bweight > MinMorphParam || + needNull ) + { + + // adjust target Breakpoint frequencies according to the reference + // Partial (if a reference has been specified): + adjustFrequency( tgtBkpt, _tgtRefPartial, newp.label(), _freqFixThresholdDb, time ); + + if ( 0 == srcPartial.numBreakpoints() ) + { + // no corresponding source Partial exists: + if ( 0 == _srcRefPartial.numBreakpoints() ) + { + // no reference Partial specified for src, + // fade tgt instead: + newp.insert( time, fadeTgtBreakpoint( tgtBkpt, time ) ); + } + else + { + // reference Partial has been provided for src, + // use it to construct a fake Breakpoint to morph + // with the tgt: + Breakpoint srcBkpt = _srcRefPartial.parametersAt( time ); + double fscale = (double) newp.label() / _srcRefPartial.label(); + srcBkpt.setFrequency( fscale * srcBkpt.frequency() ); + srcBkpt.setPhase( fscale * srcBkpt.phase() ); + srcBkpt.setAmplitude( 0 ); + srcBkpt.setBandwidth( 0 ); + + // compute interpolated Breakpoint parameters: + newp.insert( time, interpolateParameters( srcBkpt, tgtBkpt, fweight, + aweight, bweight ) ); + } + } + else + { + Breakpoint srcBkpt = srcPartial.parametersAt( time ); + + // adjust source Breakpoint frequencies according to the reference + // Partial (if a reference has been specified): + adjustFrequency( srcBkpt, _srcRefPartial, newp.label(), _freqFixThresholdDb, time ); + + // compute interpolated Breakpoint parameters: + Breakpoint morphed = interpolateParameters( srcBkpt, tgtBkpt, fweight, + aweight, bweight ); + newp.insert( time, morphed ); + } + } +} + +} // end of namespace Loris diff --git a/src/loris/Morpher.h b/src/loris/Morpher.h new file mode 100644 index 0000000..a291cac --- /dev/null +++ b/src/loris/Morpher.h @@ -0,0 +1,590 @@ +#ifndef INCLUDE_MORPHER_H +#define INCLUDE_MORPHER_H +/* + * 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 + * + * + * Morpher.h + * + * Definition of class Morpher. + * + * Kelly Fitz, 15 Oct 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "PartialList.h" +#include "Partial.h" + +#include <memory> // for auto_ptr + +// begin namespace +namespace Loris { + +class Envelope; + +// --------------------------------------------------------------------------- +// Class Morpher +// +//! Class Morpher performs sound morphing and Partial parameter +//! envelope interpolation according to a trio of frequency, amplitude, +//! and bandwidth morphing functions, described by Envelopes. +//! Sound morphing is achieved by interpolating the time-varying +//! frequencies, amplitudes, and bandwidths of corresponding partials +//! obtained from reassigned bandwidth-enhanced analysis of the source +//! and target sounds. Partial correspondences may be established by +//! labeling, using instances of the Channelizer and Distiller classes. +//! +//! The Morpher collects morphed Partials in a PartialList, that is +//! accessible to clients. +//! +//! For more information about sound morphing using +//! the Reassigned Bandwidth-Enhanced Additive Sound +//! Model, refer to the Loris website: +//! www.cerlsoundgroup.org/Loris/. +//! +//! Morpher is a leaf class, do not subclass. +// +class Morpher +{ +// -- instance variables -- + + std::auto_ptr< Envelope > _freqFunction; //! frequency morphing function + std::auto_ptr< Envelope > _ampFunction; //! amplitude morphing function + std::auto_ptr< Envelope > _bwFunction; //! bandwidth morphing function + + PartialList _partials; //! collect Partials here + + Partial _srcRefPartial; //! reference Partials + Partial _tgtRefPartial; //! for source and target sounds when + //! morphing sequences of labeled Partials, + //! default (empty Partial) implies no + //! reference Partial is used + + double _freqFixThresholdDb; //! amplitude threshold below which Partial + //! frequencies are corrected according to + //! a reference Partial, if specified. + + double _logMorphShape; //! shaping parameter that controls the + //! shape of the logarithmic morphing function, + //! mostly when one of the source values + //! is equal to zero. + //! Only relevant when _doLogAmpMorphing is true. + //! + //! Don't use this for anything, just leave it + //! at the default. + + double _minBreakpointGapSec; //! the minimum time gap between two Breakpoints + //! in the morphed Partials. Morphing two + //! Partials can generate a third Partial having + //! Breakpoints arbitrarily close together in time, + //! and this makes morphs huge. Raising this + //! threshold limits the Breakpoint density in + //! the morphed Partials. + //! Default is 1/10 ms. + + + bool _doLogAmpMorphing; //! if true (default), amplitudes and bandwidths + //! are morphed in the log domain, if false they + //! are morphed in the linear domain. + + bool _doLogFreqMorphing; //! if true, frequencies are morphed in the log + //! domain, if false (default) they are morphed + //! in the linear domain. + + +// -- public interface -- +public: +// -- construction -- + + //! Construct a new Morpher using the same morphing envelope for + //! frequency, amplitude, and bandwidth (noisiness). + //! + //! \param f is the Envelope to clone for all three morphing + //! functions. + Morpher( const Envelope & f ); + + //! Construct a new Morpher using the specified morphing envelopes for + //! frequency, amplitude, and bandwidth (noisiness). + //! + //! \param ff is the Envelope to clone for the frequency morphing function + //! \param af is the Envelope to clone for the amplitude morphing function + //! \param bwf is the Envelope to clone for the bandwidth morphing function + Morpher( const Envelope & ff, const Envelope & af, const Envelope & bwf ); + + //! Construct a new Morpher that is a duplicate of rhs. + //! + //! \param rhs is the Morpher to duplicate + Morpher( const Morpher & rhs ); + + //! Destroy this Morpher. + ~Morpher( void ); + + //! Make this Morpher a duplicate of rhs. + //! + //! \param rhs is the Morpher to duplicate + Morpher & operator= ( const Morpher & rhs ); + +// -- morphed parameter computation -- + +// -- Partial morphing -- + + //! Morph a pair of Partials to yield a new morphed Partial. + //! Dummy Partials (having no Breakpoints) don't contribute to the + //! morph, except to cause their opposite to fade out. + //! Either (or neither) the source or target Partial may be a dummy + //! Partial (no Breakpoints), but not both. The morphed + //! Partial has Breakpoints at times corresponding to every Breakpoint + //! in both source Partials, omitting Breakpoints that would be + //! closer than the minBreakpointGap to their predecessor. + //! The new morphed Partial is assigned the specified label and returned. + //! + //! \param src is the Partial corresponding to a morph function + //! value of 0, evaluated at the specified time. + //! \param tgt is the Partial corresponding to a morph function + //! value of 1, evaluated at the specified time. + //! \param assignLabel is the label assigned to the morphed Partial + //! \return the morphed Partial + Partial morphPartials( Partial src, Partial tgt, int assignLabel ); + + //! Bad legacy name for morphPartials. + //! \deprecated Use morphPartials instead. + Partial morphPartial( Partial src, Partial tgt, int assignLabel ) + { return morphPartials( src, tgt, assignLabel ); } + + //! Morph two sounds (collections of Partials labeled to indicate + //! correspondences) into a single labeled collection of Partials. + //! Unlabeled Partials (having label 0) are crossfaded. The morphed + //! and crossfaded Partials are stored in the Morpher's PartialList. + //! + //! The Partials in the first range are treated as components of the + //! source sound, corresponding to a morph function value of 0, and + //! those in the second are treated as components of the target sound, + //! corresponding to a morph function value of 1. + //! + //! \sa crossfade, morphPartials + //! + //! \param beginSrc is the beginning of the sequence of Partials + //! corresponding to a morph function value of 0. + //! \param endSrc is (one past) the end of the sequence of Partials + //! corresponding to a morph function value of 0. + //! \param beginTgt is the beginning of the sequence of Partials + //! corresponding to a morph function value of 1. + //! \param endTgt is (one past) the end of the sequence of Partials + //! corresponding to a morph function value of 1. + void morph( PartialList::const_iterator beginSrc, + PartialList::const_iterator endSrc, + PartialList::const_iterator beginTgt, + PartialList::const_iterator endTgt ); + + //! Crossfade Partials with no correspondences. + //! + //! Unlabeled Partials (having the specified label) are considered to + //! have no correspondences, so they are just faded out, and not + //! actually morphed. Consistent with the morphing behavior, + //! crossfaded Partials are thinned, if necssary, so that no + //! two Breakpoints are closer in time than the minBreakpointGap. + //! + //! The Partials in the first range are treated as components of the + //! source sound, corresponding to a morph function value of 0, and + //! those in the second are treated as components of the target sound, + //! corresponding to a morph function value of 1. + //! + //! The crossfaded Partials are stored in the Morpher's PartialList. + //! + //! \param beginSrc is the beginning of the sequence of Partials + //! corresponding to a morph function value of 0. + //! \param endSrc is (one past) the end of the sequence of Partials + //! corresponding to a morph function value of 0. + //! \param beginTgt is the beginning of the sequence of Partials + //! corresponding to a morph function value of 1. + //! \param endTgt is (one past) the end of the sequence of Partials + //! corresponding to a morph function value of 1. + //! \param label is the label to associate with unlabeled + //! Partials (default is 0). + void crossfade( PartialList::const_iterator beginSrc, + PartialList::const_iterator endSrc, + PartialList::const_iterator beginTgt, + PartialList::const_iterator endTgt, + Partial::label_type label = 0 ); + + + //! Compute morphed parameter values at the specified time, using + //! the source and target Breakpoints (assumed to correspond exactly + //! to the specified time). + //! + //! \param srcBkpt is the Breakpoint corresponding to a morph function + //! value of 0. + //! \param tgtBkpt is the Breakpoint corresponding to a morph function + //! value of 1. + //! \param time is the time corresponding to srcBkpt (used + //! to evaluate the morphing functions and tgtPartial). + //! \return the morphed Breakpoint + // + Breakpoint + morphBreakpoints( Breakpoint srcBkpt, Breakpoint tgtBkpt, + double time ) const; + + //! Compute morphed parameter values at the specified time, using + //! the source Breakpoint (assumed to correspond exactly to the + //! specified time) and the target Partial (whose parameters are + //! examined at the specified time). + //! + //! DEPRECATED do not use. + //! + //! \pre the target Partial may not be a dummy Partial (no Breakpoints). + //! + //! \param bp is the Breakpoint corresponding to a morph function + //! value of 0. + //! \param tgtPartial is the Partial corresponding to a morph function + //! value of 1, evaluated at the specified time. + //! \param time is the time corresponding to srcBkpt (used + //! to evaluate the morphing functions and tgtPartial). + //! \return the morphed Breakpoint + Breakpoint + morphSrcBreakpoint( const Breakpoint & bp, const Partial & tgtPartial, + double time ) const; + + //! Compute morphed parameter values at the specified time, using + //! the target Breakpoint (assumed to correspond exactly to the + //! specified time) and the source Partial (whose parameters are + //! examined at the specified time). + //! + //! DEPRECATED do not use. + //! + //! \pre the source Partial may not be a dummy Partial (no Breakpoints). + //! + //! \param bp is the Breakpoint corresponding to a morph function + //! value of 1. + //! \param srcPartial is the Partial corresponding to a morph function + //! value of 0, evaluated at the specified time. + //! \param time is the time corresponding to srcBkpt (used + //! to evaluate the morphing functions and tgtPartial). + //! \return the morphed Breakpoint + Breakpoint + morphTgtBreakpoint( const Breakpoint & bp, const Partial & srcPartial, + double time ) const; + + //! Compute morphed parameter values at the specified time, using + //! the source Breakpoint, assumed to correspond exactly to the + //! specified time, and assuming that there is no corresponding + //! target Partial, so the source Breakpoint should be simply faded. + //! + //! \param bp is the Breakpoint corresponding to a morph function + //! value of 0. + //! \param time is the time corresponding to bp (used + //! to evaluate the morphing functions). + //! \return the faded Breakpoint + Breakpoint fadeSrcBreakpoint( Breakpoint bp, double time ) const; + + //! Compute morphed parameter values at the specified time, using + //! the target Breakpoint, assumed to correspond exactly to the + //! specified time, and assuming that there is not corresponding + //! source Partial, so the target Breakpoint should be simply faded. + //! + //! \param bp is the Breakpoint corresponding to a morph function + //! value of 1. + //! \param time is the time corresponding to bp (used + //! to evaluate the morphing functions). + //! \return the faded Breakpoint + Breakpoint fadeTgtBreakpoint( Breakpoint bp, double time ) const; + +// -- morphing function access/mutation -- + + //! Assign a new frequency morphing envelope to this Morpher. + void setFrequencyFunction( const Envelope & f ); + + //! Assign a new amplitude morphing envelope to this Morpher. + void setAmplitudeFunction( const Envelope & f ); + + //! Assign a new bandwidth morphing envelope to this Morpher. + void setBandwidthFunction( const Envelope & f ); + + //! Return a reference to this Morpher's frequency morphing envelope. + const Envelope & frequencyFunction( void ) const; + + //! Return a reference to this Morpher's amplitude morphing envelope. + const Envelope & amplitudeFunction( void ) const; + + //! Return a reference to this Morpher's bandwidth morphing envelope. + const Envelope & bandwidthFunction( void ) const; + + //! Return the shaping parameter for the amplitude moprhing + //! function (only used in log-amplitude morphing). + //! + //! DEPRECATED + double amplitudeShape( void ) const; + + //! Set the shaping parameter for the amplitude moprhing + //! function (only used in log-amplitude morphing). + //! Only relevant when _doLogAmpMorphing is true. + //! Don't use this for anything, just leave it + //! at the default. + //! + //! DEPRECATED + void setAmplitudeShape( double x ); + + //! Enable (or disable) log-domain amplitude and bandwidth morphing. + //! Default is true. + void enableLogAmpMorphing( bool enable = true ) { _doLogAmpMorphing = enable; } + + //! Enable (or disable) log-domain frequency morphing. + //! Default is false. + void enableLogFreqMorphing( bool enable = true ) { _doLogFreqMorphing = enable; } + + + //! Return the minimum time gap (secs) between two Breakpoints + //! in the morphed Partials. Morphing two + //! Partials can generate a third Partial having + //! Breakpoints arbitrarily close together in time, + //! and this makes morphs huge. Raising this + //! threshold limits the Breakpoint density in + //! the morphed Partials. Default is 1/10 ms. + double minBreakpointGap( void ) const; + + //! Set the minimum time gap (secs) between two Breakpoints + //! in the morphed Partials. Morphing two + //! Partials can generate a third Partial having + //! Breakpoints arbitrarily close together in time, + //! and this makes morphs huge. Raising this + //! threshold limits the Breakpoint density in + //! the morphed Partials. Default is 1/10 ms. + //! + //! \param x is the new minimum gap in seconds, it must be + //! positive + //! \throw InvalidArgument if the specified gap is not positive + void setMinBreakpointGap( double x ); + + +// -- reference Partial label access/mutation -- + + //! Return the Partial to be used as a reference + //! Partial for the source sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference Partial + //! should be used for the source sequence. + const Partial & sourceReferencePartial( void ) const; + + //! Return the Partial to be used as a reference + //! Partial for the source sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference Partial + //! should be used for the source sequence. + Partial & sourceReferencePartial( void ); + + //! Return the Partial to be used as a reference + //! Partial for the target sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference Partial + //! should be used for the target sequence. + const Partial & targetReferencePartial( void ) const; + + //! Return the Partial to be used as a reference + //! Partial for the target sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference Partial + //! should be used for the target sequence. + Partial & targetReferencePartial( void ); + + //! Specify the Partial to be used as a reference + //! Partial for the source sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! The specified Partial must be labeled with its harmonic number. + //! A default (empty) Partial indicates that no reference + //! Partial should be used for the source sequence. + void setSourceReferencePartial( const Partial & p = Partial() ); + + //! Specify the Partial to be used as a reference + //! Partial for the source sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference + //! Partial should be used for the source sequence. + //! + //! \param partials a sequence of Partials to search + //! for the reference Partial + //! \param refLabel the label of the Partial in partials + //! that should be selected as the reference + void setSourceReferencePartial( const PartialList & partials, + Partial::label_type refLabel ); + + //! Specify the Partial to be used as a reference + //! Partial for the target sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! The specified Partial must be labeled with its harmonic number. + //! A default (empty) Partial indicates that no reference + //! Partial should be used for the target sequence. + void setTargetReferencePartial( const Partial & p = Partial() ); + + //! Specify the Partial to be used as a reference + //! Partial for the target sequence in a morph of two Partial + //! sequences. The reference partial is used to compute + //! frequencies for very low-amplitude Partials whose frequency + //! estimates are not considered reliable. The reference Partial + //! is considered to have good frequency estimates throughout. + //! A default (empty) Partial indicates that no reference + //! Partial should be used for the target sequence. + //! + //! \param partials a sequence of Partials to search + //! for the reference Partial + //! \param refLabel the label of the Partial in partials + //! that should be selected as the reference + void setTargetReferencePartial( const PartialList & partials, + Partial::label_type refLabel ); + +// -- PartialList access -- + + //! Return a reference to this Morpher's list of morphed Partials. + PartialList & partials( void ); + + //! Return a const reference to this Morpher's list of morphed Partials. + const PartialList & partials( void ) const; + +// -- global morphing defaults and constants -- + + //! Amplitude threshold (dB) below which + //! Partial frequencies are corrected using + //! the reference Partial frequency envelope + //! (if specified). + static const double DefaultFixThreshold; + + //! Default amplitude shaping parameter, used in + //! interpolateLogAmplitudes to perform logarithmic + //! amplitude morphs. + //! + //! Compile Loris with LINEAR_AMP_MORPHS defined for + //! legacy-style linear amplitude morphs by default. + //! + //! Change from default using setAmplitudeShape. + static const double DefaultAmpShape; + + //! Default minimum time (sec) between Breakpoints in + //! morphed Partials. + //! Change from default using setMinBreakpointGap. + static const double DefaultBreakpointGap; + +private: + +// -- helper -- + + // PartialCorrespondence represents a map from non-zero Partial + // labels to pairs of pointers to Partials that should be morphed + // into a single Partial that is assigned that label. + // MorphingPair is a pair of pointers to Partials that are + // initialized to zero, and it is the element type for the + // PartialCorrespondence map. + struct MorphingPair + { + Partial src; + Partial tgt; + }; + typedef std::map< Partial::label_type, MorphingPair > PartialCorrespondence; + + //! Helper function that performs the morph between corresponding pairs + //! of Partials identified in a PartialCorrespondence. Called by the + //! morph() implementation accepting two sequences of Partials. + void morph_aux( PartialCorrespondence & correspondence ); + + //! Compute morphed parameter values at the specified time, using + //! the source Breakpoint (assumed to correspond exactly to the + //! specified time) and the target Partial (whose parameters are + //! examined at the specified time). Append the morphed Breakpoint + //! to newp only if the target should contribute to the morph at + //! the specified time. + //! + //! \pre the target Partial may not be a dummy Partial (no Breakpoints). + //! + //! \param srcBkpt is the Breakpoint corresponding to a morph function + //! value of 0. + //! \param tgtPartial is the Partial corresponding to a morph function + //! value of 1, evaluated at the specified time. + //! \param time is the time corresponding to srcBkpt (used + //! to evaluate the morphing functions and tgtPartial). + //! \param newp is the morphed Partial under construction, the morphed + //! Breakpoint is added to this Partial. + // + void appendMorphedSrc( Breakpoint srcBkpt, const Partial & tgtPartial, + double time, Partial & newp ); + + //! Compute morphed parameter values at the specified time, using + //! the target Breakpoint (assumed to correspond exactly to the + //! specified time) and the source Partial (whose parameters are + //! examined at the specified time). Append the morphed Breakpoint + //! to newp only if the target should contribute to the morph at + //! the specified time. + //! + //! \pre the source Partial may not be a dummy Partial (no Breakpoints). + //! + //! \param tgtBkpt is the Breakpoint corresponding to a morph function + //! value of 1. + //! \param srcPartial is the Partial corresponding to a morph function + //! value of 0, evaluated at the specified time. + //! \param time is the time corresponding to srcBkpt (used + //! to evaluate the morphing functions and srcPartial). + //! \param newp is the morphed Partial under construction, the morphed + //! Breakpoint is added to this Partial. + // + void appendMorphedTgt( Breakpoint tgtBkpt, const Partial & srcPartial, + double time, Partial & newp ); + + + //! Parameterinterpolation helpers. + Breakpoint + interpolateParameters( const Breakpoint & srcBkpt, const Breakpoint & tgtBkpt, + double fweight, double aweight, double bweight ) const; + + double interpolateAmplitude( double srcAmp, double tgtAmp, double alpha ) const; + double interpolateBandwidth( double srcBw, double tgtBw, double alpha ) const; + double interpolateFrequency( double srcFreq, double tgtFreq, double alpha ) const; + double interpolatePhase( double srcphase, double tgtphase, double alpha ) const; + + + // Recompute phases for a morphed Partial, so that the synthesized phases + // match the source phases as closesly as possible at times when the + // frequency morphing function is equal to 0 or 1. + void fixMorphedPhases( Partial & newp ) const; + +}; // end of class Morpher + +} // end of namespace Loris + +#endif /* ndef INCLUDE_MORPHER_H */ diff --git a/src/loris/NoiseGenerator.C b/src/loris/NoiseGenerator.C new file mode 100644 index 0000000..c1c7c0e --- /dev/null +++ b/src/loris/NoiseGenerator.C @@ -0,0 +1,186 @@ +/* + * 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 + * + * + * NoiseGenerator.C + * + * Implementation of a class representing a gaussian noise generator, filtered and + * used as a modulator in bandwidth-enhanced synthesis. + * + * Kelly Fitz, 5 June 2003 + * revised 11 October 2009 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "NoiseGenerator.h" +#include <cmath> +#include <numeric> + + +// begin namespace +namespace Loris { + +// --- construction --- + +// --------------------------------------------------------------------------- +// (default) constructor +// --------------------------------------------------------------------------- +//! Create a new noise generator with the (optionally) specified +//! seed (default is 1.0). +//! +//! \param initSeed is the initial seed for the random number generator +// +NoiseGenerator::NoiseGenerator( double initSeed ) : + m_useed( initSeed ), + m_gset( 0 ), + m_iset( false ) +{ +} + +// --------------------------------------------------------------------------- +// seed +// --------------------------------------------------------------------------- +//! Re-seed the random number generator. +//! +//! \param newSeed is the new seed for the random number generator +// +void +NoiseGenerator::seed( double newSeed ) +{ + m_useed = newSeed; +} + +// --- random number generation --- + +// --------------------------------------------------------------------------- +// uniform random number generator +// --------------------------------------------------------------------------- +// Taken from "Random Number Generators: Good Ones Are Hard To Find," +// Stephen Park and Keith Miller, Communications of the ACM, October 1988, +// vol. 31, Number 10. +// +// This version will work as long as floating point values are represented +// with at least a 46 bit mantissa. The IEEE standard 64 bit floating point +// format has a 53 bit mantissa. +// +// The correctness of the implementation can be checked by confirming that +// after 10000 iterations, the seed, initialized to 1, is 1043618065. +// I have confirmed this. +// +// I have also confirmed that it still works (is correct after 10000 +// iterations) when I replace the divides with multiplies by oneOverM. +// +// Returns a uniformly distributed random double on the range [0., 1.). +// +// -kel 7 Nov 1997. +// +// trunc() is a problem. It's not in cmath, officially, though +// Metrowerks has it in there. SGI has it in math.h which is +// (erroneously!) included in g++ cmath, but trunc is not imported +// into std. For these two compilers, could just import std. But +// trnc doesn't seem to exist anywhere in Linux g++, so use std::modf(). +// DON'T use integer conversion, because long int ins't as long +// as double's mantissa! +// +static inline double trunc( double x ) { double y; std::modf(x, &y); return y; } + +inline double +NoiseGenerator::uniform( void ) +{ + static const double a = 16807.L; + static const double m = 2147483647.L; // == LONG_MAX + static const double oneOverM = 1.L / m; + + double temp = a * m_useed; + m_useed = temp - m * trunc( temp * oneOverM ); + return m_useed * oneOverM; +} + +// --------------------------------------------------------------------------- +// gaussian_normal +// --------------------------------------------------------------------------- +// Approximate the normal distribution using the Box-Muller transformation. +// This is a better approximation and faster algorithm than the 12 u.v. sum. +// +// This is slightly different than the thing I got off the web, I (have to) +// assume (for now) that I knew what I was doing when I altered it. +// +inline double +NoiseGenerator::gaussian_normal( void ) +{ + //static int m_iset = 0; // boolean really, now member variables + //static double m_gset; + + double r = 1., fac, v1, v2; + + if ( ! m_iset ) + { + v1 = 2. * uniform() - 1.; + v2 = 2. * uniform() - 1.; + r = v1*v1 + v2*v2; + while( r >= 1. ) + { + // v1 = 2. * uniform() - 1.; + v1 = v2; + v2 = 2. * uniform() - 1.; + r = v1*v1 + v2*v2; + } + + fac = std::sqrt( -2. * std::log(r) / r ); + m_gset = v1 * fac; + m_iset = true; + return v2 * fac; + } + else + { + m_iset = false; + return m_gset; + } +} + +// --- sample generation --- + +// --------------------------------------------------------------------------- +// sample +// --------------------------------------------------------------------------- +//! Generate and return a new sample of Gaussian noise having zero +//! mean and unity standard deviation. Approximate the normal distribution +//! using the Box-Muller transformation applied to a uniform random number +//! generator taken from "Random Number Generators: Good Ones Are Hard To Find," +//! Stephen Park and Keith Miller, Communications of the ACM, October 1988, +//! vol. 31, Number 10. +// +double +NoiseGenerator::sample( void ) +{ + double sample = gaussian_normal(); + return sample; +} + + +} // end of namespace Loris diff --git a/src/loris/NoiseGenerator.h b/src/loris/NoiseGenerator.h new file mode 100644 index 0000000..95ddccd --- /dev/null +++ b/src/loris/NoiseGenerator.h @@ -0,0 +1,99 @@ +#ifndef NOISEGENERATOR_H +#define NOISEGENERATOR_H +/* + * 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 + * + * + * NoiseGenerator.h + * + * Definition of a class representing a gaussian noise generator, filtered and + * used as a modulator in bandwidth-enhanced synthesis. + * + * Kelly Fitz, 5 June 2003 + * revised 11 October 2009 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class NoiseGenerator +// +class NoiseGenerator +{ +// --- interface --- + +public: + + //! Create a new noise generator with the (optionally) specified + //! seed (default is 1.0). + //! + //! \param initSeed is the initial seed for the random number generator + explicit NoiseGenerator( double initSeed = 1.0 ); + + + // copy and assign are free + + + //! Re-seed the random number generator. + //! + //! \param newSeed is the new seed for the random number generator + void seed( double newSeed ); + + // sample + // + //! Generate and return a new sample of Gaussian noise having zero + //! mean and unity standard deviation. Approximate the normal distribution + //! using the Box-Muller transformation applied to a uniform random number + //! generator taken from "Random Number Generators: Good Ones Are Hard To Find," + //! Stephen Park and Keith Miller, Communications of the ACM, October 1988, + //! vol. 31, Number 10. + double sample( void ); + + //! Function call operator, same as calling sample(). + //! + //! \sa sample + double operator() ( void ) { return sample(); } + + +// --- implementation --- +private: + + // random number generation helpers + inline double uniform( void ); + inline double gaussian_normal( void ); + + + // random number generator state variables + double m_useed; + double m_gset; + bool m_iset; + +}; + + +} // end of namespace Loris + +#endif /* ndef NOISEGENERATOR_H */ diff --git a/src/loris/Notifier.C b/src/loris/Notifier.C new file mode 100644 index 0000000..8c05a0d --- /dev/null +++ b/src/loris/Notifier.C @@ -0,0 +1,207 @@ +/* + * 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 + * + * + * Notifier.C + * + * Definitions of the ostreams used for notification throughout the Loris + * library (notifier and debugger), and the c-linkable functions for assigning + * notification handlers (of type NotificationHandler, see Notifier.h) + * to each. + * + * These ostreams use a streambuf derivative, NotifierBuf, to buffer characters + * in a std::string, and post them (via a handler) when a newline is received. + * NotifierBuf is defined and implemented below. + * + * Kelly Fitz, 28 Feb 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Notifier.h" +#include <string> +#include <cstdio> + +#if defined(__GNUC__) + // other compilers have this problem? + typedef int int_type; +#endif + +using namespace std; + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// defaultNotifierhandler +// --------------------------------------------------------------------------- +// By default, notifications go to stdout (no need to bring in iostreams +// for this. +// +// Is printf different from fprint to stdout? On the mac, using CW5, and +// linking into a Python module (main() written in c), only printf works. +// +static void defaultNotifierhandler( const char * s ) +{ + //cout << s << endl; + //fprintf( stdout, "%s\n", s ); + printf( "%s\n", s ); +} + +// --------------------------------------------------------------------------- +// class NotifierBuf +// +// streambuf derivative that buffers output in a std::string +// and posts it to a handler (_post) when a newline is received. +// +class NotifierBuf : public streambuf +{ +// -- public interface -- +public: +// construction: + NotifierBuf( const std::string & s = "" ) : + _str(s), _post( defaultNotifierhandler ) + // confirm construction: + // { printf( "created a NotifierBuf.\n" ); } + {} + +// virtual destructor so NotifierBuf can be subclassed: +// (use compiler generated, streambuf has virtual destructor) + //virtual ~NotifierBuf( void ); + +// handler manipulation: + NotificationHandler setHandler( NotificationHandler h ) throw() + { + NotificationHandler prev = _post; + _post = h; + return prev; + } + +protected: + // called every time a character is written: + virtual int_type overflow( int_type c ) + { + if ( c == '\n' ) { + _post( _str.c_str() ); + _str = ""; + } + else if ( c != EOF ) { + char ch(c); + _str += ch; + //_str += static_cast<char>(c); ??? + } + return c; + } + +private: + // buffer characters in a string: + std::string _str; + + // handler: + NotificationHandler _post; + +}; // end of class NotifierBuf + +// --------------------------------------------------------------------------- +// stream instances +// --------------------------------------------------------------------------- +// ostreams used throughout Loris for notification. +// +// Instead of making these globals by declaring them at file scope, +// make them static to these initializer functions, to make sure (?) +// that their constructors get called. +// +static NotifierBuf & notifierBuffer(void) +{ + static NotifierBuf buf; + return buf; +} + +std::ostream & getNotifierStream(void) +{ + static ostream os(¬ifierBuffer()); + return os; +} + + +#if defined( Debug_Loris ) + static NotifierBuf & debuggerBuffer(void) + { + static NotifierBuf buf; + return buf; + } +#else + // to do nothing at all, need a dummy streambuf: + struct Dummybuf : public streambuf + { + }; + static Dummybuf & debuggerBuffer( void ) + { + static Dummybuf buf; + return buf; + } +#endif + +std::ostream & getDebuggerStream(void) +{ + static ostream os(&debuggerBuffer()); + return os; +} + +// --------------------------------------------------------------------------- +// setNotifierHandler +// --------------------------------------------------------------------------- +// Specify a new handler for notifications. +// Does not throw. +// +extern "C" NotificationHandler +setNotifierHandler( NotificationHandler fn ) +{ + return notifierBuffer().setHandler( fn ); +} + +// --------------------------------------------------------------------------- +// setDebuggerHandler +// --------------------------------------------------------------------------- +// Specify a new handler for debugging notifications, only effective when +// Debug_Loris is defined, otherwise debugger does nothing. +// Does not throw. +// +extern "C" NotificationHandler +setDebuggerHandler( NotificationHandler fn ) +{ +#if defined( Debug_Loris ) + return debuggerBuffer().setHandler( fn ); +#else + fn = fn; + return NULL; +#endif +} + +} // end of namespace Loris + diff --git a/src/loris/Notifier.h b/src/loris/Notifier.h new file mode 100644 index 0000000..c0269f2 --- /dev/null +++ b/src/loris/Notifier.h @@ -0,0 +1,126 @@ +#ifndef INCLUDE_NOTIFIER_H +#define INCLUDE_NOTIFIER_H +/* + * 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 + * + * + * Notifier.h + * + * A pair of dedicated streams, notifier and debugger, are used for + * notification throughout the Loris class library. These streams are used + * like cout or cerr, but they buffer their contents until a newline is + * receieved. Then they post their entire contents to a notification + * handler. The default handler just prints to stderr, but other handlers + * may be dynamically specified using setNotifierHandler() and + * setDebuggerHandler(). + * + * debugger is enabled only when compiled with the preprocessor macro + * Debug_Loris defined. It cannot be enabled using setDebuggerHandler() if + * Debug_Loris is undefined.When Debug_Loris is not defined, characters + * streamed onto debugger are never posted nor are they otherwise + * accessible. + * + * Notifier.h may be included in c files. The stream declarations are + * omitted, but the notification handler routines are accessible. + * + * + * Kelly Fitz, 28 Feb 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + + +/* + * stream declaration, C++ only: + */ +#ifdef __cplusplus + +#include <iostream> + +// begin namespace +namespace Loris { + +std::ostream & getNotifierStream(void); +std::ostream & getDebuggerStream(void); + +// declare streams: +static std::ostream & notifier = getNotifierStream(); +/* This stream is used throughout Loris (and may be used by clients) + to provide user feedback. Characters streamed onto notifier are + buffered until a newline is received, and then the entire contents + of the stream are flushed to the current notification handler (stderr, + by default). + */ + +static std::ostream & debugger = getDebuggerStream(); +/* This stream is used throughout Loris (and may be used by clients) + to provide debugging information. Characters streamed onto debugger are + buffered until a newline is received, and then the entire contents + of the stream are flushed to the current debugger handler (stderr, + by default). + + debugger is enabled only when compiled with the preprocessor macro + Debug_Loris defined. It cannot be enabled using setDebuggerHandler() + if Debug_Loris is undefined. When Debug_Loris is not defined, + characters streamed onto debugger are never posted nor are they + otherwise accessible. + */ + +// for convenience, import endl and ends from std into Loris: +using std::endl; +using std::ends; + +} // end of namespace Loris + +#endif /* def __cplusplus */ + +/* + * handler assignment, c linkable: + */ + +#ifdef __cplusplus +// begin namespace +namespace Loris { +extern "C" { +#endif // def __cplusplus + +// These functions do not throw exceptions. +typedef void(*NotificationHandler)(const char * s); +NotificationHandler setNotifierHandler( NotificationHandler fn ); +/* Specify a new handling procedure for posting user feedback, and return + the current handler. + */ + +NotificationHandler setDebuggerHandler( NotificationHandler fn ); +/* Specify a new handling procedure for posting debugging information, and return + the current handler. This has no effect unless compiled with the Debug_Loris + preprocessor macro defined. + */ + +#ifdef __cplusplus +} // end extern "C" +} // end of namespace Loris +#endif // def __cplusplus + + +#endif /* ndef INCLUDE_NOTIFIER_H */ diff --git a/src/loris/Oscillator.C b/src/loris/Oscillator.C new file mode 100644 index 0000000..1ffc5c5 --- /dev/null +++ b/src/loris/Oscillator.C @@ -0,0 +1,305 @@ +/* + * 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 + * + * + * Oscillator.C + * + * Implementation of class Loris::Oscillator, a Bandwidth-Enhanced Oscillator. + * + * Kelly Fitz, 31 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Oscillator.h" + +#include "Filter.h" +#include "Partial.h" +#include "Notifier.h" + +#include <cmath> +#include <vector> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif +const double TwoPi = 2*Pi; + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// Oscillator construction +// --------------------------------------------------------------------------- +// Initialize stochastic modulators and state variables. +// +Oscillator::Oscillator( void ) : + m_modulator( 1.0 /* seed */ ), + m_filter( prototype_filter() ), + m_instfrequency( 0 ), + m_instamplitude( 0 ), + m_instbandwidth( 0 ), + m_determphase( 0 ) +{ +} + +// --------------------------------------------------------------------------- +// resetEnvelopes +// --------------------------------------------------------------------------- +// Reset the instantaneous envelope parameters +// (frequency, amplitude, bandwidth, and phase). +// The sample rate is needed to convert the +// Breakpoint frequency (Hz) to radians per sample. +// +void +Oscillator::resetEnvelopes( const Breakpoint & bp, double srate ) +{ + // Remember that the oscillator only knows about + // radian frequency! Convert! + m_instfrequency = bp.frequency() * TwoPi / srate; + m_instamplitude = bp.amplitude(); + m_instbandwidth = bp.bandwidth(); + m_determphase = bp.phase(); + + // clamp bandwidth: + if ( m_instbandwidth > 1. ) + { + debugger << "clamping bandwidth at 1." << endl; + m_instbandwidth = 1.; + } + else if ( m_instbandwidth < 0. ) + { + debugger << "clamping bandwidth at 0." << endl; + m_instbandwidth = 0.; + } + + // don't alias: + if ( m_instfrequency > Pi ) + { + debugger << "fading out aliasing Partial" << endl; + m_instamplitude = 0.; + } + + // Reset the fitler state too. + m_filter.clear(); + +} + +// --------------------------------------------------------------------------- +// m2pi +// --------------------------------------------------------------------------- +// O'Donnell's phase wrapping function. +// +static inline double m2pi( double x ) +{ + using namespace std; // floor should be in std + #define ROUND(x) (floor(.5 + (x))) + return x + ( TwoPi * ROUND(-x/TwoPi) ); +} + +// --------------------------------------------------------------------------- +// setPhase +// --------------------------------------------------------------------------- +// Reset the phase of the Oscillator to the specified +// value, and clear the accumulated phase modulation. (?) +// Or not. +// This is done when the amplitude of a Partial goes to +// zero, so that onsets are preserved in distilled +// and collated Partials. +// +void +Oscillator::setPhase( double ph ) +{ + m_determphase = m2pi(ph); +} + +// --------------------------------------------------------------------------- +// oscillate +// --------------------------------------------------------------------------- +// Accumulate bandwidth-enhanced sinusoidal samples modulating the +// oscillator state from its current values of radian frequency, +// amplitude, and bandwidth to the specified target values, into +// the specified half-open range of doubles. +// +// The caller must ensure that the range is valid. Target parameters +// are bounds-checked. +// +void +Oscillator::oscillate( double * begin, double * end, + const Breakpoint & bp, double srate ) +{ + double targetFreq = bp.frequency() * TwoPi / srate; // radians per sample + double targetAmp = bp.amplitude(); + double targetBw = bp.bandwidth(); + + // clamp bandwidth: + if ( targetBw > 1. ) + { + debugger << "clamping bandwidth at 1." << endl; + targetBw = 1.; + } + else if ( targetBw < 0. ) + { + debugger << "clamping bandwidth at 0." << endl; + targetBw = 0.; + } + + // don't alias: + if ( targetFreq > Pi ) // radian Nyquist rate + { + debugger << "fading out Partial above Nyquist rate" << endl; + targetAmp = 0.; + } + + // compute trajectories: + const double dTime = 1. / (end - begin); + const double dFreqOver2 = 0.5 * (targetFreq - m_instfrequency) * dTime; + // split frequency update in two steps, update phase using average + // frequency, after adding only half the frequency step + + const double dAmp = (targetAmp - m_instamplitude) * dTime; + const double dBw = (targetBw - m_instbandwidth) * dTime; + + // Use temporary local variables for speed. + // Probably not worth it when I am computing square roots + // and cosines... + double ph = m_determphase; + double f = m_instfrequency; + double a = m_instamplitude; + double bw = m_instbandwidth; + + // Also use a more efficient sample loop when the bandwidth is zero. + if ( 0 < bw || 0 < dBw ) + { + double am, nz; + for ( double * putItHere = begin; putItHere != end; ++putItHere ) + { + // use math functions in namespace std: + using namespace std; + + // compute amplitude modulation due to bandwidth: + // + // This will give the right amplitude modulation when scaled + // by the Partial amplitude: + // + // carrier amp: sqrt( 1. - bandwidth ) * amp + // modulation index: sqrt( 2. * bandwidth ) * amp + // + nz = m_filter.apply( m_modulator.sample() ); + am = sqrt( 1. - bw ) + ( nz * sqrt( 2. * bw ) ); + + // compute a sample and add it into the buffer: + *putItHere += am * a * cos( ph ); + + // update the instantaneous oscillator state: + f += dFreqOver2; + ph += f; // frequency is radians per sample + f += dFreqOver2; + a += dAmp; + bw += dBw; + if (bw < 0.) + { + bw = 0.; + } + } // end of sample computation loop + } + else + { + for ( double * putItHere = begin; putItHere != end; ++putItHere ) + { + // use math functions in namespace std: + using namespace std; + + // no modulation when there is no bandwidth + + // compute a sample and add it into the buffer: + *putItHere += a * cos( ph ); + + // update the instantaneous oscillator state: + f += dFreqOver2; + ph += f; // frequency is radians per sample + f += dFreqOver2; + a += dAmp; + } // end of sample computation loop + + } + + + // copy out of the local variables? + // no need because we are assigning to the target + // values below: + /* + m_instfrequency = f; + m_instamplitude = a; + m_instbandwidth = bw; + */ + + // wrap phase to prevent eventual loss of precision at + // high oscillation frequencies: + // (Doesn't really matter much exactly how we wrap it, + // as long as it brings the phase nearer to zero.) + m_determphase = m2pi( ph ); + + // set the state variables to their target values, + // just in case they didn't arrive exactly (overshooting + // amplitude or, especially, bandwidth, could be bad, and + // it does happen): + m_instfrequency = targetFreq; + m_instamplitude = targetAmp; + m_instbandwidth = targetBw; +} + +// --------------------------------------------------------------------------- +// protoype filter (static member) +// --------------------------------------------------------------------------- +// Static local function for obtaining a prototype Filter +// to use in Oscillator construction. Eventually, allow +// external (client) specification of the Filter prototype. +// +const Filter & +Oscillator::prototype_filter( void ) +{ + // Chebychev order 3, cutoff 500, ripple -1. + // + // Coefficients obtained from http://www.cs.york.ac.uk/~fisher/mkfilter/ + // Digital filter designed by mkfilter/mkshape/gencode A.J. Fisher + // + static const double Gain = 4.663939184e+04; + static const double ExtraScaling = 6.; + static const double MaCoefs[] = { 1., 3., 3., 1. }; + static const double ArCoefs[] = { 1., -2.9258684252, 2.8580608586, -0.9320209046 }; + + static const Filter proto( MaCoefs, MaCoefs + 4, ArCoefs, ArCoefs + 4, ExtraScaling/Gain ); + return proto; +} + + + +} // end of namespace Loris diff --git a/src/loris/Oscillator.h b/src/loris/Oscillator.h new file mode 100644 index 0000000..79631f4 --- /dev/null +++ b/src/loris/Oscillator.h @@ -0,0 +1,138 @@ +#ifndef INCLUDE_OSCILLATOR_H +#define INCLUDE_OSCILLATOR_H +/* + * 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 + * + * + * Oscillator.h + * + * Definition of class Loris::Oscillator, a Bandwidth-Enhanced Oscillator. + * + * Kelly Fitz, 31 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "NoiseGenerator.h" +#include "Filter.h" + +// begin namespace +namespace Loris { + +class Breakpoint; + +// --------------------------------------------------------------------------- +// class Oscillator +// +//! Class Oscillator represents the state of a single bandwidth-enhanced +//! sinusoidal oscillator used for synthesizing sounds from Reassigned +//! Bandwidth-Enhanced analysis data. Oscillator encapsulates the oscillator +//! state, including the instantaneous radian frequency (radians per +//! sample), amplitude, bandwidth coefficient, and phase, and a +//! bandlimited stochastic modulator. +//! +//! Class Synthesizer uses an instance of Oscillator to synthesize +//! bandwidth-enhanced Partials. +// +class Oscillator +{ +// --- implementation --- + + NoiseGenerator m_modulator; //! stochastic modulator + Filter m_filter; //! filter applied to the noise generator + + // instantaneous oscillator state: + double m_instfrequency; //! radians per sample + double m_instamplitude; //! absolute amplitude + double m_instbandwidth; //! bandwidth coefficient (noise energy / total energy) + + // accumulating phase state: + double m_determphase; //! deterministic phase in radians + +// --- interface --- +public: +// --- construction --- + + //! Construct a new Oscillator with all state parameters initialized to 0. + Oscillator( void ); + + // Copy, assignment, and destruction are free. + // + // Copied and assigned Oscillators have the duplicate state + // variables and the filters have the same coefficients, + // but the state of the filter delay lines is not copied. + +// --- oscillation --- + + //! Reset the instantaneous envelope parameters + //! (frequency, amplitude, bandwidth, and phase). + //! The sample rate is needed to convert the + //! Breakpoint frequency (Hz) to radians per sample. + void resetEnvelopes( const Breakpoint & bp, double srate ); + + //! Reset the phase of the Oscillator to the specified + //! value. This is done when the amplitude of a Partial + //! goes to zero, so that onsets are preserved in distilled + //! and collated Partials. + void setPhase( double ph ); + + //! Accumulate bandwidth-enhanced sinusoidal samples modulating the + //! oscillator state from its current values of radian frequency, amplitude, + //! and bandwidth to the specified target values. Accumulate samples into + //! the half-open (STL-style) range of doubles, starting at begin, and + //! ending before end (no sample is accumulated at end). The caller must + //! insure that the indices are valid. Target frequency and bandwidth are + //! checked to prevent aliasing and bogus bandwidth enhancement. + void oscillate( double * begin, double * end, + const Breakpoint & bp, double srate ); + +// --- accessors --- + + //! Return the instantaneous amplitde of the Oscillator. + double amplitude( void ) const { return m_instamplitude; } + + //! Return the instantaneous bandwidth of the Oscillator. + double bandwidth( void ) const { return m_instbandwidth; } + + //! Return the instantaneous phase of the Oscillator. + double phase( void ) const { return m_determphase; } + + //! Return the instantaneous radian frequency of the Oscillator. + double radianFreq( void ) const { return m_instfrequency; } + + //! Return access to the Filter used by this oscillator to + //! implement bandwidth-enhanced sinusoidal synthesis. + Filter & filter( void ) { return m_filter; } + +// --- static members --- + + //! Static local function for obtaining a prototype Filter + //! to use in Oscillator construction. Eventually, allow + //! external (client) specification of the Filter prototype. + static const Filter & prototype_filter( void ); + +}; // end of class Oscillator + +} // end of namespace Loris + +#endif /* ndef INCLUDE_OSCILLATOR_H */ diff --git a/src/loris/Partial.C b/src/loris/Partial.C new file mode 100644 index 0000000..b6ea15c --- /dev/null +++ b/src/loris/Partial.C @@ -0,0 +1,860 @@ +/* + * 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 + * + * + * Partial.C + * + * Implementation of class Loris::Partial. + * + * Kelly Fitz, 16 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Partial.h" +#include "Breakpoint.h" +#include "LorisExceptions.h" +#include "Notifier.h" + +#include <algorithm> +#include <cmath> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + +//long Partial::DebugCounter = 0L; + +// comparitor for elements in Partial::container_type +typedef Partial::container_type::value_type Partial_value_type; +static +bool order_by_time( const Partial_value_type & x, const Partial_value_type & y ) +{ + // Partial_value_type is a (time,Breakpoint) pair + return x.first < y.first; +} + +// --- concering the type of Partial::container_type +// +// On the surface, it would seem that a vector of (time,Breakpoint) +// pairs would be a more efficient container for the Partial +// parameter envelope points, and the changes required to implement +// Partial using vector instead of map are minimal and simple. +// +// However, the crucial factor in that change is the expiration of +// Partial::iterators. With map, iterators remain valid after +// insertions and removals, but with vector they do not. So it +// is easy to change the container type, but it is a much harder +// project to find all the places in Loris that rely on iterators +// that remain valid after insertions and removals. +#undef USE_VECTOR + + +// -- construction -- + +// --------------------------------------------------------------------------- +// Partial constructor +// --------------------------------------------------------------------------- +//! Retun a new empty (no Breakpoints) unlabeled Partial. +// +Partial::Partial( void ) : + _label( 0 ) +{ +// ++DebugCounter; +} + +// --------------------------------------------------------------------------- +// Partial initialized constructor +// --------------------------------------------------------------------------- +//! Retun a new Partial from a half-open (const) iterator range +//! of time, Breakpoint pairs. +// +Partial::Partial( const_iterator beg, const_iterator end ) : + _breakpoints( beg._iter, end._iter ), + _label( 0 ) +{ +// ++DebugCounter; +} + +// --------------------------------------------------------------------------- +// Partial copy constructor +// --------------------------------------------------------------------------- +//! Return a new Partial that is an exact copy (has an identical set +//! of Breakpoints, at identical times, and the same label) of another +//! Partial. +// +Partial::Partial( const Partial & other ) : + _breakpoints( other._breakpoints ), + _label( other._label ) +{ +// ++DebugCounter; +} + +// --------------------------------------------------------------------------- +// Partial destructor +// --------------------------------------------------------------------------- +//! Destroy this Partial. +// +Partial::~Partial( void ) +{ +// --DebugCounter; +} + +// --------------------------------------------------------------------------- +// operator= +// --------------------------------------------------------------------------- +//! Make this Partial an exact copy (has an identical set of +//! Breakpoints, at identical times, and the same label) of another +//! Partial. +// +Partial & +Partial::operator=( const Partial & rhs ) +{ + if ( this != &rhs ) + { + _breakpoints = rhs._breakpoints; + _label = rhs._label; + } + return *this; +} + +// -- container-dependent implementation -- + +// --------------------------------------------------------------------------- +// begin +// --------------------------------------------------------------------------- +//! Return a const iterator refering to the position of the first +//! Breakpoint in this Partial's envelope. +// +Partial::const_iterator Partial::begin( void ) const +{ + return _breakpoints.begin(); +} + +//! Return an iterator refering to the position of the first +//! Breakpoint in this Partial's envelope. +// +Partial::iterator Partial::begin( void ) +{ + return _breakpoints.begin(); +} + +// --------------------------------------------------------------------------- +// end +// --------------------------------------------------------------------------- +//! Return a const iterator refering to the position past the last +//! Breakpoint in this Partial's envelope. The iterator returned by +//! end() (like the iterator returned by the end() member of any STL +//! container) does not refer to a valid Breakpoint. +// +Partial::const_iterator +Partial::end( void ) const +{ + return _breakpoints.end(); +} + +//! Return an iterator refering to the position past the last +//! Breakpoint in this Partial's envelope. The iterator returned by +//! end() (like the iterator returned by the end() member of any STL +//! container) does not refer to a valid Breakpoint. +// +Partial::iterator +Partial::end( void ) +{ + return _breakpoints.end(); +} + +// --------------------------------------------------------------------------- +// erase +// --------------------------------------------------------------------------- +//! Breakpoint removal: erase the Breakpoints in the specified range, +//! and return an iterator referring to the position after the, +//! erased range. +// +Partial::iterator +Partial::erase( Partial::iterator beg, Partial::iterator end ) +{ + _breakpoints.erase( beg._iter, end._iter ); + return end; +} + +// --------------------------------------------------------------------------- +// findAfter +// --------------------------------------------------------------------------- +//! Return a const iterator refering to the insertion position for a +//! Breakpoint at the specified time (that is, the position of the first +//! Breakpoint at a time not earlier than the specified time). +// +Partial::const_iterator +Partial::findAfter( double time ) const +{ +#if defined(USE_VECTOR) + // see note above + Partial_value_type dummy( time, Breakpoint() ); + return std::upper_bound( _breakpoints.begin(), _breakpoints.end(), dummy, order_by_time ); +#else + return _breakpoints.lower_bound( time ); +#endif +} + +//! Return an iterator refering to the insertion position for a +//! Breakpoint at the specified time (that is, the position of the first +//! Breakpoint at a time later than the specified time). +// +Partial::iterator +Partial::findAfter( double time ) +{ +#if defined(USE_VECTOR) + // see note above + Partial_value_type dummy( time, Breakpoint() ); + return std::upper_bound( _breakpoints.begin(), _breakpoints.end(), dummy, order_by_time ); +#else + return _breakpoints.lower_bound( time ); +#endif +} + +// --------------------------------------------------------------------------- +// insert +// --------------------------------------------------------------------------- +//! Breakpoint insertion: insert a copy of the specified Breakpoint in the +//! parameter envelope at time (seconds), and return an iterator +//! refering to the position of the inserted Breakpoint. +// +Partial::iterator +Partial::insert( double time, const Breakpoint & bp ) +{ +#if defined(USE_VECTOR) + // see note above + // find the position at which to insert the new Breakpoint: + Partial_value_type dummy( time, Breakpoint() ); + Partial::container_type::iterator insertHere = + std::lower_bound( _breakpoints.begin(), _breakpoints.end(), dummy, order_by_time ); + + // if the time at insertHere is equal to the insertion time, + // simply replace the Breakpoint, otherwise insert: + if ( insertHere->first == time ) + { + insertHere->second = bp; + } + else + { + insertHere = _breakpoints.insert( insertHere, Partial_value_type(time, bp) ); + } + return insertHere; +#else + /* + // this allows Breakpoints to be inserted arbitrarily + // close together, which is no good, can cause trouble later: + + std::pair< container_type::iterator, bool > result = + _breakpoints.insert( container_type::value_type(time, bp) ); + if ( ! result.second ) + { + result.first->second = bp; + } + return result.first; + */ + + // do not insert a Breakpoint closer than 1ns away + // from the nearest existing Breakpoint: + static const double MinTimeDif = 1.0E-9; // 1 ns + + // find the insertion point for this time + container_type::iterator pos = _breakpoints.lower_bound( time ); + + // the time of pos is either equal to or greater + // than the insertion time, if this is too close, + // remove the Breakpoint at pos: + if ( _breakpoints.end() != pos && MinTimeDif > pos->first - time ) + { + _breakpoints.erase( pos++ ); + } + // otherwise, if the preceding position is too clase, + // remove the Breakpoint at that position + else if ( _breakpoints.begin() != pos && MinTimeDif > time - (--pos)->first ) + { + _breakpoints.erase( pos++ ); + } + + // now pos is at most one position away from the insertion point + // so insertion can be performed in constant time, and the new + // Breakpoint is at least 1ns away from any other Breakpoint: + pos = _breakpoints.insert( pos, container_type::value_type(time, bp) ); + + Assert( pos->first == time ); + + return pos; + +#endif +} + +// --------------------------------------------------------------------------- +// numBreakpoints +// --------------------------------------------------------------------------- +//! Same as size(). Return the number of Breakpoints in this Partial. +// +Partial::size_type +Partial::numBreakpoints( void ) const +{ + return _breakpoints.size(); +} +// --------------------------------------------------------------------------- +// size +// --------------------------------------------------------------------------- +//! Return the number of Breakpoints in this Partial. +// +Partial::size_type +Partial::size( void ) const +{ + return _breakpoints.size(); +} + +// --------------------------------------------------------------------------- +// label +// --------------------------------------------------------------------------- +//! Return the 32-bit label for this Partial as an integer. +// +Partial::label_type +Partial::label( void ) const +{ + return _label; +} + +// --------------------------------------------------------------------------- +// first +// --------------------------------------------------------------------------- +//! Return a reference to the first Breakpoint in the Partial's +//! envelope. Raises InvalidPartial exception if there are no +//! Breakpoints. +// +Breakpoint & +Partial::first( void ) +{ + if ( size() == 0 ) + { + Throw( InvalidPartial, "Tried find first Breakpoint in a Partial with no Breakpoints." ); + } +#if defined(USE_VECTOR) + // see note above + return _breakpoints.front().second; +#else + return begin().breakpoint(); +#endif +} + +// --------------------------------------------------------------------------- +// first +// --------------------------------------------------------------------------- +//! Return a const reference to the first Breakpoint in the Partial's +//! envelope. Raises InvalidPartial exception if there are no +//! Breakpoints. +// +const Breakpoint & +Partial::first( void ) const +{ + if ( size() == 0 ) + { + Throw( InvalidPartial, "Tried find first Breakpoint in a Partial with no Breakpoints." ); + } +#if defined(USE_VECTOR) + // see note above + return _breakpoints.front().second; +#else + return begin().breakpoint(); +#endif +} + +// --------------------------------------------------------------------------- +// last +// --------------------------------------------------------------------------- +//! Return a reference to the last Breakpoint in the Partial's +//! envelope. Raises InvalidPartial exception if there are no +//! Breakpoints. +// +Breakpoint & +Partial::last( void ) +{ + if ( size() == 0 ) + { + Throw( InvalidPartial, "Tried find last Breakpoint in a Partial with no Breakpoints." ); + } +#if defined(USE_VECTOR) + // see note above + return _breakpoints.back().second; +#else + return (--end()).breakpoint(); +#endif +} + +// --------------------------------------------------------------------------- +// last +// --------------------------------------------------------------------------- +//! Return a const reference to the last Breakpoint in the Partial's +//! envelope. Raises InvalidPartial exception if there are no +//! Breakpoints. +// +const Breakpoint & +Partial::last( void ) const +{ + if ( size() == 0 ) + { + Throw( InvalidPartial, "Tried find last Breakpoint in a Partial with no Breakpoints." ); + } +#if defined(USE_VECTOR) + // see note above + return _breakpoints.back().second; +#else + return (--end()).breakpoint(); +#endif +} + +// -- container-independent implementation -- + +// --------------------------------------------------------------------------- +// initialPhase +// --------------------------------------------------------------------------- +//! Return starting phase in radians, except (InvalidPartial) if there +//! are no Breakpoints. +// +double +Partial::initialPhase( void ) const +{ + if ( numBreakpoints() == 0 ) + { + Throw( InvalidPartial, "Tried find intial phase of a Partial with no Breakpoints." ); + } + return first().phase(); +} + +// --------------------------------------------------------------------------- +// startTime +// --------------------------------------------------------------------------- +//! Return start time in seconds, except (InvalidPartial) if there +//! are no Breakpoints. +// +double +Partial::startTime( void ) const +{ + if ( numBreakpoints() == 0 ) + { + Throw( InvalidPartial, "Tried to find start time of a Partial with no Breakpoints." ); + } + return begin().time(); +} + +// --------------------------------------------------------------------------- +// endTime +// --------------------------------------------------------------------------- +//! Return end time in seconds, except (InvalidPartial) if there +//! are no Breakpoints. +// +double +Partial::endTime( void ) const +{ + if ( numBreakpoints() == 0 ) + { + Throw( InvalidPartial, "Tried to find end time of a Partial with no Breakpoints." ); + } + return (--end()).time(); +} + +// --------------------------------------------------------------------------- +// absorb +// --------------------------------------------------------------------------- +//! Absorb another Partial's energy as noise (bandwidth), +//! by accumulating the other's energy as noise energy +//! in the portion of this Partial's envelope that overlaps +//! (in time) with the other Partial's envelope. +// +void +Partial::absorb( const Partial & other ) +{ + Partial::iterator it = findAfter( other.startTime() ); + while ( it != end() && !(it.time() > other.endTime()) ) + { + // only non-null (non-zero-amplitude) Breakpoints + // abosrb noise energy because null Breakpoints + // are used especially to reset the Partial phase, + // and are not part of the normal analyasis data: + if ( it->amplitude() > 0 ) + { + // absorb energy from other at the time + // of this Breakpoint: + double a = other.amplitudeAt( it.time() ); + it->addNoiseEnergy( a * a ); + } + ++it; + } +} + +// --------------------------------------------------------------------------- +// setLabel +// --------------------------------------------------------------------------- +//! Set the label for this Partial to the specified 32-bit value. +// +void +Partial::setLabel( label_type l ) +{ + _label = l; +} + +// --------------------------------------------------------------------------- +// duration +// --------------------------------------------------------------------------- +//! Return time, in seconds, spanned by this Partial, or 0. if there +//! are no Breakpoints. +// +double +Partial::duration( void ) const +{ + if ( numBreakpoints() == 0 ) + { + return 0.; + } + return endTime() - startTime(); +} + +// --------------------------------------------------------------------------- +// erase +// --------------------------------------------------------------------------- +//! Erase the Breakpoint at the position of the +//! given iterator (invalidating the iterator), and +//! return an iterator referring to the next position, +//! or end if pos is the last Breakpoint in the Partial. +// +Partial::iterator +Partial::erase( iterator pos ) +{ + if ( pos != end() ) + { + iterator b= pos; + iterator e = ++pos; + pos = erase( b, e ); + } + return pos; +} + +// --------------------------------------------------------------------------- +// split +// --------------------------------------------------------------------------- +//! Break this Partial at the specified position (iterator). +//! The Breakpoint at the specified position becomes the first +//! Breakpoint in a new Partial. Breakpoints at the specified +//! position and subsequent positions are removed from this +//! Partial and added to the new Partial, which is returned. +// +Partial +Partial::split( iterator pos ) +{ + Partial res( pos, end() ); + erase( pos, end() ); + return res; +} + +// --------------------------------------------------------------------------- +// findNearest (const version) +// --------------------------------------------------------------------------- +//! Return the insertion position for the Breakpoint nearest +//! the specified time. Always returns a valid iterator (the +//! position of the nearest-in-time Breakpoint) unless there +//! are no Breakpoints. +// +Partial::const_iterator +Partial::findNearest( double time ) const +{ + // if there are no Breakpoints, return end: + if ( numBreakpoints() == 0 ) + { + return end(); + } + + // get the position of the first Breakpoint after time: + Partial::const_iterator pos = findAfter( time ); + + // if there is an earlier Breakpoint that is closer in + // time, prefer that one: + if ( pos != begin() ) + { + Partial::const_iterator prev = pos; + --prev; + if ( pos == end() || pos.time() - time > time - prev.time() ) + { + return prev; + } + } + + // failing all else: + return pos; +} + +// --------------------------------------------------------------------------- +// findNearest (non-const version) +// --------------------------------------------------------------------------- +//! Return the insertion position for the Breakpoint nearest +//! the specified time. Always returns a valid iterator (the +//! position of the nearest-in-time Breakpoint) unless there +//! are no Breakpoints. +// +Partial::iterator +Partial::findNearest( double time ) +{ + // if there are no Breakpoints, return end: + if ( numBreakpoints() == 0 ) + { + return end(); + } + // get the position of the first Breakpoint after time: + Partial::iterator pos = findAfter( time ); + + // if there is an earlier Breakpoint that is closer in + // time, prefer that one: + if ( pos != begin() ) + { + Partial::iterator prev = pos; + --prev; + if ( pos == end() || pos.time() - time > time - prev.time() ) + { + return prev; + } + } + + // failing all else: + return pos; +} + +// --------------------------------------------------------------------------- +// frequencyAt +// --------------------------------------------------------------------------- +//! Return the interpolated frequency (in Hz) of this Partial at the +//! specified time. At times beyond the ends of the Partial, return +//! the frequency at the nearest envelope endpoint. Throw an +//! InvalidPartial exception if this Partial has no Breakpoints. +// +double +Partial::frequencyAt( double time ) const +{ + Breakpoint bp = parametersAt( time ); + return bp.frequency(); +} + +// --------------------------------------------------------------------------- +// ShortestSafeFadeTime +// --------------------------------------------------------------------------- +//! Define the default fade time for computing amplitude at the ends +//! of a Partial. Floating point round-off errors make fadeTime == 0.0 +//! dangerous and unpredictable. 1 ns is short enough to prevent rounding +//! errors in the least significant bit of a 48-bit mantissa for times +//! up to ten hours. +// +const double Partial::ShortestSafeFadeTime = 1.0E-9; + +// --------------------------------------------------------------------------- +// amplitudeAt +// --------------------------------------------------------------------------- +//! Return the interpolated amplitude of this Partial at the +//! specified time. Throw an InvalidPartial exception if this +//! Partial has no Breakpoints. If non-zero fadeTime is specified, +//! then the amplitude at the ends of the Partial is coomputed using +//! a linear fade. The default fadeTime is ShortestSafeFadeTime, +//! see the definition of ShortestSafeFadeTime, above. +// +double +Partial::amplitudeAt( double time, double fadeTime ) const +{ + Breakpoint bp = parametersAt( time, fadeTime ); + return bp.amplitude(); +} + + +// --------------------------------------------------------------------------- +// phaseAt +// --------------------------------------------------------------------------- +//! Return the interpolated phase (in radians) of this Partial at +//! the specified time. At times beyond the ends of the Partial, +//! return the extrapolated from the nearest envelope endpoint +//! (assuming constant frequency, as reported by frequencyAt()). +//! +//! \param time is the time in seconds at which to evaluate the phase +//! +//! \throw Throw an InvalidPartial exception if this Partial has no +//! Breakpoints. +// +double +Partial::phaseAt( double time ) const +{ + Breakpoint bp = parametersAt( time ); + return bp.phase(); +} + +// --------------------------------------------------------------------------- +// bandwidthAt +// --------------------------------------------------------------------------- +//! Return the interpolated bandwidth (noisiness) coefficient of +//! this Partial at the specified time. At times beyond the ends of +//! the Partial, return the bandwidth coefficient at the nearest +//! envelope endpoint. Throw an InvalidPartial exception if this +//! Partial has no Breakpoints. +// +double +Partial::bandwidthAt( double time ) const +{ + Breakpoint bp = parametersAt( time ); + return bp.bandwidth(); +} + +// --------------------------------------------------------------------------- +// wrapPi +// --------------------------------------------------------------------------- +// O'Donnell's phase wrapping function. +// +static inline double wrapPi( double x ) +{ + using namespace std; // floor should be in std + #define ROUND(x) (floor(.5 + (x))) + const double TwoPi = 2.0*Pi; + return x + ( TwoPi * ROUND(-x/TwoPi) ); +} + +// --------------------------------------------------------------------------- +// parametersAt +// --------------------------------------------------------------------------- +//! Return the interpolated parameters of this Partial at +//! the specified time. If non-zero fadeTime is specified, then the +//! amplitude at the ends of the Partial is coomputed using a +//! linear fade. The default fadeTime is ShortestSafeFadeTime. +//! Throw an InvalidPartial exception if this Partial has no +//! Breakpoints. +// +Breakpoint +Partial::parametersAt( double time, double fadeTime ) const +{ + if ( numBreakpoints() == 0 ) + { + Throw( InvalidPartial, "Tried to interpolate a Partial with no Breakpoints." ); + } + + double freq, amp, bw, ph; + if ( startTime() >= time ) + { + // time is before the onset of the Partial: + // frequency is starting frequency, + // amplitude is 0 (or fading), bandwidth is starting + // bandwidth, and phase is rolled back. + + const Breakpoint & bp = first(); + double tstart = startTime(); + + // frequency: + freq = bp.frequency(); + + // amplitude: + amp = 0; + if ( (fadeTime > 0) && ((tstart - time) < fadeTime) ) + { + // fade in ampltude if time is before the onset of the Partial: + double alpha = 1. - ((tstart - time) / fadeTime); + amp = alpha * bp.amplitude(); + } + + // bandwidth: + bw = bp.bandwidth(); + + // phase: + double dp = 2. * Pi * (startTime() - time) * bp.frequency(); + ph = wrapPi( bp.phase() - dp ); + + } + else if ( endTime() <= time ) + { + // time is past the end of the Partial: + // frequency is ending frequency, + // amplitude is 0 (or fading), bandwidth is ending + // bandwidth, and phase is rolled forward. + const Breakpoint & bp = last(); + double tend = endTime(); + + // frequency: + freq = bp.frequency(); + + // amplitude: + amp = 0; + if ( (fadeTime > 0) && ((time - tend) < fadeTime) ) + { + // fade out ampltude if time is past the end of the Partial: + double alpha = 1. - ((time - tend) / fadeTime); + amp = alpha * bp.amplitude(); + } + + // bandwidth: + bw = bp.bandwidth(); + + // phase: + double dp = 2. * Pi * (time - endTime()) * bp.frequency(); + ph = wrapPi( bp.phase() + dp ); + } + else + { + // findAfter returns the position of the earliest + // Breakpoint later than time, or the end + // position if no such Breakpoint exists: + Partial::const_iterator it = findAfter( time ); + + // interpolate between it and its predeccessor + // (we checked already that it is not begin or end): + const Breakpoint & hi = it.breakpoint(); + double hitime = it.time(); + const Breakpoint & lo = (--it).breakpoint(); + double lotime = it.time(); + + double alpha = (time - lotime) / (hitime - lotime); + + // frequency: + freq = (alpha * hi.frequency()) + ((1. - alpha) * lo.frequency()); + + // amplitude: + amp = (alpha * hi.amplitude()) + ((1. - alpha) * lo.amplitude()); + + // bandwidth: + bw = (alpha * hi.bandwidth()) + ((1. - alpha) * lo.bandwidth()); + + // phase: + // interpolated phase is computed from the interpolated frequency + // and offset from the phase of the preceding Breakpoint: + double favg = 0.5 * ( lo.frequency() + freq ); // + hi.frequency() ); + double dp = 2. * Pi * (time - lotime) * favg; + ph = wrapPi( lo.phase() + dp ); + } + + return Breakpoint( freq, amp, bw, ph ); +} + +} // end of namespace Loris diff --git a/src/loris/Partial.h b/src/loris/Partial.h new file mode 100644 index 0000000..1b26871 --- /dev/null +++ b/src/loris/Partial.h @@ -0,0 +1,786 @@ +#ifndef INCLUDE_PARTIAL_H +#define INCLUDE_PARTIAL_H +/* + * 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 + * + * + * Partial.h + * + * Definition of class Loris::Partial, and definitions and implementations of + * classes of const and non-const iterators over Partials, and the exception + * class InvalidPartial, thrown by some Partial members when invoked on a + * degenerate Partial having no Breakpoints. + * + * Kelly Fitz, 16 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Breakpoint.h" +#include "LorisExceptions.h" + +#include <map> +//#include <utility> +//#include <vector> + +// begin namespace +namespace Loris { + +class Partial_Iterator; +class Partial_ConstIterator; + +// --------------------------------------------------------------------------- +// class Partial +// +//! An instance of class Partial represents a single component in the +//! reassigned bandwidth-enhanced additive model. A Partial consists of a +//! chain of Breakpoints describing the time-varying frequency, amplitude, +//! and bandwidth (or noisiness) envelopes of the component, and a 4-byte +//! label. The Breakpoints are non-uniformly distributed in time. For more +//! information about Reassigned Bandwidth-Enhanced Analysis and the +//! Reassigned Bandwidth-Enhanced Additive Sound Model, refer to the Loris +//! website: www.cerlsoundgroup.org/Loris/. +//! +//! The constituent time-tagged Breakpoints are accessible through +//! Partial:iterator and Partial::const_iterator interfaces. +//! These iterator classes implement the interface for bidirectional +//! iterators in the STL, including pre and post-increment and decrement, +//! and dereferencing. Dereferencing a Partial::itertator or +//! Partial::const_itertator yields a reference to a Breakpoint. Additionally, +//! these iterator classes have breakpoint() and time() members, returning +//! the Breakpoint (by reference) at the current iterator position and the +//! time (by value) corresponding to that Breakpoint. +//! +//! Partial is a leaf class, do not subclass. +//! +//! Most of the implementation of Partial delegates to a few +//! container-dependent members. The following members are +//! container-dependent, the other members are implemented in +//! terms of these: +//! default construction +//! copy (construction) +//! operator= (assign) +//! operator== (equivalence) +//! size +//! insert( pos, Breakpoint ) +//! erase( b, e ) +//! findAfter( time ) +//! begin (const and non-const) +//! end (const and non-const) +//! first (const and non-const) +//! last (const and non-const) +// +class Partial +{ +// -- public interface -- +public: + +// -- types -- + + //! underlying Breakpoint container type, used by + //! the iterator types defined below: + typedef std::map< double, Breakpoint > container_type; + + // typedef std::vector< std::pair< double, Breakpoint > > container_type; + // see Partial.C for a discussion of issues surrounding the + // choice of std::map as a Breakpoint container. + + //! 32 bit type for labeling Partials + typedef int label_type; + + //! non-const iterator over (time, Breakpoint) pairs in this Partial + typedef Partial_Iterator iterator; + + //! const iterator over (time, Breakpoint) pairs in this Partial + typedef Partial_ConstIterator const_iterator; + + //! size type for number of Breakpoints in this Partial + typedef container_type::size_type size_type; + +// -- construction -- + + //! Retun a new empty (no Breakpoints) Partial. + Partial( void ); + + //! Retun a new Partial from a half-open (const) iterator range + //! of time-Breakpoint pairs. + //! + //! \param beg is the beginning of the range of time-Breakpoint + //! pairs to insert into the new Partial. + //! \param end is the end of the range of time-Breakpoint pairs + //! to insert into the new Partial. + Partial( const_iterator beg, const_iterator end ); + + //! Return a new Partial that is an exact copy (has an identical set + //! of Breakpoints, at identical times, and the same label) of another + //! Partial. + //! + //! \param other is the Partial to copy. + Partial( const Partial & other ); + + //! Destroy this Partial. + ~Partial( void ); + +// -- assignment -- + + //! Make this Partial an exact copy (has an identical set of + //! Breakpoints, at identical times, and the same label) of another + //! Partial. + //! + //! \param other is the Partial to copy. + Partial & operator=( const Partial & other ); + +// -- container-dependent implementation -- + + //! Return an iterator refering to the position of the first + //! Breakpoint in this Partial's envelope, or end() if there + //! are no Breakpoints in the Partial. + iterator begin( void ); + + //! Return a const iterator refering to the position of the first + //! Breakpoint in this Partial's envelope, or end() if there + //! are no Breakpoints in the Partial. + const_iterator begin( void ) const; + + //! Return an iterator refering to the position past the last + //! Breakpoint in this Partial's envelope. The iterator returned by + //! end() (like the iterator returned by the end() member of any STL + //! container) does not refer to a valid Breakpoint. + iterator end( void ); + + //! Return a const iterator refering to the position past the last + //! Breakpoint in this Partial's envelope. The iterator returned by + //! end() (like the iterator returned by the end() member of any STL + //! container) does not refer to a valid Breakpoint. + const_iterator end( void ) const; + + //! Breakpoint removal: erase the Breakpoints in the specified range, + //! and return an iterator referring to the position after the, + //! erased range. + //! + //! \param beg is the beginning of the range of Breakpoints to erase + //! \param end is the end of the range of Breakpoints to erase + //! \return The position of the first Breakpoint after the range + //! of removed Breakpoints, or end() if the last Breakpoint + //! in the Partial was removed. + iterator erase( iterator beg, iterator end ); + + //! Return an iterator refering to the insertion position for a + //! Breakpoint at the specified time (that is, the position of the first + //! Breakpoint at a time later than the specified time). + //! + //! \param time is the time in seconds to find + //! \return The last position (iterator) at which a Breakpoint at the + //! specified time could be inserted (the position of the + //! first Breakpoint later than time). + iterator findAfter( double time ); + + //! Return a const iterator refering to the insertion position for a + //! Breakpoint at the specified time (that is, the position of the first + //! Breakpoint at a time later than the specified time). + //! + //! \param time is the time in seconds to find + //! \return The last position (iterator) at which a Breakpoint at the + //! specified time could be inserted (the position of the + //! first Breakpoint later than time). + const_iterator findAfter( double time ) const; + + //! Breakpoint insertion: insert a copy of the specified Breakpoint in the + //! parameter envelope at time (seconds), and return an iterator + //! refering to the position of the inserted Breakpoint. + //! + //! \param time is the time in seconds at which to insert the new + //! Breakpoint. + //! \param bp is the new Breakpoint to insert. + //! \return the position (iterator) of the newly-inserted + //! time-Breakpoint pair. + iterator insert( double time, const Breakpoint & bp ); + + //! Return the number of Breakpoints in this Partial. + //! + //! \return The number of Breakpoints in this Partial. + size_type size( void ) const; + +// -- access -- + + //! Return the duration (in seconds) spanned by the Breakpoints in + //! this Partial. Note that the synthesized onset time will differ, + //! depending on the fade time used to synthesize this Partial (see + //! class Synthesizer). + double duration( void ) const; + + //! Return the time (in seconds) of the last Breakpoint in this + //! Partial. Note that the synthesized onset time will differ, + //! depending on the fade time used to synthesize this Partial (see + //! class Synthesizer). + double endTime( void ) const; + + //! Return a reference to the first Breakpoint in the Partial's + //! envelope. + //! + //! \throw InvalidPartial if there are no Breakpoints. + Breakpoint & first( void ); + + //! Return a const reference to the first Breakpoint in the Partial's + //! envelope. + //! + //! \throw InvalidPartial if there are no Breakpoints. + const Breakpoint & first( void ) const; + + //! Return the phase (in radians) of this Partial at its start time + //! (the phase of the first Breakpoint). Note that the initial + //! synthesized phase will differ, depending on the fade time used + //! to synthesize this Partial (see class Synthesizer). + double initialPhase( void ) const; + + //! Return the 32-bit label for this Partial as an integer. + label_type label( void ) const; + + //! Return a reference to the last Breakpoint in the Partial's + //! envelope. + //! + //! \throw InvalidPartial if there are no Breakpoints. + Breakpoint & last( void ); + + //! Return a const reference to the last Breakpoint in the Partial's + //! envelope. + //! + //! \throw InvalidPartial if there are no Breakpoints. + const Breakpoint & last( void ) const; + + //! Same as size(). Return the number of Breakpoints in this Partial. + size_type numBreakpoints( void ) const; + + //! Return the time (in seconds) of the first Breakpoint in this + //! Partial. Note that the synthesized onset time will differ, + //! depending on the fade time used to synthesize this Partial (see + //! class Synthesizer). + double startTime( void ) const; + +// -- mutation -- + + //! Absorb another Partial's energy as noise (bandwidth), + //! by accumulating the other's energy as noise energy + //! in the portion of this Partial's envelope that overlaps + //! (in time) with the other Partial's envelope. + //! + //! \param other is the Partial to absorb. + void absorb( const Partial & other ); + + //! Set the label for this Partial to the specified 32-bit value. + void setLabel( label_type l ); + + //! Remove the Breakpoint at the position of the given + //! iterator, invalidating the iterator. Return a + //! iterator referring to the next valid position, or to + //! the end of the Partial if the last Breakpoint is removed. + //! + //! \param pos is the position of the time-Breakpoint pair + //! to be removed. + //! \return The position (iterator) of the time-Breakpoint + //! pair after the one that was removed. + //! \post The iterator pos is invalid. + iterator erase( iterator pos ); + + //! Return an iterator refering to the position of the + //! Breakpoint in this Partial nearest the specified time. + //! + //! \param time is the time to find. + //! \return The position (iterator) of the time-Breakpoint + //! pair nearest (in time) to the specified time. + iterator findNearest( double time ); + + //! Return a const iterator refering to the position of the + //! Breakpoint in this Partial nearest the specified time. + //! + //! \param time is the time to find. + //! \return The position (iterator) of the time-Breakpoint + //! pair nearest (in time) to the specified time. + const_iterator findNearest( double time ) const; + + //! Break this Partial at the specified position (iterator). + //! The Breakpoint at the specified position becomes the first + //! Breakpoint in a new Partial. Breakpoints at the specified + //! position and subsequent positions are removed from this + //! Partial and added to the new Partial, which is returned. + //! + //! \param pos is the position at which to split this Partial. + //! \return A new Partial consisting of time-Breakpoint pairs + //! beginning with pos and extending to the end of this + //! Partial. + //! \post All positions beginning with pos and extending to + //! the end of this Partial have been removed. + Partial split( iterator pos ); + +// -- parameter interpolation/extrapolation -- + + //! Define the default fade time for computing amplitude at the ends + //! of a Partial. Floating point round-off errors make fadeTime == 0.0 + //! dangerous and unpredictable. 1 ns is short enough to prevent rounding + //! errors in the least significant bit of a 48-bit mantissa for times + //! up to ten hours. + //! + //! 1 nanosecond, see Partial.C + static const double ShortestSafeFadeTime; + + //! Return the interpolated amplitude of this Partial at the + //! specified time. If non-zero fadeTime is specified, + //! then the amplitude at the ends of the Partial is computed using + //! a linear fade. The default fadeTime is ShortestSafeFadeTime, + //! see the definition of ShortestSafeFadeTime, above. + //! + //! \param time is the time in seconds at which to evaluate the + //! Partial. + //! \param fadeTime is the duration in seconds over which Partial + //! amplitudes fade at the ends. The default value is + //! ShortestSafeFadeTime, 1 ns. + //! \return The amplitude of this Partial at the specified time. + //! \pre The Partial must have at least one Breakpoint. + //! \throw InvalidPartial if the Partial has no Breakpoints. + double amplitudeAt( double time, double fadeTime = ShortestSafeFadeTime ) const; + + //! Return the interpolated bandwidth (noisiness) coefficient of + //! this Partial at the specified time. At times beyond the ends of + //! the Partial, return the bandwidth coefficient at the nearest + //! envelope endpoint. + //! + //! \param time is the time in seconds at which to evaluate the + //! Partial. + //! \return The bandwidth of this Partial at the specified time. + //! \pre The Partial must have at least one Breakpoint. + //! \throw InvalidPartial if the Partial has no Breakpoints. + double bandwidthAt( double time ) const; + + //! Return the interpolated frequency (in Hz) of this Partial at the + //! specified time. At times beyond the ends of the Partial, return + //! the frequency at the nearest envelope endpoint. + //! + //! \param time is the time in seconds at which to evaluate the + //! Partial. + //! \return The frequency of this Partial at the specified time. + //! \pre The Partial must have at least one Breakpoint. + //! \throw InvalidPartial if the Partial has no Breakpoints. + double frequencyAt( double time ) const; + + //! Return the interpolated phase (in radians) of this Partial at + //! the specified time. At times beyond the ends of the Partial, + //! return the extrapolated from the nearest envelope endpoint + //! (assuming constant frequency, as reported by frequencyAt()). + //! + //! \param time is the time in seconds at which to evaluate the + //! Partial. + //! \return The phase of this Partial at the specified time. + //! \pre The Partial must have at least one Breakpoint. + //! \throw InvalidPartial if the Partial has no Breakpoints. + double phaseAt( double time ) const; + + //! Return the interpolated parameters of this Partial at + //! the specified time, same as building a Breakpoint from + //! the results of frequencyAt, ampitudeAt, bandwidthAt, and + //! phaseAt, but performs only one Breakpoint envelope search. + //! If non-zero fadeTime is specified, then the + //! amplitude at the ends of the Partial is coomputed using a + //! linear fade. The default fadeTime is ShortestSafeFadeTime. + //! + //! \param time is the time in seconds at which to evaluate the + //! Partial. + //! \param fadeTime is the duration in seconds over which Partial + //! amplitudes fade at the ends. The default value is + //! ShortestSafeFadeTime, 1 ns. + //! \return A Breakpoint describing the parameters of this Partial + //! at the specified time. + //! \pre The Partial must have at least one Breakpoint. + //! \throw InvalidPartial if the Partial has no Breakpoints. + Breakpoint parametersAt( double time, double fadeTime = ShortestSafeFadeTime ) const; + +// -- implementation -- +private: + + label_type _label; + container_type _breakpoints; // Breakpoint envelope + +}; // end of class Partial + +// --------------------------------------------------------------------------- +// class Partial_Iterator +// +//! Non-const iterator for the Loris::Partial Breakpoint map. Wraps +//! the non-const iterator for the (time,Breakpoint) pair container +//! Partial::container_type. Partial_Iterator implements a +//! bidirectional iterator interface, and additionally offers time +//! and Breakpoint (reference) access through time() and breakpoint() +//! members. +// +class Partial_Iterator +{ +// -- instance variables -- + + typedef Partial::container_type BaseContainer; + typedef BaseContainer::iterator BaseIterator; + BaseIterator _iter; + +// -- public interface -- +public: +// -- bidirectional iterator interface -- + + //! The iterator category, for copmpatibility with + //! C++ standard library algorithms + typedef BaseIterator::iterator_category iterator_category; + + //! The type of element that can be accessed through this + //! iterator (Breakpoint). + typedef Breakpoint value_type; + + //! The type representing the distance between two of these + //! iterators. + typedef BaseIterator::difference_type difference_type; + + //! The type of a pointer to the type of element that can + //! be accessed through this iterator (Breakpoint *). + typedef Breakpoint * pointer; + + //! The type of a reference to the type of element that can + //! be accessed through this iterator (Breakpoint &). + typedef Breakpoint & reference; + +// construction: + + //! Construct a new iterator referring to no position in + //! any Partial. + Partial_Iterator( void ) {} + + // (allow compiler to generate copy, assignment, and destruction) + +// pre-increment/decrement: + + //! Pre-increment operator - advance the position of the iterator + //! and return the iterator itself. + //! + //! \return This iterator (reference to self). + //! \pre The iterator must be a valid position before the end + //! in some Partial. + Partial_Iterator& operator ++ () { ++_iter; return *this; } + + //! Pre-decrement operator - move the position of the iterator + //! back by one and return the iterator itself. + //! + //! \return This iterator (reference to self). + //! \pre The iterator must be a valid position after the beginning + //! in some Partial. + Partial_Iterator& operator -- () { --_iter; return *this; } + +// post-increment/decrement: + + //! Post-increment operator - advance the position of the iterator + //! and return a copy of the iterator before it was advanced. + //! The int argument is unused compiler magic. + //! + //! \return An iterator that is a copy of this iterator before + //! being advanced. + //! \pre The iterator must be a valid position before the end + //! in some Partial. + Partial_Iterator operator ++ ( int ) { return Partial_Iterator( _iter++ ); } + + //! Post-decrement operator - move the position of the iterator + //! back by one and return a copy of the iterator before it was + //! decremented. The int argument is unused compiler magic. + //! + //! \return An iterator that is a copy of this iterator before + //! being decremented. + //! \pre The iterator must be a valid position after the beginning + //! in some Partial. + Partial_Iterator operator -- ( int ) { return Partial_Iterator( _iter-- ); } + +// dereference (for treating Partial like a +// STL collection of Breakpoints): + + //! Dereference operator. + //! + //! \return A reference to the Breakpoint at the position of this + //! iterator. + Breakpoint & operator * ( void ) const { return breakpoint(); } + + + //! Dereference operator. + //! + //! \return A reference to the Breakpoint at the position of this + //! iterator. + //Breakpoint & operator * ( void ) { return breakpoint(); } + + //! Pointer operator. + //! + //! \return A pointer to the Breakpoint at the position of this + //! iterator. + Breakpoint * operator -> ( void ) const { return & breakpoint(); } + + //! Pointer operator. + //! + //! \return A pointer to the Breakpoint at the position of this + //! iterator. + //Breakpoint * operator -> ( void ) { return & breakpoint(); } + +// comparison: + + //! Equality comparison operator. + //! + //! \param lhs the iterator on the left side of the operator. + //! \param rhs the iterator on the right side of the operator. + //! \return true if the two iterators refer to the same position + //! in the same Partial, false otherwise. + friend bool operator == ( const Partial_Iterator & lhs, + const Partial_Iterator & rhs ) + { return lhs._iter == rhs._iter; } + + //! Inequality comparison operator. + //! + //! \param lhs the iterator on the left side of the operator. + //! \param rhs the iterator on the right side of the operator. + //! \return false if the two iterators refer to the same position + //! in the same Partial, true otherwise. + friend bool operator != ( const Partial_Iterator & lhs, + const Partial_Iterator & rhs ) + { return lhs._iter != rhs._iter; } + +// -- time and Breakpoint access -- + + //! Breakpoint accessor. + //! + //! \return A const reference to the Breakpoint at the position of this + //! iterator. + Breakpoint & breakpoint( void ) const + { return _iter->second; } + + //! Breakpoint accessor. + //! + //! \return A reference to the Breakpoint at the position of this + //! iterator. + //Breakpoint & breakpoint( void ) + // { return _iter->second; } + + //! Time accessor. + //! + //! \return The time in seconds of the Breakpoint at the position + //! of this iterator. + double time( void ) const + { return _iter->first; } + +// -- BaseIterator conversions -- +private: + // construction by GenericBreakpointContainer from a BaseIterator: + Partial_Iterator( const BaseIterator & it ) : + _iter(it) {} + + friend class Partial; + + // befriend Partial_ConstIterator, + // for const construction from non-const: + friend class Partial_ConstIterator; + +}; // end of class Partial_Iterator + +// --------------------------------------------------------------------------- +// class Partial_ConstIterator +// +//! Const iterator for the Loris::Partial Breakpoint map. Wraps +//! the non-const iterator for the (time,Breakpoint) pair container +//! Partial::container_type. Partial_Iterator implements a +//! bidirectional iterator interface, and additionally offers time +//! and Breakpoint (reference) access through time() and breakpoint() +//! members. +// +class Partial_ConstIterator +{ +// -- instance variables -- + typedef Partial::container_type BaseContainer; + typedef BaseContainer::const_iterator BaseIterator; + BaseIterator _iter; + +// -- public interface -- +public: +// -- bidirectional iterator interface -- + + //! The iterator category, for copmpatibility with + //! C++ standard library algorithms + typedef BaseIterator::iterator_category iterator_category; + + //! The type of element that can be accessed through this + //! iterator (Breakpoint). + typedef Breakpoint value_type; + + //! The type representing the distance between two of these + //! iterators. + typedef BaseIterator::difference_type difference_type; + + //! The type of a pointer to the type of element that can + //! be accessed through this iterator (const Breakpoint *). + typedef const Breakpoint * pointer; + + //! The type of a reference to the type of element that can + //! be accessed through this iterator (const Breakpoint &). + typedef const Breakpoint & reference; + +// construction: + + //! Construct a new iterator referring to no position in + //! any Partial. + Partial_ConstIterator( void ) {} + + //! Construct a new const iterator from a non-const iterator. + //! + //! \param other a non-const iterator from which to make + //! a read-only copy. + Partial_ConstIterator( const Partial_Iterator & other ) : + _iter( other._iter ) {} + + // (allow compiler to generate copy, assignment, and destruction): + +// pre-increment/decrement: + + //! Pre-increment operator - advance the position of the iterator + //! and return the iterator itself. + //! + //! \return This iterator (reference to self). + //! \pre The iterator must be a valid position before the end + //! in some Partial. + Partial_ConstIterator& operator ++ () { ++_iter; return *this; } + + //! Pre-decrement operator - move the position of the iterator + //! back by one and return the iterator itself. + //! + //! \return This iterator (reference to self). + //! \pre The iterator must be a valid position after the beginning + //! in some Partial. + Partial_ConstIterator& operator -- () { --_iter; return *this; } + +// post-increment/decrement: + + //! Post-increment operator - advance the position of the iterator + //! and return a copy of the iterator before it was advanced. + //! The int argument is unused compiler magic. + //! + //! \return An iterator that is a copy of this iterator before + //! being advanced. + //! \pre The iterator must be a valid position before the end + //! in some Partial. + Partial_ConstIterator operator ++ ( int ) { return Partial_ConstIterator( _iter++ ); } + + //! Post-decrement operator - move the position of the iterator + //! back by one and return a copy of the iterator before it was + //! decremented. The int argument is unused compiler magic. + //! + //! \return An iterator that is a copy of this iterator before + //! being decremented. + //! \pre The iterator must be a valid position after the beginning + //! in some Partial. + Partial_ConstIterator operator -- ( int ) { return Partial_ConstIterator( _iter-- ); } + +// dereference (for treating Partial like a +// STL collection of Breakpoints): + + //! Dereference operator. + //! + //! \return A const reference to the Breakpoint at the position of this + //! iterator. + const Breakpoint & operator * ( void ) const { return breakpoint(); } + + //! Pointer operator. + //! + //! \return A const pointer to the Breakpoint at the position of this + //! iterator. + const Breakpoint * operator -> ( void ) const { return & breakpoint(); } + +// comparison: + + //! Equality comparison operator. + //! + //! \param lhs the iterator on the left side of the operator. + //! \param rhs the iterator on the right side of the operator. + //! \return true if the two iterators refer to the same position + //! in the same Partial, false otherwise. + friend bool operator == ( const Partial_ConstIterator & lhs, + const Partial_ConstIterator & rhs ) + { return lhs._iter == rhs._iter; } + + //! Inequality comparison operator. + //! + //! \param lhs the iterator on the left side of the operator. + //! \param rhs the iterator on the right side of the operator. + //! \return false if the two iterators refer to the same position + //! in the same Partial, true otherwise. + friend bool operator != ( const Partial_ConstIterator & lhs, + const Partial_ConstIterator & rhs ) + { return lhs._iter != rhs._iter; } + +// -- time and Breakpoint access -- + + //! Breakpoint accessor. + //! + //! \return A const reference to the Breakpoint at the position of this + //! iterator. + const Breakpoint & breakpoint( void ) const + { return _iter->second; } + + //! Time accessor. + //! + //! \return The time in seconds of the Breakpoint at the position + //! of this iterator. + double time( void ) const + { return _iter->first; } + +// -- BaseIterator conversions -- +private: + // construction by GenericBreakpointContainer from a BaseIterator: + Partial_ConstIterator( BaseIterator it ) : + _iter(it) {} + + friend class Partial; + +}; // end of class Partial_ConstIterator + +// --------------------------------------------------------------------------- +// class InvalidPartial +// +//! Class of exceptions thrown when a Partial is found to be badly configured +//! or otherwise invalid. +// +class InvalidPartial : public InvalidObject +{ +public: + + //! Construct a new instance with the specified description and, optionally + //! a string identifying the location at which the exception as thrown. The + //! Throw( Exception_Class, description_string ) macro generates a location + //! string automatically using __FILE__ and __LINE__. + //! + //! \param str is a string describing the exceptional condition + //! \param where is an option string describing the location in + //! the source code from which the exception was thrown + //! (generated automatically byt he Throw macro). + InvalidPartial( const std::string & str, const std::string & where = "" ) : + InvalidObject( std::string("Invalid Partial -- ").append( str ), where ) {} + +}; // end of class InvalidPartial + +} // end of namespace Loris + +#endif /* ndef INCLUDE_PARTIAL_H */ diff --git a/src/loris/PartialBuilder.C b/src/loris/PartialBuilder.C new file mode 100644 index 0000000..f1ce3be --- /dev/null +++ b/src/loris/PartialBuilder.C @@ -0,0 +1,319 @@ +/* + * 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 + * + * + * PartialBuilder.C + * + * Implementation of a class representing a policy for connecting peaks + * extracted from a reassigned time-frequency spectrum to form ridges + * and construct Partials. + * + * This strategy attemps to follow a mFreqWarping frequency envelope when + * forming Partials, by prewarping all peak frequencies according to the + * (inverse of) frequency mFreqWarping envelope. At the end of the analysis, + * Partial frequencies need to be un-warped by calling fixPartialFrequencies(). + * + * The first attempt was the same as the basic partial formation strategy, + * but for purposes of matching, peak frequencies are scaled by the ratio + * of the mFreqWarping envelope's value at the previous frame to its value + * at the current frame. This was not adequate, didn't store enough history + * so it wasn't really following the reference envelope, just using it to + * make a local decision about how frequency should drift from one frame to + * the next. + * + * Kelly Fitz, 28 May 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "PartialBuilder.h" + +#include "BreakpointEnvelope.h" +#include "Envelope.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialPtrs.h" +#include "SpectralPeaks.h" + +#include <algorithm> +#include <cmath> + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// HEY - remove mMaxTimeOffset and the hopTime argument, these are wrong +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// construction +// --------------------------------------------------------------------------- +// Construct a new builder that constrains Partial frequnecy +// drift by the specified drift value in Hz. +// +PartialBuilder::PartialBuilder( double drift ) : + mFreqWarping( new BreakpointEnvelope(1.0) ), + mFreqDrift( drift ) +{ +} + +// --------------------------------------------------------------------------- +// construction +// --------------------------------------------------------------------------- +// Construct a new builder that constrains Partial frequnecy +// drift by the specified drift value in Hz. The frequency +// warping envelope is applied to the spectral peak frequencies +// and the frequency drift parameter in each frame before peaks +// are linked to eligible Partials. All the Partial frequencies +// need to be un-warped at the ned of the building process, by +// calling finishBuilding(). +// +PartialBuilder::PartialBuilder( double drift, const Envelope & env ) : + mFreqWarping( env.clone() ), + mFreqDrift( drift ) +{ +} + +// --- local helpers for Partial building --- + +// --------------------------------------------------------------------------- +// end_frequency +// --------------------------------------------------------------------------- +// Return the frequency of the last Breakpoint in a Partial. +// +static inline double end_frequency( const Partial & partial ) +{ + return partial.last().frequency(); +} + +// --------------------------------------------------------------------------- +// freq_distance +// --------------------------------------------------------------------------- +// Helper function, used in formPartials(). +// Returns the (positive) frequency distance between a Breakpoint +// and the last Breakpoint in a Partial. +// +inline double +PartialBuilder::freq_distance( const Partial & partial, const SpectralPeak & pk ) +{ + double normBpFreq = pk.frequency() / mFreqWarping->valueAt( pk.time() ); + + double normPartialEndFreq = + partial.last().frequency() / mFreqWarping->valueAt( partial.endTime() ); + + return std::fabs( normPartialEndFreq - normBpFreq ); +} + +// --------------------------------------------------------------------------- +// better_match +// --------------------------------------------------------------------------- +// Predicate for choosing the better of two proposed +// Partial-to-Breakpoint matches. Note: sometimes this +// is used to compare two candidate Breakpoint matches +// to the same Partial, other times to candidate Partials +// to the same Breakpoint. +// +// Return true if the first match is better, otherwise +// return false. +// + +bool PartialBuilder::better_match( const Partial & part, const SpectralPeak & pk1, + const SpectralPeak & pk2 ) +{ + Assert( part.numBreakpoints() > 0 ); + + return freq_distance( part, pk1 ) < freq_distance( part, pk2 ); +} + +bool PartialBuilder::better_match( const Partial & part1, + const Partial & part2, const SpectralPeak & pk ) +{ + Assert( part1.numBreakpoints() > 0 ); + Assert( part2.numBreakpoints() > 0 ); + + return freq_distance( part1, pk ) < freq_distance( part2, pk ); +} + +// --- Partial building members --- + +// --------------------------------------------------------------------------- +// buildPartials +// --------------------------------------------------------------------------- +// Append spectral peaks, extracted from a reassigned time-frequency +// spectrum, to eligible Partials, where possible. Peaks that cannot +// be used to extend eliglble Partials spawn new Partials. +// +// This is similar to the basic MQ partial formation strategy, except that +// before matching, all frequencies are normalized by the value of the +// warping envelope at the time of the current frame. This means that +// the frequency envelopes of all the Partials are warped, and need to +// be un-normalized by calling finishBuilding at the end of the building +// process. +// +void +PartialBuilder::buildPartials( Peaks & peaks, double frameTime ) +{ + mNewlyEligible.clear(); + + unsigned int matchCount = 0; // for debugging + + // frequency-sort the spectral peaks: + // (the eligible partials are always sorted by + // increasing frequency if we always sort the + // peaks this way) + std::sort( peaks.begin(), peaks.end(), SpectralPeak::sort_increasing_freq ); + + PartialPtrs::iterator eligible = mEligiblePartials.begin(); + for ( Peaks::iterator bpIter = peaks.begin(); bpIter != peaks.end(); ++bpIter ) + { + //const Breakpoint & bp = bpIter->breakpoint; + const double peakTime = frameTime + bpIter->time(); + + // find the Partial that is nearest in frequency to the Peak: + PartialPtrs::iterator nextEligible = eligible; + if ( eligible != mEligiblePartials.end() && + end_frequency( **eligible ) < bpIter->frequency() ) + { + ++nextEligible; + while ( nextEligible != mEligiblePartials.end() && + end_frequency( **nextEligible ) < bpIter->frequency() ) + { + ++nextEligible; + ++eligible; + } + + if ( nextEligible != mEligiblePartials.end() && + better_match( **nextEligible, **eligible, *bpIter ) ) + { + eligible = nextEligible; + } + } + + // INVARIANT: + // + // eligible is the position of the nearest (in frequency) + // eligible Partial (pointer) or it is mEligiblePartials.end(). + // + // nextEligible is the eligible Partial with frequency + // greater than bp, or it is mEligiblePartials.end(). + +#if defined(Debug_Loris) && Debug_Loris + /* + if ( nextEligible != mEligiblePartials.end() ) + { + debugger << matchFrequency << "( " << end_frequency( **eligible ) + << ", " << end_frequency( **nextEligible ) << ")" << endl; + } + */ +#endif + + // create a new Partial if there is no eligible Partial, + // or the frequency difference to the eligible Partial is + // too great, or the next peak is a better match for the + // eligible Partial, otherwise add this peak to the eligible + // Partial: + Peaks::iterator nextPeak = //Peaks::iterator( bpIter ); ++nextPeak; + ++Peaks::iterator( bpIter ); // some compilers choke on this? + + // decide whether this match should be made: + // - can only make the match if eligible is not the end of the list + // - the match is only good if it is close enough in frequency + // - even if the match is good, only match if the next one is not better + bool makeMatch = false; + if ( eligible != mEligiblePartials.end() ) + { + bool matchIsGood = mFreqDrift > + std::fabs( end_frequency( **eligible ) - bpIter->frequency() ); + if ( matchIsGood ) + { + bool nextIsBetter = ( nextPeak != peaks.end() && + better_match( **eligible, *nextPeak, *bpIter ) ); + if ( ! nextIsBetter ) + { + makeMatch = true; + } + } + } + + Breakpoint bp = bpIter->createBreakpoint(); + + if ( makeMatch ) + { + // invariant: + // if makeMatch is true, then eligible is the position of a valid Partial + (*eligible)->insert( peakTime, bp ); + mNewlyEligible.push_back( *eligible ); + + ++matchCount; + } + else + { + Partial p; + p.insert( peakTime, bp ); + mCollectedPartials.push_back( p ); + mNewlyEligible.push_back( & mCollectedPartials.back() ); + } + + // update eligible, nextEligible is the eligible Partial + // with frequency greater than bp, or it is mEligiblePartials.end(): + eligible = nextEligible; + } + + mEligiblePartials = mNewlyEligible; + + /* + debugger << "PartialBuilder::buildPartials: matched " << matchCount << endl; + debugger << "PartialBuilder::buildPartials: " << mNewlyEligible.size() << " newly eligible partials" << endl; + */ +} + +// --------------------------------------------------------------------------- +// finishBuilding +// --------------------------------------------------------------------------- +// Un-do the frequency warping performed in buildPartials, and return +// the Partials that were built. After calling finishBuilding, the +// builder is returned to its initial state, and ready to build another +// set of Partials. Partials are returned by appending them to the +// supplied PartialList. +// +void +PartialBuilder::finishBuilding( PartialList & product ) +{ + // append the collected Partials to the product list: + product.splice( product.end(), mCollectedPartials ); + + // reset the builder state: + mEligiblePartials.clear(); + mNewlyEligible.clear(); +} + + + +} // end of namespace Loris diff --git a/src/loris/PartialBuilder.h b/src/loris/PartialBuilder.h new file mode 100644 index 0000000..2592ffc --- /dev/null +++ b/src/loris/PartialBuilder.h @@ -0,0 +1,143 @@ +#ifndef INCLUDE_PARTIALBUILDER_H +#define INCLUDE_PARTIALBUILDER_H +/* + * 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 + * + * + * PartialBuilder.h + * + * Implementation of a class representing a policy for connecting peaks + * extracted from a reassigned time-frequency spectrum to form ridges + * and construct Partials. + * + * This strategy attemps to follow a reference frequency envelope when + * forming Partials, by prewarping all peak frequencies according to the + * (inverse of) frequency reference envelope. At the end of the analysis, + * Partial frequencies need to be un-warped by calling fixPartialFrequencies(). + * + * Kelly Fitz, 28 May 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Partial.h" +#include "PartialList.h" +#include "PartialPtrs.h" +#include "SpectralPeaks.h" + +#include <memory> + +// begin namespace +namespace Loris { + +class Envelope; + +// --------------------------------------------------------------------------- +// class PartialBuilder +// +// A class representing the process of connecting peaks (ridges) on a +// reassigned time-frequency surface to form Partials. +// +class PartialBuilder +{ +// --- public interface --- + +public: + + // constructor + // + // Construct a new builder that constrains Partial frequnecy + // drift by the specified drift value in Hz. + PartialBuilder( double drift ); + + // constructor + // + // Construct a new builder that constrains Partial frequnecy + // drift by the specified drift value in Hz. The frequency + // warping envelope is applied to the spectral peak frequencies + // and the frequency drift parameter in each frame before peaks + // are linked to eligible Partials. All the Partial frequencies + // need to be un-warped at the ned of the building process, by + // calling finishBuilding(). + PartialBuilder( double drift, const Envelope & freqWarpEnv ); + + // buildPartials + // + // Append spectral peaks, extracted from a reassigned time-frequency + // spectrum, to eligible Partials, where possible. Peaks that cannot + // be used to extend eliglble Partials spawn new Partials. + // + // This is similar to the basic MQ partial formation strategy, except that + // before matching, all frequencies are normalized by the value of the + // warping envelope at the time of the current frame. This means that + // the frequency envelopes of all the Partials are warped, and need to + // be un-normalized by calling finishBuilding at the end of the building + // process. + void buildPartials( Peaks & peaks, double frameTime ); + + // finishBuilding + // + // Un-do the frequency warping performed in buildPartials, and return + // the Partials that were built. After calling finishBuilding, the + // builder is returned to its initial state, and ready to build another + // set of Partials. Partials are returned by appending them to the + // supplied PartialList. + void finishBuilding( PartialList & product ); + +private: + +// --- auxiliary member functions --- + + double freq_distance( const Partial & partial, const SpectralPeak & pk ); + + bool better_match( const Partial & part, const SpectralPeak & pk1, + const SpectralPeak & pk2 ); + bool better_match( const Partial & part1, + const Partial & part2, const SpectralPeak & pk ); + + +// --- collected partials --- + + PartialList mCollectedPartials; // collect partials here + +// --- builder state variables --- + + PartialPtrs mEligiblePartials; + PartialPtrs mNewlyEligible; // keep track of eligible partials here + +// --- parameters --- + + std::auto_ptr< Envelope > mFreqWarping; // reference envelope + + double mFreqDrift; + +// --- disallow copy and assignment --- + + PartialBuilder( const PartialBuilder & ); + PartialBuilder& operator=( const PartialBuilder & ); + +}; // end of class PartialBuilder + +} // end of namespace Loris + +#endif /* ndef INCLUDE_PARTIALBUILDER_H */ diff --git a/src/loris/PartialList.h b/src/loris/PartialList.h new file mode 100644 index 0000000..ca91509 --- /dev/null +++ b/src/loris/PartialList.h @@ -0,0 +1,61 @@ +#ifndef INCLUDE_PARTIALLIST_H +#define INCLUDE_PARTIALLIST_H +/* + * 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 + * + * + * PartialList.h + * + * Type definition of Loris::PartialList, which is just a name + * for std::list< Loris::Partial >. + * + * Kelly Fitz, 6 March 2002 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +// Seems like we shouldn't need to include Partial.h, but +// without it, I can't instantiate a PartialList. I need +// a definition of Partial for PartialList to be unambiguous. +#include "Partial.h" +#include <list> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class PartialList +// +// PartialList is a typedef for a std::list<> of Loris Partials. The +// oscciated bidirectional iterators are also defined as +// PartialListIterator and PartialListConstIterator. Since these are +// simply typedefs, they classes have identical interfaces to std::list, +// std::list::iterator, and std::list::const_iterator, respectively. +// +typedef std::list< Loris::Partial > PartialList; +typedef std::list< Loris::Partial >::iterator PartialListIterator; +typedef std::list< Loris::Partial >::const_iterator PartialListConstIterator; + +} // end of namespace Loris + +#endif /* ndef INCLUDE_PARTIALLIST_H */ diff --git a/src/loris/PartialPtrs.h b/src/loris/PartialPtrs.h new file mode 100644 index 0000000..a773aec --- /dev/null +++ b/src/loris/PartialPtrs.h @@ -0,0 +1,106 @@ +#ifndef INCLUDE_PARTIALPTRS_H +#define INCLUDE_PARTIALPTRS_H +/* + * 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 + * + * + * PartialPtrs.h + * + * Type definition of Loris::PartialPtrs. + * + * PartialPtrs is a collection of pointers to Partials that + * can be used (among other things) for algorithms that operate + * on a range of Partials, but don't rely on access to their + * container. + * + * Kelly Fitz, 23 May 2002 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Partial.h" +#include <iterator> +#include <vector> + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// class PartialPtrs +// +// PartialPtrs is a typedef for a std::vectror<> of pointers to Loris +// Partials. The oscciated bidirectional iterators are also defined as +// PartialPtrsIterator and PartialPtrsConstIterator. Since these are +// simply typedefs, they classes have identical interfaces to +// std::vector, std::vector::iterator, and std::vector::const_iterator, +// respectively. +// +// PartialPtrs is a collection of pointers to Partials that can be used +// (among other things) for algorithms that operate on a range of +// Partials, but don't rely on access to their container. A template +// function defined in a header file can convert a range of Partials to a +// PartialPtrs using the template free function fillPartialPtrs() (see +// below), and pass the latter to the algorithm implementation, thereby +// generalizing access to the algorithm across containers without +// exposing the implementation in the header file. +// +typedef std::vector< Partial * > PartialPtrs; +typedef std::vector< Partial * >::iterator PartialPtrsIterator; +typedef std::vector< Partial * >::const_iterator PartialPtrsConstIterator; + +typedef std::vector< const Partial * > ConstPartialPtrs; +typedef std::vector< const Partial * >::iterator ConstPartialPtrsIterator; +typedef std::vector< const Partial * >::const_iterator ConstPartialPtrsConstIterator; + + +// --------------------------------------------------------------------------- +// fillPartialPtrs +// --------------------------------------------------------------------------- +// Fill the specified PartialPtrs with pointers to the Partials n the +// specified half-open (STL-style) range. This is a generally useful +// operation that can be used to adapt algorithms to work with arbitrary +// containers of Partials without exposing the algorithms themselves in +// the header files. +// +template <typename Iter> +void fillPartialPtrs( Iter begin, Iter end, PartialPtrs & fillme ) +{ + fillme.reserve( std::distance( begin, end ) ); + fillme.clear(); + while ( begin != end ) + fillme.push_back( &(*begin++) ); +} + +template <typename Iter> +void fillPartialPtrs( Iter begin, Iter end, ConstPartialPtrs & fillme ) +{ + fillme.reserve( std::distance( begin, end ) ); + fillme.clear(); + while ( begin != end ) + fillme.push_back( &(*begin++) ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_PARTIALPTRS_H */ diff --git a/src/loris/PartialUtils.C b/src/loris/PartialUtils.C new file mode 100644 index 0000000..6152e09 --- /dev/null +++ b/src/loris/PartialUtils.C @@ -0,0 +1,603 @@ +/* + * 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 + * + * + * PartialUtils.C + * + * A group of Partial utility function objects for use with STL + * searching and sorting algorithms. PartialUtils is a namespace + * within the Loris namespace. + * + * Kelly Fitz, 17 June 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "PartialUtils.h" + +#include "Breakpoint.h" +#include "BreakpointEnvelope.h" +#include "BreakpointUtils.h" +#include "Envelope.h" +#include "Partial.h" + +#include "phasefix.h" + +#include <algorithm> +#include <cmath> +#include <functional> +#include <utility> + +// begin namespace +namespace Loris { + +namespace PartialUtils { + + +// -- base class -- + +// --------------------------------------------------------------------------- +// PartialMutator constructor from double +// --------------------------------------------------------------------------- +PartialMutator::PartialMutator( double x ) : + env( new BreakpointEnvelope( x ) ) +{ +} + +// --------------------------------------------------------------------------- +// PartialMutator constructor from envelope +// --------------------------------------------------------------------------- +PartialMutator::PartialMutator( const Envelope & e ) : + env( e.clone() ) +{ +} + +// --------------------------------------------------------------------------- +// PartialMutator copy constructor +// --------------------------------------------------------------------------- +PartialMutator::PartialMutator( const PartialMutator & rhs ) : + env( rhs.env->clone() ) +{ +} + +// --------------------------------------------------------------------------- +// PartialMutator destructor +// --------------------------------------------------------------------------- +PartialMutator::~PartialMutator( void ) +{ + delete env; +} + +// --------------------------------------------------------------------------- +// PartialMutator assignment operator +// --------------------------------------------------------------------------- +PartialMutator & +PartialMutator::operator=( const PartialMutator & rhs ) +{ + if ( this != &rhs ) + { + delete env; + env = rhs.env->clone(); + } + return *this; +} + +// -- amplitude scaling -- + +// --------------------------------------------------------------------------- +// AmplitudeScaler function call operator +// --------------------------------------------------------------------------- +// Scale the amplitude of the specified Partial according to +// an envelope representing a time-varying amplitude scale value. +// +void +AmplitudeScaler::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + pos.breakpoint().setAmplitude( pos.breakpoint().amplitude() * + env->valueAt( pos.time() ) ); + } +} + +// --------------------------------------------------------------------------- +// BandwidthScaler function call operator +// --------------------------------------------------------------------------- +// Scale the bandwidth of the specified Partial according to +// an envelope representing a time-varying bandwidth scale value. +// +void +BandwidthScaler::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + pos.breakpoint().setBandwidth( pos.breakpoint().bandwidth() * + env->valueAt( pos.time() ) ); + } +} + +// --------------------------------------------------------------------------- +// BandwidthSetter function call operator +// --------------------------------------------------------------------------- +// Set the bandwidth of the specified Partial according to +// an envelope representing a time-varying bandwidth value. +// +void +BandwidthSetter::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + pos.breakpoint().setBandwidth( env->valueAt( pos.time() ) ); + } +} + +// --------------------------------------------------------------------------- +// FrequencyScaler function call operator +// --------------------------------------------------------------------------- +// Scale the frequency of the specified Partial according to +// an envelope representing a time-varying frequency scale value. +// +void +FrequencyScaler::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + pos.breakpoint().setFrequency( pos.breakpoint().frequency() * + env->valueAt( pos.time() ) ); + } +} + +// --------------------------------------------------------------------------- +// NoiseRatioScaler function call operator +// --------------------------------------------------------------------------- +// Scale the relative noise content of the specified Partial according +// to an envelope representing a (time-varying) noise energy +// scale value. +// +void +NoiseRatioScaler::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + // compute new bandwidth value: + double bw = pos.breakpoint().bandwidth(); + if ( bw < 1. ) + { + double ratio = bw / (1. - bw); + ratio *= env->valueAt( pos.time() ); + bw = ratio / ( 1. + ratio ); + } + else + { + bw = 1.; + } + pos.breakpoint().setBandwidth( bw ); + } +} + +// --------------------------------------------------------------------------- +// PitchShifter function call operator +// --------------------------------------------------------------------------- +// Shift the pitch of the specified Partial according to +// the given pitch envelope. The pitch envelope is assumed to have +// units of cents (1/100 of a halfstep). +// +void +PitchShifter::operator()( Partial & p ) const +{ + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + // compute frequency scale: + double scale = + std::pow( 2., ( 0.01 * env->valueAt( pos.time() ) ) / 12. ); + pos.breakpoint().setFrequency( pos.breakpoint().frequency() * scale ); + } +} + +// --------------------------------------------------------------------------- +// Cropper function call operator +// --------------------------------------------------------------------------- +// Trim a Partial by removing Breakpoints outside a specified time span. +// Insert a Breakpoint at the boundary when cropping occurs. +// +void +Cropper::operator()( Partial & p ) const +{ + // crop beginning of Partial + Partial::iterator it = p.findAfter( minTime ); + if ( it != p.begin() ) // Partial begins earlier than minTime + { + if ( it != p.end() ) // Partial ends later than minTime + { + Breakpoint bp = p.parametersAt( minTime ); + it = p.insert( minTime, bp ); + } + it = p.erase( p.begin(), it ); + } + + // crop end of Partial + it = p.findAfter( maxTime ); + if ( it != p.end() ) // Partial ends later than maxTime + { + if ( it != p.begin() ) // Partial begins earlier than maxTime + { + Breakpoint bp = p.parametersAt( maxTime ); + it = p.insert( maxTime, bp ); + ++it; // advance, we don't want to cut this one off + } + it = p.erase( it, p.end() ); + } +} + +// --------------------------------------------------------------------------- +// TimeShifter function call operator +// --------------------------------------------------------------------------- +// Shift the time of all the Breakpoints in a Partial by a constant amount. +// +void +TimeShifter::operator()( Partial & p ) const +{ + // Since the Breakpoint times are immutable, the only way to + // shift the Partial in time is to construct a new Partial and + // assign it to the argument p. + Partial result; + result.setLabel( p.label() ); + + for ( Partial::iterator pos = p.begin(); pos != p.end(); ++pos ) + { + result.insert( pos.time() + offset, pos.breakpoint() ); + } + p = result; +} + +// --------------------------------------------------------------------------- +// peakAmplitude +// --------------------------------------------------------------------------- +//! Return the maximum amplitude achieved by a partial. +//! +//! \param p is the Partial to evaluate +//! \return the maximum (absolute) amplitude achieved by +//! the partial p +// +double peakAmplitude( const Partial & p ) +{ + double peak = 0; + for ( Partial::const_iterator it = p.begin(); + it != p.end(); + ++it ) + { + peak = std::max( peak, it->amplitude() ); + } + return peak; +} + +// --------------------------------------------------------------------------- +// avgAmplitude +// --------------------------------------------------------------------------- +//! Return the average amplitude over all Breakpoints in this Partial. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average amplitude of Breakpoints in the Partial p +// +double avgAmplitude( const Partial & p ) +{ + double avg = 0; + for ( Partial::const_iterator it = p.begin(); + it != p.end(); + ++it ) + { + avg += it->amplitude(); + } + + if ( avg != 0 ) + { + avg /= p.numBreakpoints(); + } + + return avg; +} + + +// --------------------------------------------------------------------------- +// avgFrequency +// --------------------------------------------------------------------------- +//! Return the average frequency over all Breakpoints in this Partial. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average frequency (Hz) of Breakpoints in the Partial p +// +double avgFrequency( const Partial & p ) +{ + double avg = 0; + for ( Partial::const_iterator it = p.begin(); + it != p.end(); + ++it ) + { + avg += it->frequency(); + } + + if ( avg != 0 ) + { + avg /= p.numBreakpoints(); + } + + return avg; +} + + +// --------------------------------------------------------------------------- +// weightedAvgFrequency +// --------------------------------------------------------------------------- +//! Return the average frequency over all Breakpoints in this Partial, +//! weighted by the Breakpoint amplitudes. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average frequency (Hz) of Breakpoints in the Partial p +// +double weightedAvgFrequency( const Partial & p ) +{ + double avg = 0; + double ampsum = 0; + for ( Partial::const_iterator it = p.begin(); + it != p.end(); + ++it ) + { + avg += it->amplitude() * it->frequency(); + ampsum += it->amplitude(); + } + + if ( avg != 0 && ampsum != 0 ) + { + avg /= ampsum; + } + else + { + avg = 0; + } + + return avg; +} + +// -- phase maintenance functions -- + +// --------------------------------------------------------------------------- +// fixPhaseBefore +// +//! Recompute phases of all Breakpoints earlier than the specified time +//! so that the synthesize phases of those earlier Breakpoints matches +//! the stored phase, and the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time before which phases should be adjusted. +// +void fixPhaseBefore( Partial & p, double t ) +{ + if ( 1 < p.numBreakpoints() ) + { + Partial::iterator pos = p.findNearest( t ); + Assert( pos != p.end() ); + + fixPhaseBackward( p.begin(), pos ); + } +} + +// --------------------------------------------------------------------------- +// fixPhaseAfter +// +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesize phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time after which phases should be adjusted. +// +void fixPhaseAfter( Partial & p, double t ) +{ + // nothing to do it there are not at least + // two Breakpoints in the Partial + if ( 1 < p.numBreakpoints() ) + { + Partial::iterator pos = p.findNearest( t ); + Assert( pos != p.end() ); + + fixPhaseForward( pos, --p.end() ); + } +} + +// --------------------------------------------------------------------------- +// fixPhaseForward +// +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesize phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. Breakpoints later than +//! tend are unmodified. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! HEY Is this interesting, in general? Why would you want to do this? +//! +//! \param p The Partial whose phases should be fixed. +//! \param tbeg The phases and frequencies of Breakpoints later than the +//! one nearest this time will be modified. +//! \param tend The phases and frequencies of Breakpoints earlier than the +//! one nearest this time will be modified. Should be greater +//! than tbeg, or else they will be swapped. +// +void fixPhaseForward( Partial & p, double tbeg, double tend ) +{ + if ( tbeg > tend ) + { + std::swap( tbeg, tend ); + } + + // nothing to do it there are not at least + // two Breakpoints in the Partial + if ( 1 < p.numBreakpoints() ) + { + // find the positions nearest tbeg and tend + Partial::iterator posbeg = p.findNearest( tbeg ); + Partial::iterator posend = p.findNearest( tend ); + + // if the positions are different, and tend is + // the end, back it up + if ( posbeg != posend && posend == p.end() ) + { + --posend; + } + fixPhaseForward( posbeg, posend ); + } +} + +// --------------------------------------------------------------------------- +// fixPhaseAt +// +//! Recompute phases of all Breakpoints in a Partial +//! so that the synthesize phases match the stored phases, +//! and the synthesized phase at (nearest) the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. Forward phase fixing is only applied +//! to non-null (nonzero-amplitude) Breakpoints. If a null is encountered, +//! its phase is simply left unmodified, and future phases wil be +//! recomputed from that one. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time at which phases should be made correct. +// +void fixPhaseAt( Partial & p, double t ) +{ + if ( 1 < p.numBreakpoints() ) + { + Partial::iterator pos = p.findNearest( t ); + Assert( pos != p.end() ); + + fixPhaseForward( pos, --p.end() ); + fixPhaseBackward( p.begin(), pos ); + } +} + +// --------------------------------------------------------------------------- +// fixPhaseBetween +// +//! Fix the phase travel between two times by adjusting the +//! frequency and phase of Breakpoints between those two times. +//! +//! This algorithm assumes that there is nothing interesting about the +//! phases of the intervening Breakpoints, and modifies their frequencies +//! as little as possible to achieve the correct amount of phase travel +//! such that the frequencies and phases at the specified times +//! match the stored values. The phases of all the Breakpoints between +//! the specified times are recomputed. +//! +//! THIS DOES NOT YET TREAT NULL BREAKPOINTS DIFFERENTLY FROM OTHERS. +//! +//! \pre There must be at least one Breakpoint in the +//! Partial between the specified times tbeg and tend. +//! \post The phases and frequencies of the Breakpoints in the +//! range have been recomputed such that an oscillator +//! initialized to the parameters of the first Breakpoint +//! will arrive at the parameters of the last Breakpoint, +//! and all the intervening Breakpoints will be matched. +//! \param p The partial whose phases and frequencies will be recomputed. +//! The Breakpoint at this position is unaltered. +//! \param tbeg The phases and frequencies of Breakpoints later than the +//! one nearest this time will be modified. +//! \param tend The phases and frequencies of Breakpoints earlier than the +//! one nearest this time will be modified. Should be greater +//! than tbeg, or else they will be swapped. +// +void fixPhaseBetween( Partial & p, double tbeg, double tend ) +{ + if ( tbeg > tend ) + { + std::swap( tbeg, tend ); + } + + // for Partials that do not extend over the entire + // specified time range, just recompute phases from + // beginning or end of the range: + if ( p.endTime() < tend ) + { + // OK if start time is also after tbeg, will + // just recompute phases from start of p. + fixPhaseAfter( p, tbeg ); + } + else if ( p.startTime() > tbeg ) + { + fixPhaseBefore( p, tend ); + } + else + { + // invariant: + // p begins before tbeg and ends after tend. + Partial::iterator b = p.findNearest( tbeg ); + Partial::iterator e = p.findNearest( tend ); + + // if there is a null Breakpoint n between b and e, then + // should fix forward from b to n, and backward from + // e to n. Otherwise, do this complicated thing. + Partial::iterator nullbp = std::find_if( b, e, BreakpointUtils::isNull ); + if ( nullbp != e ) + { + fixPhaseForward( b, nullbp ); + fixPhaseBackward( nullbp, e ); + } + else + { + fixPhaseBetween( b, e ); + } + } +} + +} // end of namespace PartialUtils + +} // end of namespace Loris + diff --git a/src/loris/PartialUtils.h b/src/loris/PartialUtils.h new file mode 100644 index 0000000..afc6895 --- /dev/null +++ b/src/loris/PartialUtils.h @@ -0,0 +1,1189 @@ +#ifndef INCLUDE_PARTIALUTILS_H +#define INCLUDE_PARTIALUTILS_H +/* + * 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 + * + * + * PartialUtils.h + * + * A group of Partial utility function objects for use with STL + * searching and sorting algorithms. PartialUtils is a namespace + * within the Loris namespace. + * + * This file defines three kinds of functors: + * - Partial mutators + * - predicates on Partials + * - Partial comparitors + * + * Kelly Fitz, 6 July 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Envelope.h" +#include "Partial.h" + +#include <functional> +#include <utility> + +// begin namespace +namespace Loris { + +namespace PartialUtils { + +// -- Partial mutating functors -- + +// --------------------------------------------------------------------------- +// PartialMutator +// +//! PartialMutator is an abstract base class for Partial mutators, +//! functors that operate on Partials according to a time-varying +//! envelope. The base class manages a polymorphic Envelope instance +//! that provides the time-varying mutation parameters. +//! +//! \invariant env is a non-zero pointer to a valid instance of a +//! class derived from the abstract class Envelope. +class PartialMutator : public std::unary_function< Partial, void > +{ +public: + + //! Construct a new PartialMutator from a constant mutation factor. + PartialMutator( double x ); + + //! Construct a new PartialMutator from an Envelope representing + //! a time-varying mutation factor. + PartialMutator( const Envelope & e ); + + //! Construct a new PartialMutator that is a copy of another. + PartialMutator( const PartialMutator & rhs ); + + //! Destroy this PartialMutator, deleting its Envelope. + virtual ~PartialMutator( void ); + + //! Make this PartialMutator a duplicate of another one. + //! + //! \param rhs is the PartialMutator to copy. + PartialMutator & operator=( const PartialMutator & rhs ); + + //! Function call operator: apply a mutation factor to the + //! specified Partial. Derived classes must implement this + //! member. + virtual void operator()( Partial & p ) const = 0; + +protected: + + //! pointer to an envelope that governs the + //! time-varying mutation + Envelope * env; +}; + +// --------------------------------------------------------------------------- +// AmplitudeScaler +// +//! Scale the amplitude of the specified Partial according to +//! an envelope representing a time-varying amplitude scale value. +// +class AmplitudeScaler : public PartialMutator +{ +public: + + //! Construct a new AmplitudeScaler from a constant scale factor. + AmplitudeScaler( double x ) : PartialMutator( x ) {} + + //! Construct a new AmplitudeScaler from an Envelope representing + //! a time-varying scale factor. + AmplitudeScaler( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: apply a scale factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// scaleAmplitude +// --------------------------------------------------------------------------- +//! Scale the amplitude of the specified Partial according to +//! an envelope representing a amplitude scale value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Arg > +void scaleAmplitude( Partial & p, const Arg & arg ) +{ + AmplitudeScaler scaler( arg ); + scaler( p ); +} + +// --------------------------------------------------------------------------- +// scaleAmplitude +// --------------------------------------------------------------------------- +//! Scale the amplitude of a sequence of Partials according to +//! an envelope representing a amplitude scale value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Iter, class Arg > +void scaleAmplitude( Iter b, Iter e, const Arg & arg ) +{ + AmplitudeScaler scaler( arg ); + while ( b != e ) + { + scaler( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// BandwidthScaler +// +//! Scale the bandwidth of the specified Partial according to +//! an envelope representing a time-varying bandwidth scale value. +// +class BandwidthScaler : public PartialMutator +{ +public: + + //! Construct a new BandwidthScaler from a constant scale factor. + BandwidthScaler( double x ) : PartialMutator( x ) {} + + //! Construct a new BandwidthScaler from an Envelope representing + //! a time-varying scale factor. + BandwidthScaler( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: apply a scale factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// scaleBandwidth +// --------------------------------------------------------------------------- +//! Scale the bandwidth of the specified Partial according to +//! an envelope representing a amplitude scale value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Arg > +void scaleBandwidth( Partial & p, const Arg & arg ) +{ + BandwidthScaler scaler( arg ); + scaler( p ); +} + +// --------------------------------------------------------------------------- +// scaleBandwidth +// --------------------------------------------------------------------------- +//! Scale the bandwidth of a sequence of Partials according to +//! an envelope representing a bandwidth scale value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Iter, class Arg > +void scaleBandwidth( Iter b, Iter e, const Arg & arg ) +{ + BandwidthScaler scaler( arg ); + while ( b != e ) + { + scaler( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// BandwidthSetter +// +//! Set the bandwidth of the specified Partial according to +//! an envelope representing a time-varying bandwidth value. +// +class BandwidthSetter : public PartialMutator +{ +public: + + //! Construct a new BandwidthSetter from a constant bw factor. + BandwidthSetter( double x ) : PartialMutator( x ) {} + + //! Construct a new BandwidthSetter from an Envelope representing + //! a time-varying bw factor. + BandwidthSetter( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: assign a bw factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// setBandwidth +// --------------------------------------------------------------------------- +//! Set the bandwidth of the specified Partial according to +//! an envelope representing a amplitude scale value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying bw factor. +// +template< class Arg > +void setBandwidth( Partial & p, const Arg & arg ) +{ + BandwidthSetter setter( arg ); + setter( p ); +} + +// --------------------------------------------------------------------------- +// setBandwidth +// --------------------------------------------------------------------------- +//! Set the bandwidth of a sequence of Partials according to +//! an envelope representing a bandwidth value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Iter, class Arg > +void setBandwidth( Iter b, Iter e, const Arg & arg ) +{ + BandwidthSetter setter( arg ); + while ( b != e ) + { + setter( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// FrequencyScaler +// +//! Scale the frequency of the specified Partial according to +//! an envelope representing a time-varying bandwidth scale value. +// +class FrequencyScaler : public PartialMutator +{ +public: + + //! Construct a new FrequencyScaler from a constant scale factor. + FrequencyScaler( double x ) : PartialMutator( x ) {} + + //! Construct a new FrequencyScaler from an Envelope representing + //! a time-varying scale factor. + FrequencyScaler( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: apply a scale factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// scaleFrequency +// --------------------------------------------------------------------------- +//! Scale the frequency of the specified Partial according to +//! an envelope representing a frequency scale value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Arg > +void scaleFrequency( Partial & p, const Arg & arg ) +{ + FrequencyScaler scaler( arg ); + scaler( p ); +} + +// --------------------------------------------------------------------------- +// scaleFrequency +// --------------------------------------------------------------------------- +//! Scale the frequency of a sequence of Partials according to +//! an envelope representing a frequency scale value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Iter, class Arg > +void scaleFrequency( Iter b, Iter e, const Arg & arg ) +{ + FrequencyScaler scaler( arg ); + while ( b != e ) + { + scaler( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// NoiseRatioScaler +// +//! Scale the relative noise content of the specified Partial according +//! to an envelope representing a time-varying bandwidth scale value. +// +class NoiseRatioScaler : public PartialMutator +{ +public: + + //! Construct a new NoiseRatioScaler from a constant scale factor. + NoiseRatioScaler( double x ) : PartialMutator( x ) {} + + //! Construct a new NoiseRatioScaler from an Envelope representing + //! a time-varying scale factor. + NoiseRatioScaler( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: apply a scale factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// scaleNoiseRatio +// --------------------------------------------------------------------------- +//! Scale the relative noise content of the specified Partial according to +//! an envelope representing a scale value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Arg > +void scaleNoiseRatio( Partial & p, const Arg & arg ) +{ + NoiseRatioScaler scaler( arg ); + scaler( p ); +} + +// --------------------------------------------------------------------------- +// scaleNoiseRatio +// --------------------------------------------------------------------------- +//! Scale the relative noise content of a sequence of Partials according to +//! an envelope representing a scale value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant scale factor or an Envelope +//! describing the time-varying scale factor. +// +template< class Iter, class Arg > +void scaleNoiseRatio( Iter b, Iter e, const Arg & arg ) +{ + NoiseRatioScaler scaler( arg ); + while ( b != e ) + { + scaler( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// PitchShifter +// +//! Shift the pitch of the specified Partial according to +//! the given pitch envelope. The pitch envelope is assumed to have +//! units of cents (1/100 of a halfstep). +// +class PitchShifter : public PartialMutator +{ +public: + + //! Construct a new PitchShifter from a constant scale factor. + PitchShifter( double x ) : PartialMutator( x ) {} + + //! Construct a new PitchShifter from an Envelope representing + //! a time-varying scale factor. + PitchShifter( const Envelope & e ) : PartialMutator( e ) {} + + //! Function call operator: apply a scale factor to the specified + //! Partial. + void operator()( Partial & p ) const; +}; + +// --------------------------------------------------------------------------- +// shiftPitch +// --------------------------------------------------------------------------- +//! Shift the pitch of the specified Partial according to +//! an envelope representing a pitch value or envelope. +//! +//! \param p is a Partial to mutate. +//! \param arg is either a constant pitch factor or an Envelope +//! describing the time-varying pitch factor in cents (1/100 of a +//! halfstep). +// +template< class Arg > +void shiftPitch( Partial & p, const Arg & arg ) +{ + PitchShifter shifter( arg ); + shifter( p ); +} + +// --------------------------------------------------------------------------- +// shiftPitch +// --------------------------------------------------------------------------- +//! Shift the pitch of a sequence of Partials according to +//! an envelope representing a pitch value or envelope. +//! +//! \param b is the beginning of a sequence of Partials to mutate. +//! \param e is the end of a sequence of Partials to mutate. +//! \param arg is either a constant pitch factor or an Envelope +//! describing the time-varying pitch factor in cents (1/100 of a +//! halfstep). +// +template< class Iter, class Arg > +void shiftPitch( Iter b, Iter e, const Arg & arg ) +{ + PitchShifter shifter( arg ); + while ( b != e ) + { + shifter( *b++ ); + } +} + +// These ones are not derived from PartialMutator, because +// they don't use an Envelope and cannot be time-varying. + +// --------------------------------------------------------------------------- +// Cropper +// +//! Trim a Partial by removing Breakpoints outside a specified time span. +//! Insert a Breakpoint at the boundary when cropping occurs. +class Cropper +{ +public: + + //! Construct a new Cropper from a pair of times (in seconds) + //! representing the span of time to which Partials should be + //! cropped. + Cropper( double t1, double t2 ) : + minTime( std::min( t1, t2 ) ), + maxTime( std::max( t1, t2 ) ) + { + } + + //! Function call operator: crop the specified Partial. + //! Trim a Partial by removing Breakpoints outside the span offset + //! [minTime, maxTime]. Insert a Breakpoint at the boundary when + //! cropping occurs. + void operator()( Partial & p ) const; + +private: + double minTime, maxTime; +}; + +// --------------------------------------------------------------------------- +// crop +// --------------------------------------------------------------------------- +//! Trim a Partial by removing Breakpoints outside a specified time span. +//! Insert a Breakpoint at the boundary when cropping occurs. +//! +//! This operation may leave the Partial empty, if it previously had +//! no Breakpoints in the span [t1,t2]. +//! +//! \param p is the Partial to crop. +//! \param t1 is the beginning of the time span to which the Partial +//! should be cropped. +//! \param t2 is the end of the time span to which the Partial +//! should be cropped. +// +inline +void crop( Partial & p, double t1, double t2 ) +{ + Cropper cropper( t1, t2 ); + cropper( p ); +} + +// --------------------------------------------------------------------------- +// crop +// --------------------------------------------------------------------------- +//! Trim a sequence of Partials by removing Breakpoints outside a specified +//! time span. Insert a Breakpoint at the boundary when cropping occurs. +//! +//! This operation may leave some empty Partials, if they previously had +//! no Breakpoints in the span [t1,t2]. +//! +//! \param b is the beginning of a sequence of Partials to crop. +//! \param e is the end of a sequence of Partials to crop. +//! \param t1 is the beginning of the time span to which the Partials +//! should be cropped. +//! \param t2 is the end of the time span to which the Partials +//! should be cropped. +// +template< class Iter > +void crop( Iter b, Iter e, double t1, double t2 ) +{ + Cropper cropper( t1, t2 ); + while ( b != e ) + { + cropper( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// TimeShifter +// +//! Shift the time of all the Breakpoints in a Partial by a +//! constant amount. +// +class TimeShifter +{ +public: + + //! Construct a new TimeShifter from a constant offset in seconds. + TimeShifter( double x ) : offset( x ) {} + + //! Function call operator: apply a time shift to the specified + //! Partial. + void operator()( Partial & p ) const; + +private: + double offset; +}; + +// --------------------------------------------------------------------------- +// shiftTime +// --------------------------------------------------------------------------- +//! Shift the time of all the Breakpoints in a Partial by a +//! constant amount. +//! +//! \param p is a Partial to shift. +//! \param offset is a constant offset in seconds. +// +inline +void shiftTime( Partial & p, double offset ) +{ + TimeShifter shifter( offset ); + shifter( p ); +} + +// --------------------------------------------------------------------------- +// shiftTime +// --------------------------------------------------------------------------- +//! Shift the time of all the Breakpoints in a Partial by a +//! constant amount. +//! +//! \param b is the beginning of a sequence of Partials to shift. +//! \param e is the end of a sequence of Partials to shift. +//! \param offset is a constant offset in seconds. +// +template< class Iter > +void shiftTime( Iter b, Iter e, double offset ) +{ + TimeShifter shifter( offset ); + while ( b != e ) + { + shifter( *b++ ); + } +} + +// --------------------------------------------------------------------------- +// timeSpan +// --------------------------------------------------------------------------- +//! Return the time (in seconds) spanned by a specified half-open +//! range of Partials as a std::pair composed of the earliest +//! Partial start time and latest Partial end time in the range. +// +template < typename Iterator > +std::pair< double, double > +timeSpan( Iterator begin, Iterator end ) +{ + double tmin = 0., tmax = 0.; + if ( begin != end ) + { + Iterator it = begin; + tmin = it->startTime(); + tmax = it->endTime(); + while( it != end ) + { + tmin = std::min( tmin, it->startTime() ); + tmax = std::max( tmax, it->endTime() ); + ++it; + } + } + return std::make_pair( tmin, tmax ); +} + +// --------------------------------------------------------------------------- +// peakAmplitude +// --------------------------------------------------------------------------- +//! Return the maximum amplitude achieved by a Partial. +//! +//! \param p is the Partial to evaluate +//! \return the maximum (absolute) amplitude achieved by +//! the partial p +// +double peakAmplitude( const Partial & p ); + +// --------------------------------------------------------------------------- +// avgAmplitude +// --------------------------------------------------------------------------- +//! Return the average amplitude over all Breakpoints in this Partial. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average amplitude of Breakpoints in the Partial p +// +double avgAmplitude( const Partial & p ); + +// --------------------------------------------------------------------------- +// avgFrequency +// --------------------------------------------------------------------------- +//! Return the average frequency over all Breakpoints in this Partial. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average frequency (Hz) of Breakpoints in the Partial p +// +double avgFrequency( const Partial & p ); + +// --------------------------------------------------------------------------- +// weightedAvgFrequency +// --------------------------------------------------------------------------- +//! Return the average frequency over all Breakpoints in this Partial, +//! weighted by the Breakpoint amplitudes. +//! Return zero if the Partial has no Breakpoints. +//! +//! \param p is the Partial to evaluate +//! \return the average frequency (Hz) of Breakpoints in the Partial p +// +double weightedAvgFrequency( const Partial & p ); + +// -- phase maintenance functions -- + +// --------------------------------------------------------------------------- +// fixPhaseBefore +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints earlier than the specified time +//! so that the synthesized phases of those earlier Breakpoints matches +//! the stored phase, and the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time before which phases should be adjusted. +// +void fixPhaseBefore( Partial & p, double t ); + +// --------------------------------------------------------------------------- +// fixPhaseBefore (range) +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints earlier than the specified time +//! so that the synthesized phases of those earlier Breakpoints matches +//! the stored phase, and the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. +//! +//! \param b The beginning of a range of Partials whose phases +//! should be fixed. +//! \param e The end of a range of Partials whose phases +//! should be fixed. +//! \param t The time before which phases should be adjusted. +// +template < class Iter > +void fixPhaseBefore( Iter b, Iter e, double t ) +{ + while ( b != e ) + { + fixPhaseBefore( *b, t ); + ++b; + } +} + +// --------------------------------------------------------------------------- +// fixPhaseAfter +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesized phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time after which phases should be adjusted. +// +void fixPhaseAfter( Partial & p, double t ); + +// --------------------------------------------------------------------------- +// fixPhaseAfter (range) +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesized phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! \param b The beginning of a range of Partials whose phases +//! should be fixed. +//! \param e The end of a range of Partials whose phases +//! should be fixed. +//! \param t The time after which phases should be adjusted. +// +template < class Iter > +void fixPhaseAfter( Iter b, Iter e, double t ) +{ + while ( b != e ) + { + fixPhaseAfter( *b, t ); + ++b; + } +} + +// --------------------------------------------------------------------------- +// fixPhaseForward +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesize phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. Breakpoints later than +//! tend are unmodified. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! \param p The Partial whose phases should be fixed. +//! \param tbeg The phases and frequencies of Breakpoints later than the +//! one nearest this time will be modified. +//! \param tend The phases and frequencies of Breakpoints earlier than the +//! one nearest this time will be modified. Should be greater +//! than tbeg, or else they will be swapped. +// +void fixPhaseForward( Partial & p, double tbeg, double tend ); + +// --------------------------------------------------------------------------- +// fixPhaseForward (range) +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints later than the specified time +//! so that the synthesize phases of those later Breakpoints matches +//! the stored phase, as long as the synthesized phase at the specified +//! time matches the stored (not recomputed) phase. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is simply left unmodified, +//! and future phases wil be recomputed from that one. +//! +//! \param b The beginning of a range of Partials whose phases +//! should be fixed. +//! \param e The end of a range of Partials whose phases +//! should be fixed. +//! \param tbeg The phases and frequencies of Breakpoints later than the +//! one nearest this time will be modified. +//! \param tend The phases and frequencies of Breakpoints earlier than the +//! one nearest this time will be modified. Should be greater +//! than tbeg, or else they will be swapped. +// +template < class Iter > +void fixPhaseForward( Iter b, Iter e, double tbeg, double tend ) +{ + while ( b != e ) + { + fixPhaseForward( *b, tbeg, tend ); + ++b; + } +} + + +// --------------------------------------------------------------------------- +// fixPhaseAt +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints in a Partial +//! so that the synthesized phases match the stored phases, +//! and the synthesized phase at (nearest) the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. Forward phase fixing is only applied +//! to non-null (nonzero-amplitude) Breakpoints. If a null is encountered, +//! its phase is simply left unmodified, and future phases wil be +//! recomputed from that one. +//! +//! \param p The Partial whose phases should be fixed. +//! \param t The time at which phases should be made correct. +// +void fixPhaseAt( Partial & p, double t ); + +// --------------------------------------------------------------------------- +// fixPhaseAt (range) +// --------------------------------------------------------------------------- +//! Recompute phases of all Breakpoints in a Partial +//! so that the synthesize phases match the stored phases, +//! and the synthesized phase at (nearest) the specified +//! time matches the stored (not recomputed) phase. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the Partial +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the Partial. Forward phase fixing is only applied +//! to non-null (nonzero-amplitude) Breakpoints. If a null is encountered, +//! its phase is simply left unmodified, and future phases wil be +//! recomputed from that one. +//! +//! \param b The beginning of a range of Partials whose phases +//! should be fixed. +//! \param e The end of a range of Partials whose phases +//! should be fixed. +//! \param t The time at which phases should be made correct. +// +template < class Iter > +void fixPhaseAt( Iter b, Iter e, double t ) +{ + while ( b != e ) + { + fixPhaseAt( *b, t ); + ++b; + } +} + +// --------------------------------------------------------------------------- +// fixPhaseBetween +// --------------------------------------------------------------------------- +//! Fix the phase travel between two times by adjusting the +//! frequency and phase of Breakpoints between those two times. +//! +//! This algorithm assumes that there is nothing interesting about the +//! phases of the intervening Breakpoints, and modifies their frequencies +//! as little as possible to achieve the correct amount of phase travel +//! such that the frequencies and phases at the specified times +//! match the stored values. The phases of all the Breakpoints between +//! the specified times are recomputed. +//! +//! THIS DOES NOT YET TREAT NULL BREAKPOINTS DIFFERENTLY FROM OTHERS. +//! +//! \pre Thre must be at least one Breakpoint in the +//! Partial between the specified times t1 and t2. +//! If this condition is not met, the Partial is +//! unmodified. +//! \post The phases and frequencies of the Breakpoints in the +//! range have been recomputed such that an oscillator +//! initialized to the parameters of the first Breakpoint +//! will arrive at the parameters of the last Breakpoint, +//! and all the intervening Breakpoints will be matched. +//! \param p The partial whose phases and frequencies will be recomputed. +//! The Breakpoint at this position is unaltered. +//! \param t1 The time before which Partial frequencies and phases will +//! not be modified. +//! \param t2 The time after which Partial frequencies and phases will +//! not be modified. Should be greater than t1, or else they +//! will be swapped. +// +void fixPhaseBetween( Partial & p, double t1, double t2 ); + +// --------------------------------------------------------------------------- +// fixPhaseBetween (range) +// --------------------------------------------------------------------------- +//! Fix the phase travel between two times by adjusting the +//! frequency and phase of Breakpoints between those two times. +//! +//! This algorithm assumes that there is nothing interesting about the +//! phases of the intervening Breakpoints, and modifies their frequencies +//! as little as possible to achieve the correct amount of phase travel +//! such that the frequencies and phases at the specified times +//! match the stored values. The phases of all the Breakpoints between +//! the specified times are recomputed. +//! +//! THIS DOES NOT YET TREAT NULL BREAKPOINTS DIFFERENTLY FROM OTHERS. +//! +//! \pre Thre must be at least one Breakpoint in each +//! Partial between the specified times t1 and t2. +//! If this condition is not met, the Partial is +//! unmodified. +//! \post The phases and frequencies of the Breakpoints in the +//! range have been recomputed such that an oscillator +//! initialized to the parameters of the first Breakpoint +//! will arrive at the parameters of the last Breakpoint, +//! and all the intervening Breakpoints will be matched. +//! \param b The beginning of a range of Partials whose phases +//! should be fixed. +//! \param e The end of a range of Partials whose phases +//! should be fixed. +//! \param t1 The time before which Partial frequencies and phases will +//! not be modified. +//! \param t2 The time after which Partial frequencies and phases will +//! not be modified. Should be greater than t1, or else they +//! will be swapped. +// +template < class Iter > +void fixPhaseBetween( Iter b, Iter e, double t1, double t2 ) +{ + while ( b != e ) + { + fixPhaseBetween( *b, t1, t2 ); + ++b; + } +} + + + +// -- predicates -- + +// --------------------------------------------------------------------------- +// isDurationLess +// +//! Predicate functor returning true if the duration of its +//! Partial argument is less than the specified duration in +//! seconds, and false otherwise. +// +class isDurationLess : public std::unary_function< const Partial, bool > +{ +public: + //! Initialize a new instance with the specified label. + isDurationLess( double x ) : mDurationSecs(x) {} + + //! Function call operator: evaluate a Partial. + bool operator()( const Partial & p ) const + { return p.duration() < mDurationSecs; } + + //! Function call operator: evaluate a Partial pointer. + bool operator()( const Partial * p ) const + { return p->duration() < mDurationSecs; } + +private: + double mDurationSecs; +}; + +// --------------------------------------------------------------------------- +// isLabelEqual +// +//! Predicate functor returning true if the label of its Partial argument is +//! equal to the specified 32-bit label, and false otherwise. +// +class isLabelEqual : public std::unary_function< const Partial, bool > +{ +public: + //! Initialize a new instance with the specified label. + isLabelEqual( int l ) : label(l) {} + + //! Function call operator: evaluate a Partial. + bool operator()( const Partial & p ) const + { return p.label() == label; } + + //! Function call operator: evaluate a Partial pointer. + bool operator()( const Partial * p ) const + { return p->label() == label; } + +private: + int label; +}; + +// --------------------------------------------------------------------------- +// isLabelGreater +// +//! Predicate functor returning true if the label of its Partial argument is +//! greater than the specified 32-bit label, and false otherwise. +// +class isLabelGreater : public std::unary_function< const Partial, bool > +{ +public: + //! Initialize a new instance with the specified label. + isLabelGreater( int l ) : label(l) {} + + //! Function call operator: evaluate a Partial. + bool operator()( const Partial & p ) const + { return p.label() > label; } + + //! Function call operator: evaluate a Partial pointer. + bool operator()( const Partial * p ) const + { return p->label() > label; } + +private: + int label; +}; + +// --------------------------------------------------------------------------- +// isLabelLess +// +//! Predicate functor returning true if the label of its Partial argument is +//! less than the specified 32-bit label, and false otherwise. +// +class isLabelLess : public std::unary_function< const Partial, bool > +{ +public: + //! Initialize a new instance with the specified label. + isLabelLess( int l ) : label(l) {} + + //! Function call operator: evaluate a Partial. + bool operator()( const Partial & p ) const + { return p.label() < label; } + + //! Function call operator: evaluate a Partial pointer. + bool operator()( const Partial * p ) const + { return p->label() < label; } + +private: + int label; +}; + +// --------------------------------------------------------------------------- +// isPeakLess +// +//! Predicate functor returning true if the peak amplitude achieved by its +//! Partial argument is less than the specified absolute amplitude, and +//! false otherwise. +// +class isPeakLess : public std::unary_function< const Partial, bool > +{ +public: + //! Initialize a new instance with the specified peak amplitude. + isPeakLess( double x ) : thresh(x) {} + + //! Function call operator: evaluate a Partial. + bool operator()( const Partial & p ) const + { return peakAmplitude( p ) < thresh; } + + //! Function call operator: evaluate a Partial pointer. + bool operator()( const Partial * p ) const + { return peakAmplitude( *p ) < thresh; } + +private: + double thresh; +}; + +// -- comparitors -- + +// --------------------------------------------------------------------------- +// compareLabelLess +// +//! Comparitor (binary) functor returning true if its first Partial +//! argument has a label whose 32-bit integer representation is less than +//! that of the second Partial argument's label, and false otherwise. +// +class compareLabelLess : + public std::binary_function< const Partial, const Partial, bool > +{ +public: + //! Compare two Partials, return true if its first Partial + //! argument has a label whose 32-bit integer representation is less than + //! that of the second Partial argument's label, and false otherwise. + bool operator()( const Partial & lhs, const Partial & rhs ) const + { return lhs.label() < rhs.label(); } + + //! Compare two Partials, return true if its first Partial + //! argument has a label whose 32-bit integer representation is less than + //! that of the second Partial argument's label, and false otherwise. + bool operator()( const Partial * lhs, const Partial * rhs ) const + { return lhs->label() < rhs->label(); } +}; + +// --------------------------------------------------------------------------- +// compareDurationLess +// +//! Comparitor (binary) functor returning true if its first Partial +//! argument has duration less than that of the second Partial +//! argument, and false otherwise. +// +class compareDurationLess : + public std::binary_function< const Partial, const Partial, bool > +{ +public: + //! Compare two Partials, return true if its first Partial + //! argument has duration less than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial & lhs, const Partial & rhs ) const + { return lhs.duration() < rhs.duration(); } + + //! Compare two Partials, return true if its first Partial + //! argument has duration less than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial * lhs, const Partial * rhs ) const + { return lhs->duration() < rhs->duration(); } +}; + +// --------------------------------------------------------------------------- +// compareDurationGreater +// +//! Comparitor (binary) functor returning true if its first Partial +//! argument has duration greater than that of the second Partial +//! argument, and false otherwise. +// +class compareDurationGreater : + public std::binary_function< const Partial, const Partial, bool > +{ +public: + //! Compare two Partials, return true if its first Partial + //! argument has duration greater than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial & lhs, const Partial & rhs ) const + { return lhs.duration() > rhs.duration(); } + + //! Compare two Partials, return true if its first Partial + //! argument has duration greater than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial * lhs, const Partial * rhs ) const + { return lhs->duration() > rhs->duration(); } +}; + +// --------------------------------------------------------------------------- +// compareStartTimeLess +// +//! Comparitor (binary) functor returning true if its first Partial +//! argument has start time earlier than that of the second Partial +//! argument, and false otherwise. +// +class compareStartTimeLess : + public std::binary_function< const Partial, const Partial, bool > +{ +public: + //! Compare two Partials, return true if its first Partial + //! argument has start time earlier than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial & lhs, const Partial & rhs ) const + { return lhs.startTime() < rhs.startTime(); } + + //! Compare two Partials, return true if its first Partial + //! argument has start time earlier than that of the second Partial + //! argument, and false otherwise. + bool operator()( const Partial * lhs, const Partial * rhs ) const + { return lhs->startTime() < rhs->startTime(); } +}; + + + +} // end of namespace PartialUtils + +} // end of namespace Loris + +#endif /* ndef INCLUDE_PARTIALUTILS_H */ diff --git a/src/loris/README b/src/loris/README new file mode 100644 index 0000000..0a2ab2b --- /dev/null +++ b/src/loris/README @@ -0,0 +1,4 @@ +This directory contains the source code for the Loris C++ library. In +addition to the interface presented by the C++ classes, the Loris +library presents a simplified, C-linkable procedural interface through +the functions descried in loris.h diff --git a/src/loris/ReassignedSpectrum.C b/src/loris/ReassignedSpectrum.C new file mode 100644 index 0000000..0292905 --- /dev/null +++ b/src/loris/ReassignedSpectrum.C @@ -0,0 +1,746 @@ +/* + * 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 + * + * + * ReassignedSpectrum.C + * + * Implementation of class Loris::ReassignedSpectrum. + * + * Kelly Fitz, 9 Dec 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "ReassignedSpectrum.h" +#include "Notifier.h" +#include "LorisExceptions.h" +#include <algorithm> // for std::transform(), others +#include <functional> // for bind1st, multiplies, etc. +#include <cstdlib> // for std::abs() +#include <numeric> // for std::accumulate() + +#include <cmath> // for M_PI (except when its not there), fmod, fabs +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// The old quadratic interpolation code is still around, in case +// we ever want to use it for comparison, Lemur used to use that. +#if defined(Like_Lemur) +#define USE_PARABOLIC_INTERPOLATION +#endif + +// define this symbol to compute the mixed phase derivative +#define COMPUTE_MIXED_PHASE_DERIVATIVE 1 + +// there's a lot of std in here, +// import the whole namespace, as ugly as that is. +using namespace std; + +// begin namespace +namespace Loris { + +static unsigned long nextPO2( unsigned long N ) +{ + return (unsigned long)ceil( log( double(N) ) / log( 2. ) ); +} + +// --------------------------------------------------------------------------- +// ReassignedSpectrum constructor +// --------------------------------------------------------------------------- +//! Construct a new instance using the specified short-time window. +//! Transform lengths are the smallest power of two greater than twice the +//! window length. +// +ReassignedSpectrum::ReassignedSpectrum( const std::vector< double > & window ) : + mMagnitudeTransform( 1 << ( 1 + nextPO2( window.size() ) ) ), + mCorrectionTransform( 1 << ( 1 + nextPO2( window.size() ) ) ) +{ + // Build and store the window functions. + buildReassignmentWindows( window ); + + debugger << "ReassignedSpectrum: length is " << mMagnitudeTransform.size() << endl; +} + +// --------------------------------------------------------------------------- +// ReassignedSpectrum constructor +// --------------------------------------------------------------------------- +//! Construct a new instance using the specified short-time window and +//! its time derivative. +//! Transform lengths are the smallest power of two greater than twice the +//! window length. +ReassignedSpectrum::ReassignedSpectrum( const std::vector< double > & window, + const std::vector< double > & windowDerivative ) : + mMagnitudeTransform( 1 << ( 1 + nextPO2( window.size() ) ) ), + mCorrectionTransform( 1 << ( 1 + nextPO2( window.size() ) ) ) +{ + // Build and store the window functions. + buildReassignmentWindows( window, windowDerivative ); + + debugger << "ReassignedSpectrum: length is " << mMagnitudeTransform.size() << endl; +} + + +// --------------------------------------------------------------------------- +// transform +// --------------------------------------------------------------------------- +//! Compute the reassigned Fourier transform of the samples on the half open +//! range [sampsBegin, sampsEnd), aligning sampCenter with the center of +//! the analysis window. +//! +//! \param sampsBegin pointer representing the beginning of +//! the (half-open) range of samples to transform +//! \param sampCenter the sample in the range that is to be +//! aligned with the center of the analysis window +//! \param sampsEnd pointer representing the end of +//! the (half-open) range of samples to transform +//! +//! \pre sampsBegin must not be past sampCenter +//! \pre sampsEnd must be past sampCenter +//! \post the transform buffers store the reassigned +//! short-time transform data for the specified +//! samples +// +void +ReassignedSpectrum::transform( const double * sampsBegin, + const double * sampCenter, + const double * sampsEnd ) +{ + if ( sampCenter < sampsBegin || sampCenter >= sampsEnd ) + { + Throw( InvalidArgument, "Invalid sample range boundaries." ); + } + + const long firstHalfWinLength = window().size() / 2; + const long secondHalfWinLength = (window().size() - 1) / 2; + + // ensure that samples outside the window are not used: + sampsBegin = std::max( sampsBegin, sampCenter - firstHalfWinLength ); + sampsEnd = std::min( sampsEnd, sampCenter + secondHalfWinLength + 1 ); + + // we will skip the beginning of the window + // only if pos is too close to the start of + // the buffer: + long winBeginOffset = 0; + if ( sampCenter - sampsBegin < (window().size() / 2) ) + { + winBeginOffset = (window().size() / 2) - ( sampCenter - sampsBegin ); + } + + // to get phase right, we will rotate the Fourier transform + // input by pos - sampsBegin samples: + long rotateBy = sampCenter - sampsBegin; + + // window and rotate input and compute normal transform: + // window the samples into the FT buffer: + FourierTransform::iterator it = + std::transform( sampsBegin, sampsEnd, mCplxWin_W_Wtd.begin() + winBeginOffset, + mMagnitudeTransform.begin(), std::multiplies< std::complex< double > >() ); + // fill the rest with zeros: + std::fill( it, mMagnitudeTransform.end(), 0. ); + // rotate to align phase: + std::rotate( mMagnitudeTransform.begin(), mMagnitudeTransform.begin() + rotateBy, mMagnitudeTransform.end() ); + + // compute transform: + mMagnitudeTransform.transform(); + + // compute the dual reassignment transform: + // window the samples into the reassignment FT buffer, + // using the complex-valued reassignment window: + it = std::transform( sampsBegin, sampsEnd, mCplxWin_Wd_Wt.begin() + winBeginOffset, + mCorrectionTransform.begin(), std::multiplies< std::complex<double> >() ); + // fill the rest with zeros: + std::fill( it, mCorrectionTransform.end(), 0. ); + // rotate to align phase: + std::rotate( mCorrectionTransform.begin(), mCorrectionTransform.begin() + rotateBy, mCorrectionTransform.end() ); + // compute the transform: + mCorrectionTransform.transform(); +} + +// --------------------------------------------------------------------------- +// size +// --------------------------------------------------------------------------- +//! Return the length of the Fourier transforms. +// +ReassignedSpectrum::size_type +ReassignedSpectrum::size( void ) const +{ + return mMagnitudeTransform.size(); +} + +// --------------------------------------------------------------------------- +// window +// --------------------------------------------------------------------------- +//! Return read access to the short-time window samples. +//! (Peers may need to know about the analysis window +//! or about the scale factors in introduces.) +// +const std::vector< double > & +ReassignedSpectrum::window( void ) const +{ + return mWindow; +} + +// --------------------------------------------------------------------------- +// circEvenPartAt - helper +// --------------------------------------------------------------------------- +// Extract the circular even part from Fourier transform data. +// Used for computing two real transforms using a single complex transform. +// +template< class TransformData > +static std::complex<double> +circEvenPartAt( const TransformData & td, long idx ) +{ + const long N = td.size(); + while( idx < 0 ) + { + idx += N; + } + while( idx >= N ) + { + idx -= N; + } + + long flip_idx; + if ( idx != 0 ) + { + flip_idx = N - idx; + } + else + { + flip_idx = idx; + } + + return 0.5*( td[idx] + std::conj( td[flip_idx] ) ); +} + +// --------------------------------------------------------------------------- +// circOddPartAt - helper +// --------------------------------------------------------------------------- +// Extract the circular odd part divided by j from Fourier transform data. +// Used for computing two real transforms using a single complex transform. +// +template< class TransformData > +static std::complex<double> +circOddPartAt( const TransformData & td, long idx ) +{ + const long N = td.size(); + while( idx < 0 ) + { + idx += N; + } + while( idx >= N ) + { + idx -= N; + } + + long flip_idx; + if ( idx != 0 ) + { + flip_idx = N - idx; + } + else + { + flip_idx = idx; + } + + /* + const std::complex<double> minus_j(0,-1); + std::complex<double> tra_part = minus_j * 0.5 * + ( td[idx] - std::conj( td[flip_idx] ) ); + */ + // can compute this without complex multiplies: + std::complex<double> tmp = td[idx] - std::conj( td[flip_idx] ); + return std::complex<double>( 0.5*tmp.imag(), -0.5*tmp.real() ); +} + +// --------------------------------------------------------------------------- +// frequencyCorrection +// --------------------------------------------------------------------------- +//! Compute the frequency correction at the specified frequency sample +//! using the method of Auger and Flandrin to evaluate the partial +//! derivative of spectrum phase w.r.t. time. +//! +//! Correction is computed in fractional frequency samples, because +//! that's the kind of frequency domain ramp we used on our window. +//! sample is the frequency sample index, the nominal component +//! frequency in samples. +// +// Parabolic interpolation can be tried too (see reassignedFrequency()) +// but it appears to give slightly worse results, for example, with +// a square wave. +// +double +ReassignedSpectrum::frequencyCorrection( long idx ) const +{ + std::complex<double> X_h = circEvenPartAt( mMagnitudeTransform, idx ); + std::complex<double> X_Dh = circEvenPartAt( mCorrectionTransform, idx ); + + double num = X_h.real() * X_Dh.imag() - + X_h.imag() * X_Dh.real(); + + double magSquared = std::norm( X_h ); + + // need to scale by the oversampling factor + double oversampling = (double)mCorrectionTransform.size() / mCplxWin_W_Wtd.size(); + return - oversampling * num / magSquared; +} + +// --------------------------------------------------------------------------- +// timeCorrection +// --------------------------------------------------------------------------- +//! Compute the time correction at the specified frequency sample +//! using the method of Auger and Flandrin to evaluate the partial +//! derivative of spectrum phase w.r.t. frequency. +//! +//! Correction is computed in fractional samples, because +//! that's the kind of ramp we used on our window. +// +double +ReassignedSpectrum::timeCorrection( long idx ) const +{ + std::complex<double> X_h = circEvenPartAt( mMagnitudeTransform, idx ); + std::complex<double> X_Th = circOddPartAt( mCorrectionTransform, idx ); + + double num = X_h.real() * X_Th.real() + + X_h.imag() * X_Th.imag(); + double magSquared = norm( X_h ); + + // No need to scale by the oversampling factor. + // No, seems to sound bad, why? + // (try alienthreat) + // double oversampling = (double)mCorrectionTransform.size() / mCplxWin_W_Wtd.size(); + return num / magSquared; +} + +// --------------------------------------------------------------------------- +// reassignedFrequency +// --------------------------------------------------------------------------- +//! Return the reassigned frequency in fractional frequency +//! samples computed at the specified transform index. +//! +//! \param idx the frequency sample at which to evaluate the +//! transform +// +double +ReassignedSpectrum::reassignedFrequency( long idx ) const +{ +#if ! defined(USE_PARABOLIC_INTERPOLATION) + + return double(idx) + frequencyCorrection( idx ); + +#else // defined(USE_PARABOLIC_INTERPOLATION) + + double dbLeft = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx-1 ) ) ); + double dbCandidate = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx ) ) ); + double dbRight = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx+1 ) ) ); + + double peakXOffset = 0.5 * (dbLeft - dbRight) / + (dbLeft - 2.0 * dbCandidate + dbRight); + + return idx + peakXOffset; + +#endif // defined USE_PARABOLIC_INTERPOLATION +} + +// --------------------------------------------------------------------------- +// reassignedTime +// --------------------------------------------------------------------------- +//! Return the reassigned time in fractional samples +//! computed at the specified transform index. +//! +//! \param idx the frequency sample at which to evaluate the +//! transform +// +double +ReassignedSpectrum::reassignedTime( long idx ) const +{ + return timeCorrection( idx ); +} + +// --------------------------------------------------------------------------- +// reassignedMagnitude +// --------------------------------------------------------------------------- +//! Return the spectrum magnitude (absolute) +//! computed at the specified transform index. +//! +//! \param idx the frequency sample at which to evaluate the +//! transform +// +double +ReassignedSpectrum::reassignedMagnitude( long idx ) const +{ +#if ! defined(USE_PARABOLIC_INTERPOLATION) + + // compute the nominal spectral amplitude by scaling + // the peak spectral sample: + return abs( circEvenPartAt( mMagnitudeTransform, idx ) ); + +#else // defined(USE_PARABOLIC_INTERPOLATION) + + // keep this parabolic interpolation computation around + // only for sake of comparison, it is unlikely to yield + // good results with bandwidth association: + double dbLeft = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx-1 ) ) ); + double dbCandidate = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx ) ) ); + double dbRight = 20. * log10( abs( circEvenPartAt( mMagnitudeTransform, idx+1 ) ) ); + + double peakXOffset = 0.5 * (dbLeft - dbRight) / + (dbLeft - 2.0 * dbCandidate + dbRight); + double dbmag = dbCandidate - 0.25 * (dbLeft - dbRight) * peakXOffset; + double x = pow( 10., 0.05 * dbmag ); + + return x; + +#endif // defined USE_PARABOLIC_INTERPOLATION +} + +// --------------------------------------------------------------------------- +// reassignedPhase +// --------------------------------------------------------------------------- +//! Return the phase in radians computed at the specified transform index. +//! The reassigned phase is shifted to account for the time +//! correction according to the corrected frequency. +//! +//! \param idx the frequency sample at which to evaluate the +//! transform +// +double +ReassignedSpectrum::reassignedPhase( long idx ) const +{ + double phase = arg( circEvenPartAt( mMagnitudeTransform, idx ) ); + + const double offsetTime = timeCorrection( idx ); + const double offsetFreq = frequencyCorrection( idx ); + + // adjust phase according to the frequency correction: + // first compute H(1): + // + // this seems like it would be a good idea, but in practice, + // it screws the phases up badly. + // Am I just correcting in the wrong direction? No, its + // something else. + // + // Seems like I had the slope way too big. Changed to compute + // the slope from H(1) of a rotated window, and now the slope + // is so small that it seems like there will never be any phase + // correction. + // + // Phase ought to be linear anyway, so I should just be + // able to use dumb old linear interpolation. + // offsetFreq is in fractional frequency samples + if ( offsetFreq > 0 ) + { + double nextphase = arg( circEvenPartAt( mMagnitudeTransform, idx+1 ) ); + double slope = nextphase - phase; + phase += offsetFreq * slope; + } + else + { + double prevphase = arg( circEvenPartAt( mMagnitudeTransform, idx-1 ) ); + double slope = phase - prevphase; + phase += offsetFreq * slope; + } + + + // adjust phase according to the time correction: + const double fracFreqSample = idx + offsetFreq; + phase += offsetTime * fracFreqSample * 2. * Pi / mMagnitudeTransform.size(); + + // NOTICE + // This could be pretty much anything -- a sample reassigned by a + // millisecond at 1000 Hz in a 1024 FFT at 44k sample rate is + // adjusted by 2Pi. + // + // What if the frequency estimate is bad? Corrupts the phase estimate too! + + return fmod( phase, 2. * Pi ); +} + +// --------------------------------------------------------------------------- +// convergence +// --------------------------------------------------------------------------- +//! Compute and return the convergence indicator, computed from the +//! mixed partial derivative of spectral phase, optionally used in +//! BW enhanced analysis as a convergence indicator. The convergence +//! value is on the range [0,1], 0 for a sinusoid, and 1 for an impulse. +//! +//! \param idx the frequency sample at which to evaluate the +//! transform +// +double +ReassignedSpectrum::convergence( long idx ) const +{ +#if defined(COMPUTE_MIXED_PHASE_DERIVATIVE) + + std::complex<double> X_h = circEvenPartAt( mMagnitudeTransform, idx ); + std::complex<double> X_Th = circOddPartAt( mCorrectionTransform, idx ); + std::complex<double> X_Dh = circEvenPartAt( mCorrectionTransform, idx ); + std::complex<double> X_TDh = circOddPartAt( mMagnitudeTransform, idx ); + + double term1 = (X_TDh * conj(X_h)).real() / norm( X_h ); + double term2 = ((X_Th * X_Dh) / (X_h * X_h)).real(); + + double scaleBy = 2. * Pi / mCplxWin_W_Wtd.size(); + + double bw = fabs( 1.0 + (scaleBy * (term1 - term2)) ); + bw = min( 1.0, bw ); + +#else + double bw = 0.; +#endif + + return bw; +} + +// --------------------------------------------------------------------------- +// subscript operator (deprecated) +// --------------------------------------------------------------------------- +// Included to support old code. +// The signature has changed, can no longer return a reference, +// but since the reference returned was const, this version should +// keep most old code working, if not all. +// +std::complex< double > +ReassignedSpectrum::operator[]( unsigned long idx ) const +{ + return circEvenPartAt( mMagnitudeTransform, idx ); +} + +// --------------------------------------------------------------------------- +// make_complex +// --------------------------------------------------------------------------- +// Function object for building complex numbers. +// +template <class T> +struct make_complex + : binary_function< T, T, std::complex<T> > +{ + std::complex<T> operator()(const T& re, const T& im) const + { + return std::complex<T>( re, im ); + } +}; + +// --------------------------------------------------------------------------- +// applyFreqRamp +// --------------------------------------------------------------------------- +// Adapted from the FrequencyReassignment constructor in Lemur 5. +// +// This function computes an estimate of the time derivative of the +// specified window function, scaled by N/2pi, appropriate for computing +// frequency reassignment. +// +static inline void applyFreqRamp( vector< double > & w ) +{ + // we're going to do the frequency-domain ramp + // by Fourier transforming the window, ramping, + // then transforming again. + // Use a transform exactly as long as the window. + // load, w/out rotation, and transform. + FourierTransform temp( w.size() ); + FourierTransform::iterator it = std::copy( w.begin(), w.end(), temp.begin() ); + std::fill( it, temp.end(), 0. ); + temp.transform(); + + // extract complex transform and multiply by + // a frequency (sample) ramp: + // (the frequency ramp goes from 0 to N/2 + // over the first half, then -N/2 to 0 over + // the second (aliased) half of the transform, + // and has to be scaled by the ratio of the + // transform lengths, so that k spans the length + // of the padded transforms, N) + for ( int k = 0 ; k < temp.size(); ++k ) + { + double x = (double)k; // to get type promotion right + if ( k < temp.size() / 2 ) + { + temp[ k ] *= x; + } + else + { + temp[ k ] *= ( x - temp.size() ); + } + } + + // invert the transform: + temp.transform(); + + // the DFT of a DFT gives the scaled and INDEX REVERSED + // sequence. See p. 539 of O and S. + // DFT( X[n] ) -- DFT --> Nx[ -k mod N ] + // + // seems that I want the imaginary part of the index-reversed + // transform scaled by the size of the transform: + std::reverse( temp.begin() + 1, temp.end() ); + for ( int i = 0; i < w.size(); ++i ) + { + w[i] = - imag( temp[i] ) / temp.size(); + } +} + +// --------------------------------------------------------------------------- +// applyTimeRamp +// --------------------------------------------------------------------------- +// Make a copy of mWindow scaled by a ramp from -N/2 to N/2 for computing +// time corrections in samples. +// +static inline void applyTimeRamp( vector< double > & w ) +{ + // the very center of the window should be scaled by 0., + // need a fractional value for even-length windows, a + // whole number for odd-length windows: + double offset = 0.5 * ( w.size() - 1 ); + for ( int k = 0 ; k < w.size(); ++k ) + { + w[ k ] *= ( k - offset ); + } +} + +// --------------------------------------------------------------------------- +// buildReassignmentWindows (private) +// --------------------------------------------------------------------------- +// Build a pair of complex-valued windows, one having the frequency-ramp +// (time-derivative) window in the real part and the time-ramp window in the +// imagnary part, and the other having the unmodified window in the real part +// and, if computing mixed deriviatives, the time-ramp time-derivative window +// in the imaginary part. +// +// Input is the unmodified window function. +// +void +ReassignedSpectrum::buildReassignmentWindows( const std::vector< double > & window ) +{ + mWindow.resize( window.size(), 0. ); + + // Scale the window so that the reported magnitudes + // are correct. + double winsum = std::accumulate( window.begin(), window.end(), 0. ); + std::transform( window.begin(), window.end(), mWindow.begin(), + std::bind1st( std::multiplies<double>(), 2/winsum ) ); + + + // Construct the ramped windows from the scaled window. + std::vector< double > tramp = mWindow; + applyTimeRamp( tramp ); + + std::vector< double > framp = mWindow; + applyFreqRamp( framp ); + + std::vector< double > tframp( mWindow.size(), 0. ); + +#if defined(COMPUTE_MIXED_PHASE_DERIVATIVE) + + // Do this only if we are computing the mixed + // partial derivative of phase, otherwise, leave + // that vector empty. + tframp = framp; + applyTimeRamp( tframp ); + +#endif + + // Copy the windows into real and imaginary parts of + // complex window vectors. + mCplxWin_W_Wtd.resize( mWindow.size(), 0. ); + mCplxWin_Wd_Wt.resize( mWindow.size(), 0. ); + + std::transform( framp.begin(), framp.end(), tramp.begin(), + mCplxWin_Wd_Wt.begin(), make_complex< double >() ); + + std::transform( mWindow.begin(), mWindow.end(), tframp.begin(), + mCplxWin_W_Wtd.begin(), make_complex< double >() ); +} + +// --------------------------------------------------------------------------- +// buildReassignmentWindows +// --------------------------------------------------------------------------- +// Build a pair of complex-valued windows, one having the frequency-ramp +// (time-derivative) window in the real part and the time-ramp window in the +// imagnary part, and the other having the unmodified window in the real part +// and, if computing mixed deriviatives, the time-ramp time-derivative window +// in the imaginary part. +// +// Input is the unmodified window function and its time derivative, so the +// DFT kludge is unnecessary. +// + +void +ReassignedSpectrum::buildReassignmentWindows( const std::vector< double > & window, + const std::vector< double > & windowDerivative ) +{ + + mWindow.resize( window.size(), 0. ); + + // Scale the windows so that the reported magnitudes + // are correct. + double winsum = std::accumulate( window.begin(), window.end(), 0. ); + std::transform( window.begin(), window.end(), mWindow.begin(), + std::bind1st( std::multiplies<double>(), 2/winsum ) ); + + + // The fancy frequency reassignment window needs to scale the + // time derivative window by N (its length) / 2pi, in addition + // to scaling by 2/winsum to match the amplitude scaling above. + const double fancyScale = windowDerivative.size() / ( winsum * Pi ); + std::vector< double > framp( windowDerivative.size(), 0 ); + std::transform( windowDerivative.begin(), windowDerivative.end(), framp.begin(), + std::bind1st( std::multiplies<double>(), fancyScale ) ); + + + // Construct the ramped windows from the scaled window. + std::vector< double > tramp = mWindow; + applyTimeRamp( tramp ); + + std::vector< double > tframp( mWindow.size(), 0. ); + +#if defined(COMPUTE_MIXED_PHASE_DERIVATIVE) + + // Do this only if we are computing the mixed + // partial derivative of phase, otherwise, leave + // that vector empty. + tframp = framp; + applyTimeRamp( tframp ); + +#endif + + // Copy the windows into real and imaginary parts of + // complex window vectors. + mCplxWin_W_Wtd.resize( mWindow.size(), 0. ); + mCplxWin_Wd_Wt.resize( mWindow.size(), 0. ); + + std::transform( framp.begin(), framp.end(), tramp.begin(), + mCplxWin_Wd_Wt.begin(), make_complex< double >() ); + + std::transform( mWindow.begin(), mWindow.end(), tframp.begin(), + mCplxWin_W_Wtd.begin(), make_complex< double >() ); +} + + +} // end of namespace Loris diff --git a/src/loris/ReassignedSpectrum.h b/src/loris/ReassignedSpectrum.h new file mode 100644 index 0000000..cd8ae69 --- /dev/null +++ b/src/loris/ReassignedSpectrum.h @@ -0,0 +1,229 @@ +#ifndef INCLUDE_REASSIGNEDSPECTRUM_H +#define INCLUDE_REASSIGNEDSPECTRUM_H +/* + * 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 + * + * + * ReassignedSpectrum.h + * + * Definition of class Loris::ReassignedSpectrum. + * + * Kelly Fitz, 7 Dec 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "FourierTransform.h" +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class ReassignedSpectrum +// +//! Computes a reassigned short-time Fourier spectrum using the transform +//! method of Auger and Flandrin. +// +class ReassignedSpectrum +{ +// -- public interface -- +public: + //! An unsigned integral type large enough + //! to represent the length of any transform. + typedef FourierTransform::size_type size_type; + +// --- lifecycle --- + + //! Construct a new instance using the specified short-time window. + //! Transform lengths are the smallest power of two greater than twice the + //! window length. + ReassignedSpectrum( const std::vector< double > & window ); + + //! Construct a new instance using the specified short-time window and + //! its time derivative. + //! Transform lengths are the smallest power of two greater than twice the + //! window length. + ReassignedSpectrum( const std::vector< double > & window, + const std::vector< double > & windowDerivative ); + + // compiler-generated copy, assign, and destroy are sufficient + +// --- operations --- + + //! Compute the reassigned Fourier transform of the samples on the half open + //! range [sampsBegin, sampsEnd), aligning sampCenter with the center of + //! the analysis window. + //! + //! \param sampsBegin pointer representing the beginning of + //! the (half-open) range of samples to transform + //! \param sampCenter the sample in the range that is to be + //! aligned with the center of the analysis window + //! \param sampsEnd pointer representing the end of + //! the (half-open) range of samples to transform + //! + //! \pre sampsBegin must not be past sampCenter + //! \pre sampsEnd must be past sampCenter + //! \post the transform buffers store the reassigned + //! short-time transform data for the specified + //! samples + void transform( const double * sampsBegin, const double * pos, const double * sampsEnd ); + +// --- inquiry --- + + //! Return the length of the Fourier transforms. + size_type size( void ) const; + + //! Return read access to the short-time window samples. + //! (Peers may need to know about the analysis window + //! or about the scale factors in introduces.) + const std::vector< double > & window( void ) const; + + +// --- reassigned transform access --- + + //! Compute and return the convergence indicator, computed from the + //! mixed partial derivative of spectral phase, optionally used in + //! BW enhanced analysis as a convergence indicator. The convergence + //! value is on the range [0,1], 0 for a sinusoid, and 1 for an impulse. + //! + //! \param idx the frequency sample at which to evaluate the + //! transform + double convergence( long idx ) const; + + //! Return the reassigned frequency in fractional frequency + //! samples computed at the specified transform index. + //! + //! \param idx the frequency sample at which to evaluate the + //! transform + double reassignedFrequency( long idx ) const; + + //! Return the spectrum magnitude (absolute) + //! computed at the specified transform index. + //! + //! \param idx the frequency sample at which to evaluate the + //! transform + double reassignedMagnitude( long idx ) const; + + //! Return the phase in radians computed at the specified transform index. + //! The reassigned phase is shifted to account for the time + //! correction according to the corrected frequency. + //! + //! + //! \param idx the frequency sample at which to evaluate the + //! transform + double reassignedPhase( long idx ) const; + + //! Return the reassigned time in fractional samples + //! computed at the specified transform index. + //! + //! \param idx the frequency sample at which to evaluate the + //! transform + double reassignedTime( long idx ) const; + +// --- reassignment operations --- + + //! Compute the frequency correction at the specified frequency sample + //! using the method of Auger and Flandrin to evaluate the partial + //! derivative of spectrum phase w.r.t. time. + //! + //! Correction is computed in fractional frequency samples, because + //! that's the kind of frequency domain ramp we used on our window. + //! sample is the frequency sample index, the nominal component + //! frequency in samples. + double frequencyCorrection( long sample ) const; + + //! Compute the time correction at the specified frequency sample + //! using the method of Auger and Flandrin to evaluate the partial + //! derivative of spectrum phase w.r.t. frequency. + //! + //! Correction is computed in fractional samples, because + //! that's the kind of ramp we used on our window. + double timeCorrection( long sample ) const; + +// --- legacy support --- + + // These members are deprecated, and included only + // to support old code. New code should use the + // corresponding documented members. + // All of these members are deprecated. + + double reassignedPhase( long idx, double, double ) const + { return reassignedPhase( idx ); } + double reassignedMagnitude( double, long intBinNumber ) const + { return reassignedMagnitude( intBinNumber ); } + + // subscript operator + // The signature has changed, can no longer return a reference, + // but since the reference returned was const, this version should + // keep most old code working, if not all. + std::complex< double > operator[]( unsigned long idx ) const; + +private: + +// -- window building helpers -- + + // Build a pair of complex-valued windows, one having the frequency-ramp + // (time-derivative) window in the real part and the time-ramp window in the + // imagnary part, and the other having the unmodified window in the real part + // and, if computing mixed deriviatives, the time-ramp time-derivative window + // in the imaginary part. + // + // Input is the unmodified window function. + void buildReassignmentWindows( const std::vector< double > & window ); + + // Build a pair of complex-valued windows, one having the frequency-ramp + // (time-derivative) window in the real part and the time-ramp window in the + // imagnary part, and the other having the unmodified window in the real part + // and, if computing mixed deriviatives, the time-ramp time-derivative window + // in the imaginary part. + // + // Input is the unmodified window function and its time derivative, so the + // DFT kludge is unnecessary. + void buildReassignmentWindows( const std::vector< double > & window, + const std::vector< double > & windowDerivative ); + +// -- instance variables -- + + //! the FourierTransform for computing magnitude and phase + FourierTransform mMagnitudeTransform; + + //! the FourierTransform for computing time and frequency corrections + FourierTransform mCorrectionTransform; + + //! the original short-time analysis window samples + std::vector< double > mWindow; // W(n) + + //! the complex window used to compute the + //! magnitude/phase transform + std::vector< std::complex< double > > mCplxWin_W_Wtd; // real W(n), imag nW'(n) + + //! the complex window used to compute the + //! time/frequency correction transform + std::vector< std::complex< double > > mCplxWin_Wd_Wt; // real W'(n), imag nW(n) + +}; // end of class ReassignedSpectrum + +} // end of namespace Loris + +#endif /* ndef INCLUDE_REASSIGNEDSPECTRUM_H */ diff --git a/src/loris/Resampler.C b/src/loris/Resampler.C new file mode 100644 index 0000000..be39674 --- /dev/null +++ b/src/loris/Resampler.C @@ -0,0 +1,398 @@ +/* + * 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 + * + * + * Resampler.C + * + * Implementation of class Resampler, for converting reassigned Partial envelopes + * into more conventional additive synthesis envelopes, having data points + * at regular time intervals. The benefits of reassigned analysis are NOT + * lost in this process, since the elimination of unreliable data and the + * reduction of temporal smearing are reflected in the resampled data. + * + * Lippold, 7 Aug 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + * + * Phase correction added by Kelly 13 Dec 2005. + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Resampler.h" +#include "Breakpoint.h" +#include "LinearEnvelope.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "phasefix.h" + +#include <cmath> + +// begin namespace +namespace Loris { + +// helper declarations: +static Partial::iterator insert_resampled_at( Partial & newp, const Partial & p, + double sampleTime, double insertTime ); + +/* +TODO + - remove empties (currently handled automatically in the Python module + + - remove insert_resampled_at + + - phase correct with timing? + + - fade time (for amplitude envelope sampling) - equal to interval? half? +*/ + + +// --------------------------------------------------------------------------- +// constructor - sampling interval +// --------------------------------------------------------------------------- +//! Initialize a Resampler having the specified uniform sampling +//! interval. Enable phase-correct resampling, in which frequencies +//! of resampled Partials are modified (using fixFrequency) such +//! that the resampled phases are achieved in synthesis. Phase- +//! correct resampling can be disabled using setPhaseCorrect. +//! +//! Resampled Partials will be composed of Breakpoints at every +//! integer multiple of the resampling interval. +//! +//! \sa setPhaseCorrect +//! \sa fixFrequency +//! +//! \param sampleInterval is the resampling interval in seconds, +//! Breakpoint data is computed at integer multiples of +//! sampleInterval seconds. +//! \throw InvalidArgument if sampleInterval is not positive. +// +Resampler::Resampler( double sampleInterval ) : + interval_( sampleInterval ), + phaseCorrect_( true ) +{ + if ( sampleInterval <= 0. ) + { + Throw( InvalidArgument, "Resampler sample interval must be positive." ); + } +} + +// --------------------------------------------------------------------------- +// setPhaseCorrect +// --------------------------------------------------------------------------- +//! Specify phase-corrected resampling, or not. If phase +//! correct, Partial frequencies are altered slightly +//! to match, as nearly as possible, the Breakpoint +//! phases after resampling. Phases are updated so that +//! the Partial frequencies and phases are consistent after +//! resampling. +//! +//! \param correctPhase is a boolean flag specifying that +//! (if true) frequency/phase correction should be +//! applied after resampling. +void Resampler::setPhaseCorrect( bool correctPhase ) +{ + phaseCorrect_ = correctPhase; +} + +// --------------------------------------------------------------------------- +// resample +// --------------------------------------------------------------------------- +//! Resample a Partial using this Resampler's stored quanitization interval. +//! If sparse resampling (the default) has be selected, Breakpoint times +//! are quantized to integer multiples of the resampling interval. +//! If dense resampling is selected, a Breakpoint will be provided at +//! every integer multiple of the resampling interval in the time span of +//! the Partial, starting and ending with the nearest multiples to the +//! ends of the Partial. Frequencies and phases are corrected to be in +//! agreement and to match as nearly as possible the resampled phases if +//! phase correct resampling is specified (the default). Resampling +//! is performed in-place. +//! +//! \param p is the Partial to resample +// +void +Resampler::resample( Partial & p ) const +{ + debugger << "resampling Partial labeled " << p.label() + << " having " << p.numBreakpoints() + << " Breakpoints" << endl; + + + // create the new Partial: + Partial newp; + newp.setLabel( p.label() ); + + // find time of first and last breakpoint for the resampled envelope: + double firstInsertTime = interval_ * int( 0.5 + p.startTime() / interval_ ); + double lastInsertTime = p.endTime() + ( 0.5 * interval_ ); + + // resample: + for ( double tins = firstInsertTime; tins <= lastInsertTime; tins += interval_ ) + { + // sample time is obtained from the timing envelope, if specified, + // otherwise same as the insert time: + double tsamp = tins; + insert_resampled_at( newp, p, tins, tins ); + } + + // store the new Partial: + p = newp; + + debugger << "resampled Partial has " << p.numBreakpoints() + << " Breakpoints" << endl; + + + if ( phaseCorrect_ ) + { + fixFrequency( p ); // use default maxFixPct + } +} + +// --------------------------------------------------------------------------- +// resample +// --------------------------------------------------------------------------- +//! Resample a Partial using this Resampler's stored quanitization interval. +//! If sparse resampling (the default) has be selected, Breakpoint times +//! are quantized to integer multiples of the resampling interval. +//! If dense resampling is selected, a Breakpoint will be provided at +//! every integer multiple of the resampling interval in the time span of +//! the Partial, starting and ending with the nearest multiples to the +//! ends of the Partial. Frequencies and phases are corrected to be in +//! agreement and to match as nearly as possible the resampled phases if +//! phase correct resampling is specified (the default). Resampling +//! is performed in-place. +//! +//! \param p is the Partial to resample +//! +//! \param timingEnv is the timing envelope, a map of Breakpoint +//! times in resampled Partials onto parameter sampling +//! instants in the original Partials. +//! +//! \throw InvalidArgument if timingEnv has any negative breakpoint +//! times or values. +// +void +Resampler::resample( Partial & p, const LinearEnvelope & timingEnv ) const +{ + debugger << "resampling Partial labeled " << p.label() + << " having " << p.numBreakpoints() + << " Breakpoints" << endl; + + + Assert( 0 != timingEnv.size() ); + + // create the new Partial: + Partial newp; + newp.setLabel( p.label() ); + + // find the extent of the timing envelope, if specified, otherwise + // the insert time range is the same as the sample time range: + double firstInsertTime = interval_ * int( 0.5 + timingEnv.begin()->first / interval_ ); + double lastInsertTime = (--timingEnv.end())->first + ( 0.5 * interval_ ); + + // resample: + for ( double insertTime = firstInsertTime; + insertTime <= lastInsertTime; + insertTime += interval_ ) + { + // sample time is obtained from the timing envelope, if specified, + // otherwise same as the insert time: + double sampleTime = timingEnv.valueAt( insertTime ); + + // make a resampled Breakpoint: + Breakpoint newbp = p.parametersAt( sampleTime ); + + Partial::iterator ret_pos = newp.insert( insertTime, newbp ); + + } + + // remove excess null Breakpoints at the ends of the newly-formed + // Partial, no simple way to anticipate these, without evaluating + // the timing envelope at all points. + // + // Also runs of nulls in the middle? + Partial::iterator it = newp.begin(); + while( it != newp.end() && 0 == it->amplitude() ) + { + ++it; + } + newp.erase( newp.begin(), it ); + + it = newp.end(); + while( it != newp.begin() && 0 == (--it)->amplitude() ) + { + } + if ( it != newp.end() ) + { + newp.erase( ++it, newp.end() ); + } + + // is this a good idea? generally not. + if ( phaseCorrect_ && ( 0 != newp.numBreakpoints() ) ) + { + fixFrequency( newp ); // use default maxFixPct + } + + // store the new Partial: + p = newp; + + debugger << "resampled Partial has " << p.numBreakpoints() + << " Breakpoints" << endl; +} + +// --------------------------------------------------------------------------- +// quantize +// --------------------------------------------------------------------------- +//! The Breakpoint times in the resampled Partial will comprise a +//! sparse sequence of integer multiples of the sampling interval, +//! beginning with the multiple nearest to the Partial's start time and +//! ending with the multiple nearest to the Partial's end time, and including +//! only multiples that are near to Breakpoint times in the original Partial. +//! Resampling is performed in-place. +//! +//! \param p is the Partial to resample +// +void Resampler::quantize( Partial & p ) const +{ + debugger << "quantizing Partial labeled " << p.label() + << " having " << p.numBreakpoints() + << " Breakpoints" << endl; + + // for phase-correct quantization, first make the phases correct by + // fixing them from the initial phase (ideally this should have + // no effect but there's no way to be phase-correct after quantization + // unless the phases start correct), then quantize the Breakpoint + // times, then afterwards, adjust the frequencies to match + // the interpolated phases: + if ( phaseCorrect_ ) + { + fixPhaseForward( p.begin(), --p.end() ); + } + + // create the new Partial: + Partial newp; + newp.setLabel( p.label() ); + + Partial::const_iterator iter = p.begin(); + while( iter != p.end() ) + { + const Breakpoint & bp = iter.breakpoint(); + double bpt = iter.time(); + + // find the nearest multiple of the quantization interval: + long qstep = long( 0.5 + ( bpt / interval_ ) ); + + long endstep = qstep-1; // guarantee first insertion + if ( newp.numBreakpoints() != 0 ) + { + endstep = long( 0.5 + ( newp.endTime() / interval_ ) ); + } + + // insert a new Breakpoint if it does not duplicate + // a previous insertion, or if it is a Null (needed + // for phase-correction): + if ( (endstep != qstep) || (0 == bp.amplitude()) ) + { + double qt = interval_ * qstep; + + // insert another Breakpoint and advance the Breakpoint + // iterator and the current time: + // + // sample the Partial with a long fade time so that + // the amplitudes at the ends keep their original values: + const double a_long_time = 1.; + Breakpoint newbp = p.parametersAt( qt, a_long_time ); + Partial::iterator new_pos = newp.insert( qt, newbp ); + + // tricky: if the quantized position (iter) is a null Breakpoint, + // we had better made the new position a null also, very important + // for making phase resets happen at synthesis time. + // + // Also, if new_pos is earlier than iter, the phase should be rolled + // back from iter, rather than interpolated. If new_pos is later + // than iter, then its phase will have been correctly interpolated. + if ( 0 == bp.amplitude() ) + { + new_pos.breakpoint().setAmplitude( 0 ); + + if ( new_pos.time() < bpt ) + { + double dp = phaseTravel( new_pos.breakpoint(), bp, + bpt - new_pos.time() ); + new_pos.breakpoint().setPhase( bp.phase() - dp ); + } + } + } + ++iter; + } + + // for phase-correct quantization, adjust the frequencies to match + // the interpolated phases: + if ( phaseCorrect_ ) + { + fixFrequency( newp, 5 ); + } + + + debugger << "quantized Partial has " << newp.numBreakpoints() + << " Breakpoints" << endl; + + // store the new Partial: + p = newp; +} + +// --------------------------------------------------------------------------- +// insert_resampled_at (helper) +// --------------------------------------------------------------------------- +// +static Partial::iterator +insert_resampled_at( Partial & newp, const Partial & p, + double sampleTime, double insertTime ) +{ + // make a resampled Breakpoint: + Breakpoint newbp = p.parametersAt( sampleTime ); + + // handle end points to reduce error at ends + if ( sampleTime < p.startTime() ) + { + newbp.setAmplitude( p.first().amplitude() ); + } + else if ( sampleTime > p.endTime() ) + { + newbp.setAmplitude( p.last().amplitude() ); + } + + + Partial::iterator ret_pos = newp.insert( insertTime, newbp ); + + debugger << "inserted Breakpoint having amplitude " << newbp.amplitude() + << " at time " << insertTime << endl; + + return ret_pos; +} + +} // end of namespace Loris + diff --git a/src/loris/Resampler.h b/src/loris/Resampler.h new file mode 100644 index 0000000..bc45eb3 --- /dev/null +++ b/src/loris/Resampler.h @@ -0,0 +1,449 @@ +#ifndef INCLUDE_RESAMPLER_H +#define INCLUDE_RESAMPLER_H +/* + * 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 + * + * + * Resampler.h + * + * Definition of class Resampler, for converting reassigned Partial envelopes + * into more conventional additive synthesis envelopes, having data points + * at regular time intervals. The benefits of reassigned analysis are NOT + * lost in this process, since the elimination of unreliable data and the + * reduction of temporal smearing are reflected in the resampled data. + * + * Lippold, 7 Aug 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "PartialList.h" +#include "LinearEnvelope.h" + +// begin namespace +namespace Loris { + +class Partial; + +// --------------------------------------------------------------------------- +// class Resampler +// +//! Class Resampler represents an algorithm for resampling Partial envelopes +//! at regular time intervals. Resampling makes the envelope data more suitable +//! for exchange (as SDIF data, for example) with other applications that +//! cannot process raw (continuously-distributed) reassigned data. Resampling +//! will often greatly reduce the size of the data (by greatly reducing the +//! number of Breakpoints in the Partials) without adversely affecting the +//! quality of the reconstruction. +// +class Resampler +{ +// --- public interface --- +public: +// --- lifecycle --- + + //! Initialize a Resampler having the specified uniform sampling + //! interval. Enable phase-correct resampling, in which frequencies + //! of resampled Partials are modified (using fixFrequency) such + //! that the resampled phases are achieved in synthesis. Phase- + //! correct resampling can be disabled using setPhaseCorrect. + //! + //! Resampled Partials will be composed of Breakpoints at every + //! integer multiple of the resampling interval. + //! + //! \sa setPhaseCorrect + //! \sa fixFrequency + //! + //! \param sampleInterval is the resampling interval in seconds, + //! Breakpoint data is computed at integer multiples of + //! sampleInterval seconds. + //! + //! \throw InvalidArgument if sampleInterval is not positive. + explicit Resampler( double sampleInterval ); + + + // --- use compiler-generated copy/assign/destroy --- + +// --- parameters --- + + //! Specify phase-corrected resampling, or not. If phase + //! correct, Partial frequencies are altered slightly + //! to match, as nearly as possible, the Breakpoint + //! phases after resampling. Phases are updated so that + //! the Partial frequencies and phases are consistent after + //! resampling. + //! + //! \param correctPhase is a boolean flag specifying that + //! (if true) frequency/phase correction should be + //! applied after resampling. + void setPhaseCorrect( bool correctPhase ); + +// --- resampling --- + + //! Resample the specified Partial using the stored quanitization interval. + //! The Breakpoint times will comprise a contiguous sequence of all integer + //! multiples of the sampling interval, starting and ending with the nearest + //! multiples to the ends of the Partial. If phase correct resampling is + //! specified (the default)≤ frequencies and phases are corrected to be in + //! agreement and to match as nearly as possible the resampled phases. + //! + //! Resampling is performed in-place. + //! + //! \param p is the Partial to resample + void resample( Partial & p ) const; + + //! Function call operator: same as resample( p ). + void operator() ( Partial & p ) const + { + resample( p ); + } + + //! Resample the specified Partial using the stored quanitization interval. + //! The Breakpoint times will comprise a contiguous sequence of all integer + //! multiples of the sampling interval, starting and ending with the nearest + //! multiples to the ends of the Partial. If phase correct resampling is + //! specified (the default)≤ frequencies and phases are corrected to be in + //! agreement and to match as nearly as possible the resampled phases. + //! + //! The timing envelope represents a warping of the time axis that is + //! applied during resampling. The Breakpoint times in resampled Partials + //! will a comprise contiguous sequence of all integer multiples of the + //! sampling interval between the first and last breakpoints in the timing + //! envelope, and each Breakpoint will represent the parameters of the + //! original Partial at the time that is the value of the timing envelope + //! at that instant. + //! + //! Resampling is performed in-place. + //! + //! \param p is the Partial to resample + //! + //! \param timingEnv is the timing envelope, a map of Breakpoint + //! times in resampled Partials onto parameter sampling + //! instants in the original Partials. + //! + //! \throw InvalidArgument if timingEnv has any negative breakpoint + //! times or values. + void resample( Partial & p, const LinearEnvelope & timingEnv ) const; + + //! Quantize the Breakpoint times using the specified Partial using the + //! stored quanitization interval. Each Breakpoint in the Partial is + //! replaced by a Breakpoint constructed by resampling the Partial at + //! the nearest integer multiple of the of the resampling interval. + //! + //! Quantization is performed in-place. + //! + //! \param p is the Partial to resample + void quantize( Partial & p ) const; + + + //! Resample all Partials in the specified (half-open) range using this + //! Resampler's stored quanitization interval. The Breakpoint times in + //! resampled Partials will comprise a contiguous sequence of all integer + //! multiples of the sampling interval, starting and ending with the nearest + //! multiples to the ends of the Partial. If phase correct resampling is + //! specified (the default)≤ frequencies and phases are corrected to be in + //! agreement and to match as nearly as possible the resampled phases. + //! + //! Resampling is performed in-place. + //! + //! \param begin is the beginning of the range of Partials to resample + //! \param end is (one-past) the end of the range of Partials to resample + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void resample( Iter begin, Iter end ) const; +#else + inline + void resample( PartialList::iterator begin, PartialList::iterator end ) const; +#endif + + //! Function call operator: same as resample( begin, end ). +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void operator()( Iter begin, Iter end ) const +#else + void operator()( PartialList::iterator begin, PartialList::iterator end ) const +#endif + { + resample( begin, end ); + } + + //! Resample all Partials in the specified (half-open) range using this + //! Resampler's stored quanitization interval. The Breakpoint times in + //! resampled Partials will comprise a contiguous sequence of all integer + //! multiples of the sampling interval, starting and ending with the nearest + //! multiples to the ends of the Partial. If phase correct resampling is + //! specified (the default)≤ frequencies and phases are corrected to be in + //! agreement and to match as nearly as possible the resampled phases. + //! + //! The timing envelope represents a warping of the time axis that is + //! applied during resampling. The Breakpoint times in resampled Partials + //! will a comprise contiguous sequence of all integer multiples of the + //! sampling interval between the first and last breakpoints in the timing + //! envelope, and each Breakpoint will represent the parameters of the + //! original Partial at the time that is the value of the timing envelope + //! at that instant. + //! + //! Resampling is performed in-place. + //! + //! \param begin is the beginning of the range of Partials to resample + //! \param end is (one-past) the end of the range of Partials to resample + //! \param timingEnv is the timing envelope, a map of Breakpoint + //! times in resampled Partials onto parameter sampling + //! instants in the original Partials. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void resample( Iter begin, Iter end, const LinearEnvelope & timingEnv ) const; +#else + inline + void resample( PartialList::iterator begin, PartialList::iterator end, + const LinearEnvelope & timingEnv) const; +#endif + + //! Quantize all Partials in the specified (half-open) range. + //! Each Breakpoint in the Partials is replaced by a Breakpoint + //! constructed by resampling the Partial at the nearest + //! integer multiple of the of the resampling interval. + //! + //! \param begin is the beginning of the range of Partials to quantize + //! \param end is (one-past) the end of the range of Partials to quantize + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void quantize( Iter begin, Iter end ) const; +#else + inline + void quantize( PartialList::iterator begin, PartialList::iterator end ) const; +#endif + +// -- static members -- + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Resampler using the specified resampling + //! interval, and use it to channelize a sequence of Partials. + //! + //! \param begin is the beginning of a sequence of Partials to + //! resample. + //! \param end is the end of a sequence of Partials to + //! resample. + //! \param sampleInterval is the resampling interval in seconds, + //! Breakpoint data is computed at integer multiples of + //! sampleInterval seconds. + //! \param denseResampling is a boolean flag indicating that dense + //! resamping (Breakpoint at every integer multiple of the + //! resampling interval) should be performed. If false (the + //! default), sparse resampling (Breakpoints only at multiples + //! of the resampling interval near Breakpoint times in the + //! original Partial) is performed. + //! \throw InvalidArgument if sampleInterval is not positive. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + static + void resample( Iter begin, Iter end, double sampleInterval, + bool denseResampling = false ); +#else + static inline + void resample( PartialList::iterator begin, PartialList::iterator end, + double sampleInterval, bool denseResampling = false ); +#endif + +// --- instance variables --- +private: + + //! the resampling interval in seconds + double interval_; + + //! boolean flag selecting phase-corrected resampling + //! (default is true) + bool phaseCorrect_; + +}; // end of class Resampler + +// --------------------------------------------------------------------------- +// resample (sequence of Partials) +// --------------------------------------------------------------------------- +//! Resample all Partials in the specified (half-open) range using this +//! Resampler's stored sampling interval, so that the Breakpoints in +//! the Partial envelopes will all lie on a common temporal grid. +//! The Breakpoint times in the resampled Partial will comprise a +//! contiguous sequence of integer multiples of the sampling interval, +//! beginning with the multiple nearest to the Partial's start time and +//! ending with the multiple nearest to the Partial's end time. Resampling +//! is performed in-place. +//! +//! \param begin is the beginning of the range of Partials to resample +//! \param end is (one-past) the end of the range of Partials to resample +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Resampler::resample( Iter begin, Iter end ) const +#else +inline +void Resampler::resample( PartialList::iterator begin, PartialList::iterator end ) const +#endif +{ + while ( begin != end ) + { + resample( *begin++ ); + } +} + +// --------------------------------------------------------------------------- +// resample (sequence of Partials, with timing envelope) +// --------------------------------------------------------------------------- +//! Resample all Partials in the specified (half-open) range using this +//! Resampler's stored sampling interval, so that the Breakpoints in +//! the Partial envelopes will all lie on a common temporal grid. +//! The Breakpoint times in the resampled Partial will comprise a +//! contiguous sequence of integer multiples of the sampling interval, +//! beginning with the multiple nearest to the Partial's start time and +//! ending with the multiple nearest to the Partial's end time. Resampling +//! is performed in-place. +//! +//! \param begin is the beginning of the range of Partials to resample +//! \param end is (one-past) the end of the range of Partials to resample +//! \param timingEnv is the timing envelope, a map of Breakpoint +//! times in resampled Partials onto parameter sampling +//! instants in the original Partials. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Resampler::resample( Iter begin, Iter end, const LinearEnvelope & timingEnv ) const +#else +inline +void Resampler::resample( PartialList::iterator begin, PartialList::iterator end, + const LinearEnvelope & timingEnv ) const +#endif +{ + while ( begin != end ) + { + resample( *begin++, timingEnv ); + } +} + +// --------------------------------------------------------------------------- +// quantize (sequence of Partials) +// --------------------------------------------------------------------------- +//! Quantize all Partials in the specified (half-open) range. +//! Each Breakpoint in the Partials is replaced by a Breakpoint +//! constructed by resampling the Partial at the nearest +//! integer multiple of the of the resampling interval. +//! +//! \param begin is the beginning of the range of Partials to quantize +//! \param end is (one-past) the end of the range of Partials to quantize +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void Resampler::quantize( Iter begin, Iter end ) const +#else +inline +void Resampler::quantize( PartialList::iterator begin, PartialList::iterator end ) const +#endif +{ + while ( begin != end ) + { + quantize( *begin++ ); + } +} + + +// --------------------------------------------------------------------------- +// resample (static) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! phase-correct resampling to a sequence of Partials. +//! Construct a Resampler using the specified resampling +//! interval, and use it to channelize a sequence of Partials. +//! +//! \param begin is the beginning of a sequence of Partials to +//! resample. +//! \param end is the end of a sequence of Partials to +//! resample. +//! \param sampleInterval is the resampling interval in seconds, +//! Breakpoint data is computed at integer multiples of +//! sampleInterval seconds. +//! \param denseResampling is a boolean flag indicating that dense +//! resamping (Breakpoint at every integer multiple of the +//! resampling interval) should be performed. If false (the +//! default), sparse resampling (Breakpoints only at multiples +//! of the resampling interval near Breakpoint times in the +//! original Partial) is performed. +//! \throw InvalidArgument if sampleInterval is not positive. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Resampler::resample( Iter begin, Iter end, double sampleInterval, + bool denseResampling ) +#else +inline +void Resampler::resample( PartialList::iterator begin, PartialList::iterator end, + double sampleInterval, bool denseResampling ) +#endif +{ + Resampler instance( sampleInterval ); + + if ( denseResampling ) + { + instance.resample( begin, end ); + } + else + { + instance.quantize( begin, end ); + } +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_RESAMPLER_H */ + diff --git a/src/loris/SdifFile.C b/src/loris/SdifFile.C new file mode 100644 index 0000000..9ca879e --- /dev/null +++ b/src/loris/SdifFile.C @@ -0,0 +1,2176 @@ +/* + * 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 + * + * + * SdifFile.C + * + * Implementation of class SdifFile, which reads and writes SDIF files. + * + * Lippold Haken, 4 July 2000, using CNMAT SDIF library + * Lippold Haken, 20 October 2000, using IRCAM SDIF library (tutorial by Diemo Schwarz) + * Lippold Haken, 22 December 2000, using 1LBL frames + * Lippold Haken, 27 March 2001, write only 7-column 1TRC, combine reading and writing classes + * Lippold Haken, 31 Jan 2002, write either 4-column 1TRC or 6-column RBEP + * Lippold Haken, 20 Apr 2004, back to using CNMAT SDIF library + * Lippold Haken, 06 Oct 2004, write 64-bit float files, read both 32-bit and 64-bit float + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +/* + +Portions of this code are from the CNMAT SDIF library. + +Copyright (c) 1996. 1997, 1998, 1999. The Regents of the University of California +(Regents). All Rights Reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation, without fee and without a signed licensing agreement, is hereby +granted, provided that the above copyright notice, this paragraph and the +following two paragraphs appear in all copies, modifications, and +distributions. Contact The Office of Technology Licensing, UC Berkeley, 2150 +Shattuck Avenue, Suite 510, Berkeley, CA 94720-1620, (510) 643-7201, for +commercial licensing opportunities. + +Written by Matt Wright, Amar Chaudhary, and Sami Khoury, The Center for New +Music and Audio Technologies, University of California, Berkeley. + + IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, + SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST + PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING + DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". + REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + ENHANCEMENTS, OR MODIFICATIONS. + +SDIF spec: http://www.cnmat.berkeley.edu/SDIF/ + +*/ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "SdifFile.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialPtrs.h" + +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <list> +#include <string> +#include <vector> + +#if HAVE_M_PI + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +using namespace std; + +// begin namespace +namespace Loris { + +// -- CNMAT SDIF definitions -- +// --------------------------------------------------------------------------- +// CNMAT SDIF types +// --------------------------------------------------------------------------- + +// try to use the information gathered by configure -- if not using +// config.h, then pick some (hopefully-) reasonable values for +// these things and hope for the best... +#if ! defined( SIZEOF_SHORT ) +#define SIZEOF_SHORT 2 +#endif + +#if ! defined( SIZEOF_INT ) +#define SIZEOF_INT 4 +#endif + +#if ! defined( SIZEOF_LONG ) +#define SIZEOF_LONG 4 +#endif + +#if defined(SIZEOF_SHORT) && (SIZEOF_SHORT == 2) +typedef unsigned short sdif_unicode; +typedef short sdif_int16; +#elif defined(SIZEOF_INT) && (SIZEOF_INT == 2) +typedef unsigned int sdif_unicode; +typedef int sdif_int16; +#else +#error "SdifFile.C: cannot identify a two-byte integer type, define SIZEOF_SHORT or SIZEOF_INT" +#endif + +#if defined(SIZEOF_INT) && (SIZEOF_INT == 4) +typedef int sdif_int32; +typedef unsigned int sdif_uint32; +#elif defined(SIZEOF_LONG) && (SIZEOF_LONG == 4) +typedef long sdif_int32; +typedef unsigned long sdif_uint32; +#else +#error "SdifFile.C: cannot identify a four-byte integer type, define SIZEOF_INT or SIZEOF_LONG" +#endif + +// It is probably an unnecessary nuisance to check these, since there's +// no alternative type to try. Requiring builders to define these +// symbols doesn't really serve any purpose. C++ doesn't provide a +// standard way to find a data type having a particular size +// (as stdint.h does for interger types in C99). +/* +#if SIZEOF_FLOAT == 4 +typedef float sdif_float32; +#else +#error "SdifFile.C: cannot identify a four-byte floating-point type" +#endif + +#if SIZEOF_DOUBLE == 8 +typedef double sdif_float64; +#else +#error "SdifFile.C: cannot identify a eight-byte floating-point type" +#endif +*/ +typedef float sdif_float32; +typedef double sdif_float64; + + + +// --------------------------------------------------------------------------- +// SDIF_GlobalHeader +// --------------------------------------------------------------------------- +typedef struct { + char SDIF[4]; /* must be 'S', 'D', 'I', 'F' */ + sdif_int32 size; /* size of header frame, not including SDIF or size. */ + sdif_int32 SDIFversion; + sdif_int32 SDIFStandardTypesVersion; +} SDIF_GlobalHeader; + +// --------------------------------------------------------------------------- +// SDIF_FrameHeader +// --------------------------------------------------------------------------- +typedef struct { + char frameType[4]; /* should be a registered frame type */ + sdif_int32 size; /* # bytes in this frame, not including + frameType or size */ + sdif_float64 time; /* time corresponding to frame */ + sdif_int32 streamID; /* frames that go together have the same ID */ + sdif_int32 matrixCount; /* number of matrices in frame */ +} SDIF_FrameHeader; + + +// --------------------------------------------------------------------------- +// SDIF_MatrixHeader +// --------------------------------------------------------------------------- +typedef struct { + char matrixType[4]; + sdif_int32 matrixDataType; + sdif_int32 rowCount; + sdif_int32 columnCount; +} SDIF_MatrixHeader; + + +/* Version numbers for SDIF_GlobalHeader associated with this library */ +#define SDIF_SPEC_VERSION 3 +#define SDIF_LIBRARY_VERSION 1 + +// --------------------------------------------------------------------------- +// Enumerations for type definitions in matrices. +// --------------------------------------------------------------------------- +typedef enum { + SDIF_FLOAT32 = 0x0004, + SDIF_FLOAT64 = 0x0008, + SDIF_INT16 = 0x0102, + SDIF_INT32 = 0x0104, + SDIF_INT64 = 0x0108, + SDIF_UINT32 = 0x0204, + SDIF_UTF8 = 0x0301, + SDIF_BYTE = 0x0401, + SDIF_NO_TYPE = -1 +} SDIF_MatrixDataType; + +typedef enum { + SDIF_FLOAT = 0, + SDIF_INT = 1, + SDIF_UINT = 2, + SDIF_TEXT = 3, + SDIF_ARBITRARY = 4 +} SDIF_MatrixDataTypeHighOrder; + +/* SDIF_GetMatrixDataTypeSize -- + Find the size in bytes of the data type indicated by "d" */ +#define SDIF_GetMatrixDataTypeSize(d) ((d) & 0xff) + +// -- CNMAT SDIF errors -- +// --------------------------------------------------------------------------- +// CNMAT SDIF error handling machinery. +// --------------------------------------------------------------------------- +typedef enum { + ESDIF_SUCCESS=0, + ESDIF_SEE_ERRNO=1, + ESDIF_BAD_SDIF_HEADER=2, + ESDIF_BAD_FRAME_HEADER=3, + ESDIF_SKIP_FAILED=4, + ESDIF_BAD_MATRIX_DATA_TYPE=5, + ESDIF_BAD_SIZEOF=6, + ESDIF_END_OF_DATA=7, /* Not necessarily an error */ + ESDIF_BAD_MATRIX_HEADER=8, + ESDIF_OBSOLETE_FILE_VERSION=9, + ESDIF_OBSOLETE_TYPES_VERSION=10, + ESDIF_WRITE_FAILED=11, + ESDIF_READ_FAILED=12, + ESDIF_OUT_OF_MEMORY=13, /* Used only by sdif-mem.c */ + ESDIF_DUPLICATE_MATRIX_TYPE_IN_FRAME=14 +} SDIFresult; +static const char *error_string_array[] = { + "Everything's cool", + "This program should display strerror(errno) instead of this string", + "Bad SDIF header", + "Frame header's size is too low for time tag and stream ID", + "fseek() failed while skipping over data", + "Unknown matrix data type encountered in SDIF_WriteFrame().", + (char *) NULL, /* this will be set by SizeofSanityCheck() */ + "End of data", + "Bad SDIF matrix header", + "Obsolete SDIF file from an old version of SDIF", + "Obsolete version of the standard SDIF frame and matrix types", + "I/O error: couldn't write", + "I/O error: couldn't read", + "Out of memory", + "Frame has two matrices with the same MatrixType" +}; + +// -- CNMAT SDIF endian -- +// --------------------------------------------------------------------------- +// CNMAT SDIF little endian machinery. +// --------------------------------------------------------------------------- + +// WORDS_BIGENDIAN is defined (or not) in config.h, determined +// at configure-time, changed from test of LITTLE_ENDIAN +// which might be erroneously defined in some standard header. + +// If we didn't run configure, try to make a good guess. +#if !(HAVE_CONFIG_H) && !defined(WORDS_BIGENDIAN) + #if (defined(__ppc__) || defined(__ppc64__)) + #define WORDS_BIGENDIAN 1 + #else + #undef WORDS_BIGENDIAN + #endif +#endif + +#if !defined(WORDS_BIGENDIAN) +#define BUFSIZE 4096 +static char p[BUFSIZE]; +#endif + + +static SDIFresult SDIF_Write1(const void *block, size_t n, FILE *f) { + return (fwrite (block,1,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +} + + +static SDIFresult SDIF_Write2(const void *block, size_t n, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + const char *q = (const char *)block; + int i, m = 2*n; + + if ((n << 1) > BUFSIZE) { + /* Too big for buffer */ + int num = BUFSIZE >> 1; + if (r = SDIF_Write2(block, num, f)) return r; + return SDIF_Write2(((char *) block) + (num<<1), n-num, f); + } + + for (i = 0; i < m; i += 2) { + p[i] = q[i+1]; + p[i+1] = q[i]; + } + + return (fwrite(p,2,n,f)==n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; + +#else + return (fwrite (block,2,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +#endif +} + + + +static SDIFresult SDIF_Write4(const void *block, size_t n, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + const char *q = (const char *)block; + int i, m = 4*n; + + if ((n << 2) > BUFSIZE) + { + int num = BUFSIZE >> 2; + if (r = SDIF_Write4(block, num, f)) return r; + return SDIF_Write4(((char *) block) + (num<<2), n-num, f); + } + + for (i = 0; i < m; i += 4) + { + p[i] = q[i+3]; + p[i+3] = q[i]; + p[i+1] = q[i+2]; + p[i+2] = q[i+1]; + } + + return (fwrite(p,4,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +#else + return (fwrite(block,4,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +#endif +} + + + +static SDIFresult SDIF_Write8(const void *block, size_t n, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + const char *q = (const char *)block; + int i, m = 8*n; + + if ((n << 3) > BUFSIZE) { + int num = BUFSIZE >> 3; + if (r = SDIF_Write8(block, num, f)) return r; + return SDIF_Write8(((char *) block) + (num<<3), n-num, f); + } + + for (i = 0; i < m; i += 8) { + p[i] = q[i+7]; + p[i+7] = q[i]; + p[i+1] = q[i+6]; + p[i+6] = q[i+1]; + p[i+2] = q[i+5]; + p[i+5] = q[i+2]; + p[i+3] = q[i+4]; + p[i+4] = q[i+3]; + } + + return (fwrite(p,8,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +#else + return (fwrite(block,8,n,f) == n) ? ESDIF_SUCCESS : ESDIF_WRITE_FAILED; +#endif +} + + +static SDIFresult SDIF_Read1(void *block, size_t n, FILE *f) { + return (fread (block,1,n,f) == n) ? ESDIF_SUCCESS : ESDIF_READ_FAILED; +} + + +static SDIFresult SDIF_Read2(void *block, size_t n, FILE *f) { + +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + char *q = (char *)block; + int i, m = 2*n; + + if ((n << 1) > BUFSIZE) { + int num = BUFSIZE >> 1; + if (r = SDIF_Read2(block, num, f)) return r; + return SDIF_Read2(((char *) block) + (num<<1), n-num, f); + } + + if (fread(p,2,n,f) != n) return ESDIF_READ_FAILED; + + for (i = 0; i < m; i += 2) { + q[i] = p[i+1]; + q[i+1] = p[i]; + } + + return ESDIF_SUCCESS; +#else + return (fread(block,2,n,f) == n) ? ESDIF_SUCCESS : ESDIF_READ_FAILED; +#endif + +} + + +static SDIFresult SDIF_Read4(void *block, size_t n, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + char *q = (char *)block; + int i, m = 4*n; + + if ((n << 2) > BUFSIZE) { + int num = BUFSIZE >> 2; + if (r = SDIF_Read4(block, num, f)) return r; + return SDIF_Read4(((char *) block) + (num<<2), n-num, f); + } + + if (fread(p,4,n,f) != n) return ESDIF_READ_FAILED; + + for (i = 0; i < m; i += 4) { + q[i] = p[i+3]; + q[i+3] = p[i]; + q[i+1] = p[i+2]; + q[i+2] = p[i+1]; + } + + return ESDIF_SUCCESS; + +#else + return (fread(block,4,n,f) == n) ? ESDIF_SUCCESS : ESDIF_READ_FAILED; +#endif + +} + + +static SDIFresult SDIF_Read8(void *block, size_t n, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + char *q = (char *)block; + int i, m = 8*n; + + if ((n << 3) > BUFSIZE) { + int num = BUFSIZE >> 3; + if (r = SDIF_Read8(block, num, f)) return r; + return SDIF_Read8(((char *) block) + (num<<3), n-num, f); + } + + if (fread(p,8,n,f) != n) return ESDIF_READ_FAILED; + + for (i = 0; i < m; i += 8) { + q[i] = p[i+7]; + q[i+7] = p[i]; + q[i+1] = p[i+6]; + q[i+6] = p[i+1]; + q[i+2] = p[i+5]; + q[i+5] = p[i+2]; + q[i+3] = p[i+4]; + q[i+4] = p[i+3]; + } + + return ESDIF_SUCCESS; + +#else + return (fread(block,8,n,f) == n) ? ESDIF_SUCCESS : ESDIF_READ_FAILED; +#endif +} + +// -- CNMAT SDIF intialization -- +// --------------------------------------------------------------------------- +// CNMAT SDIF initialization. +// --------------------------------------------------------------------------- +static int SizeofSanityCheck(void) { + int OK = 1; + static char errorMessage[sizeof("sizeof(sdif_float64) is 999!!!")]; + + if (sizeof(sdif_int16) != 2) { + sprintf(errorMessage, "sizeof(sdif_int16) is %d!", (int)sizeof(sdif_int16)); + OK = 0; + } + + if (sizeof(sdif_int32) != 4) { + sprintf(errorMessage, "sizeof(sdif_int32) is %d!", (int)sizeof(sdif_int32)); + OK = 0; + } + + if (sizeof(sdif_float32) != 4) { + sprintf(errorMessage, "sizeof(sdif_float32) is %d!", (int)sizeof(sdif_float32)); + OK = 0; + } + + if (sizeof(sdif_float64) != 8) { + sprintf(errorMessage, "sizeof(sdif_float64) is %d!", (int)sizeof(sdif_float64)); + OK = 0; + } + + if (!OK) { + error_string_array[ESDIF_BAD_SIZEOF] = errorMessage; + } + return OK; +} + + + +static SDIFresult SDIF_Init(void) { + if (!SizeofSanityCheck()) { + return ESDIF_BAD_SIZEOF; + } + return ESDIF_SUCCESS; +} + +// -- CNMAT SDIF frame header -- +// --------------------------------------------------------------------------- +// CNMAT SDIF frame headers. +// --------------------------------------------------------------------------- +static void SDIF_Copy4Bytes(char *target, const char *string) { + target[0] = string[0]; + target[1] = string[1]; + target[2] = string[2]; + target[3] = string[3]; +} + +static int SDIF_Char4Eq(const char *ths, const char *that) { + return ths[0] == that[0] && ths[1] == that[1] && + ths[2] == that[2] && ths[3] == that[3]; +} + +static void SDIF_FillGlobalHeader(SDIF_GlobalHeader *h) { + SDIF_Copy4Bytes(h->SDIF, "SDIF"); + h->size = 8; + h->SDIFversion = SDIF_SPEC_VERSION; + h->SDIFStandardTypesVersion = SDIF_LIBRARY_VERSION; +} + +static SDIFresult SDIF_WriteGlobalHeader(const SDIF_GlobalHeader *h, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + if (r = SDIF_Write1(&(h->SDIF), 4, f)) return r; + if (r = SDIF_Write4(&(h->size), 1, f)) return r; + if (r = SDIF_Write4(&(h->SDIFversion), 1, f)) return r; + if (r = SDIF_Write4(&(h->SDIFStandardTypesVersion), 1, f)) return r; + return ESDIF_SUCCESS; +#else + + return (fwrite(h, sizeof(*h), 1, f) == 1) ?ESDIF_SUCCESS:ESDIF_WRITE_FAILED; + +#endif +} + +static SDIFresult SDIF_ReadFrameHeader(SDIF_FrameHeader *fh, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + + if (SDIF_Read1(&(fh->frameType),4,f)) { + if (feof(f)) { + return ESDIF_END_OF_DATA; + } + return ESDIF_READ_FAILED; + } + if (r = SDIF_Read4(&(fh->size),1,f)) return r; + if (r = SDIF_Read8(&(fh->time),1,f)) return r; + if (r = SDIF_Read4(&(fh->streamID),1,f)) return r; + if (r = SDIF_Read4(&(fh->matrixCount),1,f)) return r; + return ESDIF_SUCCESS; +#else + size_t amount_read; + + amount_read = fread(fh, sizeof(*fh), 1, f); + if (amount_read == 1) return ESDIF_SUCCESS; + if (amount_read == 0) { + /* Now that fread failed, maybe we're at EOF. */ + if (feof(f)) { + return ESDIF_END_OF_DATA; + } + } + return ESDIF_READ_FAILED; +#endif /* ! WORDS_BIGENDIAN */ +} + + +static SDIFresult SDIF_WriteFrameHeader(const SDIF_FrameHeader *fh, FILE *f) { + +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + + if (r = SDIF_Write1(&(fh->frameType),4,f)) return r; + if (r = SDIF_Write4(&(fh->size),1,f)) return r; + if (r = SDIF_Write8(&(fh->time),1,f)) return r; + if (r = SDIF_Write4(&(fh->streamID),1,f)) return r; + if (r = SDIF_Write4(&(fh->matrixCount),1,f)) return r; +#ifdef __WIN32__ + fflush(f); +#endif + return ESDIF_SUCCESS; +#else + + return (fwrite(fh, sizeof(*fh), 1, f) == 1)?ESDIF_SUCCESS:ESDIF_WRITE_FAILED; + +#endif +} + +static SDIFresult SkipBytes(FILE *f, int bytesToSkip) { +#ifdef STREAMING + /* Can't fseek in a stream, so waste some time needlessly copying + some bytes in memory */ + { +#define BLOCK_SIZE 1024 + char buf[BLOCK_SIZE]; + while (bytesToSkip > BLOCK_SIZE) + { + if (fread (buf, BLOCK_SIZE, 1, f) != 1) + { + return ESDIF_READ_FAILED; + } + bytesToSkip -= BLOCK_SIZE; + } + + if (fread (buf, bytesToSkip, 1, f) != 1) + { + return ESDIF_READ_FAILED; + } + } +#else + /* More efficient implementation */ + if (fseek(f, bytesToSkip, SEEK_CUR) != 0) + { + return ESDIF_SKIP_FAILED; + } +#endif + return ESDIF_SUCCESS; +} + +static SDIFresult SDIF_SkipFrame(const SDIF_FrameHeader *head, FILE *f) { + /* The header's size count includes the 8-byte time tag, 4-byte + stream ID and 4-byte matrix count that we already read. */ + int bytesToSkip = head->size - 16; + + if (bytesToSkip < 0) { + return ESDIF_BAD_FRAME_HEADER; + } + + return SkipBytes(f, bytesToSkip); +} + +// -- CNMAT SDIF matrix header -- +// --------------------------------------------------------------------------- +// CNMAT SDIF matrix headers. +// --------------------------------------------------------------------------- +static SDIFresult SDIF_ReadMatrixHeader(SDIF_MatrixHeader *m, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + if (r = SDIF_Read1(&(m->matrixType),4,f)) return r; + if (r = SDIF_Read4(&(m->matrixDataType),1,f)) return r; + if (r = SDIF_Read4(&(m->rowCount),1,f)) return r; + if (r = SDIF_Read4(&(m->columnCount),1,f)) return r; + return ESDIF_SUCCESS; +#else + if (fread(m, sizeof(*m), 1, f) == 1) { + return ESDIF_SUCCESS; + } else { + return ESDIF_READ_FAILED; + } +#endif + +} + +static SDIFresult SDIF_WriteMatrixHeader(const SDIF_MatrixHeader *m, FILE *f) { +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + if (r = SDIF_Write1(&(m->matrixType),4,f)) return r; + if (r = SDIF_Write4(&(m->matrixDataType),1,f)) return r; + if (r = SDIF_Write4(&(m->rowCount),1,f)) return r; + if (r = SDIF_Write4(&(m->columnCount),1,f)) return r; + return ESDIF_SUCCESS; +#else + return (fwrite(m, sizeof(*m), 1, f) == 1) ? ESDIF_SUCCESS:ESDIF_READ_FAILED; +#endif +} + + +static int SDIF_GetMatrixDataSize(const SDIF_MatrixHeader *m) { + int size; + size = SDIF_GetMatrixDataTypeSize(m->matrixDataType) * + m->rowCount * m->columnCount; + + if ((size % 8) != 0) { + size += (8 - (size % 8)); + } + + return size; +} + +static int SDIF_PaddingRequired(const SDIF_MatrixHeader *m) { + int size; + size = SDIF_GetMatrixDataTypeSize(m->matrixDataType) * + m->rowCount * m->columnCount; + + if ((size % 8) != 0) { + return (8 - (size % 8)); + } else { + return 0; + } +} + +// -- CNMAT SDIF matrix data -- +// --------------------------------------------------------------------------- +// CNMAT SDIF matrix data. +// --------------------------------------------------------------------------- +static SDIFresult SDIF_SkipMatrix(const SDIF_MatrixHeader *head, FILE *f) { + int size = SDIF_GetMatrixDataSize(head); + + if (size < 0) { + return ESDIF_BAD_MATRIX_HEADER; + } + + return SkipBytes(f, size); +} + + +static SDIFresult SDIF_WriteMatrixPadding(FILE *f, const SDIF_MatrixHeader *head) { + int paddingBytes; + sdif_int32 paddingBuffer[2] = {0,0}; + SDIFresult r; + + paddingBytes = SDIF_PaddingRequired(head); + if ((r = SDIF_Write1(paddingBuffer, paddingBytes, f))) return r; + + return ESDIF_SUCCESS; +} + + +static SDIFresult SDIF_WriteMatrixData(FILE *f, const SDIF_MatrixHeader *head, void *data) { + size_t datumSize = (size_t) SDIF_GetMatrixDataTypeSize(head->matrixDataType); + size_t numItems = (size_t) (head->rowCount * head->columnCount); + +#if !defined(WORDS_BIGENDIAN) + SDIFresult r; + switch (datumSize) { + case 1: + if (r = SDIF_Write1(data, numItems, f)) return r; + break; + case 2: + if (r = SDIF_Write2(data, numItems, f)) return r; + break; + case 4: + if (r = SDIF_Write4(data, numItems, f)) return r; + break; + case 8: + if (r = SDIF_Write8(data, numItems, f)) return r; + break; + default: + return ESDIF_BAD_MATRIX_DATA_TYPE; + } +#else + if (fwrite(data, datumSize, numItems, f) != numItems) { + return ESDIF_READ_FAILED; + } +#endif + + /* Handle padding */ + return SDIF_WriteMatrixPadding(f, head); +} + +// -- CNMAT SDIF open and close -- +// --------------------------------------------------------------------------- +// CNMAT SDIF file open and close. +// --------------------------------------------------------------------------- + +static SDIFresult SDIF_BeginWrite(FILE *output) { + SDIF_GlobalHeader h; + + SDIF_FillGlobalHeader(&h); + return SDIF_WriteGlobalHeader(&h, output); +} + +static SDIFresult SDIF_OpenWrite(const char *filename, FILE **resultp) { + FILE *result; + SDIFresult r; + + if ((result = fopen(filename, "wb")) == NULL) + { + return ESDIF_SEE_ERRNO; + } + if ((r = SDIF_BeginWrite(result))) + { + fclose(result); + return r; + } + *resultp = result; + return ESDIF_SUCCESS; +} + +static SDIFresult SDIF_CloseWrite(FILE *f) { + fflush(f); + if (fclose(f) == 0) { + return ESDIF_SUCCESS; + } else { + return ESDIF_SEE_ERRNO; + } +} + +static SDIFresult SDIF_BeginRead(FILE *input) { + SDIF_GlobalHeader sgh; + SDIFresult r; + + /* make sure the header is OK. */ + if ((r = SDIF_Read1(sgh.SDIF, 4, input))) return r; + if (!SDIF_Char4Eq(sgh.SDIF, "SDIF")) return ESDIF_BAD_SDIF_HEADER; + if ((r = SDIF_Read4(&sgh.size, 1, input))) return r; + if (sgh.size % 8 != 0) return ESDIF_BAD_SDIF_HEADER; + if (sgh.size < 8) return ESDIF_BAD_SDIF_HEADER; + if ((r = SDIF_Read4(&sgh.SDIFversion, 1, input))) return r; + if ((r = SDIF_Read4(&sgh.SDIFStandardTypesVersion, 1, input))) return r; + + if (sgh.SDIFversion < 3) { + return ESDIF_OBSOLETE_FILE_VERSION; + } + + if (sgh.SDIFStandardTypesVersion < 1) { + return ESDIF_OBSOLETE_TYPES_VERSION; + } + + /* skip size-8 bytes. (We already read the first two version numbers, + but maybe there's more data in the header frame.) */ + + if (sgh.size == 8) { + return ESDIF_SUCCESS; + } + + if (SkipBytes(input, sgh.size-8)) { + return ESDIF_BAD_SDIF_HEADER; + } + + return ESDIF_SUCCESS; +} + +static SDIFresult SDIF_OpenRead(const char *filename, FILE **resultp) { + FILE *result = NULL; + SDIFresult r; + + if ((result = fopen(filename, "rb")) == NULL) { + return ESDIF_SEE_ERRNO; + } + + if ((r = SDIF_BeginRead(result))) { + fclose(result); + return r; + } + + *resultp = result; + return ESDIF_SUCCESS; +} + +static SDIFresult SDIF_CloseRead(FILE *f) { + if (fclose(f) == 0) { + return ESDIF_SUCCESS; + } else { + return ESDIF_SEE_ERRNO; + } +} + +// -- construction -- + +// --------------------------------------------------------------------------- +// SdifFile construction helpers +// --------------------------------------------------------------------------- + +// import_sdif reads SDIF data from the specified file path and +// stores data in its PartialList and MarkerContainer arguments. +static void import_sdif( const std::string &, SdifFile::partials_type &, + SdifFile::markers_type & ); + +// export_sdif writes the data in its PartialList and MarkerContainer +// arguments to a specified SDIF file path. Writes bandwidth-enhanced +// Partials if enhanced is true, otherwise writes sinusoidal partials. +static void export_sdif( const std::string &, const SdifFile::partials_type &, + const SdifFile::markers_type &, bool enhanced ); + +// --------------------------------------------------------------------------- +// SdifFile constructor from filename +// --------------------------------------------------------------------------- +// Initialize an instance of SdifFile by importing Partial data from +// from the file having the specified filename or path. +// +SdifFile::SdifFile( const std::string & filename ) +{ + import_sdif( filename, partials_, markers_ ); +} + +// --------------------------------------------------------------------------- +// SdifFile constructor, empty +// --------------------------------------------------------------------------- +// Initialize an empty instance of SdifFile having no Partials. +// +SdifFile::SdifFile( void ) +{ +} + +// -- access -- +// --------------------------------------------------------------------------- +// markers +// --------------------------------------------------------------------------- +// Return a reference to the MarkerContainer (see Marker.h) for this SdifFile. +SdifFile::markers_type & SdifFile::markers( void ) +{ + return markers_; +} + +const SdifFile::markers_type & SdifFile::markers( void ) const +{ + return markers_; +} + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +// Return a reference (or const reference) to the bandwidth-enhanced +// Partials represented by the envelope parameter streams in this SdifFile. +SdifFile::partials_type & SdifFile::partials( void ) +{ + return partials_; +} + +const SdifFile::partials_type & SdifFile::partials( void ) const +{ + return partials_; +} + +// -- mutation -- +// --------------------------------------------------------------------------- +// addPartial +// --------------------------------------------------------------------------- +// Add a copy of the specified Partial to this SdifFile. +// +// This member exists only for consistency with other File I/O +// classes in Loris. The same operation can be achieved by directly +// accessing the PartialList. +// +void SdifFile::addPartial( const Loris::Partial & p ) +{ + partials_.push_back( p ); +} + +// --------------------------------------------------------------------------- +// write (to path) +// --------------------------------------------------------------------------- +// Export the envelope Partials represented by this SdifFile to +// the file having the specified filename or path. +// +void SdifFile::write( const std::string & path ) +{ + export_sdif( path, partials_, markers_, true ); +} + +// --------------------------------------------------------------------------- +// write (to path) +// --------------------------------------------------------------------------- +// Export the envelope Partials represented by this SdifFile to +// the file having the specified filename or path in the 1TRC +// format, resampled, and without phase or bandwidth information. +// +void SdifFile::write1TRC( const std::string & path ) +{ + export_sdif( path, partials_, markers_, false ); +} + + +// -- Loris SDIF definitions -- +// --------------------------------------------------------------------------- +// Loris SDIF types +// --------------------------------------------------------------------------- +// Row of matrix data in SDIF RBEP, 1TRC, or RBEL format. +// +// The RBEP matrices are for reassigned bandwidth enhanced partials (in 6 columns). +// The 1TRC matrices are for sine-only partials (in 4 columns). +// The first four columns of an RBEP matrix correspond to the 4 columns in 1TRC. +// In the past, Loris exported a 7-column 1TRC; this is no longer exported, but can be imported. +// +// The RBEL format always has two columns, index and partial label. +// The RBEL matrix is optional; it has partial label information (in 2 columns). +int lorisRowMaxElements = 7; +int lorisRowEnhancedElements = 6; +int lorisRowSineOnlyElements = 4; + +typedef struct { + sdif_float64 index, freqOrLabel, amp, phase, noise, timeOffset, resampledFlag; +} RowOfLorisData64; + +typedef struct { + sdif_float32 index, freqOrLabel, amp, phase, noise, timeOffset, resampledFlag; +} RowOfLorisData32; + + +// SDIF signatures used by Loris. +typedef char sdif_signature[4]; +static sdif_signature lorisEnhancedSignature = { 'R','B','E','P' }; +static sdif_signature lorisLabelsSignature = { 'R','B','E','L' }; +static sdif_signature lorisSineOnlySignature = { '1','T','R','C' }; +static sdif_signature lorisMarkersSignature = { 'R','B','E','M' }; + + +// Exception class for handling errors in SDIF library: +class SdifLibraryError : public FileIOException +{ +public: + SdifLibraryError( const std::string & str, const std::string & where = "" ) : + FileIOException( std::string("SDIF library error -- ").append( str ), where ) {} +}; // end of class SdifLibraryError + +// macro to check for SDIF library errors and throw exceptions when +// they occur, which we really ought to do after every SDIF library +// call: +#define ThrowIfSdifError( errNum, report ) \ + if (errNum) \ + { \ + const char* errPtr = error_string_array[errNum]; \ + if (errPtr) \ + { \ + debugger << "SDIF error " << errPtr << endl; \ + std::string s(report); \ + s.append(", SDIF error message: "); \ + s.append(errPtr); \ + Throw( SdifLibraryError, s ); \ + } \ + } + +// -- SDIF reading helpers -- +// --------------------------------------------------------------------------- +// processRow64 +// --------------------------------------------------------------------------- +// Add to existing Loris partials, or create new Loris partials for this data. +// +static void +processRow64( const sdif_signature msig, const RowOfLorisData64 & rowData, const double frameTime, + std::vector< Partial > & partialsVector ) +{ + +// +// Skip this if the data point is not from the original data (7-column 1TRC format). +// + if (rowData.resampledFlag) + return; + +// +// Make sure we have enough partials for this partial's index. +// + if (partialsVector.size() <= rowData.index) + { + partialsVector.resize( long(rowData.index) + 500 ); + } + +// +// Create a new breakpoint and insert it. +// + if (SDIF_Char4Eq(msig, lorisEnhancedSignature) || SDIF_Char4Eq(msig, lorisSineOnlySignature)) + { + Breakpoint newbp( rowData.freqOrLabel, rowData.amp, rowData.noise, rowData.phase ); + partialsVector[long(rowData.index)].insert( frameTime + rowData.timeOffset, newbp ); + } +// +// Set partial label. +// + else if (SDIF_Char4Eq(msig, lorisLabelsSignature)) + { + partialsVector[long(rowData.index)].setLabel( (int) rowData.freqOrLabel ); + } + +} + +// --------------------------------------------------------------------------- +// processRow32 +// --------------------------------------------------------------------------- +// Add to existing Loris partials, or create new Loris partials for this data. +// This is for reading 32-bit float files. +// +static void +processRow32( const sdif_signature msig, const RowOfLorisData32 & rowData, const double frameTime, + std::vector< Partial > & partialsVector ) +{ + +// +// Skip this if the data point is not from the original data (7-column 1TRC format). +// + if (rowData.resampledFlag) + return; + +// +// Make sure we have enough partials for this partial's index. +// + if (partialsVector.size() <= rowData.index) + { + partialsVector.resize( long(rowData.index) + 500 ); + } + +// +// Create a new breakpoint and insert it. +// + if (SDIF_Char4Eq(msig, lorisEnhancedSignature) || SDIF_Char4Eq(msig, lorisSineOnlySignature)) + { + Breakpoint newbp( rowData.freqOrLabel, rowData.amp, rowData.noise, rowData.phase ); + partialsVector[long(rowData.index)].insert( frameTime + rowData.timeOffset, newbp ); + } +// +// Set partial label. +// + else if (SDIF_Char4Eq(msig, lorisLabelsSignature)) + { + partialsVector[long(rowData.index)].setLabel( (int) rowData.freqOrLabel ); + } + +} + +// --------------------------------------------------------------------------- +// readMarkers +// --------------------------------------------------------------------------- +// +static void +readMarkers( FILE * file, SDIF_FrameHeader fh, SdifFile::markers_type & markersVector ) +{ +// +// Read Loris markers from SDIF file in a RBEM frame. +// This precedes the envelope data in the file. +// Let exceptions propagate. +// + SDIFresult ret; + int cols = 1; +// +// The frame must contain exactly two matrices. +// + if (fh.matrixCount != 2) + { + Throw( FileIOException, "Markers frame has bad format." ); + } + +// +// Read the numeric (marker times) matrix. +// + { + SDIF_MatrixHeader mh; + ret = SDIF_ReadMatrixHeader(&mh,file); + ThrowIfSdifError( ret, "Error reading SDIF file" ); + + // Error if matrix has unexpected data type. + if ((mh.matrixDataType != SDIF_FLOAT32 && mh.matrixDataType != SDIF_FLOAT64) || mh.columnCount != cols) + { + Throw( FileIOException, "Markers frame has bad format." ); + } + + // Read each row of matrix data. + for (int row = 0; row < mh.rowCount; row++) + { + if (mh.matrixDataType == SDIF_FLOAT64) + { + sdif_float64 markerTime64; + SDIF_Read8(&markerTime64,1,file); + markersVector.push_back(Marker(markerTime64, "")); + } + else + { + sdif_float32 markerTime32; + SDIF_Read4(&markerTime32,1,file); + markersVector.push_back(Marker(markerTime32, "")); + } + } + + // Skip over padding, if any. + if ((mh.matrixDataType == SDIF_FLOAT32) && ((mh.rowCount * mh.columnCount) & 0x1)) + { + sdif_float32 pad; + SDIF_Read4(&pad,1,file); + } + } + +// +// Read the string (marker names) matrix. +// + { + SDIF_MatrixHeader mh; + ret = SDIF_ReadMatrixHeader(&mh,file); + ThrowIfSdifError( ret, "Error reading SDIF file" ); + + // Error if matrix has unexpected data type. + if (mh.matrixDataType != SDIF_UTF8 || mh.columnCount != cols) + { + Throw( FileIOException, "Markers frame has bad format." ); + } + + // Read strings. + std::string markerName; + int markerNumber = 0; + for (int row = 0; row < mh.rowCount; row++) + { + char ch; + SDIF_Read1(&ch,1,file); + + // If we have reached the end of a name, assign it to a marker. + if (ch == '\0') + { + // Save the name of the marker. + markersVector[markerNumber].setName(markerName); + + // Prepare to get name of next marker. + markerNumber++; + if (markerNumber > markersVector.size()) + { + Throw( FileIOException, "Markers frame has bad format." ); + } + markerName.erase(); + } + else + { + markerName += ch; + } + } + + // There should be one marker name for each marker time. + if (markerNumber != markersVector.size()) + { + Throw( FileIOException, "Markers frame has bad format." ); + } + + // Skip padding. + ret = SkipBytes(file, SDIF_PaddingRequired(&mh)); + } +} + +// --------------------------------------------------------------------------- +// readLorisMatrices +// --------------------------------------------------------------------------- +// Let exceptions propagate. +// +static void +readLorisMatrices( FILE *file, std::vector< Partial > & partialsVector, SdifFile::markers_type & markersVector ) +{ + SDIFresult ret; + +// +// Read all frames matching the file selection. +// + SDIF_FrameHeader fh; + while (!(ret = SDIF_ReadFrameHeader(&fh, file))) + { + + // Check for Loris Markers frame. + if (SDIF_Char4Eq(fh.frameType, lorisMarkersSignature)) + { + readMarkers( file, fh, markersVector ); + continue; + } + + // Skip frames until we find one we are interested in. + if (!SDIF_Char4Eq(fh.frameType, lorisEnhancedSignature) + && !SDIF_Char4Eq(fh.frameType, lorisSineOnlySignature) + && !SDIF_Char4Eq(fh.frameType, lorisLabelsSignature)) + { + ret = SDIF_SkipFrame(&fh, file); + ThrowIfSdifError( ret, "Error reading SDIF file" ); + continue; + } + + + // Read all matrices in this frame. + for (int m = 0; m < fh.matrixCount; m++) + { + SDIF_MatrixHeader mh; + ret = SDIF_ReadMatrixHeader(&mh,file); + ThrowIfSdifError( ret, "Error reading SDIF file" ); + + // Skip matrix if it has unexpected data type. + if ((mh.matrixDataType != SDIF_FLOAT32 && mh.matrixDataType != SDIF_FLOAT64) + || mh.columnCount > lorisRowMaxElements) + { + ret = SDIF_SkipMatrix(&mh, file); + ThrowIfSdifError( ret, "Error reading SDIF file" ); + continue; + } + + // Read each row of matrix data. + for (int row = 0; row < mh.rowCount; row++) + { + + if (mh.matrixDataType == SDIF_FLOAT64) + { + // Fill a rowData structure with one row from the matrix. + RowOfLorisData64 rowData64 = { 0.0 }; + sdif_float64 *rowDataPtr = &rowData64.index; + for (int col = 1; col <= mh.columnCount; col++) + { + SDIF_Read8(rowDataPtr++,1,file); + } + + // Add rowData as a new breakpoint in a partial, or, + // if its a RBEL matrix, read label mapping. + processRow64(mh.matrixType, rowData64, fh.time, partialsVector); + } + else + { + // Fill a rowData structure with one row from the matrix. + RowOfLorisData32 rowData32 = { 0.0 }; + sdif_float32 *rowDataPtr = &rowData32.index; + for (int col = 1; col <= mh.columnCount; col++) + { + SDIF_Read4(rowDataPtr++,1,file); + } + + // Add rowData as a new breakpoint in a partial, or, + // if its a RBEL matrix, read label mapping. + processRow32(mh.matrixType, rowData32, fh.time, partialsVector); + } + } + + // Skip over padding, if any. + if ((mh.matrixDataType == SDIF_FLOAT32) && ((mh.rowCount * mh.columnCount) & 0x1)) + { + sdif_float32 pad; + SDIF_Read4(&pad,1,file); + } + } + } + + // At this point, ret should be ESDIF_END_OF_DATA. + if (ret != ESDIF_END_OF_DATA) + ThrowIfSdifError( ret, "Error reading SDIF file" ); +} + +// --------------------------------------------------------------------------- +// read +// --------------------------------------------------------------------------- +// Let exceptions propagate. +// +static void import_sdif( const std::string &infilename, + SdifFile::partials_type & partials, + SdifFile::markers_type & markers) +{ + +// +// Initialize CNMSAT SDIF routines. +// + SDIFresult ret = SDIF_Init(); + if (ret) + { + Throw( FileIOException, "Could not initialize SDIF routines." ); + } + +// +// Open SDIF file for reading. +// Note: Currently we do not specify any selection criterion in this call. +// + FILE *file; + ret = SDIF_OpenRead(infilename.c_str(), &file); + if (ret) + { + Throw( FileIOException, "Could not open SDIF file for reading." ); + } + +// +// Read SDIF data. +// + try + { + + // Build up partialsVector. + std::vector< Partial > partialsVector; + SdifFile::markers_type markersVector; + readLorisMatrices( file, partialsVector, markersVector ); + + // Copy partialsVector to partials list. + for (int i = 0; i < partialsVector.size(); ++i) + { + if (partialsVector[i].numBreakpoints() > 0) + { + partials.push_back( partialsVector[i] ); + } + } + + // Copy markersVector to markers list. + for (int i = 0; i < markersVector.size(); ++i) + { + markers.push_back( markersVector[i] ); + } + } + catch ( Exception & ex ) + { + partials.clear(); + markers.clear(); + ex.append(" Failed to read SDIF file."); + SDIF_CloseRead(file); + throw; + } + +// +// Close SDIF input file. +// + SDIF_CloseRead(file); + +// +// Complain if no Partials were imported: +// + if ( partials.size() == 0 ) + { + notifier << "No Partials were imported from " << infilename + << ", no (non-empty) SDIF frames found." << endl; + } + +} + +// -- SDIF writing helpers -- +// --------------------------------------------------------------------------- +// makeSortedBreakpointTimes +// --------------------------------------------------------------------------- +// Collect the times of all breakpoints in the analysis, and sort by time. +// Sorted breakpoints are used in finding frame start times in SDIF writing. +// +struct BreakpointTime +{ + long index; // index identifying which partial has the breakpoint + double time; // time of the breakpoint +}; + +struct earlier_time +{ + bool operator()( const BreakpointTime & lhs, const BreakpointTime & rhs ) const + { return lhs.time < rhs.time; } +}; + +static void +makeSortedBreakpointTimes( const ConstPartialPtrs & partialsVector, + std::list< BreakpointTime > & allBreakpoints ) +{ + +// Make list of all breakpoint times from all partials. + for (int i = 0; i < partialsVector.size(); i++) + { + for ( Partial::const_iterator it = partialsVector[i]->begin(); + it != partialsVector[i]->end(); + ++it ) + { + BreakpointTime bpt; + bpt.index = i; + bpt.time = it.time(); + allBreakpoints.push_back( bpt ); + } + } + +// Sort list of all breakpoint times. + allBreakpoints.sort( earlier_time() ); +} + +// --------------------------------------------------------------------------- +// getNextFrameTime +// --------------------------------------------------------------------------- +// Get time of next frame. +// This helps make SDIF files with exact timing (7-column 1TRC format). +// This uses the previously sorted allBreakpoints list. +// +// All Breakpoints should be const, but for some reason, gcc (on SGI at +// least) makes trouble converting and comparing iterators and const_iterators. +// +static double getNextFrameTime( const double frameTime, + std::list< BreakpointTime > & allBreakpoints, + std::list< BreakpointTime >::iterator & bpTimeIter) +{ +// +// Build up vector of partials that have a breakpoint in this frame, update the vector +// as we increase the frame duration. Return when a partial gets a second breakpoint. +// +// This vector is only used locally. We search this vector of indices to determine +// whether or not a Partial has already contributed a Breakpoint to the current +// frame. +// + double nextFrameTime = frameTime; + std::vector< long > partialsWithBreakpointsInFrame; + + // const std::list< BreakpointTime >::iterator & first = bpTimeIter; + + // invariant: + // Breakpoints in allBreakpoints before the position + // of bpTimeIter have be added to a SDIF frame, either + // the current one or an earlier one. If it is not + // equal to bpTimeIter, then all Breakpoints between + // those two positions have the same time. + std::list< BreakpointTime >::iterator it = bpTimeIter; + while ( it != allBreakpoints.end() && + ( std::find( partialsWithBreakpointsInFrame.begin(), + partialsWithBreakpointsInFrame.end(), + it->index ) == + partialsWithBreakpointsInFrame.end() ) ) + { + // Add breakpoint to list of potential breakpoints for frame, + // then iterate to soonest breakpoint on any partial. The final decision + // to add this breakpoint to the frame is made below, if bpTimeIter is + // updated. + partialsWithBreakpointsInFrame.push_back( it->index ); + + + // If the new breakpoint is at a new time, it could potentially be the + // first breakpoint in the next frame. If there are several breakpoints at + // the exact same time (could happen if these envelopes came from a spc + // file or from resampled envelopes), always start the frame at the first + // of these. Set bpTimeIter if this is a good start of a new frame. + // + // Don't want to increment bpTimeIter until we are certain that all + // coincident Breakpoints can be added to the current frame (that is, + // that none of them are from Partials that already have a Breakpoint + // in this frame). + // + // epsilon controls how close together in time two breakpoints can be. + // Keep this large enough that double-precision floating point math + // can find a time between two breakpoints close in time. One nanosecond + // ought to be plenty close. + ++it; + const double epsilon = 1e-9; + if ( ( it == allBreakpoints.end() ) || ( (it->time - bpTimeIter->time) > epsilon ) ) + { + bpTimeIter = it; + } + } + + if ( bpTimeIter == allBreakpoints.end() ) + { + // We are at the end of the sound; no "next frame" there, + // set the next frame time to something later than the last + // Breakpoint and the current frame time (the current frame + // might be empty, so have to check both). + nextFrameTime = std::max( (double)allBreakpoints.back().time, frameTime ) + 1; + } + else + { + Assert( bpTimeIter != allBreakpoints.begin() ); + + // Compute the next frame time: + // If possible, round it to the nearest millisecond before + // the first Breakpoint in the next frame, otherwise just + // pick a time between the last Breakpoint in the current + // frame and the first Breakpoint in the next. + std::list< BreakpointTime >::iterator prev = bpTimeIter; + --prev; + + // prev and bpTimeIter cannot have the same time, because + // if there are several Breakpoints at the same time, bpTimeIter + // will be the first of them in the list: + Assert( bpTimeIter->time > prev->time ); + + // This seems to be sensitive to floating point error, + // probably because times are stored in 32 bit floats. + // We need the error to round toward the later time. + // + // Note: times are no longer stored in 32 bit floats, + // why is this still so flakey? + nextFrameTime = bpTimeIter->time - ( 0.5 * ( bpTimeIter->time - prev->time ) ); + /* + notifier << "next frame time " << nextFrameTime << endl; + notifier << " bp time " << bpTimeIter->time << endl; + notifier << " prev time " << prev->time << endl; + notifier << " diff = " << bpTimeIter->time - prev->time << endl; + */ + Assert( bpTimeIter->time >= nextFrameTime ); + Assert( nextFrameTime > prev->time ); + + // Try to make frame times whole milliseconds. + // MUST use 32-bit floats for time, or else floating + // point rounding errors cause us to drop breakpoints! + double nextFramePrevRnd = 0.001 * std::floor( 1000. * nextFrameTime ); + if ( ( nextFramePrevRnd < nextFrameTime ) && ( nextFramePrevRnd > prev->time ) ) + { + nextFrameTime = nextFramePrevRnd; + } + else + { + // Try tenth-milliseconds, otherwise give up. + nextFramePrevRnd = 0.0001 * std::floor( 10000. * nextFrameTime ); + if ( ( nextFramePrevRnd < nextFrameTime ) && ( nextFramePrevRnd > prev->time ) ) + { + nextFrameTime = nextFramePrevRnd; + } + } + } + + // notifier << " returning next frame time " << nextFrameTime << endl; + +#if Debug_Loris + if ( ! ( nextFrameTime > frameTime ) ) + { + if ( bpTimeIter != allBreakpoints.end() ) + { + std::cout << bpTimeIter->time << std::endl; + } + else + { + std::cout << "end" << std::endl; + } + // std::cout << first->time << ", index " << first->index << std::endl; + std::cout << nextFrameTime << std::endl; + std::cout << frameTime << std::endl; + std::cout << partialsWithBreakpointsInFrame.size() << std::endl; + } + Assert( nextFrameTime > frameTime ); +#endif + return nextFrameTime; +} + + +// --------------------------------------------------------------------------- +// indexPartials +// --------------------------------------------------------------------------- +// Make a vector of partial pointers. +// The vector index will be the sdif 1TRC index for the partial. +// +static void +indexPartials( const PartialList & partials, ConstPartialPtrs & partialsVector ) +{ + for ( PartialList::const_iterator it = partials.begin(); it != partials.end(); ++it ) + { + if ( it->size() != 0 ) + { + partialsVector.push_back( (Partial *)&(*it) ); //@@@ Kluge Here (Partial *) + // partialsVector.push_back( &(*it) ); + } + } +} + + +// --------------------------------------------------------------------------- +// collectActiveIndices +// --------------------------------------------------------------------------- +// Collect all partials active in a particular frame. +// +// Return true if frameTime is beyond end of all the partials. +// Don't need to return this, can just check frame time against +// the time of the last BreakpointTime in the allBreakpoints vector. +// +static void +collectActiveIndices( const ConstPartialPtrs & partialsVector, + const bool enhanced, + const double frameTime, + const double nextFrameTime, + std::vector< int > & activeIndices ) +{ +#if 1 //Debug_Loris + if ( ! ( nextFrameTime > frameTime ) ) + { + std::cout << nextFrameTime << " <= " << frameTime << std:: endl; + //std::cout << "amp 128 : " << partialsVector[128]->amplitudeAt( frameTime ) << std::endl; + + } +#endif + Assert( nextFrameTime > frameTime ); + + for ( int i = 0; i < partialsVector.size(); i++ ) + { + Assert( partialsVector[ i ] != 0 ); + + const Partial & mightBeActive = *( partialsVector[ i ] ); + + // Is there a breakpoint within the frame? + // Skip the partial if there is no breakpoint and either: + // (1) we are writing enhanced format, + // or (2) the partial has zero amplitude. + // + // Include this Partial if: + // (1) it has a Breakpoint in the frame, or + // (2A) we are not writing enhanced data, and + // (2B) the Partial has non-zero amplitude at the time of + // this frame. + // + Partial::const_iterator it = mightBeActive.findAfter( frameTime ); + if ( it != mightBeActive.end() ) + { +#if Debug_Loris + // DEBUGGING + // if this one is in this frame, then + // the one before it had better be in the previous frame! + if ( ( it != mightBeActive.begin() ) && ( it.time() < nextFrameTime ) ) + { + Partial::const_iterator prev = it; + --prev; + Assert( prev.time() < frameTime ); + } + Assert( it != mightBeActive.end() ); +#endif + + // mightBeActive is active in this frame if the Breakpoint + // at it is earlier than the next frame time. + // + // 1TRC (non-enhanced) contains data for every non-silent + // active Partial at the time of the frame. + if ( ( it.time() < nextFrameTime ) || + ( !enhanced && mightBeActive.amplitudeAt( frameTime ) != 0.0 ) ) + { + activeIndices.push_back( i ); + } + } + + } +} + +// --------------------------------------------------------------------------- +// writeEnvelopeLabels +// --------------------------------------------------------------------------- +// +static void +writeEnvelopeLabels( FILE * out, const ConstPartialPtrs & partialsVector ) +{ +// +// Write Loris labels to SDIF file in a RBEL matrix. +// This precedes the 1TRC data in the file. +// Let exceptions propagate. +// + + int streamID = 2; // stream id different from envelope's stream id + double frameTime = 0.0; + +// +// Allocate RBEL matrix data. +// + int cols = 2; + sdif_float64 *data = new sdif_float64[ partialsVector.size() * cols ]; + +// +// For each partial index, specify the partial label. +// + sdif_float64 *dp = data; + int anyLabel = false; + for (int i = 0; i < partialsVector.size(); i++) + { + int labl = partialsVector[i]->label(); + anyLabel |= (labl != 0); + *dp++ = i; // column 1: index + *dp++ = labl; // column 2: label + } + +// +// Write out matrix data, if there were any labels. +// + if (anyLabel) + { + // Write the frame header. + SDIF_FrameHeader fh; + SDIF_Copy4Bytes(fh.frameType, lorisLabelsSignature); + fh.size = + // size of remaining frame header + sizeof(sdif_float64) + 2 * sizeof(sdif_int32) + // size of matrix header + + sizeof(SDIF_MatrixHeader) + // size of matrix data plus any padding + + 8 * ((partialsVector.size() * cols * sizeof(sdif_float64) + 7) / 8); + fh.time = frameTime; + fh.streamID = streamID; + fh.matrixCount = 1; + SDIFresult ret = SDIF_WriteFrameHeader(&fh, out); + + // Write the matrix header. + SDIF_MatrixHeader mh; + SDIF_Copy4Bytes(mh.matrixType, lorisLabelsSignature); + mh.matrixDataType = SDIF_FLOAT64; + mh.rowCount = partialsVector.size(); + mh.columnCount = cols; + ret = SDIF_WriteMatrixHeader(&mh, out); + + // Write the matrix data, and any necessary padding. + ret = SDIF_WriteMatrixData(out, &mh, data); + } + +// +// Free RBEL matrix space. +// + delete [] data; +} + +// --------------------------------------------------------------------------- +// writeMarkers +// --------------------------------------------------------------------------- +// +static void +writeMarkers( FILE * out, const SdifFile::markers_type &markers ) +{ +// +// Write Loris markers to SDIF file in a RBEM frame. +// This precedes the envelope data in the file. +// Let exceptions propagate. +// + +// +// Exit if there are no markers. +// + if ( markers.empty() ) + { + return; + } + + int streamID = 2; // stream id different from envelope's stream id + double frameTime = 0.0; + +// +// We will need two matrices: one numeric (marker times) matrix data and character (marker names) matrix. +// + std::vector<sdif_float64> markerTimes; + std::string markerNames; + +// +// Get matrix data from each marker. +// + for (int marker = 0; marker < markers.size(); marker++) + { + markerTimes.push_back( markers[marker].time() ); + markerNames += markers[marker].name() + '\0'; + } + +// +// Write out frame with two marker matrices. +// + // Write the frame header. + int cols = 1; + { + SDIF_FrameHeader fh; + SDIF_Copy4Bytes( fh.frameType, lorisMarkersSignature ); + fh.size = + // size of remaining frame header + sizeof( sdif_float64 ) + 2 * sizeof( sdif_int32 ) + // size of matrix headers + + 2 * sizeof( SDIF_MatrixHeader ) + // size of numeric (time) matrix data + + markerTimes.size() * sizeof( sdif_float64 ) + // size of marker names data plus padding + + 8 * ( ( markerNames.size() + 7 ) / 8 ); + fh.time = frameTime; + fh.streamID = streamID; + fh.matrixCount = 2; + // SDIFresult ret = return value never checked! + SDIF_WriteFrameHeader(&fh, out); + } + + // Write the numeric (marker times) matrix. + { + // Write the numeric (time) matrix header. + SDIF_MatrixHeader mh; + SDIF_Copy4Bytes( mh.matrixType, lorisMarkersSignature ); + mh.matrixDataType = SDIF_FLOAT64; + mh.rowCount = markerTimes.size(); + mh.columnCount = cols; + SDIFresult ret = SDIF_WriteMatrixHeader( &mh, out ); + + // Write the numeric (time) matrix data, and any necessary padding. + ret = SDIF_WriteMatrixData( out, &mh, &markerTimes[0] ); + } + + // Write the string (marker names) matrix. + { + // Write the string (names) matrix header. + SDIF_MatrixHeader mh; + SDIF_Copy4Bytes( mh.matrixType, lorisMarkersSignature ); + mh.matrixDataType = SDIF_UTF8; + mh.rowCount = markerNames.size(); + mh.columnCount = cols; + SDIFresult ret = SDIF_WriteMatrixHeader( &mh, out ); + + // Write the string (names) matrix data, and any necessary padding. + ret = SDIF_WriteMatrixData( out, &mh, &markerNames[0] ); + } +} + + +// --------------------------------------------------------------------------- +// assembleMatrixData +// --------------------------------------------------------------------------- +// The activeIndices vector contains indices for partials that have data at this time. +// Assemble SDIF matrix data for these partials. +// +static void +assembleMatrixData( sdif_float64 *data, const bool enhanced, + const ConstPartialPtrs & partialsVector, + const std::vector< int > & activeIndices, + const double frameTime ) +{ + // The array matrix data is row-major order at "data". + sdif_float64 *rowDataPtr = data; + + for ( int i = 0; i < activeIndices.size(); i++ ) + { + + int index = activeIndices[ i ]; + const Partial * par = partialsVector[ index ]; + + // For enhanced format we use exact timing; the activeIndices only includes + // partials that have breakpoints in this frame. + // For sine-only format we resample at frame times, for enhanced, use + // the Breakpoints themselves. + Assert( par->endTime() >= frameTime ); + double tim = frameTime; + Breakpoint params; + if ( enhanced ) + { + Partial::const_iterator pos = par->findAfter( frameTime ); + tim = pos.time(); + params = pos.breakpoint(); + } + else + { + params = par->parametersAt( frameTime ); + } + + // Must have phase between 0 and 2*Pi. + double phas = params.phase(); + if (phas < 0) + { + phas += 2. * Pi; + } + + // Fill in values for this row of matrix data. + *rowDataPtr++ = index; // first row of matrix (standard) + *rowDataPtr++ = params.frequency(); // second row of matrix (standard) + *rowDataPtr++ = params.amplitude(); // third row of matrix (standard) + *rowDataPtr++ = phas; // fourth row of matrix (standard) + if (enhanced) + { + *rowDataPtr++ = params.bandwidth(); // fifth row of matrix (loris) + *rowDataPtr++ = tim - frameTime; // sixth row of matrix (loris) + } + } +} + + +// --------------------------------------------------------------------------- +// writeEnvelopeData +// --------------------------------------------------------------------------- +// +static void +writeEnvelopeData( FILE * out, + const ConstPartialPtrs & partialsVector, + const bool enhanced ) +{ +// +// Export SDIF file from Loris data. +// Let exceptions propagate. +// + + int streamID = 1; // one stream id for all SDIF frames + +// +// Make a sorted list of all breakpoints in all partials, and initialize the list iterater. +// This stuff does nothing if we are writing 5-column 1TRC format. +// + std::list< BreakpointTime > allBreakpoints; + makeSortedBreakpointTimes( partialsVector, allBreakpoints ); + std::list< BreakpointTime >::iterator bpTimeIter = allBreakpoints.begin(); + +#if Debug_Loris + const std::list< BreakpointTime >::size_type DEBUG_allBreakpointsSize = allBreakpoints.size(); + std::list< BreakpointTime >::size_type DEBUG_cumNumTracks = 0; +#endif + +// +// Output Loris envelope data in SDIF frame format. +// First frame starts at millisecond of first breakpoint. +// + double nextFrameTime = allBreakpoints.front().time; + if ( 1000. * nextFrameTime - int( 1000. * nextFrameTime ) != 0. ) + { + // HEY! Looks like this could give negative frame times, + // is that allowed? + nextFrameTime = std::floor( 1000. * nextFrameTime - .001 ) / 1000.0; + } + + do + { + +// +// Go to next frame. +// + double frameTime = nextFrameTime; + nextFrameTime = getNextFrameTime( frameTime, allBreakpoints, bpTimeIter ); + +// +// Make a vector of partial indices that includes all partials active at this time. +// + std::vector< int > activeIndices; + collectActiveIndices( partialsVector, enhanced, frameTime, + nextFrameTime, activeIndices ); + +// +// Write frame header, matrix header, and matrix data. +// We always have one matrix per frame. +// The matrix size depends on the number of partials active at this time. +// + int numTracks = activeIndices.size(); + +#if Debug_Loris + DEBUG_cumNumTracks += numTracks; +#endif + + if ( numTracks > 0 ) // could activeIndices ever be empty? + { + + // Allocate matrix data. + int cols = ( enhanced ? lorisRowEnhancedElements : lorisRowSineOnlyElements ); + + // I think this will go a lot faster if we aren't doing so much + // dynamic memory allocation for each frame. if I use a vector, + // then we only need to allocate more memory when a frame has more + // columns than any previous frame. Construct the vector once, + // resize it for each frame, and clear it when done (doesn't + // deallocate memory). + // sdif_float64 *data = new sdif_float64[numTracks * cols]; + static std::vector< sdif_float64 > dataVector; + dataVector.resize( numTracks * cols ); + + // Fill in matrix data. + sdif_float64 *data = &dataVector[ 0 ]; + assembleMatrixData( data, enhanced, partialsVector, activeIndices, frameTime ); + + // Write the frame header. + SDIF_FrameHeader fh; + SDIF_Copy4Bytes( fh.frameType, enhanced ? lorisEnhancedSignature : lorisSineOnlySignature ); + fh.size = + // size of remaining frame header + sizeof(sdif_float64) + 2 * sizeof(sdif_int32) + // size of matrix header + + sizeof(SDIF_MatrixHeader) + // size of matrix data plus any padding + + 8*((numTracks * cols * sizeof(sdif_int32) + 7)/8); + fh.streamID = streamID; + fh.time = frameTime; + fh.matrixCount = 1; + SDIFresult ret = SDIF_WriteFrameHeader(&fh, out); + + // Write the matrix header. + SDIF_MatrixHeader mh; + SDIF_Copy4Bytes( mh.matrixType, enhanced ? lorisEnhancedSignature : lorisSineOnlySignature ); + mh.matrixDataType = SDIF_FLOAT64; + mh.rowCount = numTracks; + mh.columnCount = cols; + ret = SDIF_WriteMatrixHeader( &mh, out ); + + // Write the matrix data, and any necessary padding. + ret = SDIF_WriteMatrixData( out, &mh, data ); + + // Free matrix space. + // delete [] data; + // Instead of deallocating, just clear the static + // vector, setting its logical size to zero. + dataVector.clear(); + } + } + while ( nextFrameTime < allBreakpoints.back().time ); + +#if Debug_Loris + std::cout << "SDIF export found " << DEBUG_allBreakpointsSize << " Breakpoints" << std::endl; + std::cout << "and exported " << DEBUG_cumNumTracks << std::endl; +#endif +} + +// --------------------------------------------------------------------------- +// Export +// --------------------------------------------------------------------------- +// Export SDIF file. +// +static void export_sdif( const std::string & filename, + const SdifFile::partials_type & partials, + const SdifFile::markers_type &markers, const bool enhanced ) +{ +// +// Initialize CNMAT SDIF routines. +// + SDIFresult ret = SDIF_Init(); + if (ret) + { + Throw( FileIOException, "Could not initialize SDIF routines." ); + } +// +// Open SDIF file for writing. +// + FILE *out; + ret = SDIF_OpenWrite(filename.c_str(), &out); + if (ret) + { + Throw( FileIOException, "Could not open SDIF file for writing: " + filename ); + } + + // We are no longer defining frame types. + + // // Define RBEP matrix and frame type for enhanced partials. + // if (enhanced) + // { + // SdifMatrixTypeT *parsMatrixType = SdifCreateMatrixType(lorisEnhancedSignature,NULL); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"Index"); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"Frequency"); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"Amplitude"); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"Phase"); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"Noise"); + // SdifMatrixTypeInsertTailColumnDef(parsMatrixType,"TimeOffset"); + // SdifPutMatrixType(out->MatrixTypesTable, parsMatrixType);// + // + // SdifFrameTypeT *parsFrameType = SdifCreateFrameType(lorisEnhancedSignature,NULL); + // SdifFrameTypePutComponent(parsFrameType, lorisEnhancedSignature, "RABWE_Partials"); + // SdifPutFrameType(out->FrameTypesTable, parsFrameType); + // } + // + // // Define RBEL matrix and frame type for labels. + // SdifMatrixTypeT *labelsMatrixType = SdifCreateMatrixType(lorisLabelsSignature,NULL); + // SdifMatrixTypeInsertTailColumnDef(labelsMatrixType,"Index"); + // SdifMatrixTypeInsertTailColumnDef(labelsMatrixType,"Label"); + // SdifPutMatrixType(out->MatrixTypesTable, labelsMatrixType); + // + // SdifFrameTypeT *labelsFrameType = SdifCreateFrameType(lorisLabelsSignature,NULL); + // SdifFrameTypePutComponent(labelsFrameType, lorisLabelsSignature, "RABWE_Labels"); + // SdifPutFrameType(out->FrameTypesTable, labelsFrameType); + // + // // Write file header information + // SdifFWriteGeneralHeader( out ); + // + // // Write ASCII header information + // SdifFWriteAllASCIIChunks( out ); + +// +// Write SDIF data. +// + try + { + // Make vector of pointers to partials. + ConstPartialPtrs partialsVector; + indexPartials( partials, partialsVector ); + + // Write labels. + writeEnvelopeLabels( out, partialsVector ); + + // Write markers. + writeMarkers(out, markers); + + // Write partials to SDIF file. + writeEnvelopeData( out, partialsVector, enhanced ); + } + catch ( Exception & ex ) + { + ex.append( " Failed to write SDIF file." ); + SDIF_CloseWrite( out ); + throw; + } + +// +// Close SDIF input file. +// + SDIF_CloseWrite( out ); +} + +// --------------------------------------------------------------------------- +// Export +// --------------------------------------------------------------------------- +// Legacy static export member. +// +void +SdifFile::Export( const std::string & filename, const PartialList & partials, + const bool enhanced ) +{ + SdifFile fout( partials.begin(), partials.end() ); + if ( enhanced ) + fout.write( filename ); + else + fout.write1TRC( filename ); +} + + +} // end of namespace Loris + + diff --git a/src/loris/SdifFile.h b/src/loris/SdifFile.h new file mode 100644 index 0000000..8408c28 --- /dev/null +++ b/src/loris/SdifFile.h @@ -0,0 +1,245 @@ +/* + * 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 + * + * + * SdifFile.h + * + * Definition of SdifFile class for Partial import and export in Loris. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Marker.h" +#include "Partial.h" +#include "PartialList.h" + +#include <string> +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class SdifFile +// +//! Class SdifFile represents reassigned bandwidth-enhanced Partial +//! data in a SDIF-format data file. Construction of an SdifFile +//! from a stream or filename automatically imports the Partial +//! data. +//! +//! Loris stores partials in SDIF RBEP and RBEL frames. The RBEP and RBEL +//! frame and matrix definitions are included in the SDIF file's header. +//! Each RBEP frame contains one RBEP matrix, and each row in a RBEP matrix +//! describes one breakpoint in a Loris partial. The data in RBEP matrices +//! are SDIF 32-bit floats. +//! +//! The six columns in an RBEP matrix are: partialIndex, frequency, +//! amplitude, phase, noise, timeOffset. The partialIndex uniquely +//! identifies a partial. When Loris exports SDIF data, each partial is +//! assigned a unique partialIndex. The frequency (Hz), amplitude (0..1), +//! phase (radians), and noise (bandwidth) are encoded the same as Loris +//! breakpoints. The timeOffset is an offset from the RBEP frame time, +//! specifying the exact time of the breakpoint. Loris always specifies +//! positive timeOffsets, and the breakpoint's exact time is always be +//! earlier than the next RBEP frame's time. +//! +//! Since reassigned bandwidth-enhanced partial breakpoints are +//! non-uniformly spaced in time, the RBEP frame times are also +//! non-uniformly spaced. Each RBEP frame will contain at most one +//! breakpoint for any given partial. A partial may extend over a RBEP frame +//! and have no breakpoint specified by the RBEP frame, as happens when one +//! active partial has a lower temporal density of breakpoints than other +//! active partials. +//! +//! If partials have nonzero labels in Loris, then a RBEL frame describing +//! the labeling of the partials will precede the first RBEP frame in the +//! SDIF file. The RBEL frame contains a single, two-column RBEL matrix The +//! first column is the partialIndex, and the second column specifies the +//! label for the partial. +//! +//! If markers are associated with the partials in Loris, then a RBEM frame +//! describing the markers will precede the first RBEP frame in the SDIF +//! file. The RBEM frame contains two single-column RBEM matrices. The +//! first matrix contains 32-bit floats indicating the time (in seconds) +//! for each marker. The second matrix contains UTF-8 data, the names of +//! each of the markers separated by the ASCII character 0. +//! +//! In addition to RBEP frames, Loris can also read and write SDIF 1TRC +//! frames (refer to IRCAM's SDIF web site, www.ircam.fr/sdif/, for +//! definitions of standard SDIF description types). Since 1TRC frames do +//! not represent bandwidth-enhancement or the exact timing of Loris +//! breakpoints, their use is not recommended. 1TRC capabilities are +//! provided in Loris to allow interchange with programs that are unable to +//! interpret RBEP frames. +// +class SdifFile +{ +// -- public interface -- +public: + +// -- types -- + + //! The type of marker storage in an SdifFile. + typedef std::vector< Marker > markers_type; + + //! The type of the Partial storage in an AiffFile. + typedef PartialList partials_type; + +// -- construction -- + + //! Initialize an instance of SdifFile by importing Partial data from + //! the file having the specified filename or path. + explicit SdifFile( const std::string & filename ); + + //! Initialize an instance of SdifFile with copies of the Partials + //! on the specified half-open (STL-style) range. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + SdifFile( Iter begin_partials, Iter end_partials ); +#else + SdifFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ); +#endif + + //! Initialize an empty instance of SdifFile having no Partials. + SdifFile( void ); + + // copy, assign, and delete are compiler-generated + +// -- access -- + + //! Return a reference to the Markers (see Marker.h) + //! for this SdifFile. + markers_type & markers( void ); + + //! Return a reference to the Markers (see Marker.h) + //! for this SdifFile. + const markers_type & markers( void ) const; + + //! Return a reference to the bandwidth-enhanced + //! Partials represented by this SdifFile. + partials_type & partials( void ); + + //! Return a reference to the bandwidth-enhanced + //! Partials represented by this SdifFile. + const partials_type & partials( void ) const; +// -- mutation -- + + //! Add a copy of the specified Partial to this SdifFile. + void addPartial( const Loris::Partial & p ); + + //! Add a copy of each Partial on the specified half-open (STL-style) + //! range to this SdifFile. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void addPartials( Iter begin_partials, Iter end_partials ); +#else + void addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ); +#endif + +// -- export -- + + //! Export the envelope Partials represented by this SdifFile to + //! the file having the specified filename or path. + void write( const std::string & path ); + + //! Export the envelope Partials represented by this SdifFile to + //! the file having the specified filename or path in the 1TRC + //! format, resampled, and without phase or bandwidth information. + void write1TRC( const std::string & path ); + +// -- legacy export -- + + //! Export the Partials in the specified PartialList to a SDIF file having + //! the specified file name or path. If enhanced is true (the default), + //! reassigned bandwidth-enhanced Partial data are exported in the + //! six-column RBEP format. Otherwise, the Partial data is exported as + //! resampled sinusoidal analysis data in the 1TRC format. + //! Provided for backwards compatability. + //! + //! \deprecated This function is included only for legacy support + //! and may be removed at any time. + static void Export( const std::string & filename, + const PartialList & plist, + const bool enhanced = true ); + +private: +// -- implementation -- + partials_type partials_; // Partials to store in SDIF format + markers_type markers_; // AIFF Markers + +}; // end of class SdifFile + +// -- template members -- + +// --------------------------------------------------------------------------- +// constructor from Partial range +// --------------------------------------------------------------------------- +// Initialize an instance of SdifFile with copies of the Partials +// on the specified half-open (STL-style) range. +// +// If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +// only PartialList::const_iterator arguments. +// +#if !defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +SdifFile::SdifFile( Iter begin_partials, Iter end_partials ) +#else +SdifFile::SdifFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ) +#endif +{ + addPartials( begin_partials, end_partials ); +} + +// --------------------------------------------------------------------------- +// addPartials +// --------------------------------------------------------------------------- +// Add a copy of each Partial on the specified half-open (STL-style) +// range to this SdifFile. +// +// If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +// only PartialList::const_iterator arguments. +// +#if !defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void SdifFile::addPartials( Iter begin_partials, Iter end_partials ) +#else +void SdifFile::addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ) +#endif +{ + partials_.insert( partials_.end(), begin_partials, end_partials ); +} + +} // end of namespace Loris + diff --git a/src/loris/Sieve.C b/src/loris/Sieve.C new file mode 100644 index 0000000..dc25992 --- /dev/null +++ b/src/loris/Sieve.C @@ -0,0 +1,235 @@ +/* + * 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 + * + * + * Sieve.C + * + * Implementation of class Sieve. + * + * Lippold Haken, 20 Jan 2001 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Sieve.h" +#include "Breakpoint.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include <algorithm> + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// Sieve constructor +// --------------------------------------------------------------------------- +//! Construct a new Sieve using the specified partial fade +//! time. If unspecified, the default fade time (same as the +//! default fade time for the Distiller) is used. +//! +//! \param partialFadeTime is the extra time (in seconds) +//! added to each end of a Partial to accomodate +//! the fade to and from zero amplitude. Fade time +//! must be non-negative. A default value is used +//! if unspecified. +//! \throw InvalidArgument if partialFadeTime is negative. +// +Sieve::Sieve( double partialFadeTime ) : + _fadeTime( partialFadeTime ) +{ + if ( _fadeTime < 0.0 ) + { + Throw( InvalidArgument, "the Partial fade time must be non-negative" ); + } +} + +// Definition of a comparitor for sorting a collection of pointers +// to Partials by label (increasing) and duration (decreasing), so +// that Partial ptrs are arranged by label, with the lowest labels +// first, and then with the longest Partials having each label +// before the shorter ones. +struct SortPartialPtrs : + public std::binary_function< const Partial *, const Partial *, bool > +{ + bool operator()( const Partial * lhs, const Partial * rhs ) const + { + return ( lhs->label() != rhs->label() ) ? + ( lhs->label() < rhs->label() ) : + ( lhs->duration() > rhs->duration() ); + } +}; + +// Definition of predicate for finding the end of a Patial * +// range having a common label. +struct PartialPtrLabelNE : + public std::unary_function< const Partial *, bool > +{ + int label; + PartialPtrLabelNE( int l ) : label(l) {} + + bool operator()( const Partial * p ) const + { return p->label() != label; } +}; + + +// --------------------------------------------------------------------------- +// find_overlapping (template function) +// --------------------------------------------------------------------------- +// Iterate over a range of Partials (presumably) with same labeling. +// The range is specified by iterators over a collection of pointers +// to Partials (not Partials themselves). Return the first position +// in the specified range corresponding to a Partial that overlaps +// (in time) the specified Partial, p, or the end of the range if +// no Partials in the range overlap. +// +// Overlap is defined by the minimum time gap between Partials +// (minGapTime), so Partials that have less then minGapTime +// between them are considered overlapping. +// +template <typename Iter> // Iter is the position of a Partial * +Iter +find_overlapping( Partial & p, double minGapTime, Iter start, Iter end) +{ + for ( Iter it = start; it != end; ++it ) + { + // skip if other partial is already sifted out. + if ( (*it)->label() == 0 ) + continue; + + // skip the source Partial: + // (identity test: compare addresses) + // (this is a sanity check, should not happen since + // src should be at position end) + Assert( (*it) != &p ); + + // test for overlap: + if ( p.startTime() < (*it)->endTime() + minGapTime && + p.endTime() + minGapTime > (*it)->startTime() ) + { + // Does the overlapping Partial have longer duration? + // (this should never be true, since the Partials + // are sorted by duration) + Assert( p.duration() <= (*it)->duration() ); + +#if Debug_Loris + debugger << "Partial starting " << p.startTime() << ", " + << p.begin().breakpoint().frequency() << " ending " + << p.endTime() << ", " << (--p.end()).breakpoint().frequency() + << " zapped by overlapping Partial starting " + << (*it)->startTime() << ", " << (*it)->begin().breakpoint().frequency() + << " ending " << (*it)->endTime() << ", " + << (--(*it)->end()).breakpoint().frequency() << endl; +#endif + return it; + } + } + + // no overlapping Partial found: + return end; +} + +// --------------------------------------------------------------------------- +// sift_ptrs (private helper) +// --------------------------------------------------------------------------- +//! Sift labeled Partials. If any two Partials having the same (non-zero) +//! label overlap in time (where overlap includes the fade time at both +//! ends of each Partial), then set the label of the Partial having the +//! shorter duration to zero. Sifting is performed on a collection of +//! pointers to Partials so that the it can be performed without changing +//! the order of the Partials in the sequence. +//! +//! \param ptrs is a collection of pointers to the Partials in the +//! sequence to be sifted. +void +Sieve::sift_ptrs( PartialPtrs & ptrs ) +{ + // the minimum gap between Partials is twice the + // specified fadeTime: + double minGapTime = _fadeTime * 2.; + + // sort the collection of pointers to Partials: + // (sort is by increasing label, then + // decreasing duration) + std::sort( ptrs.begin(), ptrs.end(), SortPartialPtrs() ); + + PartialPtrs::iterator sift_begin = ptrs.begin(); + PartialPtrs::iterator sift_end = ptrs.end(); + + int zapped = 0; + + // iterate over labels and sift each one: + PartialPtrs::iterator lowerbound = sift_begin; + while ( lowerbound != sift_end ) + { + int label = (*lowerbound)->label(); + + // first the first element in l after sieve_begin + // having a label not equal to 'label': + PartialPtrs::iterator upperbound = + std::find_if( lowerbound, sift_end, PartialPtrLabelNE(label) ); + +#ifdef Debug_Loris + // don't want to compute this iterator distance unless debugging: + debugger << "sifting Partials labeled " << label << endl; + debugger << "Sieve found " << std::distance( lowerbound, upperbound ) << + " Partials labeled " << label << endl; +#endif + // sift all partials with this label, unless the + // label is 0: + if ( label != 0 ) + { + PartialPtrs::iterator it; + for ( it = lowerbound; it != upperbound; ++it ) + { + // find_overlapping only needs to consider Partials on the + // half-open range [lowerbound, it), because all + // Partials after it are shorter, thanks to the + // sorting of the sift_set: + if( it != find_overlapping( **it, minGapTime, lowerbound, it ) ) + { + (*it)->setLabel(0); + ++zapped; + } + } + } + + // advance Partial set iterator: + lowerbound = upperbound; + } + +#ifdef Debug_Loris + debugger << "Sifted out (relabeled) " << zapped << " of " << ptrs.size() << "." << endl; +#endif +} + +} // end of namespace Loris + diff --git a/src/loris/Sieve.h b/src/loris/Sieve.h new file mode 100644 index 0000000..aff3f16 --- /dev/null +++ b/src/loris/Sieve.h @@ -0,0 +1,269 @@ +#ifndef INCLUDE_SIEVE_H +#define INCLUDE_SIEVE_H +/* + * 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 + * + * + * Sieve.h + * + * Definition of class Sieve. + * + * Lippold Haken, 20 Jan 2001 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Distiller.h" // for default fade time and silent time + +#if defined(NO_TEMPLATE_MEMBERS) +#include "PartialList.h" +#endif + +#include "PartialPtrs.h" + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Sieve +// +//! A Sieve eliminating temporal overlap among Partials. +//! +//! Class Sieve represents an algorithm for identifying channelized (see also +//! Channelizer) Partials that overlap in time, and selecting the longer +//! one to represent the channel. The identification of overlap includes +//! the time needed for Partials to fade to and from zero amplitude in +//! synthesis (see also Synthesizer) or distillation. (see also Distiller) +//! +//! In some cases, the energy redistribution effected by the distiller +//! (see also Distiller) is undesirable. In such cases, the partials can be +//! sifted before distillation. The sifting process in Loris identifies +//! all the partials that would be rejected (and converted to noise +//! energy) by the distiller and assigns them a label of 0. These sifted +//! partials can then be identified and treated sepearately or removed +//! altogether, or they can be passed through the distiller unlabeled, and +//! crossfaded in the morphing process (see also Morpher). +//! +//! \sa Channelizer, Distiller, Morpher, Synthesizer +// +class Sieve +{ +// -- instance variables -- + + double _fadeTime; //! extra time (in seconds) added to each end of + //! a Partial when determining overlap, to accomodate + //! the fade to and from zero amplitude. + +// -- public interface -- +public: + +// -- global defaults and constants -- + + enum + { + + //! Default time in milliseconds over which Partials joined by + //! distillation fade to and from zero amplitude. Divide by + //! 1000 to use as a member function parameter. This parameter + //! should be the same in Distiller, Sieve, and Collator. + DefaultFadeTimeMs = Distiller::DefaultFadeTimeMs, + + //! Default minimum duration in milliseconds of the silent + //! (zero-amplitude) gap between two Partials joined by + //! distillation. Divide by 1000 to use as a member function + //! parameter. This parameter should be the same in Distiller, + //! Sieve, and Collator. + DefaultSilentTimeMs = Distiller::DefaultSilentTimeMs + }; + +// -- construction -- + + //! Construct a new Sieve using the specified partial fade + //! time. If unspecified, the default fade time (same as the + //! default fade time for the Distiller) is used. + //! + //! \param partialFadeTime is the extra time (in seconds) + //! added to each end of a Partial to accomodate + //! the fade to and from zero amplitude. Fade time + //! must be non-negative. A default value is used + //! if unspecified. + //! \throw InvalidArgument if partialFadeTime is negative. + explicit Sieve( double partialFadeTime = Sieve::DefaultFadeTimeMs/1000.0 ); + + // Use compiler-generated copy, assign, and destroy. + +// -- sifting -- + + //! Sift labeled Partials on the specified half-open (STL-style) + //! range. If any two Partials having same label overlap in time, keep + //! only the longer of the two Partials. Set the label of the shorter + //! duration partial to zero. No Partials are removed from the + //! sequence and the sequence order is unaltered. + //! + //! \param sift_begin is the beginning of the range of Partials to sift + //! \param sift_end is (one-past) the end of the range of Partials to sift + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then sift_begin and + //! sift_end must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void sift( Iter sift_begin, Iter sift_end ); +#else + inline + void sift( PartialList::iterator sift_begin, PartialList::iterator sift_end ); +#endif + + //! Sift labeled Partials in the specified container + //! If any two Partials having same label overlap in time, keep + //! only the longer of the two Partials. Set the label of the shorter + //! duration partial to zero. No Partials are removed from the + //! container and the container order is unaltered. + //! + //! \param partials is the collection of Partials to sift in-place + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then partials + //! must be a PartialList, otherwise it can be any container type + //! storing Partials that supports at least bidirectional iterators. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Container > + void sift( Container & partials ) +#else + inline + void sift( PartialList & partials ) +#endif + { + sift( partials.begin(), partials.end() ); + } + +// -- static members -- + + //! Static member that constructs an instance and applies + //! it to a sequence of Partials. + //! Construct a Sieve using the specified Partial + //! fade time (in seconds), and use it to sift a + //! sequence of Partials. + //! + //! \param sift_begin is the beginning of the range of Partials to sift + //! \param sift_end is (one-past) the end of the range of Partials to sift + //! \param partialFadeTime is the extra time (in seconds) + //! added to each end of a Partial to accomodate + //! the fade to and from zero amplitude. The Partial fade time + //! must be non-negative. + //! \throw InvalidArgument if partialFadeTime is negative. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + static + void sift( Iter sift_begin, Iter sift_end, + double partialFadeTime ); +#else + static inline + void sift( PartialList::iterator sift_begin, PartialList::iterator sift_end, + double partialFadeTime ); +#endif + +// -- helper -- +private: + + //! Sift labeled Partials. If any two Partials having the same (non-zero) + //! label overlap in time (where overlap includes the fade time at both + //! ends of each Partial), then set the label of the Partial having the + //! shorter duration to zero. Sifting is performed on a collection of + //! pointers to Partials so that the it can be performed without changing + //! the order of the Partials in the sequence. + //! + //! \param ptrs is a collection of pointers to the Partials in the + //! sequence to be sifted. + void sift_ptrs( PartialPtrs & ptrs ); + +}; // end of class Sieve + +// --------------------------------------------------------------------------- +// sift +// --------------------------------------------------------------------------- +//! Sift labeled Partials on the specified half-open (STL-style) +//! range. If any two Partials having same label overlap in time, keep +//! only the longer of the two Partials. Set the label of the shorter +//! duration partial to zero. No Partials are removed from the +//! sequence and the sequence order is unaltered. +//! +//! \param sift_begin is the beginning of the range of Partials to sift +//! \param sift_end is (one-past) the end of the range of Partials to sift +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then sift_begin and +//! sift_end must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Sieve::sift( Iter sift_begin, Iter sift_end ) +#else +inline +void Sieve::sift( PartialList::iterator sift_begin, PartialList::iterator sift_end ) +#endif +{ + PartialPtrs ptrs; + fillPartialPtrs( sift_begin, sift_end, ptrs ); + sift_ptrs( ptrs ); +} + +// --------------------------------------------------------------------------- +// sift (static) +// --------------------------------------------------------------------------- +//! Static member that constructs an instance and applies +//! it to a sequence of Partials. +//! Construct a Sieve using the specified Partial +//! fade time (in seconds), and use it to sift a +//! sequence of Partials. +//! +//! \param sift_begin is the beginning of the range of Partials to sift +//! \param sift_end is (one-past) the end of the range of Partials to sift +//! \param partialFadeTime is the extra time (in seconds) +//! added to each end of a Partial to accomodate +//! the fade to and from zero amplitude. The Partial fade time +//! must be non-negative. +//! \throw InvalidArgument if partialFadeTime is negative. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then begin and end +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +void Sieve::sift( Iter sift_begin, Iter sift_end, + double partialFadeTime ) +#else +inline +void Sieve::sift( PartialList::iterator sift_begin, PartialList::iterator sift_end, + double partialFadeTime ) +#endif +{ + Sieve instance( partialFadeTime ); + instance.sift( sift_begin, sift_end ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_SIEVE_H */ diff --git a/src/loris/SpcFile.C b/src/loris/SpcFile.C new file mode 100644 index 0000000..66a7a17 --- /dev/null +++ b/src/loris/SpcFile.C @@ -0,0 +1,1311 @@ +/* + * 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 + * + * + * SpcFile.C + * + * Implementation of SpcFile class for Partial import and export for + * real-time synthesis in Kyma. + * + * Spc files always represent a number of Partials that is a power of + * two. This is not necessary for purely-sinusoidal files, but might be + * (not clear) for enhanced data to be properly processed in Kyma. + * + * All of this is kind of disgusting right now. This code has evolved + * somewhat randomly, and we are awaiting full support for bandwidth- + * enhanced data in Kyma.. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "SpcFile.h" + +#include "AiffData.h" +#include "Breakpoint.h" +#include "BigEndian.h" +#include "LorisExceptions.h" +#include "Marker.h" +#include "Notifier.h" +#include "PartialUtils.h" + +#include <algorithm> +#include <cmath> +#include <fstream> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + + +// Define this if SpcFiles should always have a number of +// Partials that is a power of two. Cannot decide whether +// or not they should. Lip? +#define PO2 + + +using std::exp; +using std::log; +using std::sqrt; + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// constants, can, or should, these be made variable? +// --------------------------------------------------------------------------- + +// can't we change this? Seems like we could, but +// its part of the size of the magic junk in the +// Sose chunk. +const int LargestLabel = 256; // max number of partials for SPC file + + // 1 Mar 05 + // Found that this cannot actually be 512 for enhanced + // Partials, it crashes with a segmentation fault above 256. + // Unfortunately, whatever is causing the problem is not + // using this constant, but some other hardcoded thing. + // if I change this to 256, then I can still export + // 256 enhanced Partials, but I don't know what to change + // to allow 512! Ugh! + // -kel + + +// this used to be hard coded into Partial, don't know +// whether it is needed to make spc files work. +const double Fade = 0.001; + +// this always has to be 24 bits, 1 channel +const int Bps = 24; +const int Nchans = 1; + + +// --------------------------------------------------------------------------- +// static helper prototypes, defined at bottom +// --------------------------------------------------------------------------- +static void +configureEnvelopeDataCk( SoundDataCk & ck, const SpcFile::partials_type & partials ); + +static void +configureSosMarkerCk( MarkerCk & ck, const std::vector< Marker > & markers ); + +static void +configureSosEnvelopesCk( SosEnvelopesCk & ck ); + +static std::ostream & +writeSosEnvelopesChunk( std::ostream & s, const SosEnvelopesCk & ck ); + +static void +configureExportStruct( const SpcFile::partials_type & partials, double midipitch, + double endApproachTime, int enhanced ); + +static unsigned long getNumSampleFrames( void ); + +const int SpcFile::MinNumPartials = 32; +const double SpcFile::DefaultRate = 44100.; + +// -- construction -- +// --------------------------------------------------------------------------- +// SpcFile constructor from filename +// --------------------------------------------------------------------------- +// Initialize an instance of SpcFile by importing envelope parameter +// streams from the file having the specified filename or path. +// +SpcFile::SpcFile( const std::string & filename ) : + notenum_( 60 ), + rate_( DefaultRate ) +{ + readSpcData( filename ); +} + +// --------------------------------------------------------------------------- +// SpcFile constructor, empty. +// --------------------------------------------------------------------------- +// Initialize an instance of SpcFile having the specified fractional +// MIDI note number, and no Partials (or envelope parameter streams). +// +SpcFile::SpcFile( double midiNoteNum ) : + notenum_( midiNoteNum ), + rate_( DefaultRate ) +{ + growPartials( MinNumPartials ); +} + +// --------------------------------------------------------------------------- +// write +// --------------------------------------------------------------------------- +// Export the phase-correct bandwidth-enhanced envelope parameter +// streams represented by this SpcFile to the file having the specified +// filename or path. +// +// A nonzero endApproachTime indicates that the Partials do not include a +// release or decay, but rather end in a static spectrum corresponding to the +// final Breakpoint values of the partials. The endApproachTime specifies how +// long before the end of the sound the amplitude, frequency, and bandwidth +// values are to be modified to make a gradual transition to the static spectrum. +// +// If the endApproachTime is not specified, it is assumed to be zero, +// corresponding to Partials that decay or release normally. +// +void +SpcFile::write( const std::string & filename, double endApproachTime ) +{ + write( filename, true, endApproachTime ); +} + +// --------------------------------------------------------------------------- +// write +// --------------------------------------------------------------------------- +// Export the pure sinsoidal (omitting phase and bandwidth data) envelope +// parameter streams represented by this SpcFile to the file having the +// specified filename or path. +// +// A nonzero endApproachTime indicates that the Partials do not include a +// release or decay, but rather end in a static spectrum corresponding to the +// final Breakpoint values of the partials. The endApproachTime specifies how +// long before the end of the sound the amplitude, frequency, and bandwidth +// values are to be modified to make a gradual transition to the static spectrum. +// +// If the endApproachTime is not specified, it is assumed to be zero, +// corresponding to Partials that decay or release normally. +// +void +SpcFile::writeSinusoidal( const std::string & filename, double endApproachTime ) +{ + write( filename, false, endApproachTime ); +} + +// --------------------------------------------------------------------------- +// write +// --------------------------------------------------------------------------- +// Export the envelope parameter streams represented by this SpcFile to +// the file having the specified filename or path. Export phase-correct +// bandwidth-enhanced envelope parameter streams if enhanced is true +// (the default), or pure sinsoidal streams otherwise. +// +// +// A nonzero endApproachTime indicates that the Partials do not include a +// release or decay, but rather end in a static spectrum corresponding to the +// final Breakpoint values of the partials. The endApproachTime specifies how +// long before the end of the sound the amplitude, frequency, and bandwidth +// values are to be modified to make a gradual transition to the static spectrum. +// +// If the endApproachTime is not specified, it is assumed to be zero, +// corresponding to Partials that decay or release normally. +// +// This version of write is deprecated, but is handy for internal use, +// called by the above write and writeSiunsoidal members. +// +void +SpcFile::write( const std::string & filename, bool enhanced, double endApproachTime ) +{ + if ( endApproachTime < 0 ) + { + Throw( InvalidArgument, "End Approach Time may not be negative." ); + } + + std::ofstream s( filename.c_str(), std::ofstream::binary ); + if ( ! s ) + { + std::string s = "Could not create file \""; + s += filename; + s += "\". Failed to write Spc file."; + Throw( FileIOException, s ); + } + + // have to do this before trying to do anything else: + configureExportStruct( partials_, notenum_, endApproachTime, enhanced ); + + unsigned long dataSize = 0; + + CommonCk commonChunk; + configureCommonCk( commonChunk, getNumSampleFrames(), Nchans, Bps, rate_ ); + dataSize += commonChunk.header.size + sizeof(CkHeader); + + SoundDataCk soundDataChunk; + configureEnvelopeDataCk( soundDataChunk, partials_ ); + dataSize += soundDataChunk.header.size + sizeof(CkHeader); + + InstrumentCk instrumentChunk; + configureInstrumentCk( instrumentChunk, notenum_ ); + dataSize += instrumentChunk.header.size + sizeof(CkHeader); + + MarkerCk markerChunk; + if ( ! markers_.empty() ) + { + configureSosMarkerCk( markerChunk, markers_ ); + dataSize += markerChunk.header.size + sizeof(CkHeader); + } + + SosEnvelopesCk soseChunk; + configureSosEnvelopesCk( soseChunk ); + dataSize += soseChunk.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 ); + writeSosEnvelopesChunk( s, soseChunk ); + writeSampleData( s, soundDataChunk ); + + s.close(); + } + catch ( Exception & ex ) + { + ex.append( " Failed to write Spc file." ); + throw; + } +} + +// -- access -- + +// --------------------------------------------------------------------------- +// markers +// --------------------------------------------------------------------------- +// Return a reference to the Marker (see Marker.h) container +// for this SpcFile. +// +SpcFile::markers_type & +SpcFile::markers( void ) +{ + return markers_; +} + +const SpcFile::markers_type & +SpcFile::markers( void ) const +{ + return markers_; +} + +// --------------------------------------------------------------------------- +// midiNoteNumber +// --------------------------------------------------------------------------- +// Return the fractional MIDI note number assigned to this SpcFile. +// If the sound has no definable pitch, note number 60.0 is used. +// +double +SpcFile::midiNoteNumber( void ) const +{ + return notenum_; +} + +// --------------------------------------------------------------------------- +// partials +// --------------------------------------------------------------------------- +// Return a read-only (const) reference to the bandwidth-enhanced +// Partials represented by the envelope parameter streams in this SpcFile. +// +const SpcFile::partials_type & +SpcFile::partials( void ) const +{ + return partials_; +} + +// --------------------------------------------------------------------------- +// sampleRate +// --------------------------------------------------------------------------- +// Return the sampling freqency in Hz for the spc data in this +// SpcFile. This is the rate at which Kyma must be running to ensure +// proper playback of bandwidth-enhanced Spc data. +// +double +SpcFile::sampleRate( void ) const +{ + return rate_; +} + +// -- mutation -- + +// --------------------------------------------------------------------------- +// addPartial +// --------------------------------------------------------------------------- +// Add the specified Partial to the enevelope parameter streams +// represented by this SpcFile. +// +// A SpcFile can contain only one Partial having any given (non-zero) +// label, so an added Partial will replace a Partial having the +// same label, if such a Partial exists. +// +// This may throw an InvalidArgument exception if an attempt is made +// to add unlabeled Partials, or Partials labeled higher than the +// allowable maximum. +// +void +SpcFile::addPartial( const Partial & p ) +{ + addPartial( p, p.label() ); +} + +// --------------------------------------------------------------------------- +// addPartial +// --------------------------------------------------------------------------- +// Add a Partial, assigning it the specified label (and position in the +// Spc data). +// +// A SpcFile can contain only one Partial having any given (non-zero) +// label, so an added Partial will replace a Partial having the +// same label, if such a Partial exists. +// +// This may throw an InvalidArgument exception if an attempt is made +// to add unlabeled Partials, or Partials labeled higher than the +// allowable maximum. +// +void +SpcFile::addPartial( const Partial & p, int label ) +{ + if ( p.label() == 0 ) + { + Throw( InvalidArgument, "Spc Partials must be labeled." ); + } + if ( label < 1 ) + { + Throw( InvalidArgument, "Spc Partials must have positive labels." ); + } + if ( label > LargestLabel ) + { + Throw( InvalidArgument, "Spc Partial label is too large, cannot have more than 256." ); + } + + if ( label > partials_.size() ) + { + growPartials( label ); + } + + partials_[label - 1] = p; + partials_[label - 1].setLabel( label ); +} + +// --------------------------------------------------------------------------- +// setMidiNoteNumber +// --------------------------------------------------------------------------- +// Set the fractional MIDI note number assigned to this SpcFile. +// If the sound has no definable pitch, use note number 60.0 (the default). +// +void +SpcFile::setMidiNoteNumber( double nn ) +{ + if ( nn < 0 || nn > 128 ) + { + Throw( InvalidArgument, "MIDI note number outside of the valid range [1,128]" ); + } + notenum_ = nn; +} + +// --------------------------------------------------------------------------- +// setSampleRate +// --------------------------------------------------------------------------- +// Set the sampling freqency in Hz for the spc data in this +// SpcFile. This is the rate at which Kyma must be running to ensure +// proper playback of bandwidth-enhanced Spc data. +// +void +SpcFile::setSampleRate( double rate ) +{ + if ( rate <= 0 ) + { + Throw( InvalidArgument, "Sample rate must be positive." ); + } + rate_ = rate; +} + +// -- helpers -- + +// --------------------------------------------------------------------------- +// growPartials +// --------------------------------------------------------------------------- +// +void +SpcFile::growPartials( partials_type::size_type sz ) +{ + if ( partials_.size() < sz ) + { +#ifdef PO2 + partials_type::size_type po2sz = MinNumPartials; + while ( po2sz < sz ) + { + po2sz *= 2; + } + partials_.resize( po2sz ); +#else + partials_.resize( sz ); +#endif + for ( partials_type::size_type j = 0; j < partials_.size(); ++j ) + { + partials_[j].setLabel( j+1 ); + } + } +} + +// -- export structures -- + +// --------------------------------------------------------------------------- +// Export Structures +// --------------------------------------------------------------------------- +// + +// structure for export information +struct SpcExportInfo +{ + double midipitch; // note number (69.00 = A440) for spc file; + // this is the core parameter, others are, by default, + // computed from this one + double endApproachTime; // in seconds, this indicates how long before the end of the sound the + // amplitude, frequency, and bandwidth values are to be modified to + // make a gradual transition to the spectral content at the end, + // 0.0 indicates no such modifications are to be done + int numPartials; // number of partials in spc file + int fileNumPartials; // the actual number of partials plus padding to make a 2**n value + int enhanced; // true for bandwidth-enhanced spc file, false for pure sines + double startTime; // in seconds, time of first frame in spc file + double endTime; // in seconds, this indicates the time at which to truncate the end + // of the spc file, 0.0 indicates no truncation + double markerTime; // in seconds, this indicates time at which a marker is inserted in the + // spc file, 0.0 indicates no marker is desired + double sampleRate; // in hertz, intended sample rate for synthesis of spc file + double hop; // hop size, based on numPartials and sampleRate + double ampEpsilon; // small amplitude value (related to lsb value in spc file log-amp) +}; + +static struct SpcExportInfo spcEI; // yikky global spc Export information + + +// -- export helpers by Lippold -- + +// --------------------------------------------------------------------------- +// fileNumPartials +// --------------------------------------------------------------------------- +// Find number of partials in SOS file. This is the actual number of partials, +// plus padding to make a 2**n value. +// +static int fileNumPartials( int partials ) +{ + if ( partials <= 32 ) + return 32; + if ( partials <= 64 ) + return 64; + if ( partials <= 128 ) + return 128; + else if ( partials <= 256 ) + return 256; + else if ( partials <= LargestLabel ) + return LargestLabel; + + Throw( FileIOException, "Too many SPC partials!" ); + return LargestLabel; +} + +// --------------------------------------------------------------------------- +// envLog( ) +// --------------------------------------------------------------------------- +// For a range 0 to 1, this returns a log value, 0x0000 to 0xFFFF. +// +static unsigned long envLog( double floatingValue ) +{ + static double coeff = 65535.0 / log( 32768. ); + + return (unsigned long)( coeff * log( 32768.0 * floatingValue + 1.0 ) ); + +} // end of envLog( ) + +// --------------------------------------------------------------------------- +// envExp( ) +// --------------------------------------------------------------------------- +// For a range 0x0000 to 0xFFFF, this returns an exponentiated value in the range 0..1. +// This is the counterpart of SpcFile::envLog(). +// +static double envExp( long intValue ) +{ + static double coeff = 65535.0 / log( 32768. ); + + return ( exp( intValue / coeff ) - 1.0 ) / 32768.0; + +} // end of envExp( ) + +// --------------------------------------------------------------------------- +// getPhaseRefTime +// --------------------------------------------------------------------------- +// Find the time at which to reference phase. +// The time will be shortly after amplitude onset, if we are before the onset. +// +static double getPhaseRefTime( int label, const Partial & p, double time ) +{ +// Keep array of previous values to optimize spc export. +// This depends on this routine being called in increasing-time order. + static double prevPRT[LargestLabel + 1]; + if ( prevPRT[label] > time && time > spcEI.startTime ) + return prevPRT[ label ]; + +// Go forward to nonzero amplitude. + while ( p.amplitudeAt( time, Fade ) < spcEI.ampEpsilon && time < spcEI.endTime + spcEI.hop) + { + time += spcEI.hop; + } + + prevPRT[ label ] = time; + +// Use phase value at initial onset time. + return time; + +} + +// --------------------------------------------------------------------------- +// afbp +// --------------------------------------------------------------------------- +// Find amplitude, frequency, bandwidth, phase value. +// +static void afbp( const Partial & p, double time, double phaseRefTime, + double magMult, double freqMult, + double & amp, double & freq, double & bw, double & phase) +{ + +// Optional endApproachTime processing: +// Approach amp, freq, and bw values at endTime, and stick at endTime amplitude. +// We avoid a sudden transition when using stick-at-end-frame sustains. +// Compute weighting factor between "normal" envelope point and static point. + if ( spcEI.endApproachTime && time > spcEI.endTime - spcEI.endApproachTime ) + { + if ( time > p.endTime() && p.endTime() > spcEI.endTime - 2 * spcEI.hop) + time = p.endTime(); + double wt = ( spcEI.endTime - time ) / spcEI.endApproachTime; + amp = magMult * ( wt * p.amplitudeAt( time, Fade ) + + (1.0 - wt) * p.amplitudeAt( spcEI.endTime, Fade ) ); + freq = freqMult * ( wt * p.frequencyAt( time ) + (1.0 - wt) * p.frequencyAt( spcEI.endTime ) ); + bw = ( wt * p.bandwidthAt( time ) + (1.0 - wt) * p.bandwidthAt( spcEI.endTime ) ); + phase = p.phaseAt( time ); + } + +// If we are before the phase reference time, or on the final frame, +// use zero amp and offset phase. + else if ( time < phaseRefTime - spcEI.hop / 2 || time > spcEI.endTime - spcEI.hop / 2 ) + { + amp = 0.; + freq = freqMult * p.frequencyAt( phaseRefTime ); + bw = 0.; + phase = p.phaseAt( phaseRefTime ) - 2. * Pi * (phaseRefTime - time) * freq; + } + +// Use envelope values at "time". + else + { + amp = magMult * p.amplitudeAt( time, Fade ); + freq = freqMult * p.frequencyAt( time ); + bw = p.bandwidthAt( time ); + phase = p.phaseAt( time ); + } +} + +// --------------------------------------------------------------------------- +// pack +// --------------------------------------------------------------------------- +// Pack envelope breakpoint value for interpretation by Envelope Reader sounds +// in Kyma. The packed result is two 24-bit quantities, lval and rval. +// +// In lval, the log of the sine magnitude occupies the top 8 bits, the log of the +// frequency occupies the bottom 16 bits. +// +// In rval, the log of the noise magnitude occupies the top 8 bits, the scaled +// linear phase occupies the bottom 16 bits. +// +// lval and rval are pointers to 3-bytes each, filled in by this function. +// +static void pack( double amp, double freq, double bw, double phase, + Byte * lbytes, Byte * rbytes ) +{ + +// Set phase for one hop earlier, so that Kyma synthesis target phase is correct. +// Add offset to phase for difference between Kyma and Loris representation. + phase -= 2. * Pi * spcEI.hop * freq; + phase += Pi / 2; + +// Make phase into range 0..1. + phase = std::fmod( phase, 2. * Pi ); + while ( phase < 0. ) // used to be if, I think it should be while + { // -kel 12 May 2006 + phase += 2. * Pi; + } + double zeroToOnePhase = phase / (2. * Pi); + +// Make frequency into range 0..1. + double zeroToOneFreq = freq / 22050.0; // 0..1 , 1 is 22.050 kHz + +// Compute sine magnitude and noise magnitude from amp and bw. + double theSineMag = amp * sqrt( 1. - bw ); + double theNoiseMag = 64.0 * amp * sqrt( bw ); + if (theNoiseMag > 1.0) + theNoiseMag = 1.0; + +// Pack amp and freq into 24 least-significant bits of lval: +// 7 bits of log-sine-amplitude with 16 bits of zero to right. +// 16 bits of log-frequency with 0 bits of zero to right. + unsigned long lval; + lval = ( envLog( theSineMag ) & 0xFE00 ) << 7; + lval |= ( envLog( zeroToOneFreq ) & 0xFFFF ); + +// store in lbytes: +// store the sample bytes in big endian order, +// most significant byte first: + const int BytesPerSample = 3; + for ( int j = BytesPerSample; j > 0; --j ) + { + // mask the lowest byte after shifting: + *(lbytes++) = 0xFF & (lval >> (8*(j-1))); + } + +// Pack noise amp and phase into 24 least-significant bits of rval: +// 7 bits of log-noise-amplitude with 16 bits of zero to right. +// 16 bits of phase with 0 bits of zero to right. + unsigned long rval; + rval = ( envLog( theNoiseMag ) & 0xFE00 ) << 7; + rval |= ( (unsigned long) ( zeroToOnePhase * 0xFFFF ) ); + +// store in rbytes: +// store the sample bytes in big endian order, +// most significant byte first: + for ( int j = BytesPerSample; j > 0; --j ) + { + // mask the lowest byte after shifting: + *(rbytes++) = 0xFF & (rval >> (8*(j-1))); + } +} + +// --------------------------------------------------------------------------- +// packEnvelopes +// --------------------------------------------------------------------------- +// The partials should be labeled and distilled before this is called. +// +static bool notEmpty( const Partial & p ) { return p.size() > 0; } + +static void packEnvelopes( const SpcFile::partials_type & partials, + std::vector< Byte > & bytes ) +{ +// Assert( partials.size() == spcEI.fileNumPartials ); + + int frames = int( ( spcEI.endTime - spcEI.startTime ) / spcEI.hop ) + 1; + unsigned long dataSize = + frames * spcEI.fileNumPartials * ( 24 / 8 ) * (spcEI.enhanced ? 2 : 1); + bytes.clear(); + bytes.reserve( dataSize ); + + // get the reference partial; the lowest-nonzero-labeled partial with any breakpoints + SpcFile::partials_type::const_iterator pos = + std::find_if( partials.begin(), partials.end(), notEmpty ); + Assert( pos != partials.end() ); + const Partial & refPar = *pos; + int refLabel = refPar.label(); + Assert( (refLabel - 1) == (pos - partials.begin()) ); + + // write out one frame at a time: + for (double tim = spcEI.startTime; tim <= spcEI.endTime; tim += spcEI.hop ) + { + // for each frame, write one value for every partial: + // (this loop extends to the pad partials) + for (unsigned int label = 1; label <= spcEI.fileNumPartials; ++label ) + { + double amp, freq, bw, phase; + // find partial with the correct label + // if partial with the correct is empty, + // frequency-multiply the reference partial +#ifndef PO2 + if ( label > partials.size() || partials[ label - 1 ].size() == 0 ) +#else + if ( partials[ label - 1 ].size() == 0 ) +#endif + { + // find the reference time for the phase + double phaseRefTime = getPhaseRefTime( label, refPar, tim ); + + // find amplitude, frequency, bandwidth, phase value + double freqMult = (double) label / (double) refLabel; + double magMult = 0.0; + afbp( refPar, tim, phaseRefTime, magMult, freqMult, amp, freq, bw, phase ); + } + else + { + // find the reference time for the phase + double phaseRefTime = getPhaseRefTime( label, partials[ label - 1 ], tim ); + + // find amplitude, frequency, bandwidth, phase value + afbp( partials[ label - 1 ], tim, phaseRefTime, 1, 1, amp, freq, bw, phase ); + } + + // pack log amplitude and log frequency into 24-bit lval, + // log bandwidth and phase into 24-bit rval: + Byte leftbytes[3], rightbytes[3]; + pack( amp, freq, bw, phase, leftbytes, rightbytes); + + // pack integer samples into the Byte vector without + // byte swapping, they are already correctly packed + // (see pack above): + bytes.insert( bytes.end(), leftbytes, leftbytes + 3 ); + if ( spcEI.enhanced ) + { + bytes.insert( bytes.end(), rightbytes, rightbytes + 3 ); + } + } + } + + Assert( bytes.size() == dataSize ); +} + +// --------------------------------------------------------------------------- +// configureEnvelopeDataCk +// --------------------------------------------------------------------------- +// Configure a special SoundDataCk for exporting Spc envelopes. +// +static void +configureEnvelopeDataCk( SoundDataCk & ck, const SpcFile::partials_type & partials ) +{ + packEnvelopes( partials, ck.sampleBytes ); + + ck.header.id = SoundDataId; + + // size is everything after the header: + ck.header.size = sizeof(Uint_32) + // offset + sizeof(Uint_32) + // block size + ck.sampleBytes.size(); // sample data + + // no block alignment: + ck.offset = 0; + ck.blockSize = 0; +} + +// --------------------------------------------------------------------------- +// configureSosMarkerCk +// --------------------------------------------------------------------------- +// Spc needs a special version of this, because Marker times have to be +// rounded to the nearest frame. +// +void +configureSosMarkerCk( MarkerCk & ck, const std::vector< Marker > & markers ) +{ + ck.header.id = MarkerId; + + // accumulate data size + Uint_32 dataSize = sizeof(Uint_16); // num markers + + ck.numMarkers = markers.size(); + ck.markers.resize( markers.size() ); + for ( int j = 0; j < markers.size(); ++j ) + { + MarkerCk::Marker & m = ck.markers[j]; + m.markerID = j+1; + //m.position = Uint_32((markers[j].time() * srate) + 0.5); + + // align marker with nearest frame time: + m.position = Uint_32( markers[j].time() / spcEI.hop ) + * spcEI.fileNumPartials + * ( spcEI.enhanced ? 2 : 1 ); + + m.markerName = markers[j].name(); + + #define MAX_PSTRING_CHARS 254 + if ( m.markerName.size() > MAX_PSTRING_CHARS ) + m.markerName.resize( MAX_PSTRING_CHARS ); + + // the size of a pascal string is the number of + // characters plus the size byte, plus the terminal '\0': + // + // Actualy, at least one web source indicates that Pascal + // strings are not null-terminated, but that they _are_ + // padded with an extra (not part of the count) byte + // if necessary to ensure that the total length (including + // count) is even, and this seems to work better with other + // programs (e.g. Kyma) + if ( m.markerName.size()%2 == 0 ) + m.markerName += '\0'; + dataSize += sizeof(Uint_16) + sizeof(Uint_32) + (m.markerName.size() + 1); + } + + // must be an even number of bytes + if ( dataSize%2 ) + ++dataSize; + + ck.header.size = dataSize; +} + + +// --------------------------------------------------------------------------- +// configureSosEnvelopesCk +// --------------------------------------------------------------------------- +// Configure a the application-specific chunk for exporting Spc envelopes. +// +static void +configureSosEnvelopesCk( SosEnvelopesCk & ck ) +{ + ck.header.id = ApplicationSpecificId; + + // size is everything after the header: + ck.header.size = sizeof(Uint_32) + // signature + sizeof(Uint_32) + // enhanced + sizeof(Uint_32) + // validPartials + // this last bit is a big, obsolete array, that + // we now use two positions in, an they aren't + // even the first two! Truly nasty. + 4*LargestLabel + 8 * sizeof(Int_32);// initPhase[] et al + + ck.signature = SosEnvelopesId; + + ck.enhanced = spcEI.enhanced; + + // the number of partials is doubled in bandwidth-enhanced spc files + ck.validPartials = spcEI.numPartials * ( spcEI.enhanced ? 2 : 1 ); + + // resolution in microseconds + ck.resolution = long( 1000000.0 * spcEI.hop ); + + // all partials quasiharmonic + // the number of partials is doubled in bandwidth-enhanced spc files + ck.quasiHarmonic = spcEI.numPartials * ( spcEI.enhanced ? 2 : 1); + +} + +// --------------------------------------------------------------------------- +// writeSosEnvelopesChunk +// --------------------------------------------------------------------------- +// +std::ostream & +writeSosEnvelopesChunk( std::ostream & s, const SosEnvelopesCk & ck ) +{ + // write it out: + try + { + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.id ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.header.size ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.signature ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.enhanced ); + BigEndian::write( s, 1, sizeof(Int_32), (char *)&ck.validPartials ); + + // The SOSresultion and SOSquasiHarmonic fields are in the phase table memory. + //BigEndian::write( s, initPhaseLth, sizeof(Int_32), (char *)&ck.initPhase[0] ); + static const int InitPhaseLth = ( LargestLabel + 8 ); + Int_32 bogus[ InitPhaseLth ]; // obsolete initial phase array + std::fill( bogus, bogus + InitPhaseLth, 0 ); + bogus[ ck.validPartials ] = ck.resolution; + bogus[ ck.validPartials + 1 ] = ck.quasiHarmonic; + BigEndian::write( s, InitPhaseLth, sizeof(Int_32), (char *)bogus ); + } + catch( FileIOException & ex ) + { + ex.append( "Failed to write AIFF file Container chunk." ); + throw; + } + + return s; + +} + +// --------------------------------------------------------------------------- +// computeHop +// --------------------------------------------------------------------------- +// Find the hop size, based on number of partials and sample rate. +// +static double computeHop( int numPartials, double sampleRate ) +{ + return 2 * numPartials / sampleRate; +} + + +// --------------------------------------------------------------------------- +// computeStartTime +// --------------------------------------------------------------------------- +// Find the start time: the earliest time of any labeled partial. +// +static double computeStartTime( const SpcFile::partials_type & pars ) +{ + double startTime = 1000.; + for ( SpcFile::partials_type::const_iterator pIter = pars.begin(); pIter != pars.end(); ++pIter ) + if ( pIter->size() > 0 && startTime > pIter->startTime() && pIter->label() > 0 ) + startTime = pIter->startTime(); + return startTime; +} + + +// --------------------------------------------------------------------------- +// computeEndTime +// --------------------------------------------------------------------------- +// Find the end time: the latest time of any labeled partial. +// +static double computeEndTime( const SpcFile::partials_type & pars ) +{ + double endTime = -1000.; + for ( SpcFile::partials_type::const_iterator pIter = pars.begin(); pIter != pars.end(); ++pIter ) + if ( pIter->size() > 0 && endTime < pIter->endTime() && pIter->label() > 0 ) + endTime = pIter->endTime(); + return endTime; +} + + +// --------------------------------------------------------------------------- +// computeNumPartials +// --------------------------------------------------------------------------- +// Find the number of partials. +// +static long computeNumPartials( const SpcFile::partials_type & pars ) +{ + +// We purposely consider partials with no breakpoints, to allow +// a larger number of partials than actually have data. + int numPartials = 0; + for ( SpcFile::partials_type::const_iterator pIter = pars.begin(); pIter != pars.end(); ++pIter ) + if ( numPartials < pIter->label() ) + numPartials = pIter->label(); + +// To ensure a reasonable hop time, make at least 32 partials. + return numPartials ? std::max( 32, numPartials ) : 0; +} + +// --------------------------------------------------------------------------- +// configureExportStruct +// --------------------------------------------------------------------------- +static void +configureExportStruct( const SpcFile::partials_type & plist, double midipitch, + double endApproachTime, int enhanced ) +{ + // note number (69.00 = A440) for spc file + spcEI.midipitch = midipitch; + + // enhanced indicates a bandwidth-enhanced spc file; by default it is true. + // if enhanced is false, no bandwidth or noise information is exported. + spcEI.enhanced = enhanced; + + // endApproachTime is in seconds; by default it is zero (and has no effect). + // a nonzero endApproachTime indicates that the plist does not include a + // release, but rather ends in a static spectrum corresponding to the final + // breakpoint values of the partials. the endApproachTime specifies how + // long before the end of the sound the amplitude, frequency, and bandwidth + // values are to be modified to make a gradual transition to the static spectrum. + spcEI.endApproachTime = endApproachTime; + + // number of partials in spc file + spcEI.numPartials = computeNumPartials( plist ); + spcEI.fileNumPartials = fileNumPartials( spcEI.numPartials ); + + // start and end time of spc file + spcEI.startTime = computeStartTime( plist ); + spcEI.endTime = computeEndTime( plist ); + + // in seconds, this indicates time at which a marker is inserted + // in the spc file, 0.0 indicates no marker. this is not being used currently. + spcEI.markerTime = 0.; + + // in hertz, intended sample rate for synthesis of spc file + spcEI.sampleRate = 44100.; + + // compute hop size + spcEI.hop = computeHop( spcEI.numPartials, spcEI.sampleRate ); + + // compute ampEpsilon, a small amplitude value twice the lsb value + // of log amp in packed spc format. + spcEI.ampEpsilon = 2. * envExp( 0x200 ); + + // Max number of partials is due to (arbitrary) size of initPhase[]. + if ( spcEI.numPartials < 1 || spcEI.numPartials > LargestLabel ) + Throw( FileIOException, "Partials must be distilled and labeled between 1 and 512." ); + + debugger << "startTime = " << spcEI.startTime << " endTime = " << spcEI.endTime + << " hop = " << spcEI.hop << " partials = " << spcEI.numPartials << endl; +} + +// --------------------------------------------------------------------------- +// getNumSampleFrames +// --------------------------------------------------------------------------- +// The number of exported sample frames is computed from data stored in +// the icky global export struct. +// +static unsigned long getNumSampleFrames( void ) +{ + int frames = int( ( spcEI.endTime - spcEI.startTime ) / spcEI.hop ) + 1; + return frames * spcEI.fileNumPartials * ( spcEI.enhanced ? 2 : 1 ); +} + +// -- import helpers by Lippold -- +// --------------------------------------------------------------------------- +// processEnhancedPoint +// --------------------------------------------------------------------------- +// Add ehanced-spc breakpoint to existing Loris partials. +// +static void +processEnhancedPoint( Byte * leftbytes, Byte * rightbytes, + const double frameTime, + Partial & par ) +{ +// represent bytes as 24 bit integers: + const int BytesPerSample = 3; + + // assign the leading byte, so that the sign + // is preserved: + long left = static_cast<char>(*(leftbytes++)); + for ( int j = 1; j < BytesPerSample; ++j ) + { + // OR bytes after the most significant, so + // that their sign is ignored: + left = (left << 8) + (unsigned char)*(leftbytes++); + } + + long right = static_cast<char>(*(rightbytes++)); + for ( int j = 1; j < BytesPerSample; ++j ) + { + // OR bytes after the most significant, so + // that their sign is ignored: + right = (right << 8) + (unsigned char)*(rightbytes++); + } +// +// Unpack values. +// + double freq = envExp( left & 0xffff ) * 22050.0; + double sineMag = envExp( (left >> 7) & 0xfe00 ); + double noiseMag = envExp( (right >> 7) & 0xfe00 ) / 64.; + double phase = ( right & 0xffff ) * ( 2. * Pi / 0xffff ); + + double total = sineMag * sineMag + noiseMag * noiseMag; + + double amp = sqrt( total ); + + double noise = 0.; + if (total != 0.) + noise = noiseMag * noiseMag / total; + if (noise > 1.) + noise = 1.; + + phase -= Pi / 2.; + if (phase < 0.) + phase += 2. * Pi; + +// +// Create a new breakpoint and insert it. +// + Breakpoint newbp( freq, amp, noise, phase ); + par.insert( frameTime, newbp ); +} + +// --------------------------------------------------------------------------- +// processSineOnlyPoint +// --------------------------------------------------------------------------- +// Add sine-only spc breakpoint to existing Loris partials. +// +static void +processSineOnlyPoint( Byte * bytes, + const double frameTime, + Partial & par ) +{ +// represent bytes as 24 bit integers: + const int BytesPerSample = 3; + + // assign the leading byte, so that the sign + // is preserved: + long packed = static_cast<char>(*(bytes++)); + for ( int j = 1; j < BytesPerSample; ++j ) + { + // OR bytes after the most significant, so + // that their sign is ignored: + packed = (packed << 8) + (unsigned char)*(bytes++); + } + +// +// Unpack values. +// + double freq = envExp( packed & 0xffff ) * 22050.0; + double amp = envExp( (packed >> 7) & 0xfe00 ); + double noise = 0.; + double phase = 0.; + +// +// Create a new breakpoint and insert it. +// + Breakpoint newbp( freq, amp, noise, phase ); + par.insert( frameTime, newbp ); +} + +// --------------------------------------------------------------------------- +// readSpcData +// --------------------------------------------------------------------------- +// +void +SpcFile::readSpcData( const std::string & filename ) +{ + ContainerCk containerChunk; + CommonCk commonChunk; + SoundDataCk soundDataChunk; + InstrumentCk instrumentChunk; + MarkerCk markerChunk; + SosEnvelopesCk soseChunk; + + try + { + std::ifstream s( filename.c_str(), std::ifstream::binary ); + + // the Container chunk must be first, read it: + readChunkHeader( s, containerChunk.header ); + 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; + case ApplicationSpecificId: + readApplicationSpecifcData( s, soseChunk, 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." ); + } + if ( soseChunk.signature != SosEnvelopesId ) + { + Throw( FileIOException, + "Reached end of file before finding a Spc Envelope Data chunk, this must not be a Spc file." ); + } + } + catch ( Exception & ex ) + { + ex.append( " Failed to read Spc file." ); + throw; + } + + // all the chunks have been read, use them to initialize + // the SpcFile members: + rate_ = commonChunk.srate; + + // why was this like this: + // double rate = commonChunk.srate; + + if ( instrumentChunk.header.id ) + { + notenum_ = instrumentChunk.baseNote; + notenum_ -= 0.01 * instrumentChunk.detune; + } + + // extract information from SOSe chunk: + // enhanced file format has number of partials doubled + // sine-only file format has proper number of partials + bool enhanced = soseChunk.enhanced != 0; + int numPartials = enhanced ? soseChunk.validPartials / 2 : soseChunk.validPartials; + int numFrames = commonChunk.sampleFrames / ( fileNumPartials( numPartials ) * ( enhanced ? 2 : 1 ) ); + double hop = soseChunk.resolution * 0.000001; // resolution is in microseconds + + // read markers, need to compute times corresponding to + // spc frames: + if ( markerChunk.header.id ) + { + for ( int j = 0; j < markerChunk.numMarkers; ++j ) + { + MarkerCk::Marker & m = markerChunk.markers[j]; + double markerTime = m.position * hop / ( fileNumPartials( numPartials ) * ( enhanced ? 2 : 1 ) ); + markers_.push_back( Marker( markerTime, m.markerName ) ); + } + } + + + // check for valid file + if ( numPartials == 0 || commonChunk.bitsPerSample != 24 ) + Throw( FileIOException, "Not an SPC file." ); + if ( numPartials < MinNumPartials || numPartials > LargestLabel ) + Throw( FileIOException, "Bad number of partials in SPC file." ); + + // check the number of bytes of Spc data: + const int BytesPerSample = 3; + const int PredictedNumBytes = + BytesPerSample * numFrames * fileNumPartials( numPartials ) * ( enhanced ? 2 : 1 ); + if ( soundDataChunk.sampleBytes.size() != PredictedNumBytes ) + { + notifier << "Found " << soundDataChunk.sampleBytes.size() << " bytes of " + << commonChunk.bitsPerSample << "-bit sample data." << endl; + notifier << "Header says there should be " << PredictedNumBytes + << "." << endl; + } + + // process SPC data points + partials_.clear(); + growPartials( numPartials ); + Byte * bytes = &soundDataChunk.sampleBytes.front(); + for ( int frame = 0; frame < numFrames; ++frame ) + { + for ( int partial = 0; partial < fileNumPartials( numPartials ); ++partial ) + { + if (enhanced) + { + Byte * lbytes = bytes; + bytes += BytesPerSample; + Byte * rbytes = bytes; + bytes += BytesPerSample; + if ( partial < partials_.size() ) + processEnhancedPoint( lbytes, rbytes, frame * hop, partials_[partial] ); + } + else + { + if ( partial < partials_.size() ) + processSineOnlyPoint( bytes, frame * hop, partials_[partial] ); + bytes += BytesPerSample; + } + } + } +} + +} // end of namespace Loris diff --git a/src/loris/SpcFile.h b/src/loris/SpcFile.h new file mode 100644 index 0000000..118b30d --- /dev/null +++ b/src/loris/SpcFile.h @@ -0,0 +1,368 @@ +/* + * 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 + * + * + * SpcFile.h + * + * Definition of SpcFile class for Partial import and export for + * real-time synthesis in Kyma. + * + * Spc files always represent a number of Partials that is a power of + * two. This is not necessary for purely-sinusoidal files, but might be + * (not clear) for enhanced data to be properly processed in Kyma. + * + * All of this is kind of disgusting right now. This code has evolved + * somewhat randomly, and we are awaiting full support for bandwidth- + * enhanced data in Kyma.. + * + * Kelly Fitz, 8 Jan 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "Marker.h" +#include "Partial.h" + +#if defined(NO_TEMPLATE_MEMBERS) +#include "PartialList.h" +#endif + +#include <string> +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class SpcFile +// +//! Class SpcFile represents a collection of reassigned bandwidth-enhanced +//! Partial data in a SPC-format envelope stream data file, used by the +//! real-time bandwidth-enhanced additive synthesizer implemented on the +//! Symbolic Sound Kyma Sound Design Workstation. Class SpcFile manages +//! file I/O and conversion between Partials and envelope parameter streams. +// +class SpcFile +{ +// -- public interface -- +public: + +// -- types -- + + //! the type of the container of Markers + //! stored with an SpcFile + typedef std::vector< Marker > markers_type; + + //! the type of the container of Partials + //! stored with an SpcFile + typedef std::vector< Partial > partials_type; + +// -- construction -- + + //! Initialize an instance of SpcFile by importing envelope parameter + //! streams from the file having the specified filename or path. + //! + //! \param filename the name of the file to import + explicit SpcFile( const std::string & filename ); + + //! Initialize an instance of SpcFile with copies of the Partials + //! on the specified half-open (STL-style) range. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. + //! + //! \param begin_partials the beginning of a range of Partials to prepare + //! for Spc export + //! \param end_partials the end of a range of Partials to prepare + //! for Spc export + //! \param midiNoteNum the fractional MIDI note number, if specified + //! (default is 60) +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + SpcFile( Iter begin_partials, Iter end_partials, double midiNoteNum = 60 ); +#else + SpcFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double midiNoteNum = 60 ); +#endif + + //! Initialize an instance of SpcFile having the specified fractional + //! MIDI note number, and no Partials (or envelope parameter streams). + //! + //! \param midiNoteNum the fractional MIDI note number, if specified + //! (default is 60) + explicit SpcFile( double midiNoteNum = 60 ); + + // copy, assign, and delete are compiler-generated + +// -- access -- + + //! Return a reference to the MarkerContainer (see Marker.h) for this SpcFile. + markers_type & markers( void ); + + //! Return a reference to the MarkerContainer (see Marker.h) for this SpcFile. + const markers_type & markers( void ) const; + + //! Return the fractional MIDI note number assigned to this SpcFile. + //! If the sound has no definable pitch, note number 60.0 is used. + double midiNoteNumber( void ) const; + + //! Return a read-only (const) reference to the bandwidth-enhanced + //! Partials represented by the envelope parameter streams in this SpcFile. + const partials_type & partials( void ) const; + + //! Return the sampling freqency in Hz for the spc data in this + //! SpcFile. This is the rate at which Kyma must be running to ensure + //! proper playback of bandwidth-enhanced Spc data. + double sampleRate( void ) const; + +// -- mutation -- + + //! Add the specified Partial to the enevelope parameter streams + //! represented by this SpcFile. + //! + //! A SpcFile can contain only one Partial having any given (non-zero) + //! label, so an added Partial will replace a Partial having the + //! same label, if such a Partial exists. + //! + //! This may throw an InvalidArgument exception if an attempt is made + //! to add unlabeled Partials, or Partials labeled higher than the + //! allowable maximum. + //! + //! \param p the Partial to add to this SpcFile + void addPartial( const Loris::Partial & p ); + + //! Add a Partial, assigning it the specified label (and position in the + //! Spc data). + //! + //! A SpcFile can contain only one Partial having any given (non-zero) + //! label, so an added Partial will replace a Partial having the + //! same label, if such a Partial exists. + //! + //! This may throw an InvalidArgument exception if an attempt is made + //! to add unlabeled Partials, or Partials labeled higher than the + //! allowable maximum. + //! + //! \param p the Partial to add to this SpcFile + //! \param label the label to associate with this Partial in + //! the Spc file (the Partial's own label is ignored). + void addPartial( const Loris::Partial & p, int label ); + + //! Add all Partials on the specified half-open (STL-style) range + //! to the enevelope parameter streams represented by this SpcFile. + //! + //! A SpcFile can contain only one Partial having any given (non-zero) + //! label, so an added Partial will replace a Partial having the + //! same label, if such a Partial exists. + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts + //! only PartialList::const_iterator arguments. + //! + //! This may throw an InvalidArgument exception if an attempt is made + //! to add unlabeled Partials, or Partials labeled higher than the + //! allowable maximum. + //! + //! \param begin_partials the beginning of a range of Partials + //! to add to this Spc file + //! \param end_partials the end of a range of Partials + //! to add to this Spc file +#if !defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void addPartials( Iter begin_partials, Iter end_partials ); +#else + void addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ); +#endif + + //! Set the fractional MIDI note number assigned to this SpcFile. + //! If the sound has no definable pitch, use note number 60.0 + //! (the default). + void setMidiNoteNumber( double nn ); + + //! Set the sampling freqency in Hz for the spc data in this + //! SpcFile. This is the rate at which Kyma must be running to ensure + //! proper playback of bandwidth-enhanced Spc data. + //! + //! The default sample rate is 44100 Hz. + void setSampleRate( double rate ); + +// -- export -- + + //! Export the phase-correct bandwidth-enhanced envelope parameter + //! streams represented by this SpcFile to the file having the specified + //! filename or path. + //! + //! A nonzero endApproachTime indicates that the Partials do not include a + //! release or decay, but rather end in a static spectrum corresponding to the + //! final Breakpoint values of the partials. The endApproachTime specifies how + //! long before the end of the sound the amplitude, frequency, and bandwidth + //! values are to be modified to make a gradual transition to the static spectrum. + //! + //! If the endApproachTime is not specified, it is assumed to be zero, + //! corresponding to Partials that decay or release normally. + //! + //! \param filename the name of the file to create + //! \param endApproachTime the duration in seconds of the gradual transition + //! to a static spectrum at the end of the sound (default 0) + void write( const std::string & filename, double endApproachTime = 0 ); + + //! Export the pure sinsoidal (omitting phase and bandwidth data) envelope + //! parameter streams represented by this SpcFile to the file having the + //! specified filename or path. + //! + //! A nonzero endApproachTime indicates that the Partials do not include a + //! release or decay, but rather end in a static spectrum corresponding to the + //! final Breakpoint values of the partials. The endApproachTime specifies how + //! long before the end of the sound the amplitude, frequency, and bandwidth + //! values are to be modified to make a gradual transition to the static spectrum. + //! + //! If the endApproachTime is not specified, it is assumed to be zero, + //! corresponding to Partials that decay or release normally. + //! + //! \param filename the name of the file to create + //! \param endApproachTime the duration in seconds of the gradual transition + //! to a static spectrum at the end of the sound (default 0) + void writeSinusoidal( const std::string & filename, double endApproachTime = 0 ); + + //! Export the envelope parameter streams represented by this SpcFile to + //! the file having the specified filename or path. Export phase-correct + //! bandwidth-enhanced envelope parameter streams if enhanced is true + //! (the default), or pure sinsoidal streams otherwise. + //! + //! A nonzero endApproachTime indicates that the Partials do not include a + //! release or decay, but rather end in a static spectrum corresponding to the + //! final Breakpoint values of the partials. The endApproachTime specifies how + //! long before the end of the sound the amplitude, frequency, and bandwidth + //! values are to be modified to make a gradual transition to the static spectrum. + //! + //! If the endApproachTime is not specified, it is assumed to be zero, + //! corresponding to Partials that decay or release normally. + //! + //! \deprecated This version of write is deprecated, use the two-argument + //! versions write and writeSinusoidal. + //! + //! \param filename the name of the file to create + //! \param enhanced flag indicating whether to export enhanced (true) + //! or sinusoidal (false) data + //! \param endApproachTime the duration in seconds of the gradual transition + //! to a static spectrum at the end of the sound (default 0) + void write( const std::string & filename, bool enhanced, + double endApproachTime = 0 ); + +private: +// -- implementation -- + partials_type partials_; //! Partials to store in Spc format + markers_type markers_; //! AIFF Markers + + double notenum_; //! fractional MIDI note number + double rate_; //! sample rate in Hz at which the data plays at the + //! correction default pitch + + static const int MinNumPartials; //! the minimum number of Partials to export (32) + //! (if necessary, extra empty (silent) Partials + //! will be exported to make up the difference between + //! the size of partials_ and the next larger power + //! of two, not less than MinNumPartials + static const double DefaultRate; //! the default Spc export sample rate in Hz (44kHz) + +// -- helpers -- + void readSpcData( const std::string & filename ); + void growPartials( partials_type::size_type sz ); + +}; // end of class SpcFile + + +// --------------------------------------------------------------------------- +// constructor from Partial range +// --------------------------------------------------------------------------- +//! Initialize an instance of SpcFile with copies of the Partials +//! on the specified half-open (STL-style) range. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. +//! +//! \param begin_partials the beginning of a range of Partials to prepare +//! for Spc export +//! \param end_partials the end of a range of Partials to prepare +//! for Spc export +//! \param midiNoteNum the fractional MIDI note number, if specified +//! (default is 60) +#if !defined(NO_TEMPLATE_MEMBERS) +template< typename Iter > +SpcFile::SpcFile( Iter begin_partials, Iter end_partials, double midiNoteNum ) : +#else +SpcFile::SpcFile( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials, + double midiNoteNum ) : +#endif +// initializers: + notenum_( midiNoteNum ), + rate_( DefaultRate ) +{ + growPartials( MinNumPartials ); + addPartials( begin_partials, end_partials ); +} + +// --------------------------------------------------------------------------- +// addPartials +// --------------------------------------------------------------------------- +//! Add all Partials on the specified half-open (STL-style) range +//! to the enevelope parameter streams represented by this SpcFile. +//! +//! A SpcFile can contain only one Partial having any given (non-zero) +//! label, so an added Partial will replace a Partial having the +//! same label, if such a Partial exists. +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, this member accepts +//! only PartialList::const_iterator arguments. +//! +//! This may throw an InvalidArgument exception if an attempt is made +//! to add unlabeled Partials, or Partials labeled higher than the +//! allowable maximum. +//! +//! \param begin_partials the beginning of a range of Partials +//! to add to this Spc file +//! \param end_partials the end of a range of Partials +//! to add to this Spc file +#if !defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void SpcFile::addPartials( Iter begin_partials, Iter end_partials ) +#else +void SpcFile::addPartials( PartialList::const_iterator begin_partials, + PartialList::const_iterator end_partials ) +#endif +{ + while ( begin_partials != end_partials ) + { + // do not try to add unlabeled Partials, or + // Partials labeled beyond 256: + if ( 0 != begin_partials->label() && 257 > begin_partials->label() ) + { + addPartial( *begin_partials ); + } + ++begin_partials; + } +} + +} // end of namespace Loris + + diff --git a/src/loris/SpectralPeakSelector.C b/src/loris/SpectralPeakSelector.C new file mode 100644 index 0000000..a6cbc15 --- /dev/null +++ b/src/loris/SpectralPeakSelector.C @@ -0,0 +1,248 @@ +/* + * 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 + * + * + * SpectralPeakSelector.C + * + * Implementation of a class representing a policy for selecting energy + * peaks in a reassigned spectrum to be used in Partial formation. + * + * Kelly Fitz, 28 May 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "SpectralPeakSelector.h" + + +#include "Breakpoint.h" +#include "Notifier.h" +#include "ReassignedSpectrum.h" + + +#include <cmath> // for abs and fabs + + +// define this to use local minima in frequency +// reassignment to detect "peaks", otherwise +// magnitude peaks are used. +#define USE_REASSIGNMENT_MINS 1 +//#undef USE_REASSIGNMENT_MINS + + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// construction - constant resolution +// --------------------------------------------------------------------------- +SpectralPeakSelector::SpectralPeakSelector( double srate, double maxTimeCorrection ) : + mSampleRate( srate ), + mMaxTimeOffset( maxTimeCorrection ) +{ +} + +// --------------------------------------------------------------------------- +// selectPeaks +// --------------------------------------------------------------------------- +// Collect and return magnitude peaks in the lower half of the spectrum, +// ignoring those having frequencies below the specified minimum, and +// those having large time corrections. +// +// If the minimumFrequency is unspecified, 0 Hz is used. +// +// There are two strategies for doing. Probably each one should be a +// separate class, but for now, they are just separate functions. + +Peaks +SpectralPeakSelector::selectPeaks( ReassignedSpectrum & spectrum, + double minFrequency ) +{ +#if defined(USE_REASSIGNMENT_MINS) && USE_REASSIGNMENT_MINS + + return selectReassignmentMinima( spectrum, minFrequency ); + +#else + + return selectMagnitudePeaks( spectrum, minFrequency ); + +#endif +} + +// --------------------------------------------------------------------------- +// selectReassignmentMinima (private) +// --------------------------------------------------------------------------- +Peaks +SpectralPeakSelector::selectReassignmentMinima( ReassignedSpectrum & spectrum, + double minFrequency ) +{ + using namespace std; // for abs and fabs + + const double sampsToHz = mSampleRate / spectrum.size(); + const double oneOverSR = 1. / mSampleRate; + const double minFreqSample = minFrequency / sampsToHz; + const double maxCorrectionSamples = mMaxTimeOffset * mSampleRate; + + Peaks peaks; + + int start_j = 1, end_j = (spectrum.size() / 2) - 2; + + double fsample = start_j; + do + { + fsample = spectrum.reassignedFrequency( start_j++ ); + } while( fsample < minFreqSample ); + + for ( int j = start_j; j < end_j; ++j ) + { + + // look for changes in the frequency reassignment, + // from positive to negative correction, indicating + // a concentration of energy in the spectrum: + double next_fsample = spectrum.reassignedFrequency( j+1 ); + if ( fsample > j && next_fsample < j + 1 ) + { + // choose the smaller correction of fsample or next_fsample: + // (could also choose the larger magnitude?) + double freq; + int peakidx; + if ( (fsample-j) < (j+1-next_fsample) ) + { + freq = fsample * sampsToHz; + peakidx = j; + } + else + { + freq = next_fsample * sampsToHz; + peakidx = j+1; + } + + // still possible that the frequency winds up being + // below the specified minimum + if ( freq >= minFrequency ) + { + // keep only peaks with small time corrections: + double timeCorrectionSamps = spectrum.reassignedTime( peakidx ); + if ( fabs(timeCorrectionSamps) < maxCorrectionSamples ) + { + double mag = spectrum.reassignedMagnitude( peakidx ); + double phase = spectrum.reassignedPhase( peakidx ); + + // this will be overwritten later in analysis, + // might be ignored altogether, only used if the + // mixed derivative convergence indicator is stored + // as bandwidth in Analyzer: + double bw = spectrum.convergence( j ); + + + // also store the corrected peak time in seconds, won't + // be able to compute it later: + double time = timeCorrectionSamps * oneOverSR; + Breakpoint bp( freq, mag, bw, phase ); + peaks.push_back( SpectralPeak( time, bp ) ); + } + } + + } + fsample = next_fsample; + } + + /* + debugger << "SpectralPeakSelector::selectReassignmentMinima: found " + << peaks.size() << " peaks" << endl; + */ + + return peaks; + +} + +// --------------------------------------------------------------------------- +// selectMagnitudePeaks (private) +// --------------------------------------------------------------------------- +Peaks +SpectralPeakSelector::selectMagnitudePeaks( ReassignedSpectrum & spectrum, + double minFrequency ) +{ + using namespace std; // for abs and fabs + + const double sampsToHz = mSampleRate / spectrum.size(); + const double oneOverSR = 1. / mSampleRate; + const double minFreqSample = minFrequency / sampsToHz; + const double maxCorrectionSamples = mMaxTimeOffset * mSampleRate; + + Peaks peaks; + + int start_j = 1, end_j = (spectrum.size() / 2) - 2; + + double fsample = start_j; + do + { + fsample = spectrum.reassignedFrequency( start_j++ ); + } while( fsample < minFreqSample ); + + for ( int j = start_j; j < end_j; ++j ) + { + if ( spectrum.reassignedMagnitude(j) > spectrum.reassignedMagnitude(j-1) && + spectrum.reassignedMagnitude(j) > spectrum.reassignedMagnitude(j+1) ) + { + // skip low-frequency peaks: + double fsample = spectrum.reassignedFrequency( j ); + if ( fsample < minFreqSample ) + continue; + + // skip peaks with large time corrections: + double timeCorrectionSamps = spectrum.reassignedTime( j ); + if ( fabs(timeCorrectionSamps) > maxCorrectionSamples ) + continue; + + double mag = spectrum.reassignedMagnitude( j ); + double phase = spectrum.reassignedPhase( j ); + + // this will be overwritten later in analysis, + // might be ignored altogether, only used if the + // mixed derivative convergence indicator is stored + // as bandwidth in Analyzer: + double bw = spectrum.convergence( j ); + + // also store the corrected peak time in seconds, won't + // be able to compute it later: + double time = timeCorrectionSamps * oneOverSR; + Breakpoint bp ( fsample * sampsToHz, mag, bw, phase ); + peaks.push_back( SpectralPeak( time, bp ) ); + + } // end if itsa peak + } + + /* + debugger << "SpectralPeakSelector::selectMagnitudePeaks: found " + << peaks.size() << " peaks" << endl; + */ + return peaks; +} + + +} // end of namespace Loris diff --git a/src/loris/SpectralPeakSelector.h b/src/loris/SpectralPeakSelector.h new file mode 100644 index 0000000..c55056e --- /dev/null +++ b/src/loris/SpectralPeakSelector.h @@ -0,0 +1,90 @@ +#ifndef INCLUDE_SPECTRALPEAKSELECTOR_H +#define INCLUDE_SPECTRALPEAKSELECTOR_H +/* + * 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 + * + * + * SpectralPeakSelector.h + * + * Definition of a class representing a policy for selecting energy + * peaks in a reassigned spectrum to be used in Partial formation. + * + * Kelly Fitz, 28 May 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "SpectralPeaks.h" + +// begin namespace +namespace Loris { + +class ReassignedSpectrum; + +// --------------------------------------------------------------------------- +// class SpectralPeakSelector +// +// A class representing the process of selecting +// peaks (ridges) on a reassigned time-frequency surface. +// +class SpectralPeakSelector +{ +// --- interface --- +public: + // construction: + SpectralPeakSelector( double srate, double maxTimeCorrection ); + + // Collect and return magnitude peaks in the lower half of the spectrum, + // ignoring those having frequencies below the specified minimum (in Hz), and + // those having large time corrections. + // + // If the minimumFrequency is unspecified, 0 Hz is used. + // + // There are two strategies for doing. Probably each one should be a + // separate class, but for now, they are just separate functions. + Peaks selectPeaks( ReassignedSpectrum & spectrum, double minFrequency = 0 ); + + +// --- implementation --- +private: + + // There are two strategies for doing. Probably each one should be a + // separate class, but for now, they are just separate functions. + // + // Currently, the reassignment minima are used. + + Peaks selectReassignmentMinima( ReassignedSpectrum & spectrum, double minFrequency ); + Peaks selectMagnitudePeaks( ReassignedSpectrum & spectrum, double minFrequency ); + + +// --- member data --- + + double mSampleRate; + double mMaxTimeOffset; + + +}; // end of class SpectralPeakSelector + +} // end of namespace Loris + +#endif /* ndef INCLUDE_SPECTRALPEAKSELECTOR_H */ diff --git a/src/loris/SpectralPeaks.h b/src/loris/SpectralPeaks.h new file mode 100644 index 0000000..e5103cf --- /dev/null +++ b/src/loris/SpectralPeaks.h @@ -0,0 +1,111 @@ +#ifndef INCLUDE_SPECTRALPEAKS_H +#define INCLUDE_SPECTRALPEAKS_H +/* + * 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 + * + * + * SpectralPeaks.h + * + * Definition of a type representing a collection (vector) of + * reassigned spectral peaks or ridges. Shared by analysis policies. + * + * Kelly Fitz, 29 May 2003 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "Breakpoint.h" + +#include <vector> + +// begin namespace +namespace Loris { + +// define a spectral peak data structure +// +// HEY +// Clean this mess up! Upgrade this struct into a class that can +// store more kinds of information (like reassignment values), and +// creates a Breakpoint when needed. + +class SpectralPeak +{ +private: + + double m_time; + Breakpoint m_breakpoint; + +public: + + // --- lifecycle --- + + SpectralPeak( double t, const Breakpoint & bp ) : m_time( t ), m_breakpoint( bp ) {} + + + // --- access --- + + double time( void ) const { return m_time; } + + double amplitude( void ) const { return m_breakpoint.amplitude(); } + double frequency( void ) const { return m_breakpoint.frequency(); } + double bandwidth( void ) const { return m_breakpoint.bandwidth(); } + + // --- mutation --- + + void setAmplitude( double x ) { m_breakpoint.setAmplitude(x); } + void setBandwidth( double x ) { m_breakpoint.setBandwidth(x); } + + // this REALLY shouldn't be in here... + void addNoiseEnergy( double enoise ) { m_breakpoint.addNoiseEnergy(enoise); } + + // --- Breakpoint creation --- + + Breakpoint createBreakpoint( void ) const { return m_breakpoint; } + + // --- comparitors --- + + // Comparitor for sorting spectral peaks in order of + // increasing frequency, or finding maximum frequency. + + static bool sort_increasing_freq( const SpectralPeak & lhs, + const SpectralPeak & rhs ) + { + return lhs.frequency() < rhs.frequency(); + } + + // predicate used for sorting peaks in order of decreasing amplitude: + static bool sort_greater_amplitude( const SpectralPeak & lhs, + const SpectralPeak & rhs ) + { + return lhs.amplitude() > rhs.amplitude(); + } +}; + +// define the structure used to collect spectral peaks: +typedef std::vector< SpectralPeak > Peaks; + + + +} // end of namespace Loris + +#endif /* ndef INCLUDE_SPECTRALPEAKS_H */ diff --git a/src/loris/SpectralSurface.C b/src/loris/SpectralSurface.C new file mode 100644 index 0000000..69e7f5e --- /dev/null +++ b/src/loris/SpectralSurface.C @@ -0,0 +1,376 @@ +/* + * 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 + * + * + * SpectralSurface.C + * + * Implementation of class SpectralSurface, a class representing + * a smoothed time-frequency surface that can be used to + * perform cross-synthesis, the filtering of one sound by the + * time-varying spectrum of another. + * + * Kelly Fitz, 21 Dec 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "SpectralSurface.h" + +#include "BreakpointUtils.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" + +#include <algorithm> +#include <iterator> + +namespace Loris { + +// --------------------------------------------------------------------------- +// peakAmp - local helper for addPartialAux +// --------------------------------------------------------------------------- +static double peakAmp( const Partial & p ) +{ + if ( 0 == p.numBreakpoints() ) + { + return 0; + } + return std::max_element( p.begin(), p.end(), + BreakpointUtils::compareAmplitudeLess() + ).breakpoint().amplitude(); +} + +// --------------------------------------------------------------------------- +// findemfaster - local helper +// --------------------------------------------------------------------------- +static std::pair< const Partial *, const Partial * > +findemfaster( double freq, double time, const std::vector< Partial > & parray ) +{ + static std::vector< Partial >::size_type cacheLastHit = 0; + + std::vector< Partial >::size_type i = cacheLastHit; + const Partial * p1 = 0; + const Partial * p2 = 0; + if ( parray[i].frequencyAt( time ) < freq ) + { + // search up the list + while ( i < parray.size() && parray[i].frequencyAt( time ) < freq ) + { + ++i; + } + if ( i > 0 ) + { + p1 = &parray[i-1]; + cacheLastHit = i-1; + } + else + { + p1 = 0; + cacheLastHit = 0; + } + if ( i < parray.size() ) + { + p2 = &parray[i]; + } + else + { + p2 = 0; + } + } + else + { + // search down the list + while ( i > 0 && parray[i].frequencyAt( time ) > freq ) + { + --i; + } + if ( i > 0 || parray[i].frequencyAt( time ) < freq ) + { + p1 = &parray[i]; + cacheLastHit = i; + } + else + { + p1 = 0; + cacheLastHit = 0; + } + if ( i + 1 < parray.size() ) + { + p2 = &parray[i+1]; + } + else + { + p2 = 0; + } + } + // debugger << "findemfaster caching " << cacheLastHit << endl; + return std::make_pair(p1, p2); +} + +// --------------------------------------------------------------------------- +// smoothInTime - local helper +// --------------------------------------------------------------------------- +static double smoothInTime( const Partial & p, double t ) +{ + const double spanT = 30; // ms + const int steps = 13; + const double incrT = (2 * spanT) / (steps - 1); + + double a = p.amplitudeAt( t ); + if ( 0 == a ) + { + for (double dehr = -spanT; dehr <= spanT; dehr += incrT ) + { + a += p.amplitudeAt( t + ( .001*dehr ) ); + } + a = a / steps; + } + return a; +} + +// --------------------------------------------------------------------------- +// surfaceAt - local helper +// --------------------------------------------------------------------------- +static double surfaceAt( double f, double t, const std::vector< Partial > & parray ) +{ + std::pair< const Partial *, const Partial * > both = findemfaster( f, t, parray ); + const Partial * p1 = both.first; + const Partial * p2 = both.second; + + double moo1 = 0, moo2 = 0, interp = 0; + + if ( 0 != p1 && 0 != p2 ) + { + interp = (f - p1->frequencyAt( t )) / ( p2->frequencyAt( t ) - p1->frequencyAt( t ) ); + moo1 = smoothInTime( *p1, t ); + moo2 = smoothInTime( *p2, t ); + } + else if ( 0 != p2 ) + { + interp = 1; + moo2 = smoothInTime( *p2, t ); + moo1 = moo2; + } + else if ( 0 != p1 ) + { + interp = 1. / (f - p1->frequencyAt( t )); + moo1 = smoothInTime( *p1, t ); + moo2 = 0; + } + else + { + moo1 = moo2 = interp = 0; + } + return ((1-interp)*moo1 + interp*moo2); +} + +// --------------------------------------------------------------------------- +// scaleAmplitudes +// --------------------------------------------------------------------------- +//! Scale the amplitude of every Breakpoint in a Partial +//! according to the amplitude of the spectral surface +//! at the corresponding time and frequency. +//! +//! \param p the Partial to modify +// +void SpectralSurface::scaleAmplitudes( Partial & p ) +{ + const double FreqScale = 1.0 / mStretchFreq; + const double TimeScale = 1.0 / mStretchTime; + + Partial::iterator iter; + for ( iter = p.begin(); iter != p.end(); ++iter ) + { + Breakpoint & bp = iter.breakpoint(); + double f = bp.frequency(); + double t = iter.time(); + + double ampscale = surfaceAt( FreqScale * f, TimeScale * t, mPartials ) / mMaxSurfaceAmp; + + double a = bp.amplitude() * ( (1.-mEffect) + (mEffect*ampscale) ); + bp.setAmplitude( a ); + } +} + +// --------------------------------------------------------------------------- +// setAmplitudes +// --------------------------------------------------------------------------- +//! Set the amplitude of every Breakpoint in a Partial +//! equal to the amplitude of the spectral surface +//! at the corresponding time and frequency. +//! +//! \param p the Partial to modify +// +void SpectralSurface::setAmplitudes( Partial & p ) +{ + const double FreqScale = 1.0 / mStretchFreq; + const double TimeScale = 1.0 / mStretchTime; + + Partial::iterator iter; + for ( iter = p.begin(); iter != p.end(); ++iter ) + { + Breakpoint & bp = iter.breakpoint(); + if ( 0 != bp.amplitude() ) + { + double f = bp.frequency(); + double t = iter.time(); + + double surfaceAmp = surfaceAt( FreqScale * f, TimeScale * t, mPartials ); + double a = ( bp.amplitude()*(1.-mEffect) ) + ( mEffect*surfaceAmp ); + bp.setAmplitude( a ); + } + } +} + +// --- access/mutation --- + +// --------------------------------------------------------------------------- +// frequencyStretch +// --------------------------------------------------------------------------- +//! Return the amount of strecthing in the frequency dimension +//! (default 1, no stretching). Values greater than 1 stretch +//! the surface in the frequency dimension, values less than 1 +//! (but greater than 0) compress the surface in the frequency +//! dimension. +// +double SpectralSurface::frequencyStretch( void ) const +{ + return mStretchFreq; +} + +// --------------------------------------------------------------------------- +// timeStretch +// --------------------------------------------------------------------------- +//! Return the amount of strecthing in the time dimension +//! (default 1, no stretching). Values greater than 1 stretch +//! the surface in the time dimension, values less than 1 +//! (but greater than 0) compress the surface in the time +//! dimension. +// +double SpectralSurface::timeStretch( void ) const +{ + return mStretchTime; +} + +// --------------------------------------------------------------------------- +// effect +// --------------------------------------------------------------------------- +//! Return the amount of effect applied by scaleAmplitudes +//! and setAmplitudes (default 1, full effect). Values +//! less than 1 (but greater than 0) reduce the amount of +//! amplitude modified performed by application of the +//! surface. (This is rarely a good way of controlling the +//! amount of the effect.) +// +double SpectralSurface::effect( void ) const +{ + return mEffect; +} + +// --------------------------------------------------------------------------- +// setFrequencyStretch +// --------------------------------------------------------------------------- +//! Set the amount of strecthing in the frequency dimension +//! (default 1, no stretching). Values greater than 1 stretch +//! the surface in the frequency dimension, values less than 1 +//! (but greater than 0) compress the surface in the frequency +//! dimension. +//! +//! \pre stretch must be positive +//! \param stretch the new stretch factor for the frequency dimension +// +void SpectralSurface::setFrequencyStretch( double stretch ) +{ + if ( 0 > stretch ) + { + Throw( InvalidArgument, + "SpectralSurface frequency stretch must be non-negative." ); + } + mStretchFreq = stretch; +} + +// --------------------------------------------------------------------------- +// setTimeStretch +// --------------------------------------------------------------------------- +//! Set the amount of strecthing in the time dimension +//! (default 1, no stretching). Values greater than 1 stretch +//! the surface in the time dimension, values less than 1 +//! (but greater than 0) compress the surface in the time +//! dimension. +//! +//! \pre stretch must be positive +//! \param stretch the new stretch factor for the time dimension +// +void SpectralSurface::setTimeStretch( double stretch ) +{ + if ( 0 > stretch ) + { + Throw( InvalidArgument, + "SpectralSurface time stretch must be non-negative." ); + } + mStretchTime = stretch; +} + +// --------------------------------------------------------------------------- +// setEffect +// --------------------------------------------------------------------------- +//! Set the amount of effect applied by scaleAmplitudes +//! and setAmplitudes (default 1, full effect). Values +//! less than 1 (but greater than 0) reduce the amount of +//! amplitude modified performed by application of the +//! surface. (This is rarely a good way of controlling the +//! amount of the effect.) +//! +//! \pre effect must be between 0 and 1, inclusive +//! \param effect the new factor controlling the amount of +//! amplitude modification performed by scaleAmplitudes +//! and setAmplitudes +// +void SpectralSurface::setEffect( double effect ) +{ + if ( 0 > effect || 1 < effect ) + { + Throw( InvalidArgument, + "SpectralSurface effect must be non-negative and not greater than 1." ); + } + mEffect = effect; +} + +// --- private helpers --- + +// --------------------------------------------------------------------------- +// addPartialAux +// --------------------------------------------------------------------------- +// Helper function used by constructor for adding Partials one +// by one. Still have to sort after adding all the Partials +// using this helper! This just adds the Partial and keeps track +// of the largest amplitude seen so far. +// +void SpectralSurface::addPartialAux( const Partial & p ) +{ + mPartials.push_back( p ); + mMaxSurfaceAmp = std::max( mMaxSurfaceAmp, peakAmp( p ) ); +} + + +} //end namespace + diff --git a/src/loris/SpectralSurface.h b/src/loris/SpectralSurface.h new file mode 100644 index 0000000..3291f3d --- /dev/null +++ b/src/loris/SpectralSurface.h @@ -0,0 +1,370 @@ +#ifndef INCLUDE_SPECTRALSURFACE_H +#define INCLUDE_SPECTRALSURFACE_H +/* + * 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 + * + * + * SpectralSurface.h + * + * Definition of class SpectralSurface, a class representing + * a smoothed time-frequency surface that can be used to + * perform cross-synthesis, the filtering of one sound by the + * time-varying spectrum of another. + * + * Kelly Fitz, 21 Dec 2005 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "LorisExceptions.h" +#include "Partial.h" +#include "PartialList.h" +#include "PartialUtils.h" // for compareLabelLess + +#include <algorithm> // for sort +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// Class SpectralSurface +// +//! SpectralSurface represents a smoothed time-frequency surface that +//! can be used to perform cross-synthesis, the filtering of one sound +//! by the time-varying spectrum of another. +// +class SpectralSurface +{ +// -- public interface -- +public: + +// -- lifecycle -- + + //! Contsruct a new SpectralSurface from a sequence of distilled + //! Partials. + //! + //! \pre the specified Partials must be channelized and distilled. + //! \param b the beginning of the sequence of Partials + //! \param e the end of the sequence of Partials + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + SpectralSurface( Iter b, Iter e ); +#else + inline + SpectralSurface( PartialList::iterator b, PartialList::iterator e ); +#endif + + // use compiler-generated copy/assign/destroy + +// --- operations --- + + //! Scale the amplitude of every Breakpoint in a Partial + //! according to the amplitude of the spectral surface + //! at the corresponding time and frequency. + //! + //! \param p the Partial to modify + void scaleAmplitudes( Partial & p ); + + //! Scale the amplitudes of a sequence of Partials + //! according to the amplitude of the spectral surface + //! at the corresponding times and frequencies, + //! performing cross-synthesis, the filtering of one + //! sound (the sequence of Partials) by the time-varying + //! spectrum of another sound (the Partials used to + //! construct the surface). The surface is stretched + //! in time and frequency according to the values of + //! the two stretch factors (default 1, no stretching) + //! and the amount of the effect is governed by the + //! `effect' parameter (default 1, full effect). + //! + //! \param b the beginning of the sequence of Partials + //! \param e the end of the sequence of Partials + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void scaleAmplitudes( Iter b, Iter e ); +#else + inline + void scaleAmplitudes( PartialList::iterator b, PartialList::iterator e ); +#endif + + //! Set the amplitude of every Breakpoint in a Partial + //! equal to the amplitude of the spectral surface + //! at the corresponding time and frequency. + //! + //! \param p the Partial to modify + void setAmplitudes( Partial & p ); + + //! Set the amplitudes of a sequence of Partials + //! equal to the amplitude of the spectral surface + //! at the corresponding times and frequencies. + //! This can be used to perform formant-corrected + //! pitch shifting of a sound: construct the surface + //! from the unmodified Partials, perform the pitch + //! shift on the Partials, then apply the surface + //! to the shifted Partials using setAmplitudes. + //! The surface is stretched + //! in time and frequency according to the values of + //! the two stretch factors (default 1, no stretching) + //! and the amount of the effect is governed by the + //! `effect' parameter (default 1, full effect). + //! + //! \param b the beginning of the sequence of Partials + //! \param e the end of the sequence of Partials + //! + //! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e + //! must be PartialList::iterators, otherwise they can be any type + //! of iterators over a sequence of Partials. +#if ! defined(NO_TEMPLATE_MEMBERS) + template<typename Iter> + void setAmplitudes( Iter b, Iter e ); +#else + inline + void setAmplitudes( PartialList::iterator b, PartialList::iterator e ); +#endif + +// --- access/mutation --- + + //! Return the amount of strecthing in the frequency dimension + //! (default 1, no stretching). Values greater than 1 stretch + //! the surface in the frequency dimension, values less than 1 + //! (but greater than 0) compress the surface in the frequency + //! dimension. + double frequencyStretch( void ) const; + + //! Return the amount of strecthing in the time dimension + //! (default 1, no stretching). Values greater than 1 stretch + //! the surface in the time dimension, values less than 1 + //! (but greater than 0) compress the surface in the time + //! dimension. + double timeStretch( void ) const; + + //! Return the amount of effect applied by scaleAmplitudes + //! and setAmplitudes (default 1, full effect). Values + //! less than 1 (but greater than 0) reduce the amount of + //! amplitude modified performed by application of the + //! surface. (This is rarely a good way of controlling the + //! amount of the effect.) + double effect( void ) const; + + //! Set the amount of strecthing in the frequency dimension + //! (default 1, no stretching). Values greater than 1 stretch + //! the surface in the frequency dimension, values less than 1 + //! (but greater than 0) compress the surface in the frequency + //! dimension. + //! + //! \pre stretch must be positive + //! \param stretch the new stretch factor for the frequency dimension + void setFrequencyStretch( double stretch ); + + //! Set the amount of strecthing in the time dimension + //! (default 1, no stretching). Values greater than 1 stretch + //! the surface in the time dimension, values less than 1 + //! (but greater than 0) compress the surface in the time + //! dimension. + //! + //! \pre stretch must be positive + //! \param stretch the new stretch factor for the time dimension + void setTimeStretch( double stretch ); + + //! Set the amount of effect applied by scaleAmplitudes + //! and setAmplitudes (default 1, full effect). Values + //! less than 1 (but greater than 0) reduce the amount of + //! amplitude modified performed by application of the + //! surface. (This is rarely a good way of controlling the + //! amount of the effect.) + //! + //! \pre effect must be between 0 and 1, inclusive + //! \param effect the new factor controlling the amount of + //! amplitude modification performed by scaleAmplitudes + //! and setAmplitudes + void setEffect( double effect ); + +private: + +// -- instance variables -- + + std::vector< Partial > mPartials; //! the Partials comprising the surface are + //! stored in a vector for easy random access + double mStretchFreq; //! stretch factor for the frequency dimension + double mStretchTime; //! stretch factor for the time dimension + double mEffect; //! factor for controlling the amount of + //! of the effect applied in setAmplitudes and + //! scaleAmplitudes: 0 gives unmodified amplitudes + //! 1 gives fully modified amplitudes. + double mMaxSurfaceAmp; //! the maximum amplitude of any Breakpoint on + //! the surface, used for normalizing the surface + //! amplitude for scaleAmplitudes + +// --- private helpers --- + + // helper used by constructor for adding Partials one by one + void addPartialAux( const Partial & p ); + +}; + +// --------------------------------------------------------------------------- +// constructor +// --------------------------------------------------------------------------- +//! Contsruct a new SpectralSurface from a sequence of distilled +//! Partials. +//! +//! \pre the specified Partials must be channelized and distilled. +//! \param b the beginning of the sequence of Partials +//! \param e the end of the sequence of Partials +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +SpectralSurface::SpectralSurface( Iter b, Iter e ) : +#else +inline +SpectralSurface::SpectralSurface( PartialList::iterator b, + PartialList::iterator e ) : +#endif + mStretchFreq( 1.0 ), + mStretchTime( 1.0 ), + mEffect( 1.0 ), + mMaxSurfaceAmp( 0.0 ) +{ + // add only labeled Partials: + while ( b != e ) + { + if ( b->label() != 0 ) + { + addPartialAux( *b ); + } + ++b; + } + + // complain if the Partials were not distilled + if ( mPartials.empty() ) + { + Throw( InvalidArgument, "Partals need to be distilled to build a SpectralSurface" ); + } + + if ( 0 == mMaxSurfaceAmp ) + { + Throw( InvalidArgument, "The SpectralSurface is zero amplitude everywhere!" ); + } + + // sort by label + std::sort( mPartials.begin(), mPartials.end(), PartialUtils::compareLabelLess() ); +} + + +// --------------------------------------------------------------------------- +// scaleAmplitudes +// --------------------------------------------------------------------------- +//! Scale the amplitudes of a sequence of Partials +//! according to the amplitude of the spectral surface +//! at the corresponding times and frequencies, +//! performing cross-synthesis, the filtering of one +//! sound (the sequence of Partials) by the time-varying +//! spectrum of another sound (the Partials used to +//! construct the surface). The surface is stretched +//! in time and frequency according to the values of +//! the two stretch factors (default 1, no stretching) +//! and the amount of the effect is governed by the +//! `effect' parameter (default 1, full effect). +//! +//! \param b the beginning of the sequence of Partials +//! \param e the end of the sequence of Partials +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void SpectralSurface::scaleAmplitudes( Iter b, Iter e ) +#else +inline +void SpectralSurface::scaleAmplitudes( PartialList::iterator b, + PartialList::iterator e ) +#endif +{ + while ( b != e ) + { + // debugger << b->label() << endl; + scaleAmplitudes( *b ); + ++b; + } +} + +// --------------------------------------------------------------------------- +// setAmplitudes +// --------------------------------------------------------------------------- +//! Set the amplitudes of a sequence of Partials +//! equal to the amplitude of the spectral surface +//! at the corresponding times and frequencies. +//! This can be used to perform formant-corrected +//! pitch shifting of a sound: construct the surface +//! from the unmodified Partials, perform the pitch +//! shift on the Partials, then apply the surface +//! to the shifted Partials using setAmplitudes. +//! The surface is stretched +//! in time and frequency according to the values of +//! the two stretch factors (default 1, no stretching) +//! and the amount of the effect is governed by the +//! `effect' parameter (default 1, full effect). +//! +//! \param b the beginning of the sequence of Partials +//! \param e the end of the sequence of Partials +//! +//! If compiled with NO_TEMPLATE_MEMBERS defined, then b and e +//! must be PartialList::iterators, otherwise they can be any type +//! of iterators over a sequence of Partials. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void SpectralSurface::setAmplitudes( Iter b, Iter e ) +#else +inline +void SpectralSurface::setAmplitudes( PartialList::iterator b, + PartialList::iterator e ) +#endif +{ + while ( b != e ) + { + // debugger << b->label() << endl; + setAmplitudes( *b ); + ++b; + } +} + +} // namespace Loris + +#endif /* ndef INCLUDE_SPECTRALSURFACE_H */ + diff --git a/src/loris/Synthesizer.C b/src/loris/Synthesizer.C new file mode 100644 index 0000000..db102e4 --- /dev/null +++ b/src/loris/Synthesizer.C @@ -0,0 +1,518 @@ +/* + * 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 + * + * + * Synthesizer.C + * + * Implementation of class Loris::SynthesizerSynthesizer, a synthesizer of + * bandwidth-enhanced Partials. + * + * Kelly Fitz, 16 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "Synthesizer.h" +#include "Oscillator.h" +#include "Breakpoint.h" +#include "BreakpointUtils.h" +#include "Envelope.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "Resampler.h" +#include "phasefix.h" + +#include <algorithm> +#include <cmath> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + +// begin namespace +namespace Loris { + + +// --------------------------------------------------------------------------- +// Synthesizer constructor +// --------------------------------------------------------------------------- +//! Construct a Synthesizer using the default parameters and sample +//! buffer (a standard library vector). Since Partials generated by the +//! Loris Analyzer generally begin and end at non-zero amplitude, zero-amplitude +//! Breakpoints are inserted at either end of the Partial, at a temporal +//! distance equal to the fade time, to reduce turn-on and turn-off +//! artifacts. +//! +//! \sa Synthesizer::Parameters +//! +//! \param buffer The vector (of doubles) into which rendered samples +//! should be accumulated. +//! \throw InvalidArgument if any of the parameters is invalid. +Synthesizer::Synthesizer( std::vector<double> & buffer ) : + m_sampleBuffer( & buffer ), + m_fadeTimeSec( DefaultParameters().fadeTime ), + m_srateHz( DefaultParameters().sampleRate ) +{ +} + + +// --------------------------------------------------------------------------- +// Synthesizer constructor +// --------------------------------------------------------------------------- +//! Construct a Synthesizer using the specified parameters and sample +//! buffer (a standard library vector). Since Partials generated by the +//! Loris Analyzer generally begin and end at non-zero amplitude, zero-amplitude +//! Breakpoints are inserted at either end of the Partial, at a temporal +//! distance equal to the fade time, to reduce turn-on and turn-off +//! artifacts. If the fade time is unspecified, the default value of one +//! millisecond (0.001 seconds) is used. +//! +//! \param params A Parameters struct storing the configuration of +//! Synthesizer parameters. +//! \param buffer The vector (of doubles) into which rendered samples +//! should be accumulated. +//! \throw InvalidArgument if any of the parameters is invalid. +// +Synthesizer::Synthesizer( Parameters params, std::vector<double> & buffer ) : + m_sampleBuffer( & buffer ) +{ + // make sure that the parameters are valid before proceeding + if ( IsValidParameters( params ) ) + { + m_fadeTimeSec = params.fadeTime; + m_srateHz = params.sampleRate; + m_osc.filter() = params.filter; + } +} + +// --------------------------------------------------------------------------- +// Synthesizer constructor +// --------------------------------------------------------------------------- +//! Construct a Synthesizer using the specified sampling rate, sample +//! buffer (a standard library vector), and the default fade time +//! stored in the DefaultParameters. Since Partials generated by the Loris +//! Analyzer generally begin and end at non-zero amplitude, zero-amplitude +//! Breakpoints are inserted at either end of the Partial, at a temporal +//! distance equal to the fade time, to reduce turn-on and turn-off +//! artifacts. +//! +//! \param srate The rate (Hz) at which to synthesize samples +//! (must be positive). +//! \param buffer The vector (of doubles) into which rendered samples +//! should be accumulated. +//! \throw InvalidArgument if the specfied sample rate is non-positive. +Synthesizer::Synthesizer( double samplerate, std::vector<double> & buffer ) : + m_sampleBuffer( & buffer ), + m_fadeTimeSec( DefaultParameters().fadeTime ), + m_srateHz( samplerate ) +{ + // check to make sure that the sample rate is valid: + if ( m_srateHz <= 0. ) + { + Throw( InvalidArgument, "Synthesizer sample rate must be positive." ); + } + + // assign the default bw enhancement filter to the Oscillator + m_osc.filter() = DefaultParameters().filter; + +} + +// --------------------------------------------------------------------------- +// Synthesizer constructor +// --------------------------------------------------------------------------- +//! Construct a Synthesizer using the specified sampling rate, sample +//! buffer (a standard library vector), and Partial +//! fade time (in seconds). Since Partials generated by the Loris Analyzer +//! generally begin and end at non-zero amplitude, zero-amplitude +//! Breakpoints are inserted at either end of the Partial, at a temporal +//! distance equal to the fade time, to reduce turn-on and turn-off +//! artifacts. If the fade time is unspecified, the default value of one +//! millisecond (0.001 seconds) is used. +//! +//! \param samplerate The rate (Hz) at which to synthesize samples +//! (must be positive). +//! \param buffer The vector (of doubles) into which rendered samples +//! should be accumulated. +//! \param fade The Partial fade time in seconds (must be non-negative). +//! \throw InvalidArgument if the specfied sample rate is non-positive. +//! \throw InvalidArgument if the specified fade time is negative. +Synthesizer::Synthesizer( double samplerate, std::vector<double> & buffer, + double fade ) : + m_sampleBuffer( & buffer ), + m_fadeTimeSec( fade ), + m_srateHz( samplerate ) +{ + // check to make sure that the sample rate is valid: + if ( m_srateHz <= 0. ) + { + Throw( InvalidArgument, "Synthesizer sample rate must be positive." ); + } + + // check to make sure that the specified fade time + // is valid: + if ( m_fadeTimeSec < 0. ) + { + Throw( InvalidArgument, + "Synthesizer Partial fade time must be non-negative." ); + } + + // assign the default bw enhancement filter to the Oscillator + m_osc.filter() = DefaultParameters().filter; + +} + +// -- synthesis -- + +// --------------------------------------------------------------------------- +// synthesize +// --------------------------------------------------------------------------- +//! Synthesize a bandwidth-enhanced sinusoidal Partial. Zero-amplitude +//! Breakpoints are inserted at either end of the Partial to reduce +//! turn-on and turn-off artifacts, as described above. The synthesizer +//! will resize the buffer as necessary to accommodate all the samples, +//! including the fade out. Previous contents of the buffer are not +//! overwritten. Partials with start times earlier than the Partial fade +//! time will have shorter onset fades. Partials are not rendered at +//! frequencies above the half-sample rate. +//! +//! \param p The Partial to synthesize. +//! \return Nothing. +//! \pre The partial must have non-negative start time. +//! \post This Synthesizer's sample buffer (vector) has been +//! resized to accommodate the entire duration of the +//! Partial, p, including fade out at the end. +//! \throw InvalidPartial if the Partial has negative start time. +// +void +Synthesizer::synthesize( Partial p ) +{ + if ( p.numBreakpoints() == 0 ) + { + debugger << "Synthesizer ignoring a partial that contains no Breakpoints" << endl; + return; + } + + if ( p.startTime() < 0 ) + { + Throw( InvalidPartial, "Tried to synthesize a Partial having start time less than 0." ); + } + + debugger << "synthesizing Partial from " << p.startTime() * m_srateHz + << " to " << p.endTime() * m_srateHz << " starting phase " + << p.initialPhase() << " starting frequency " + << p.first().frequency() << endl; + + // better to compute this only once: + const double OneOverSrate = 1. / m_srateHz; + + + // use a Resampler to quantize the Breakpoint times and + // correct the phases: + Resampler quantizer( OneOverSrate ); + quantizer.setPhaseCorrect( true ); + quantizer.quantize( p ); + + + // resize the sample buffer if necessary: + typedef unsigned long index_type; + index_type endSamp = index_type( ( p.endTime() + m_fadeTimeSec ) * m_srateHz ); + if ( endSamp+1 > m_sampleBuffer->size() ) + { + // pad by one sample: + m_sampleBuffer->resize( endSamp+1 ); + } + + // compute the starting time for synthesis of this Partial, + // m_fadeTimeSec before the Partial's startTime, but not before 0: + double itime = ( m_fadeTimeSec < p.startTime() ) ? ( p.startTime() - m_fadeTimeSec ) : 0.; + index_type currentSamp = index_type( (itime * m_srateHz) + 0.5 ); // cheap rounding + + // reset the oscillator: + // all that really needs to happen here is setting the frequency + // correctly, the phase will be reset again in the loop over + // Breakpoints below, and the amp and bw can start at 0. + m_osc.resetEnvelopes( BreakpointUtils::makeNullBefore( p.first(), p.startTime() - itime ), m_srateHz ); + + // cache the previous frequency (in Hz) so that it + // can be used to reset the phase when necessary + // in the sample computation loop below (this saves + // having to recompute from the oscillator's radian + // frequency): + double prevFrequency = p.first().frequency(); + + // synthesize linear-frequency segments until + // there aren't any more Breakpoints to make segments: + double * bufferBegin = &( m_sampleBuffer->front() ); + for ( Partial::const_iterator it = p.begin(); it != p.end(); ++it ) + { + index_type tgtSamp = index_type( (it.time() * m_srateHz) + 0.5 ); // cheap rounding + Assert( tgtSamp >= currentSamp ); + + // if the current oscillator amplitude is + // zero, and the target Breakpoint amplitude + // is not, reset the oscillator phase so that + // it matches exactly the target Breakpoint + // phase at tgtSamp: + if ( m_osc.amplitude() == 0. ) + { + // recompute the phase so that it is correct + // at the target Breakpoint (need to do this + // because the null Breakpoint phase was computed + // from an interval in seconds, not samples, so + // it might be inaccurate): + // + // double favg = 0.5 * ( prevFrequency + it.breakpoint().frequency() ); + // double dphase = 2 * Pi * favg * ( tgtSamp - currentSamp ) / m_srateHz; + // + double dphase = Pi * ( prevFrequency + it.breakpoint().frequency() ) + * ( tgtSamp - currentSamp ) * OneOverSrate; + m_osc.setPhase( it.breakpoint().phase() - dphase ); + } + + m_osc.oscillate( bufferBegin + currentSamp, bufferBegin + tgtSamp, + it.breakpoint(), m_srateHz ); + + currentSamp = tgtSamp; + + // remember the frequency, may need it to reset the + // phase if a Null Breakpoint is encountered: + prevFrequency = it.breakpoint().frequency(); + } + + // render a fade out segment: + m_osc.oscillate( bufferBegin + currentSamp, bufferBegin + endSamp, + BreakpointUtils::makeNullAfter( p.last(), m_fadeTimeSec ), m_srateHz ); + +} + +// -- sample access -- + +// --------------------------------------------------------------------------- +// samples (const) +// --------------------------------------------------------------------------- +//! Return a const reference to the sample buffer used (not +//! owned) by this Synthesizer. +const std::vector<double> & +Synthesizer::samples( void ) const +{ + return *m_sampleBuffer; +} + +// --------------------------------------------------------------------------- +// samples (non-const) +// --------------------------------------------------------------------------- +//! Return a reference to the sample buffer used (not +//! owned) by this Synthesizer. +std::vector<double> & +Synthesizer::samples( void ) +{ + return *m_sampleBuffer; +} + +// -- parameter access and mutation -- + +// --------------------------------------------------------------------------- +// fadeTime +// --------------------------------------------------------------------------- +//! Return this Synthesizer's Partial fade time, in seconds. +double +Synthesizer::fadeTime( void ) const +{ + return m_fadeTimeSec; +} + +// --------------------------------------------------------------------------- +// sampleRate +// --------------------------------------------------------------------------- +//! Return the sampling rate (in Hz) for this Synthesizer. +double +Synthesizer::sampleRate( void ) const +{ + return m_srateHz; +} + + +// --------------------------------------------------------------------------- +// setFadeTime +// --------------------------------------------------------------------------- +//! Set this Synthesizer's fade time to the specified value +//! (in seconds, must be non-negative). +//! +//! \param t The new Partial fade time. +//! \throw InvalidArgument if the specified fade time is negative. +void +Synthesizer::setFadeTime( double partialFadeTime ) +{ + // check to make sure that the specified fade time + // is valid: + if ( partialFadeTime < 0. ) + { + Throw( InvalidArgument, "Synthesizer Partial fade time must be non-negative." ); + } + + m_fadeTimeSec = partialFadeTime; +} + +// --------------------------------------------------------------------------- +// setSampleRate +// --------------------------------------------------------------------------- +//! Set this Synthesizer's sample rate to the specified value +//! (in Hz, must be positive). +//! +//! \param rate The new synthesis sample rate. +//! \throw InvalidArgument if the specified rate is nonpositive. +void +Synthesizer::setSampleRate( double rate ) +{ + // check to make sure that the specified rate + // is valid: + if ( rate <= 0. ) + { + Throw( InvalidArgument, "Synthesizer sample rate must be positive." ); + } + + m_srateHz = rate; +} + +// --------------------------------------------------------------------------- +// filter +// --------------------------------------------------------------------------- +//! Return access to the Filter used by this Synthesizer's +//! Oscillator to implement bandwidth-enhanced sinusoidal +//! synthesis. (Can use this access to make changes to the +//! filter coefficients.) +Filter & +Synthesizer::filter( void ) +{ + return m_osc.filter(); +} + +// -- parameters structure -- + +// --------------------------------------------------------------------------- +// Synthesizer::Parameters default constructor +// --------------------------------------------------------------------------- +//! Assign default initial values to the Synthesizer parameters, Filter +//! defaults are defined in Oscillator.C. + +static const double Default_FadeTime_Ms = 1; +static const double Default_SampleRate_Hz = 44100; +//static const Synthesizer::EnhancementFlag Default_Enhancement_Flag = Synthesizer::BwEnhanced; + + +Synthesizer::Parameters::Parameters( void ) : + fadeTime( Default_FadeTime_Ms * 0.001 ), + sampleRate( Default_SampleRate_Hz ), + // enhancement( Default_Enhancement_Flag ), + filter( Oscillator::prototype_filter() ) +{ +} + +// --------------------------------------------------------------------------- +// Synthesizer default Parameters local access only +// --------------------------------------------------------------------------- + +static +Synthesizer::Parameters & TheSynthesizerDefaultParameters( void ) +{ + static Synthesizer::Parameters params; + return params; + +} + +// --------------------------------------------------------------------------- +// Synthesizer default Parameters access (static) +// --------------------------------------------------------------------------- +//! Default configuration of a Loris::Synthesizer. Modify the values +//! in this structure to alter the configuration of all Synthesizers, +//! including those used by the AiffFile class to render Partials. +const Synthesizer::Parameters & +Synthesizer::DefaultParameters( void ) +{ + return TheSynthesizerDefaultParameters(); +} + +// --------------------------------------------------------------------------- +// Synthesizer default Parameters access (static) +// --------------------------------------------------------------------------- +//! Assign a new default configuration of a Loris::Synthesizer +//! to alter the configuration of all Synthesizers, +//! including those used by the AiffFile class to render Partials. +//! +//! \param params A Parameters struct describing the new default +//! configuration for Synthesizers. +//! \throw InvalidArgument if any of the parameters is invalid. +void Synthesizer::SetDefaultParameters( const Synthesizer::Parameters & params ) +{ + if ( IsValidParameters( params ) ) + { + TheSynthesizerDefaultParameters() = params; + } +} + +// --------------------------------------------------------------------------- +// IsValidParameters (static) +// --------------------------------------------------------------------------- +//! Check the validty of a Parameters structure. Returns true if the +//! struct represents a valid Synthesizer configuration, and false otherwise. +//! +//! \param params A Parameters struct describing a configuration for +//! Synthesizers. +bool Synthesizer::IsValidParameters( const Parameters & params ) +{ + // check to make sure that the sample rate is valid: + if ( 0. >= params.sampleRate ) + { + Throw( InvalidArgument, "Synthesizer sample rate must be positive." ); + } + + // check to make sure that the specified fade time + // is valid: + if ( 0. > params.fadeTime ) + { + Throw( InvalidArgument, + "Synthesizer Partial fade time must be non-negative." ); + } + + // check that the filter coefficients are valid -- pretty much + // any coefficients are valid as long as the zeroeth feedback + // coefficient is non-zero: + if ( 0. == params.filter.denominator()[0] ) + { + Throw( InvalidArgument, + "Synthesizer filter zeroeth feedback coefficient must be non-zero." ); + } + + // if no exception has been raised, return true indicating valid params + return true; + +} + +} // end of namespace Loris diff --git a/src/loris/Synthesizer.h b/src/loris/Synthesizer.h new file mode 100644 index 0000000..e0268ba --- /dev/null +++ b/src/loris/Synthesizer.h @@ -0,0 +1,391 @@ +#ifndef INCLUDE_SYNTHESIZER_H +#define INCLUDE_SYNTHESIZER_H +/* + * 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 + * + * + * Synthesizer.h + * + * Definition of class Loris::Synthesizer, a renderer of + * bandwidth-enhanced Partials. + * + * Kelly Fitz, 16 Aug 1999 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + + + // TODO: + // Passing the sample vector in to Synthesizer no longer makes any sense, + // if the Synthesizer can (must) resize the buffer anyway, it may as well + // own it. In a future version, the Synthesizer owns a vector (need to add + // a method to clear it), and provides access to it (as it currently does). + // When this change is made, it probably makes sense to remove the function + // call operators. + + +#include "Oscillator.h" +#include "PartialList.h" +#include "PartialUtils.h" + +#include <vector> + +// begin namespace +namespace Loris { + +// --------------------------------------------------------------------------- +// class Synthesizer +// +//! A Synthesizer renders bandwidth-enhanced Partials into a buffer +//! of samples. +//! +//! Class Synthesizer represents an algorithm for rendering +//! bandwidth-enhanced Partials as floating point (double) samples at a +//! specified sampling rate, and accumulating them into a buffer. +//! +//! The Synthesizer does not own the sample buffer, the client is responsible +//! for its construction and destruction, and many Synthesizers may share +//! a buffer. +// +class Synthesizer +{ +// -- public interface -- +public: +// -- construction -- + + struct Parameters; // defined below + + //! Construct a Synthesizer using the default parameters and sample + //! buffer (a standard library vector). Since Partials generated by + //! the Loris Analyzer generally begin and end at non-zero amplitude, + //! zero-amplitude Breakpoints are inserted at either end of the Partial, + //! at a temporal distance equal to the fade time, to reduce turn-on and + //! turn-off artifacts. + //! + //! \sa Synthesizer::Parameters + //! + //! \param buffer The vector (of doubles) into which rendered samples + //! should be accumulated. + //! \throw InvalidArgument if any of the parameters is invalid. + Synthesizer( std::vector<double> & buffer ); + + + //! Construct a Synthesizer using the specified parameters and sample + //! buffer (a standard library vector). Since Partials generated by the + //! Loris Analyzer generally begin and end at non-zero amplitude, zero-amplitude + //! Breakpoints are inserted at either end of the Partial, at a temporal + //! distance equal to the fade time, to reduce turn-on and turn-off + //! artifacts. + //! + //! \param params A Parameters struct storing the configuration of + //! Synthesizer parameters. + //! \param buffer The vector (of doubles) into which rendered samples + //! should be accumulated. + //! \throw InvalidArgument if any of the parameters is invalid. + Synthesizer( Parameters params, std::vector<double> & buffer ); + + //! Construct a Synthesizer using the specified sampling rate, sample + //! buffer (a standard library vector), and the default fade time + //! stored in the DefaultParameters. Since Partials generated by the Loris + //! Analyzer generally begin and end at non-zero amplitude, zero-amplitude + //! Breakpoints are inserted at either end of the Partial, at a temporal + //! distance equal to the fade time, to reduce turn-on and turn-off + //! artifacts. + //! + //! \param srate The rate (Hz) at which to synthesize samples + //! (must be positive). + //! \param buffer The vector (of doubles) into which rendered samples + //! should be accumulated. + //! \throw InvalidArgument if the specfied sample rate is non-positive. + Synthesizer( double srate, std::vector<double> & buffer ); + + //! Construct a Synthesizer using the specified sampling rate, sample + //! buffer (a standard library vector), and Partial fade time + //! (in seconds). Since Partials generated by the Loris Analyzer + //! generally begin and end at non-zero amplitude, zero-amplitude + //! Breakpoints are inserted at either end of the Partial, at a temporal + //! distance equal to the fade time, to reduce turn-on and turn-off + //! artifacts. + //! + //! \param srate The rate (Hz) at which to synthesize samples + //! (must be positive). + //! \param buffer The vector (of doubles) into which rendered samples + //! should be accumulated. + //! \param fadeTime The Partial fade time in seconds (must be non-negative). + //! \throw InvalidArgument if the specfied sample rate is non-positive. + //! \throw InvalidArgument if the specified fade time is negative. + Synthesizer( double srate, std::vector<double> & buffer, double fadeTime ); + + // Compiler can generate copy, assign, and destroy. + // Synthesizer( const Synthesizer & other ); + // ~Synthesizer( void ); + // Synthesizer & operator= ( const Synthesizer & other ); + +// -- synthesis -- + + //! Synthesize a bandwidth-enhanced sinusoidal Partial. Zero-amplitude + //! Breakpoints are inserted at either end of the Partial to reduce + //! turn-on and turn-off artifacts, as described above. The synthesizer + //! will resize the buffer as necessary to accommodate all the samples, + //! including the fade out. Previous contents of the buffer are not + //! overwritten. Partials with start times earlier than the Partial fade + //! time will have shorter onset fades. Partials are not rendered at + //! frequencies above the half-sample rate. + //! + //! \param p The Partial to synthesize. + //! \return Nothing. + //! \pre The partial must have non-negative start time. + //! \post This Synthesizer's sample buffer (vector) has been + //! resized to accommodate the entire duration of the + //! Partial, p, including fade out at the end. + //! \throw InvalidPartial if the Partial has negative start time. + void synthesize( Partial p ); + + //! Function call operator: same as synthesize( p ). + void operator() ( const Partial & p ) { synthesize( p ) ; } + + + //! Synthesize all Partials on the specified half-open (STL-style) range. + //! Null Breakpoints are inserted at either end of the Partial to reduce + //! turn-on and turn-off artifacts, as described above. The synthesizer + //! will resize the buffer as necessary to accommodate all the samples, + //! including the fade outs. Previous contents of the buffer are not + //! overwritten. Partials with start times earlier than the Partial fade + //! time will have shorter onset fades. Partials are not rendered at + //! frequencies above the half-sample rate. + //! + //! \param begin_partials The beginning of the range of Partials + //! to synthesize. + //! \param end_partials The end of the range of Partials + //! to synthesize. + //! \return Nothing. + //! \pre The partials must have non-negative start times. + //! \post This Synthesizer's sample buffer (vector) has been + //! resized to accommodate the entire duration of all the + //! Partials including fade out at the ends. + //! \throw InvalidPartial if any Partial has negative start time. +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + void synthesize( Iter begin_partials, Iter end_partials ); +#else + inline + void synthesize( PartialList::iterator begin_partials, + PartialList::iterator end_partials ); +#endif + + //! Function call operator: same as + //! synthesize( begin_partials, end_partials ). +#if ! defined(NO_TEMPLATE_MEMBERS) + template< typename Iter > + void operator() ( Iter begin_partials, Iter end_partials ); +#else + inline + void operator() ( PartialList::iterator begin_partials, + PartialList::iterator end_partials ); +#endif + +// -- sample access -- + + //! Return a const reference to the sample buffer used (not + //! owned) by this Synthesizer. + const std::vector<double> & samples( void ) const; + + //! Return a reference to the sample buffer used (not + //! owned) by this Synthesizer. + std::vector<double> & samples( void ); + + +// -- parameter access and mutation -- + + + //! Return this Synthesizer's Partial fade time, in seconds. + double fadeTime( void ) const; + + //! Return the sampling rate (in Hz) for this Synthesizer. + double sampleRate( void ) const; + + //! Set this Synthesizer's fade time to the specified value + //! (in seconds, must be non-negative). + //! + //! \param partialFadeTime The new Partial fade time. + //! \throw InvalidArgument if the specified fade time is negative. + void setFadeTime( double partialFadeTime ); + + //! Set this Synthesizer's sample rate to the specified value + //! (in Hz, must be positive). + //! + //! \param rate The new synthesis sample rate. + //! \throw InvalidArgument if the specified rate is nonpositive. + void setSampleRate( double rate ); + + //! Return access to the Filter used by this Synthesizer's + //! Oscillator to implement bandwidth-enhanced sinusoidal + //! synthesis. (Can use this access to make changes to the + //! filter coefficients.) + Filter & filter( void ); + + +// -- parameters structure -- + + enum + { + Default_FadeTime_Ms = 1, + Default_SampleRate_Hz = 44100 + }; + + // enum EnhancementFlag { Sinusoidal = 0, BwEnhanced = 1 }; + + //! Structure storing a configuration of Synthesizer parameters. + //! Elements in this struct can be freely modified, the configuration + //! is validated before it can be used to construct a Synthesizer. + //! + //! \sa IsValidParameters + struct Parameters + { + double fadeTime; + double sampleRate; + // EnhancementFlag enhancement; + + Filter filter; + + // default constructor + // + //! Assign default initial values to the Synthesizer parameters, Filter + //! defaults are defined in Filter.C, others in Synthesizer.C. + Parameters( void ); + + // copy, assign, and destroy are free + }; + + //! Default configuration of a Loris::Synthesizer. Modify the values + //! in this structure to alter the configuration of all Synthesizers, + //! including those used by the AiffFile class to render Partials. + static const Parameters & DefaultParameters( void ); + + //! Assign a new default configuration of a Loris::Synthesizer + //! to alter the configuration of all Synthesizers, + //! including those used by the AiffFile class to render Partials. + //! + //! \param params A Parameters struct describing the new default + //! configuration for Synthesizers. + //! \throw InvalidArgument if params reprsents an invalid configuration. + static void SetDefaultParameters( const Parameters & params ); + + //! Check the validty of a Parameters structure. Returns true if the + //! struct represents a valid Synthesizer configuration, otherwise + //! raise InvalidArgument reporting the specific error. + //! + //! \param params A Parameters struct describing a configuration for + //! Synthesizers. + //! \throw InvalidArgument if params reprsents an invalid configuration. + static bool IsValidParameters( const Parameters & params ); + + +// -- implementation -- +private: + + Oscillator m_osc; // the Synthesizer has-a Oscillator that it uses to render + // all the Partials one by one. + + std::vector< double > * m_sampleBuffer; // samples are computed and stored here, BUT + // Synthesizer does NOT own this buffer. + + double m_fadeTimeSec; // Partial fade in/out time in seconds + double m_srateHz; // sample rate in Hz + +}; // end of class Synthesizer + + +// --------------------------------------------------------------------------- +// synthesize +// --------------------------------------------------------------------------- +//! Synthesize all Partials on the specified half-open (STL-style) range. +//! Null Breakpoints are inserted at either end of the Partial to reduce +//! turn-on and turn-off artifacts, as described above. The synthesizer +//! will resize the buffer as necessary to accommodate all the samples, +//! including the fade outs. Previous contents of the buffer are not +//! overwritten. Partials with start times earlier than the Partial fade +//! time will have shorter onset fades. +//! +//! \param begin_partials The beginning of the range of Partials +//! to synthesize. +//! \param end_partials The end of the range of Partials +//! to synthesize. +//! \return Nothing. +//! \pre The partials must have non-negative start times. +//! \post This Synthesizer's sample buffer (vector) has been +//! resized to accommodate the entire duration of all the +//! Partials including fade out at the ends. +//! \throw InvalidPartial if any Partial has negative start time. +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void +Synthesizer::synthesize( Iter begin_partials, Iter end_partials ) +#else +inline void +Synthesizer::synthesize( PartialList::iterator begin_partials, + PartialList::iterator end_partials ) +#endif +{ + // grow the sample buffer, if necessary, to accommodate the latest + // Partial, with the fade time tacked on the end + double duration = + PartialUtils::timeSpan( begin_partials, end_partials ).second + + m_fadeTimeSec; + + typedef std::vector< double >::size_type Sz_Type; + Sz_Type Nsamps = 1 + Sz_Type( duration * m_srateHz ); + if ( m_sampleBuffer->size() < Nsamps ) + { + m_sampleBuffer->resize( Nsamps ); + } + + while ( begin_partials != end_partials ) + { + synthesize( *(begin_partials++) ); + } +} + +// --------------------------------------------------------------------------- +// operator() +// --------------------------------------------------------------------------- +//! Function call operator: same as +//! synthesize( begin_partials, end_partials, timeShift ). +// +#if ! defined(NO_TEMPLATE_MEMBERS) +template<typename Iter> +void +Synthesizer::operator() ( Iter begin_partials, Iter end_partials ) +#else +inline void +Synthesizer::operator() ( PartialList::iterator begin_partials, + PartialList::iterator end_partials ) +#endif +{ + synthesize( begin_partials, end_partials ); +} + +} // end of namespace Loris + +#endif /* ndef INCLUDE_SYNTHESIZER_H */ diff --git a/src/loris/fftsg.c b/src/loris/fftsg.c new file mode 100644 index 0000000..dc3e237 --- /dev/null +++ b/src/loris/fftsg.c @@ -0,0 +1,3331 @@ +/* + double-precision floating point transform functions + adapted from the tranform library of Ooura. + http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html + + Identical to fftsg.c, wrapped in extern "C" + to prevent name-mangling by a C++ compiler. + + Kelly Fitz 21 July 2006 + kfitz@cerlsoundgroup.org +*/ +/* +Fast Fourier/Cosine/Sine Transform + dimension :one + data length :power of 2 + decimation :frequency + radix :split-radix + data :inplace + table :use +functions + cdft: Complex Discrete Fourier Transform + rdft: Real Discrete Fourier Transform + ddct: Discrete Cosine Transform + ddst: Discrete Sine Transform + dfct: Cosine Transform of RDFT (Real Symmetric DFT) + dfst: Sine Transform of RDFT (Real Anti-symmetric DFT) +function prototypes + void cdft(int, int, double *, int *, double *); + void rdft(int, int, double *, int *, double *); + void ddct(int, int, double *, int *, double *); + void ddst(int, int, double *, int *, double *); + void dfct(int, double *, double *, int *, double *); + void dfst(int, double *, double *, int *, double *); +macro definitions + USE_CDFT_PTHREADS : default=not defined + CDFT_THREADS_BEGIN_N : must be >= 512, default=8192 + CDFT_4THREADS_BEGIN_N : must be >= 512, default=65536 + USE_CDFT_WINTHREADS : default=not defined + CDFT_THREADS_BEGIN_N : must be >= 512, default=32768 + CDFT_4THREADS_BEGIN_N : must be >= 512, default=524288 + + +-------- Complex DFT (Discrete Fourier Transform) -------- + [definition] + <case1> + X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k<n + <case2> + X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k<n + (notes: sum_j=0^n-1 is a summation from j=0 to n-1) + [usage] + <case1> + ip[0] = 0; // first time only + cdft(2*n, 1, a, ip, w); + <case2> + ip[0] = 0; // first time only + cdft(2*n, -1, a, ip, w); + [parameters] + 2*n :data length (int) + n >= 1, n = power of 2 + a[0...2*n-1] :input/output data (double *) + input data + a[2*j] = Re(x[j]), + a[2*j+1] = Im(x[j]), 0<=j<n + output data + a[2*k] = Re(X[k]), + a[2*k+1] = Im(X[k]), 0<=k<n + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n) + strictly, + length of ip >= + 2+(1<<(int)(log(n+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + cdft(2*n, -1, a, ip, w); + is + cdft(2*n, 1, a, ip, w); + for (j = 0; j <= 2 * n - 1; j++) { + a[j] *= 1.0 / n; + } + . + + +-------- Real DFT / Inverse of Real DFT -------- + [definition] + <case1> RDFT + R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 + I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0<k<n/2 + <case2> IRDFT (excluding scale) + a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + + sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + + sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k<n + [usage] + <case1> + ip[0] = 0; // first time only + rdft(n, 1, a, ip, w); + <case2> + ip[0] = 0; // first time only + rdft(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + <case1> + output data + a[2*k] = R[k], 0<=k<n/2 + a[2*k+1] = I[k], 0<k<n/2 + a[1] = R[n/2] + <case2> + input data + a[2*j] = R[j], 0<=j<n/2 + a[2*j+1] = I[j], 0<j<n/2 + a[1] = R[n/2] + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + rdft(n, 1, a, ip, w); + is + rdft(n, -1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DCT (Discrete Cosine Transform) / Inverse of DCT -------- + [definition] + <case1> IDCT (excluding scale) + C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k<n + <case2> DCT + C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k<n + [usage] + <case1> + ip[0] = 0; // first time only + ddct(n, 1, a, ip, w); + <case2> + ip[0] = 0; // first time only + ddct(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = C[k], 0<=k<n + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddct(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddct(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DST (Discrete Sine Transform) / Inverse of DST -------- + [definition] + <case1> IDST (excluding scale) + S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k<n + <case2> DST + S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0<k<=n + [usage] + <case1> + ip[0] = 0; // first time only + ddst(n, 1, a, ip, w); + <case2> + ip[0] = 0; // first time only + ddst(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + <case1> + input data + a[j] = A[j], 0<j<n + a[0] = A[n] + output data + a[k] = S[k], 0<=k<n + <case2> + output data + a[k] = S[k], 0<k<n + a[0] = S[n] + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddst(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddst(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Cosine Transform of RDFT (Real Symmetric DFT) -------- + [definition] + C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n + [usage] + ip[0] = 0; // first time only + dfct(n, a, t, ip, w); + [parameters] + n :data length - 1 (int) + n >= 2, n = power of 2 + a[0...n] :input/output data (double *) + output data + a[k] = C[k], 0<=k<=n + t[0...n/2] :work area (double *) + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + is + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + for (j = 0; j <= n; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Sine Transform of RDFT (Real Anti-symmetric DFT) -------- + [definition] + S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0<k<n + [usage] + ip[0] = 0; // first time only + dfst(n, a, t, ip, w); + [parameters] + n :data length + 1 (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = S[k], 0<k<n + (a[0] is used for work area) + t[0...n/2-1] :work area (double *) + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + dfst(n, a, t, ip, w); + is + dfst(n, a, t, ip, w); + for (j = 1; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +Appendix : + The cos/sin table is recalculated when the larger table required. + w[] and ip[] are compatible with all routines. +*/ + +#if defined(__cplusplus) +extern "C" { +#endif + +void cdft(int n, int isgn, double *a, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + int nw; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + if (isgn >= 0) { + cftfsub(n, a, ip, nw, w); + } else { + cftbsub(n, a, ip, nw, w); + } +} + + +void rdft(int n, int isgn, double *a, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void rftbsub(int n, double *a, int nc, double *c); + int nw, nc; + double xi; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 2)) { + nc = n >> 2; + makect(nc, ip, w + nw); + } + if (isgn >= 0) { + if (n > 4) { + cftfsub(n, a, ip, nw, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, ip, nw, w); + } + xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } else { + a[1] = 0.5 * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + cftbsub(n, a, ip, nw, w); + } else if (n == 4) { + cftbsub(n, a, ip, nw, w); + } + } +} + + +void ddct(int n, int isgn, double *a, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void rftbsub(int n, double *a, int nc, double *c); + void dctsub(int n, double *a, int nc, double *c); + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = a[j] - a[j - 1]; + a[j] += a[j - 1]; + } + a[1] = a[0] - xr; + a[0] += xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + cftbsub(n, a, ip, nw, w); + } else if (n == 4) { + cftbsub(n, a, ip, nw, w); + } + } + dctsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + cftfsub(n, a, ip, nw, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, ip, nw, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = a[j] - a[j + 1]; + a[j] += a[j + 1]; + } + a[n - 1] = xr; + } +} + + +void ddst(int n, int isgn, double *a, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void rftbsub(int n, double *a, int nc, double *c); + void dstsub(int n, double *a, int nc, double *c); + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = -a[j] - a[j - 1]; + a[j] -= a[j - 1]; + } + a[1] = a[0] + xr; + a[0] -= xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + cftbsub(n, a, ip, nw, w); + } else if (n == 4) { + cftbsub(n, a, ip, nw, w); + } + } + dstsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + cftfsub(n, a, ip, nw, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, ip, nw, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = -a[j] - a[j + 1]; + a[j] -= a[j + 1]; + } + a[n - 1] = -xr; + } +} + + +void dfct(int n, double *a, double *t, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void dctsub(int n, double *a, int nc, double *c); + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + m = n >> 1; + yi = a[m]; + xi = a[0] + a[n]; + a[0] -= a[n]; + t[0] = xi - yi; + t[m] = xi + yi; + if (n > 2) { + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] - a[n - j]; + xi = a[j] + a[n - j]; + yr = a[k] - a[n - k]; + yi = a[k] + a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi - yi; + t[k] = xi + yi; + } + t[mh] = a[mh] + a[n - mh]; + a[mh] -= a[n - mh]; + dctsub(m, a, nc, w + nw); + if (m > 4) { + cftfsub(m, a, ip, nw, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, ip, nw, w); + } + a[n - 1] = a[0] - a[1]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] + a[j + 1]; + a[2 * j - 1] = a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dctsub(m, t, nc, w + nw); + if (m > 4) { + cftfsub(m, t, ip, nw, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, ip, nw, w); + } + a[n - l] = t[0] - t[1]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = t[j] - t[j + 1]; + a[k + l] = t[j] + t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 0; j < mh; j++) { + k = m - j; + t[j] = t[m + k] - t[m + j]; + t[k] = t[m + k] + t[m + j]; + } + t[mh] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + a[n] = t[2] - t[1]; + a[0] = t[2] + t[1]; + } else { + a[1] = a[0]; + a[2] = t[0]; + a[0] = t[1]; + } +} + + +void dfst(int n, double *a, double *t, int *ip, double *w) +{ + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void dstsub(int n, double *a, int nc, double *c); + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + if (n > 2) { + m = n >> 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] + a[n - j]; + xi = a[j] - a[n - j]; + yr = a[k] + a[n - k]; + yi = a[k] - a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi + yi; + t[k] = xi - yi; + } + t[0] = a[mh] - a[n - mh]; + a[mh] += a[n - mh]; + a[0] = a[m]; + dstsub(m, a, nc, w + nw); + if (m > 4) { + cftfsub(m, a, ip, nw, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, ip, nw, w); + } + a[n - 1] = a[1] - a[0]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] - a[j + 1]; + a[2 * j - 1] = -a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dstsub(m, t, nc, w + nw); + if (m > 4) { + cftfsub(m, t, ip, nw, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, ip, nw, w); + } + a[n - l] = t[1] - t[0]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = -t[j] - t[j + 1]; + a[k + l] = t[j] - t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + t[j] = t[m + k] + t[m + j]; + t[k] = t[m + k] - t[m + j]; + } + t[0] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + } + a[0] = 0; +} + + +/* -------- initializing routines -------- */ + + +#include <math.h> + +void makewt(int nw, int *ip, double *w) +{ + void makeipt(int nw, int *ip); + int j, nwh, nw0, nw1; + double delta, wn4r, wk1r, wk1i, wk3r, wk3i; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) { + nwh = nw >> 1; + delta = atan(1.0) / nwh; + wn4r = cos(delta * nwh); + w[0] = 1; + w[1] = wn4r; + if (nwh == 4) { + w[2] = cos(delta * 2); + w[3] = sin(delta * 2); + } else if (nwh > 4) { + makeipt(nw, ip); + w[2] = 0.5 / cos(delta * 2); + w[3] = 0.5 / cos(delta * 6); + for (j = 4; j < nwh; j += 4) { + w[j] = cos(delta * j); + w[j + 1] = sin(delta * j); + w[j + 2] = cos(3 * delta * j); + w[j + 3] = -sin(3 * delta * j); + } + } + nw0 = 0; + while (nwh > 2) { + nw1 = nw0 + nwh; + nwh >>= 1; + w[nw1] = 1; + w[nw1 + 1] = wn4r; + if (nwh == 4) { + wk1r = w[nw0 + 4]; + wk1i = w[nw0 + 5]; + w[nw1 + 2] = wk1r; + w[nw1 + 3] = wk1i; + } else if (nwh > 4) { + wk1r = w[nw0 + 4]; + wk3r = w[nw0 + 6]; + w[nw1 + 2] = 0.5 / wk1r; + w[nw1 + 3] = 0.5 / wk3r; + for (j = 4; j < nwh; j += 4) { + wk1r = w[nw0 + 2 * j]; + wk1i = w[nw0 + 2 * j + 1]; + wk3r = w[nw0 + 2 * j + 2]; + wk3i = w[nw0 + 2 * j + 3]; + w[nw1 + j] = wk1r; + w[nw1 + j + 1] = wk1i; + w[nw1 + j + 2] = wk3r; + w[nw1 + j + 3] = wk3i; + } + } + nw0 = nw1; + } + } +} + + +void makeipt(int nw, int *ip) +{ + int j, l, m, m2, p, q; + + ip[2] = 0; + ip[3] = 16; + m = 2; + for (l = nw; l > 32; l >>= 2) { + m2 = m << 1; + q = m2 << 3; + for (j = m; j < m2; j++) { + p = ip[j] << 2; + ip[m + j] = p; + ip[m2 + j] = p + q; + } + m = m2; + } +} + + +void makect(int nc, int *ip, double *c) +{ + int j, nch; + double delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = atan(1.0) / nch; + c[0] = cos(delta * nch); + c[nch] = 0.5 * c[0]; + for (j = 1; j < nch; j++) { + c[j] = 0.5 * cos(delta * j); + c[nc - j] = 0.5 * sin(delta * j); + } + } +} + + +/* -------- child routines -------- */ + + +#ifdef USE_CDFT_PTHREADS +#define USE_CDFT_THREADS +#ifndef CDFT_THREADS_BEGIN_N +#define CDFT_THREADS_BEGIN_N 8192 +#endif +#ifndef CDFT_4THREADS_BEGIN_N +#define CDFT_4THREADS_BEGIN_N 65536 +#endif +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#define cdft_thread_t pthread_t +#define cdft_thread_create(thp,func,argp) { \ + if (pthread_create(thp, NULL, func, (void *) argp) != 0) { \ + fprintf(stderr, "cdft thread error\n"); \ + exit(1); \ + } \ +} +#define cdft_thread_wait(th) { \ + if (pthread_join(th, NULL) != 0) { \ + fprintf(stderr, "cdft thread error\n"); \ + exit(1); \ + } \ +} +#endif /* USE_CDFT_PTHREADS */ + + +#ifdef USE_CDFT_WINTHREADS +#define USE_CDFT_THREADS +#ifndef CDFT_THREADS_BEGIN_N +#define CDFT_THREADS_BEGIN_N 32768 +#endif +#ifndef CDFT_4THREADS_BEGIN_N +#define CDFT_4THREADS_BEGIN_N 524288 +#endif +#include <windows.h> +#include <stdio.h> +#include <stdlib.h> +#define cdft_thread_t HANDLE +#define cdft_thread_create(thp,func,argp) { \ + DWORD thid; \ + *(thp) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) func, (LPVOID) argp, 0, &thid); \ + if (*(thp) == 0) { \ + fprintf(stderr, "cdft thread error\n"); \ + exit(1); \ + } \ +} +#define cdft_thread_wait(th) { \ + WaitForSingleObject(th, INFINITE); \ + CloseHandle(th); \ +} +#endif /* USE_CDFT_WINTHREADS */ + + +void cftfsub(int n, double *a, int *ip, int nw, double *w) +{ + void bitrv2(int n, int *ip, double *a); + void bitrv216(double *a); + void bitrv208(double *a); + void cftf1st(int n, double *a, double *w); + void cftrec4(int n, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftfx41(int n, double *a, int nw, double *w); + void cftf161(double *a, double *w); + void cftf081(double *a, double *w); + void cftf040(double *a); + void cftx020(double *a); +#ifdef USE_CDFT_THREADS + void cftrec4_th(int n, double *a, int nw, double *w); +#endif /* USE_CDFT_THREADS */ + + if (n > 8) { + if (n > 32) { + cftf1st(n, a, &w[nw - (n >> 2)]); +#ifdef USE_CDFT_THREADS + if (n > CDFT_THREADS_BEGIN_N) { + cftrec4_th(n, a, nw, w); + } else +#endif /* USE_CDFT_THREADS */ + if (n > 512) { + cftrec4(n, a, nw, w); + } else if (n > 128) { + cftleaf(n, 1, a, nw, w); + } else { + cftfx41(n, a, nw, w); + } + bitrv2(n, ip, a); + } else if (n == 32) { + cftf161(a, &w[nw - 8]); + bitrv216(a); + } else { + cftf081(a, w); + bitrv208(a); + } + } else if (n == 8) { + cftf040(a); + } else if (n == 4) { + cftx020(a); + } +} + + +void cftbsub(int n, double *a, int *ip, int nw, double *w) +{ + void bitrv2conj(int n, int *ip, double *a); + void bitrv216neg(double *a); + void bitrv208neg(double *a); + void cftb1st(int n, double *a, double *w); + void cftrec4(int n, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftfx41(int n, double *a, int nw, double *w); + void cftf161(double *a, double *w); + void cftf081(double *a, double *w); + void cftb040(double *a); + void cftx020(double *a); +#ifdef USE_CDFT_THREADS + void cftrec4_th(int n, double *a, int nw, double *w); +#endif /* USE_CDFT_THREADS */ + + if (n > 8) { + if (n > 32) { + cftb1st(n, a, &w[nw - (n >> 2)]); +#ifdef USE_CDFT_THREADS + if (n > CDFT_THREADS_BEGIN_N) { + cftrec4_th(n, a, nw, w); + } else +#endif /* USE_CDFT_THREADS */ + if (n > 512) { + cftrec4(n, a, nw, w); + } else if (n > 128) { + cftleaf(n, 1, a, nw, w); + } else { + cftfx41(n, a, nw, w); + } + bitrv2conj(n, ip, a); + } else if (n == 32) { + cftf161(a, &w[nw - 8]); + bitrv216neg(a); + } else { + cftf081(a, w); + bitrv208neg(a); + } + } else if (n == 8) { + cftb040(a); + } else if (n == 4) { + cftx020(a); + } +} + + +void bitrv2(int n, int *ip, double *a) +{ + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } +} + + +void bitrv2conj(int n, int *ip, double *a) +{ + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } else { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += nm; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } +} + + +void bitrv216(double *a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x7r, x7i, x8r, x8i, x10r, x10i, + x11r, x11i, x12r, x12i, x13r, x13i, x14r, x14i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + a[2] = x8r; + a[3] = x8i; + a[4] = x4r; + a[5] = x4i; + a[6] = x12r; + a[7] = x12i; + a[8] = x2r; + a[9] = x2i; + a[10] = x10r; + a[11] = x10i; + a[14] = x14r; + a[15] = x14i; + a[16] = x1r; + a[17] = x1i; + a[20] = x5r; + a[21] = x5i; + a[22] = x13r; + a[23] = x13i; + a[24] = x3r; + a[25] = x3i; + a[26] = x11r; + a[27] = x11i; + a[28] = x7r; + a[29] = x7i; +} + + +void bitrv216neg(double *a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i, x8r, x8i, + x9r, x9i, x10r, x10i, x11r, x11i, x12r, x12i, + x13r, x13i, x14r, x14i, x15r, x15i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x9r = a[18]; + x9i = a[19]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + x15r = a[30]; + x15i = a[31]; + a[2] = x15r; + a[3] = x15i; + a[4] = x7r; + a[5] = x7i; + a[6] = x11r; + a[7] = x11i; + a[8] = x3r; + a[9] = x3i; + a[10] = x13r; + a[11] = x13i; + a[12] = x5r; + a[13] = x5i; + a[14] = x9r; + a[15] = x9i; + a[16] = x1r; + a[17] = x1i; + a[18] = x14r; + a[19] = x14i; + a[20] = x6r; + a[21] = x6i; + a[22] = x10r; + a[23] = x10i; + a[24] = x2r; + a[25] = x2i; + a[26] = x12r; + a[27] = x12i; + a[28] = x4r; + a[29] = x4i; + a[30] = x8r; + a[31] = x8i; +} + + +void bitrv208(double *a) +{ + double x1r, x1i, x3r, x3i, x4r, x4i, x6r, x6i; + + x1r = a[2]; + x1i = a[3]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x6r = a[12]; + x6i = a[13]; + a[2] = x4r; + a[3] = x4i; + a[6] = x6r; + a[7] = x6i; + a[8] = x1r; + a[9] = x1i; + a[12] = x3r; + a[13] = x3i; +} + + +void bitrv208neg(double *a) +{ + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + a[2] = x7r; + a[3] = x7i; + a[4] = x3r; + a[5] = x3i; + a[6] = x5r; + a[7] = x5i; + a[8] = x1r; + a[9] = x1i; + a[10] = x6r; + a[11] = x6i; + a[12] = x2r; + a[13] = x2i; + a[14] = x4r; + a[15] = x4i; +} + + +void cftf1st(int n, double *a, double *w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = a[j + 3] + a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = a[j + 3] - a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = a[j0 - 1] + a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = a[j0 - 1] + a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i + x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = a[j0 + 3] + a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = a[j0 + 3] - a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i + x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + + +void cftb1st(int n, double *a, double *w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = -a[1] - a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = -a[1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j2] = x1r + x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r - x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = -a[j + 1] - a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = -a[j + 1] + a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = -a[j + 3] - a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = -a[j + 3] + a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = -a[j0 - 1] - a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = -a[j0 - 1] - a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i - x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = -a[j0 + 3] - a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = -a[j0 + 3] + a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i - x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + + +#ifdef USE_CDFT_THREADS +struct cdft_arg_st { + int n0; + int n; + double *a; + int nw; + double *w; +}; +typedef struct cdft_arg_st cdft_arg_t; + + +void cftrec4_th(int n, double *a, int nw, double *w) +{ + void *cftrec1_th(void *p); + void *cftrec2_th(void *p); + int i, idiv4, m, nthread; + cdft_thread_t th[4]; + cdft_arg_t ag[4]; + + nthread = 2; + idiv4 = 0; + m = n >> 1; + if (n > CDFT_4THREADS_BEGIN_N) { + nthread = 4; + idiv4 = 1; + m >>= 1; + } + for (i = 0; i < nthread; i++) { + ag[i].n0 = n; + ag[i].n = m; + ag[i].a = &a[i * m]; + ag[i].nw = nw; + ag[i].w = w; + if (i != idiv4) { + cdft_thread_create(&th[i], cftrec1_th, &ag[i]); + } else { + cdft_thread_create(&th[i], cftrec2_th, &ag[i]); + } + } + for (i = 0; i < nthread; i++) { + cdft_thread_wait(th[i]); + } +} + + +void *cftrec1_th(void *p) +{ + int cfttree(int n, int j, int k, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftmdl1(int n, double *a, double *w); + int isplt, j, k, m, n, n0, nw; + double *a, *w; + + n0 = ((cdft_arg_t *) p)->n0; + n = ((cdft_arg_t *) p)->n; + a = ((cdft_arg_t *) p)->a; + nw = ((cdft_arg_t *) p)->nw; + w = ((cdft_arg_t *) p)->w; + m = n0; + while (m > 512) { + m >>= 2; + cftmdl1(m, &a[n - m], &w[nw - (m >> 1)]); + } + cftleaf(m, 1, &a[n - m], nw, w); + k = 0; + for (j = n - m; j > 0; j -= m) { + k++; + isplt = cfttree(m, j, k, a, nw, w); + cftleaf(m, isplt, &a[j - m], nw, w); + } + return (void *) 0; +} + + +void *cftrec2_th(void *p) +{ + int cfttree(int n, int j, int k, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftmdl2(int n, double *a, double *w); + int isplt, j, k, m, n, n0, nw; + double *a, *w; + + n0 = ((cdft_arg_t *) p)->n0; + n = ((cdft_arg_t *) p)->n; + a = ((cdft_arg_t *) p)->a; + nw = ((cdft_arg_t *) p)->nw; + w = ((cdft_arg_t *) p)->w; + k = 1; + m = n0; + while (m > 512) { + m >>= 2; + k <<= 2; + cftmdl2(m, &a[n - m], &w[nw - m]); + } + cftleaf(m, 0, &a[n - m], nw, w); + k >>= 1; + for (j = n - m; j > 0; j -= m) { + k++; + isplt = cfttree(m, j, k, a, nw, w); + cftleaf(m, isplt, &a[j - m], nw, w); + } + return (void *) 0; +} +#endif /* USE_CDFT_THREADS */ + + +void cftrec4(int n, double *a, int nw, double *w) +{ + int cfttree(int n, int j, int k, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftmdl1(int n, double *a, double *w); + int isplt, j, k, m; + + m = n; + while (m > 512) { + m >>= 2; + cftmdl1(m, &a[n - m], &w[nw - (m >> 1)]); + } + cftleaf(m, 1, &a[n - m], nw, w); + k = 0; + for (j = n - m; j > 0; j -= m) { + k++; + isplt = cfttree(m, j, k, a, nw, w); + cftleaf(m, isplt, &a[j - m], nw, w); + } +} + + +int cfttree(int n, int j, int k, double *a, int nw, double *w) +{ + void cftmdl1(int n, double *a, double *w); + void cftmdl2(int n, double *a, double *w); + int i, isplt, m; + + if ((k & 3) != 0) { + isplt = k & 1; + if (isplt != 0) { + cftmdl1(n, &a[j - n], &w[nw - (n >> 1)]); + } else { + cftmdl2(n, &a[j - n], &w[nw - n]); + } + } else { + m = n; + for (i = k; (i & 3) == 0; i >>= 2) { + m <<= 2; + } + isplt = i & 1; + if (isplt != 0) { + while (m > 128) { + cftmdl1(m, &a[j - m], &w[nw - (m >> 1)]); + m >>= 2; + } + } else { + while (m > 128) { + cftmdl2(m, &a[j - m], &w[nw - m]); + m >>= 2; + } + } + } + return isplt; +} + + +void cftleaf(int n, int isplt, double *a, int nw, double *w) +{ + void cftmdl1(int n, double *a, double *w); + void cftmdl2(int n, double *a, double *w); + void cftf161(double *a, double *w); + void cftf162(double *a, double *w); + void cftf081(double *a, double *w); + void cftf082(double *a, double *w); + + if (n == 512) { + cftmdl1(128, a, &w[nw - 64]); + cftf161(a, &w[nw - 8]); + cftf162(&a[32], &w[nw - 32]); + cftf161(&a[64], &w[nw - 8]); + cftf161(&a[96], &w[nw - 8]); + cftmdl2(128, &a[128], &w[nw - 128]); + cftf161(&a[128], &w[nw - 8]); + cftf162(&a[160], &w[nw - 32]); + cftf161(&a[192], &w[nw - 8]); + cftf162(&a[224], &w[nw - 32]); + cftmdl1(128, &a[256], &w[nw - 64]); + cftf161(&a[256], &w[nw - 8]); + cftf162(&a[288], &w[nw - 32]); + cftf161(&a[320], &w[nw - 8]); + cftf161(&a[352], &w[nw - 8]); + if (isplt != 0) { + cftmdl1(128, &a[384], &w[nw - 64]); + cftf161(&a[480], &w[nw - 8]); + } else { + cftmdl2(128, &a[384], &w[nw - 128]); + cftf162(&a[480], &w[nw - 32]); + } + cftf161(&a[384], &w[nw - 8]); + cftf162(&a[416], &w[nw - 32]); + cftf161(&a[448], &w[nw - 8]); + } else { + cftmdl1(64, a, &w[nw - 32]); + cftf081(a, &w[nw - 8]); + cftf082(&a[16], &w[nw - 8]); + cftf081(&a[32], &w[nw - 8]); + cftf081(&a[48], &w[nw - 8]); + cftmdl2(64, &a[64], &w[nw - 64]); + cftf081(&a[64], &w[nw - 8]); + cftf082(&a[80], &w[nw - 8]); + cftf081(&a[96], &w[nw - 8]); + cftf082(&a[112], &w[nw - 8]); + cftmdl1(64, &a[128], &w[nw - 32]); + cftf081(&a[128], &w[nw - 8]); + cftf082(&a[144], &w[nw - 8]); + cftf081(&a[160], &w[nw - 8]); + cftf081(&a[176], &w[nw - 8]); + if (isplt != 0) { + cftmdl1(64, &a[192], &w[nw - 32]); + cftf081(&a[240], &w[nw - 8]); + } else { + cftmdl2(64, &a[192], &w[nw - 64]); + cftf082(&a[240], &w[nw - 8]); + } + cftf081(&a[192], &w[nw - 8]); + cftf082(&a[208], &w[nw - 8]); + cftf081(&a[224], &w[nw - 8]); + } +} + + +void cftmdl1(int n, double *a, double *w) +{ + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + k = 0; + for (j = 2; j < mh; j += 2) { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + } + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); +} + + +void cftmdl2(int n, double *a, double *w) +{ + int j, j0, j1, j2, j3, k, kr, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i, wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, y0r, y0i, y2r, y2i; + + mh = n >> 3; + m = 2 * mh; + wn4r = w[1]; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] - a[j2 + 1]; + x0i = a[1] + a[j2]; + x1r = a[0] + a[j2 + 1]; + x1i = a[1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wn4r * (x2r - x2i); + y0i = wn4r * (x2i + x2r); + a[0] = x0r + y0r; + a[1] = x0i + y0i; + a[j1] = x0r - y0r; + a[j1 + 1] = x0i - y0i; + y0r = wn4r * (x3r - x3i); + y0i = wn4r * (x3i + x3r); + a[j2] = x1r - y0i; + a[j2 + 1] = x1i + y0r; + a[j3] = x1r + y0i; + a[j3 + 1] = x1i - y0r; + k = 0; + kr = 2 * m; + for (j = 2; j < mh; j += 2) { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + kr -= 4; + wd1i = w[kr]; + wd1r = w[kr + 1]; + wd3i = w[kr + 2]; + wd3r = w[kr + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] - a[j2 + 1]; + x0i = a[j + 1] + a[j2]; + x1r = a[j] + a[j2 + 1]; + x1i = a[j + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wd1r * x2r - wd1i * x2i; + y2i = wd1r * x2i + wd1i * x2r; + a[j] = y0r + y2r; + a[j + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk3r * x1r + wk3i * x1i; + y0i = wk3r * x1i - wk3i * x1r; + y2r = wd3r * x3r + wd3i * x3i; + y2i = wd3r * x3i - wd3i * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wd1i * x0r - wd1r * x0i; + y0i = wd1i * x0i + wd1r * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wd3i * x1r + wd3r * x1i; + y0i = wd3i * x1i - wd3r * x1r; + y2r = wk3i * x3r + wk3r * x3i; + y2i = wk3i * x3i - wk3r * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + } + wk1r = w[m]; + wk1i = w[m + 1]; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk1i * x1r - wk1r * x1i; + y0i = wk1i * x1i + wk1r * x1r; + y2r = wk1r * x3r - wk1i * x3i; + y2i = wk1r * x3i + wk1i * x3r; + a[j2] = y0r - y2r; + a[j2 + 1] = y0i - y2i; + a[j3] = y0r + y2r; + a[j3 + 1] = y0i + y2i; +} + + +void cftfx41(int n, double *a, int nw, double *w) +{ + void cftf161(double *a, double *w); + void cftf162(double *a, double *w); + void cftf081(double *a, double *w); + void cftf082(double *a, double *w); + + if (n == 128) { + cftf161(a, &w[nw - 8]); + cftf162(&a[32], &w[nw - 32]); + cftf161(&a[64], &w[nw - 8]); + cftf161(&a[96], &w[nw - 8]); + } else { + cftf081(a, &w[nw - 8]); + cftf082(&a[16], &w[nw - 8]); + cftf081(&a[32], &w[nw - 8]); + cftf081(&a[48], &w[nw - 8]); + } +} + + +void cftf161(double *a, double *w) +{ + double wn4r, wk1r, wk1i, + x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + x0r = a[0] + a[16]; + x0i = a[1] + a[17]; + x1r = a[0] - a[16]; + x1i = a[1] - a[17]; + x2r = a[8] + a[24]; + x2i = a[9] + a[25]; + x3r = a[8] - a[24]; + x3i = a[9] - a[25]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y4r = x0r - x2r; + y4i = x0i - x2i; + y8r = x1r - x3i; + y8i = x1i + x3r; + y12r = x1r + x3i; + y12i = x1i - x3r; + x0r = a[2] + a[18]; + x0i = a[3] + a[19]; + x1r = a[2] - a[18]; + x1i = a[3] - a[19]; + x2r = a[10] + a[26]; + x2i = a[11] + a[27]; + x3r = a[10] - a[26]; + x3i = a[11] - a[27]; + y1r = x0r + x2r; + y1i = x0i + x2i; + y5r = x0r - x2r; + y5i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y9r = wk1r * x0r - wk1i * x0i; + y9i = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y13r = wk1i * x0r - wk1r * x0i; + y13i = wk1i * x0i + wk1r * x0r; + x0r = a[4] + a[20]; + x0i = a[5] + a[21]; + x1r = a[4] - a[20]; + x1i = a[5] - a[21]; + x2r = a[12] + a[28]; + x2i = a[13] + a[29]; + x3r = a[12] - a[28]; + x3i = a[13] - a[29]; + y2r = x0r + x2r; + y2i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y10r = wn4r * (x0r - x0i); + y10i = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + y14r = wn4r * (x0r + x0i); + y14i = wn4r * (x0i - x0r); + x0r = a[6] + a[22]; + x0i = a[7] + a[23]; + x1r = a[6] - a[22]; + x1i = a[7] - a[23]; + x2r = a[14] + a[30]; + x2i = a[15] + a[31]; + x3r = a[14] - a[30]; + x3i = a[15] - a[31]; + y3r = x0r + x2r; + y3i = x0i + x2i; + y7r = x0r - x2r; + y7i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y11r = wk1i * x0r - wk1r * x0i; + y11i = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y15r = wk1r * x0r - wk1i * x0i; + y15i = wk1r * x0i + wk1i * x0r; + x0r = y12r - y14r; + x0i = y12i - y14i; + x1r = y12r + y14r; + x1i = y12i + y14i; + x2r = y13r - y15r; + x2i = y13i - y15i; + x3r = y13r + y15r; + x3i = y13i + y15i; + a[24] = x0r + x2r; + a[25] = x0i + x2i; + a[26] = x0r - x2r; + a[27] = x0i - x2i; + a[28] = x1r - x3i; + a[29] = x1i + x3r; + a[30] = x1r + x3i; + a[31] = x1i - x3r; + x0r = y8r + y10r; + x0i = y8i + y10i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + x3r = y9r - y11r; + x3i = y9i - y11i; + a[16] = x0r + x2r; + a[17] = x0i + x2i; + a[18] = x0r - x2r; + a[19] = x0i - x2i; + a[20] = x1r - x3i; + a[21] = x1i + x3r; + a[22] = x1r + x3i; + a[23] = x1i - x3r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + x0r = y5r + y7i; + x0i = y5i - y7r; + x3r = wn4r * (x0r - x0i); + x3i = wn4r * (x0i + x0r); + x0r = y4r - y6i; + x0i = y4i + y6r; + x1r = y4r + y6i; + x1i = y4i - y6r; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[10] = x0r - x2r; + a[11] = x0i - x2i; + a[12] = x1r - x3i; + a[13] = x1i + x3r; + a[14] = x1r + x3i; + a[15] = x1i - x3r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + x3r = y1r - y3r; + x3i = y1i - y3i; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x0r - x2r; + a[3] = x0i - x2i; + a[4] = x1r - x3i; + a[5] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + + +void cftf162(double *a, double *w) +{ + double wn4r, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + x0r, x0i, x1r, x1i, x2r, x2i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[4]; + wk1i = w[5]; + wk3r = w[6]; + wk3i = -w[7]; + wk2r = w[8]; + wk2i = w[9]; + x1r = a[0] - a[17]; + x1i = a[1] + a[16]; + x0r = a[8] - a[25]; + x0i = a[9] + a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y0r = x1r + x2r; + y0i = x1i + x2i; + y4r = x1r - x2r; + y4i = x1i - x2i; + x1r = a[0] + a[17]; + x1i = a[1] - a[16]; + x0r = a[8] + a[25]; + x0i = a[9] - a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y8r = x1r - x2i; + y8i = x1i + x2r; + y12r = x1r + x2i; + y12i = x1i - x2r; + x0r = a[2] - a[19]; + x0i = a[3] + a[18]; + x1r = wk1r * x0r - wk1i * x0i; + x1i = wk1r * x0i + wk1i * x0r; + x0r = a[10] - a[27]; + x0i = a[11] + a[26]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y1r = x1r + x2r; + y1i = x1i + x2i; + y5r = x1r - x2r; + y5i = x1i - x2i; + x0r = a[2] + a[19]; + x0i = a[3] - a[18]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[10] + a[27]; + x0i = a[11] - a[26]; + x2r = wk1r * x0r + wk1i * x0i; + x2i = wk1r * x0i - wk1i * x0r; + y9r = x1r - x2r; + y9i = x1i - x2i; + y13r = x1r + x2r; + y13i = x1i + x2i; + x0r = a[4] - a[21]; + x0i = a[5] + a[20]; + x1r = wk2r * x0r - wk2i * x0i; + x1i = wk2r * x0i + wk2i * x0r; + x0r = a[12] - a[29]; + x0i = a[13] + a[28]; + x2r = wk2i * x0r - wk2r * x0i; + x2i = wk2i * x0i + wk2r * x0r; + y2r = x1r + x2r; + y2i = x1i + x2i; + y6r = x1r - x2r; + y6i = x1i - x2i; + x0r = a[4] + a[21]; + x0i = a[5] - a[20]; + x1r = wk2i * x0r - wk2r * x0i; + x1i = wk2i * x0i + wk2r * x0r; + x0r = a[12] + a[29]; + x0i = a[13] - a[28]; + x2r = wk2r * x0r - wk2i * x0i; + x2i = wk2r * x0i + wk2i * x0r; + y10r = x1r - x2r; + y10i = x1i - x2i; + y14r = x1r + x2r; + y14i = x1i + x2i; + x0r = a[6] - a[23]; + x0i = a[7] + a[22]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[14] - a[31]; + x0i = a[15] + a[30]; + x2r = wk1i * x0r - wk1r * x0i; + x2i = wk1i * x0i + wk1r * x0r; + y3r = x1r + x2r; + y3i = x1i + x2i; + y7r = x1r - x2r; + y7i = x1i - x2i; + x0r = a[6] + a[23]; + x0i = a[7] - a[22]; + x1r = wk1i * x0r + wk1r * x0i; + x1i = wk1i * x0i - wk1r * x0r; + x0r = a[14] + a[31]; + x0i = a[15] - a[30]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y11r = x1r + x2r; + y11i = x1i + x2i; + y15r = x1r - x2r; + y15i = x1i - x2i; + x1r = y0r + y2r; + x1i = y0i + y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + a[0] = x1r + x2r; + a[1] = x1i + x2i; + a[2] = x1r - x2r; + a[3] = x1i - x2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r - y3r; + x2i = y1i - y3i; + a[4] = x1r - x2i; + a[5] = x1i + x2r; + a[6] = x1r + x2i; + a[7] = x1i - x2r; + x1r = y4r - y6i; + x1i = y4i + y6r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[8] = x1r + x2r; + a[9] = x1i + x2i; + a[10] = x1r - x2r; + a[11] = x1i - x2i; + x1r = y4r + y6i; + x1i = y4i - y6r; + x0r = y5r + y7i; + x0i = y5i - y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[12] = x1r - x2i; + a[13] = x1i + x2r; + a[14] = x1r + x2i; + a[15] = x1i - x2r; + x1r = y8r + y10r; + x1i = y8i + y10i; + x2r = y9r - y11r; + x2i = y9i - y11i; + a[16] = x1r + x2r; + a[17] = x1i + x2i; + a[18] = x1r - x2r; + a[19] = x1i - x2i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + a[20] = x1r - x2i; + a[21] = x1i + x2r; + a[22] = x1r + x2i; + a[23] = x1i - x2r; + x1r = y12r - y14i; + x1i = y12i + y14r; + x0r = y13r + y15i; + x0i = y13i - y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[24] = x1r + x2r; + a[25] = x1i + x2i; + a[26] = x1r - x2r; + a[27] = x1i - x2i; + x1r = y12r + y14i; + x1i = y12i - y14r; + x0r = y13r - y15i; + x0i = y13i + y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[28] = x1r - x2i; + a[29] = x1i + x2r; + a[30] = x1r + x2i; + a[31] = x1i - x2r; +} + + +void cftf081(double *a, double *w) +{ + double wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + x0r = a[0] + a[8]; + x0i = a[1] + a[9]; + x1r = a[0] - a[8]; + x1i = a[1] - a[9]; + x2r = a[4] + a[12]; + x2i = a[5] + a[13]; + x3r = a[4] - a[12]; + x3i = a[5] - a[13]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[2] + a[10]; + x0i = a[3] + a[11]; + x1r = a[2] - a[10]; + x1i = a[3] - a[11]; + x2r = a[6] + a[14]; + x2i = a[7] + a[15]; + x3r = a[6] - a[14]; + x3i = a[7] - a[15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[8] = y1r + y5r; + a[9] = y1i + y5i; + a[10] = y1r - y5r; + a[11] = y1i - y5i; + a[12] = y3r - y7i; + a[13] = y3i + y7r; + a[14] = y3r + y7i; + a[15] = y3i - y7r; + a[0] = y0r + y4r; + a[1] = y0i + y4i; + a[2] = y0r - y4r; + a[3] = y0i - y4i; + a[4] = y2r - y6i; + a[5] = y2i + y6r; + a[6] = y2r + y6i; + a[7] = y2i - y6r; +} + + +void cftf082(double *a, double *w) +{ + double wn4r, wk1r, wk1i, x0r, x0i, x1r, x1i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + y0r = a[0] - a[9]; + y0i = a[1] + a[8]; + y1r = a[0] + a[9]; + y1i = a[1] - a[8]; + x0r = a[4] - a[13]; + x0i = a[5] + a[12]; + y2r = wn4r * (x0r - x0i); + y2i = wn4r * (x0i + x0r); + x0r = a[4] + a[13]; + x0i = a[5] - a[12]; + y3r = wn4r * (x0r - x0i); + y3i = wn4r * (x0i + x0r); + x0r = a[2] - a[11]; + x0i = a[3] + a[10]; + y4r = wk1r * x0r - wk1i * x0i; + y4i = wk1r * x0i + wk1i * x0r; + x0r = a[2] + a[11]; + x0i = a[3] - a[10]; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + x0r = a[6] - a[15]; + x0i = a[7] + a[14]; + y6r = wk1i * x0r - wk1r * x0i; + y6i = wk1i * x0i + wk1r * x0r; + x0r = a[6] + a[15]; + x0i = a[7] - a[14]; + y7r = wk1r * x0r - wk1i * x0i; + y7i = wk1r * x0i + wk1i * x0r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y4r + y6r; + x1i = y4i + y6i; + a[0] = x0r + x1r; + a[1] = x0i + x1i; + a[2] = x0r - x1r; + a[3] = x0i - x1i; + x0r = y0r - y2r; + x0i = y0i - y2i; + x1r = y4r - y6r; + x1i = y4i - y6i; + a[4] = x0r - x1i; + a[5] = x0i + x1r; + a[6] = x0r + x1i; + a[7] = x0i - x1r; + x0r = y1r - y3i; + x0i = y1i + y3r; + x1r = y5r - y7r; + x1i = y5i - y7i; + a[8] = x0r + x1r; + a[9] = x0i + x1i; + a[10] = x0r - x1r; + a[11] = x0i - x1i; + x0r = y1r + y3i; + x0i = y1i - y3r; + x1r = y5r + y7r; + x1i = y5i + y7i; + a[12] = x0r - x1i; + a[13] = x0i + x1r; + a[14] = x0r + x1i; + a[15] = x0i - x1r; +} + + +void cftf040(double *a) +{ + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + + +void cftb040(double *a) +{ + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r + x3i; + a[3] = x1i - x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r - x3i; + a[7] = x1i + x3r; +} + + +void cftx020(double *a) +{ + double x0r, x0i; + + x0r = a[0] - a[2]; + x0i = a[1] - a[3]; + a[0] += a[2]; + a[1] += a[3]; + a[2] = x0r; + a[3] = x0i; +} + + +void rftfsub(int n, double *a, int nc, double *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + + +void rftbsub(int n, double *a, int nc, double *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + + +void dctsub(int n, double *a, int nc, double *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[j] - wkr * a[k]; + a[j] = wkr * a[j] + wki * a[k]; + a[k] = xr; + } + a[m] *= c[0]; +} + + +void dstsub(int n, double *a, int nc, double *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[k] - wkr * a[j]; + a[k] = wkr * a[k] + wki * a[j]; + a[j] = xr; + } + a[m] *= c[0]; +} + +#if defined(__cplusplus) +} // end of extern "C" +#endif diff --git a/src/loris/loris.h b/src/loris/loris.h new file mode 100644 index 0000000..26d0d15 --- /dev/null +++ b/src/loris/loris.h @@ -0,0 +1,1020 @@ +#ifndef INCLUDE_LORIS_H +#define INCLUDE_LORIS_H +/* + * 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 + * + * + * loris.h + * + * Header specifying C-linkable procedural interface for Loris. + * + * Main components of this interface: + * - version identification symbols + * - type declarations + * - Analyzer configuration + * - LinearEnvelope (formerly BreakpointEnvelope) operations + * - PartialList operations + * - Partial operations + * - Breakpoint operations + * - sound modeling functions for preparing PartialLists + * - utility functions for manipulating PartialLists + * - notification and exception handlers (all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function. The default one in Loris uses printf()). + * + * loris.h is generated automatically from loris.h.in. Do not modify loris.h + * + * Kelly Fitz, 4 Feb 2002 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +/* ---------------------------------------------------------------- */ +/* Version +/* +/* Define symbols that facilitate version/release identification. + */ + +#define LORIS_MAJOR_VERSION 1 +#define LORIS_MINOR_VERSION 8 +#define LORIS_SUBMINOR_VERSION +#define LORIS_VERSION_STR "Loris 1.8" + +/* ---------------------------------------------------------------- */ +/* Types +/* +/* The (class) types Breakpoint, LinearEnvelope, Partial, + and PartialList are imported from the Loris namespace. + The first three are classes, the latter is a typedef + for std::list< Loris::Partial >. + */ +#if defined(__cplusplus) + // include std library list header, declaring templates + // is too painful and fragile: + #include <list> + + // declare Loris classes in Loris namespace: + namespace Loris + { + class Breakpoint; + class LinearEnvelope; + class Partial; + + // this typedef has to be copied from PartialList.h + typedef std::list< Loris::Partial > PartialList; + } + + // import those names into the global namespace + using Loris::Breakpoint; + using Loris::LinearEnvelope; + using Loris::Partial; + using Loris::PartialList; +#else + /* no classes, just declare types and use + opaque C pointers + */ + typedef struct Breakpoint Breakpoint; + typedef struct LinearEnvelope LinearEnvelope; + typedef struct PartialList PartialList; + typedef struct Partial Partial; +#endif + +/* + TODO + Maybe should also have loris_label_t and loris_size_t + defined, depending on configure. +*/ + +#if defined(__cplusplus) + extern "C" { +#endif + +/* ---------------------------------------------------------------- */ +/* Analyzer configuration +/* +/* An Analyzer represents a configuration of parameters for + performing Reassigned Bandwidth-Enhanced Additive Analysis + of sampled waveforms. This analysis process yields a collection + of Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ + +void analyze( const double * buffer, unsigned int bufferSize, + double srate, PartialList * partials ); +/* Analyze an array of bufferSize (mono) samples at the given sample rate + (in Hz) and append the extracted Partials to the given + PartialList. + */ + +void analyzer_configure( double resolution, double windowWidth ); +/* Configure the sole Analyzer instance with the specified + frequency resolution (minimum instantaneous frequency + difference between Partials). All other Analyzer parameters + are computed from the specified frequency resolution. + + Construct the Analyzer instance if necessary. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ + +double analyzer_getAmpFloor( void ); +/* Return the amplitude floor (lowest detected spectral amplitude), + in (negative) dB, for the Loris Analyzer. + */ + +double analyzer_getCropTime( void ); +/* Return the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for the Loris Analyzer. + */ + +double analyzer_getFreqDrift( void ); +/* Return the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for the Loris Analyzer. + */ + +double analyzer_getFreqFloor( void ); +/* Return the frequency floor (minimum instantaneous Partial + frequency), in Hz, for the Loris Analyzer. + */ + +double analyzer_getFreqResolution( void ); +/* Return the frequency resolution (minimum instantaneous frequency + difference between Partials) for the Loris Analyzer. + */ + +double analyzer_getHopTime( void ); +/* Return the hop time (which corresponds approximately to the + average density of Partial envelope Breakpoint data) for this + Analyzer. + */ + +double analyzer_getSidelobeLevel( void ); +/* Return the sidelobe attenutation level for the Kaiser analysis window in + positive dB. Higher numbers (e.g. 90) give very good sidelobe + rejection but cause the window to be longer in time. Smaller + numbers raise the level of the sidelobes, increasing the likelihood + of frequency-domain interference, but allow the window to be shorter + in time. + */ + +double analyzer_getWindowWidth( void ); +/* Return the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by the Loris Analyzer. + */ + +void analyzer_setAmpFloor( double x ); +/* Set the amplitude floor (lowest detected spectral amplitude), in + (negative) dB, for the Loris Analyzer. + */ + +void analyzer_setBwRegionWidth( double x ); +/* Deprecated, use analyzer_storeResidueBandwidth instead. + */ + +void analyzer_setCropTime( double x ); +/* Set the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for the Loris Analyzer. + */ + +void analyzer_setFreqDrift( double x ); +/* Set the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for the Loris Analyzer. + */ + +void analyzer_setFreqFloor( double x ); +/* Set the amplitude floor (minimum instantaneous Partial + frequency), in Hz, for the Loris Analyzer. + */ + +void analyzer_setFreqResolution( double x ); +/* Set the frequency resolution (minimum instantaneous frequency + difference between Partials) for the Loris Analyzer. (Does not cause + other parameters to be recomputed.) + */ + +void analyzer_setHopTime( double x ); +/* Set the hop time (which corresponds approximately to the average + density of Partial envelope Breakpoint data) for the Loris Analyzer. + */ + +void analyzer_setSidelobeLevel( double x ); +/* Set the sidelobe attenutation level for the Kaiser analysis window in + positive dB. Larger numbers (e.g. 90) give very good sidelobe + rejection but cause the window to be longer in time. Smaller + numbers raise the level of the sidelobes, increasing the likelihood + of frequency-domain interference, but allow the window to be shorter + in time. + */ + +void analyzer_setWindowWidth( double x ); +/* Set the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by the Loris Analyzer. + */ + +void analyzer_storeResidueBandwidth( double regionWidth ); +/* Construct Partial bandwidth envelopes during analysis + by associating residual energy in the spectrum (after + peak extraction) with the selected spectral peaks that + are used to construct Partials. + + regionWidth is the width (in Hz) of the bandwidth + association regions used by this process, must be positive. + */ + +void analyzer_storeConvergenceBandwidth( double tolerancePct ); +/* Construct Partial bandwidth envelopes during analysis + by storing the mixed derivative of short-time phase, + scaled and shifted so that a value of 0 corresponds + to a pure sinusoid, and a value of 1 corresponds to a + bandwidth-enhanced sinusoid with maximal energy spread + (minimum sinusoidal convergence). + + tolerance is the amount of range over which the + mixed derivative indicator should be allowed to drift away + from a pure sinusoid before saturating. This range is mapped + to bandwidth values on the range [0,1]. Must be positive and + not greater than 1. + */ + +void analyzer_storeNoBandwidth( void ); +/* Disable bandwidth envelope construction. Bandwidth + will be zero for all Breakpoints in all Partials. + */ + +double analyzer_getBwRegionWidth( void ); +/* Return the width (in Hz) of the Bandwidth Association regions + used by this Analyzer, only if the spectral residue method is + used to compute bandwidth envelopes. Return zero if the mixed + derivative method is used, or if no bandwidth is computed. + */ + +double analyzer_getBwConvergenceTolerance( void ); +/* Return the mixed derivative convergence tolerance + only if the convergence indicator is used to compute + bandwidth envelopes. Return zero if the spectral residue + method is used or if no bandwidth is computed. + */ + + +/* ---------------------------------------------------------------- */ +/* LinearEnvelope object interface +/* +/* A LinearEnvelope represents a linear segment breakpoint + function with infinite extension at each end (that is, the + values past either end of the breakpoint function have the + values at the nearest end). + + In C++, a LinearEnvelope is a Loris::LinearEnvelope. + */ + +LinearEnvelope * createLinearEnvelope( void ); +/* Construct and return a new LinearEnvelope having no + breakpoints and an implicit value of 0. everywhere, + until the first breakpoint is inserted. + */ + +LinearEnvelope * copyLinearEnvelope( const LinearEnvelope * ptr_this ); +/* Construct and return a new LinearEnvelope that is an + exact copy of the specified LinearEnvelopes, having + an identical set of breakpoints. + */ + +void destroyLinearEnvelope( LinearEnvelope * ptr_this ); +/* Destroy this LinearEnvelope. + */ + +void linearEnvelope_insertBreakpoint( LinearEnvelope * ptr_this, + double time, double val ); +/* Insert a breakpoint representing the specified (time, value) + pair into this LinearEnvelope. If there is already a + breakpoint at the specified time, it will be replaced with + the new breakpoint. + */ + +double linearEnvelope_valueAt( const LinearEnvelope * ptr_this, + double time ); +/* Return the interpolated value of this LinearEnvelope at the + specified time. + */ + +/* ---------------------------------------------------------------- */ +/* PartialList object interface +/* +/* A PartialList represents a collection of Bandwidth-Enhanced + Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Bandwidth-Enhanced Partials and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to + the Loris website: www.cerlsoundgroup.org/Loris/. + + In C++, a PartialList is a Loris::PartialList. + */ +PartialList * createPartialList( void ); +/* Return a new empty PartialList. + */ + +void destroyPartialList( PartialList * ptr_this ); +/* Destroy this PartialList. + */ + +void partialList_clear( PartialList * ptr_this ); +/* Remove (and destroy) all the Partials from this PartialList, + leaving it empty. + */ + +void partialList_copy( PartialList * ptr_this, + const PartialList * src ); +/* Make this PartialList a copy of the source PartialList by making + copies of all of the Partials in the source and adding them to + this PartialList. + */ + +unsigned long partialList_size( const PartialList * ptr_this ); +/* Return the number of Partials in this PartialList. + */ + +void partialList_splice( PartialList * ptr_this, + PartialList * src ); +/* Splice all the Partials in the source PartialList onto the end of + this PartialList, leaving the source empty. + */ + +/* ---------------------------------------------------------------- */ +/* Partial object interface +/* +/* A Partial represents a single component in the + reassigned bandwidth-enhanced additive model. A Partial consists of a + chain of Breakpoints describing the time-varying frequency, amplitude, + and bandwidth (or noisiness) envelopes of the component, and a 4-byte + label. The Breakpoints are non-uniformly distributed in time. For more + information about Reassigned Bandwidth-Enhanced Analysis and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to the Loris + website: www.cerlsoundgroup.org/Loris/. + */ + +double partial_startTime( const Partial * p ); +/* Return the start time (seconds) for the specified Partial. + */ + +double partial_endTime( const Partial * p ); +/* Return the end time (seconds) for the specified Partial. + */ + +double partial_duration( const Partial * p ); +/* Return the duration (seconds) for the specified Partial. + */ + +double partial_initialPhase( const Partial * p ); +/* Return the initial phase (radians) for the specified Partial. + */ + +int partial_label( const Partial * p ); +/* Return the integer label for the specified Partial. + */ + +unsigned long partial_numBreakpoints( const Partial * p ); +/* Return the number of Breakpoints in the specified Partial. + */ + +double partial_frequencyAt( const Partial * p, double t ); +/* Return the frequency (Hz) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_bandwidthAt( const Partial * p, double t ); +/* Return the bandwidth of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_phaseAt( const Partial * p, double t ); +/* Return the phase (radians) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_amplitudeAt( const Partial * p, double t ); +/* Return the (absolute) amplitude of the specified Partial interpolated + at a particular time. Partials are assumed to fade out + over 1 millisecond at the ends (rather than instantaneously). + It is an error to apply this function to a Partial having no Breakpoints. + */ + +void partial_setLabel( Partial * p, int label ); +/* Assign a new integer label to the specified Partial. + */ + +/* ---------------------------------------------------------------- */ +/* Breakpoint object interface +/* +/* A Breakpoint represents a single breakpoint in the + Partial parameter (frequency, amplitude, bandwidth) envelope. + Instantaneous phase is also stored, but is only used at the onset of + a partial, or when it makes a transition from zero to nonzero amplitude. + + Loris Partials represent reassigned bandwidth-enhanced model components. + A Partial consists of a chain of Breakpoints describing the time-varying + frequency, amplitude, and bandwidth (noisiness) of the component. + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: + www.cerlsoundgroup.org/Loris/. + */ + +double breakpoint_getAmplitude( const Breakpoint * bp ); +/* Return the (absolute) amplitude of the specified Breakpoint. + */ + +double breakpoint_getBandwidth( const Breakpoint * bp ); +/* Return the bandwidth coefficient of the specified Breakpoint. + */ + +double breakpoint_getFrequency( const Breakpoint * bp ); +/* Return the frequency (Hz) of the specified Breakpoint. + */ + +double breakpoint_getPhase( const Breakpoint * bp ); +/* Return the phase (radians) of the specified Breakpoint. + */ + +void breakpoint_setAmplitude( Breakpoint * bp, double a ); +/* Assign a new (absolute) amplitude to the specified Breakpoint. + */ + +void breakpoint_setBandwidth( Breakpoint * bp, double bw ); +/* Assign a new bandwidth coefficient to the specified Breakpoint. + */ + +void breakpoint_setFrequency( Breakpoint * bp, double f ); +/* Assign a new frequency (Hz) to the specified Breakpoint. + */ + +void breakpoint_setPhase( Breakpoint * bp, double phi ); +/* Assign a new phase (radians) to the specified Breakpoint. + */ + +/* ---------------------------------------------------------------- */ +/* non-object-based procedures +/* +/* Operations in Loris that need not be accessed though object + interfaces are represented as simple functions. + */ + +void channelize( PartialList * partials, + LinearEnvelope * refFreqEnvelope, int refLabel ); +/* Label Partials in a PartialList with the integer nearest to + the amplitude-weighted average ratio of their frequency envelope + to a reference frequency envelope. The frequency spectrum is + partitioned into non-overlapping channels whose time-varying + center frequencies track the reference frequency envelope. + The reference label indicates which channel's center frequency + is exactly equal to the reference envelope frequency, and other + channels' center frequencies are multiples of the reference + envelope frequency divided by the reference label. Each Partial + in the PartialList is labeled with the number of the channel + that best fits its frequency envelope. The quality of the fit + is evaluated at the breakpoints in the Partial envelope and + weighted by the amplitude at each breakpoint, so that high- + amplitude breakpoints contribute more to the channel decision. + Partials are labeled, but otherwise unmodified. In particular, + their frequencies are not modified in any way. + */ + +void collate( PartialList * partials ); +/* Collate unlabeled (zero-labeled) Partials into the smallest-possible + number of Partials that does not combine any overlapping Partials. + Collated Partials appear at the end of the sequence, after all + labeled Partials. + */ + +LinearEnvelope * +createFreqReference( PartialList * partials, + double minFreq, double maxFreq, long numSamps ); +/* Return a newly-constructed LinearEnvelope using the legacy + FrequencyReference class. The envelope will have approximately + the specified number of samples. The specified number of samples + must be greater than 1. Uses the FundamentalEstimator + (FundamentalFromPartials) class to construct an estimator of + fundamental frequency, configured to emulate the behavior of + the FrequencyReference class in Loris 1.4-1.5.2. If numSamps + is zero, construct the reference envelope from fundamental + estimates taken every five milliseconds. + + For simple sounds, this frequency reference may be a + good first approximation to a reference envelope for + channelization (see channelize()). + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ + +LinearEnvelope * +createF0Estimate( PartialList * partials, double minFreq, double maxFreq, + double interval ); +/* Return a newly-constructed LinearEnvelope that estimates + the time-varying fundamental frequency of the sound + represented by the Partials in a PartialList. This uses + the FundamentalEstimator (FundamentalFromPartials) + class to construct an estimator of fundamental frequency, + and returns a LinearEnvelope that samples the estimator at the + specified time interval (in seconds). Default values are used + to configure the estimator. Only estimates in the specified + frequency range will be considered valid, estimates outside this + range will be ignored. + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ + +void dilate( PartialList * partials, + const double * initial, const double * target, int npts ); +/* Dilate Partials in a PartialList according to the given + initial and target time points. Partial envelopes are + stretched and compressed so that temporal features at + the initial time points are aligned with the final time + points. Time points are sorted, so Partial envelopes are + are only stretched and compressed, but breakpoints are not + reordered. Duplicate time points are allowed. There must be + the same number of initial and target time points. + */ + +void distill( PartialList * partials ); +/* Distill labeled (channelized) Partials in a PartialList into a + PartialList containing at most one Partial per label. Unlabeled + (zero-labeled) Partials are left unmodified at the end of the + distilled Partials. + */ + +void exportAiff( const char * path, const double * buffer, + unsigned int bufferSize, double samplerate, int bitsPerSamp ); +/* Export mono audio samples stored in an array of size bufferSize to + an AIFF file having the specified sample rate at the given file path + (or name). The floating point samples in the buffer are clamped to the + range (-1.,1.) and converted to integers having bitsPerSamp bits. + */ + +void exportSdif( const char * path, PartialList * partials ); +/* Export Partials in a PartialList to a SDIF file at the specified + file path (or name). SDIF data is described by RBEM and RBEL + matrices. + For more information about SDIF, see the SDIF web site at: + www.ircam.fr/equipes/analyse-synthese/sdif/ + */ + +void exportSpc( const char * path, PartialList * partials, double midiPitch, + int enhanced, double endApproachTime ); +/* Export Partials in a PartialList to a Spc file at the specified file + path (or name). The fractional MIDI pitch must be specified. The + enhanced parameter defaults to true (for bandwidth-enhanced spc files), + but an be specified false for pure-sines spc files. The endApproachTime + parameter is in seconds. A nonzero endApproachTime indicates that the plist does + not include a release, but rather ends in a static spectrum corresponding + to the final breakpoint values of the partials. The endApproachTime + specifies how long before the end of the sound the amplitude, frequency, + and bandwidth values are to be modified to make a gradual transition to + the static spectrum. + */ + +/* Apply a reference Partial to fix the frequencies of Breakpoints + whose amplitude is below threshold_dB. 0 harmonifies full-amplitude + Partials, to apply only to quiet Partials, specify a lower + threshold like -90). The reference Partial is the first Partial + in the PartialList labeled refLabel (usually 1). The LinearEnvelope + is a time-varying weighting on the harmonifing process. When 1, + harmonic frequencies are used, when 0, breakpoint frequencies are + unmodified. + */ +void harmonify( PartialList * partials, long refLabel, + const LinearEnvelope * env, double threshold_dB ); + +unsigned int importAiff( const char * path, double * buffer, unsigned int bufferSize, + double * samplerate ); +/* Import audio samples stored in an AIFF file at the given file + path (or name). The samples are converted to floating point + values on the range (-1.,1.) and stored in an array of doubles. + The value returned is the number of samples in buffer, and it is at + most bufferSize. If samplerate is not a NULL pointer, + then, on return, it points to the value of the sample rate (in + Hz) of the AIFF samples. The AIFF file must contain only a single + channel of audio data. The prior contents of buffer, if any, are + overwritten. + */ + +void importSdif( const char * path, PartialList * partials ); +/* Import Partials from an SDIF file at the given file path (or + name), and append them to a PartialList. + */ + +void importSpc( const char * path, PartialList * partials ); +/* Import Partials from an Spc file at the given file path (or + name), and return them in a PartialList. + */ + +void morph( const PartialList * src0, const PartialList * src1, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ); +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ + +void morphWithReference( const PartialList * src0, + const PartialList * src1, + long src0RefLabel, + long src1RefLabel, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ); +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Specify the labels of the Partials to be used as + reference Partial for the two morph sources. The reference + partial is used to compute frequencies for very low-amplitude + Partials whose frequency estimates are not considered reliable. + The reference Partial is considered to have good frequency + estimates throughout. A reference label of 0 indicates that + no reference Partial should be used for the corresponding + morph source. + + Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ + +void morpher_setAmplitudeShape( double shape ); +/* Set the shaping parameter for the amplitude morphing + function. This shaping parameter controls the slope of + the amplitude morphing function, for values greater than + 1, this function gets nearly linear (like the old + amplitude morphing function), for values much less than + 1 (e.g. 1E-5) the slope is gently curved and sounds + pretty "linear", for very small values (e.g. 1E-12) the + curve is very steep and sounds un-natural because of the + huge jump from zero amplitude to very small amplitude. + + Use LORIS_DEFAULT_AMPMORPHSHAPE to obtain the default + amplitude morphing shape for Loris, (equal to 1E-5, + which works well for many musical instrument morphs, + unless Loris was compiled with the symbol + LINEAR_AMP_MORPHS defined, in which case + LORIS_DEFAULT_AMPMORPHSHAPE is equal to + LORIS_LINEAR_AMPMORPHSHAPE). + + Use LORIS_LINEAR_AMPMORPHSHAPE to approximate the linear + amplitude morphs performed by older versions of Loris. + + The amplitude shape must be positive. + */ + +extern const double LORIS_DEFAULT_AMPMORPHSHAPE; +extern const double LORIS_LINEAR_AMPMORPHSHAPE; + +void resample( PartialList * partials, double interval ); +/* Resample all Partials in a PartialList using the specified + sampling interval, so that the Breakpoints in the Partial + envelopes will all lie on a common temporal grid. + The Breakpoint times in resampled Partials will comprise a + contiguous sequence of integer multiples of the sampling interval, + beginning with the multiple nearest to the Partial's start time and + ending with the multiple nearest to the Partial's end time. Resampling + is performed in-place. + + */ + +void shapeSpectrum( PartialList * partials, PartialList * surface, + double stretchFreq, double stretchTime ); +/* Scale the amplitudes of a set of Partials by applying + a spectral suface constructed from another set. + Stretch the spectral surface in time and frequency + using the specified stretch factors. Set the stretch + factors to one for no stretching. + */ + +void sift( PartialList * partials ); +/* Identify overlapping Partials having the same (nonzero) + label. If any two partials with same label + overlap in time, set the label of the weaker + (having less total energy) partial to zero. + + */ + +unsigned int +synthesize( const PartialList * partials, + double * buffer, unsigned int bufferSize, + double srate ); +/* Synthesize Partials in a PartialList at the given sample + rate, and store the (floating point) samples in a buffer of + size bufferSize. The buffer is neither resized nor + cleared before synthesis, so newly synthesized samples are + added to any previously computed samples in the buffer, and + samples beyond the end of the buffer are lost. Return the + number of samples synthesized, that is, the index of the + latest sample in the buffer that was modified. + */ + +/* ---------------------------------------------------------------- */ +/* utility functions +/* +/* Operations for transforming and manipulating collections + of Partials. + */ + +double avgAmplitude( const Partial * p ); +/* Return the average amplitude over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ + +double avgFrequency( const Partial * p ); +/* Return the average frequency over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ + +void copyIf( const PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Append copies of Partials in the source PartialList satisfying the + specified predicate to the destination PartialList. The source list + is unmodified. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void copyLabeled( const PartialList * src, long label, PartialList * dst ); +/* Append copies of Partials in the source PartialList having the + specified label to the destination PartialList. The source list + is unmodified. + */ + +void crop( PartialList * partials, double t1, double t2 ); +/* Trim Partials by removing Breakpoints outside a specified time span. + Insert a Breakpoint at the boundary when cropping occurs. Remove + any Partials that are left empty after cropping (Partials having no + Breakpoints between t1 and t2). + */ + +void extractIf( PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Remove Partials in the source PartialList satisfying the + specified predicate from the source list and append them to + the destination PartialList. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void extractLabeled( PartialList * src, long label, PartialList * dst ); +/* Remove Partials in the source PartialList having the specified + label from the source list and append them to the destination + PartialList. + */ + +void fixPhaseAfter( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + corrected from its non-Null successor, if it has one, otherwise + it is unmodified. + */ + +void fixPhaseAt( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints in a Partial + so that the synthesized phases match the stored phases, + and the synthesized phase at (nearest) the specified + time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) + Breakpoint is encountered, because nulls are interpreted as + phase reset points in Loris. If a null is encountered, the + remainder of the Partial (the front part) is fixed in the + forward direction, beginning at the start of the Partial. + Forward phase fixing is only applied to non-null + (nonzero-amplitude) Breakpoints. If a null is encountered, + its phase is corrected from its non-Null successor, if + it has one, otherwise it is unmodified. + */ + +void fixPhaseBefore( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints earlier than the specified + time so that the synthesized phases of those earlier Breakpoints + matches the stored phase, and the synthesized phase at the + specified time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) Breakpoint + is encountered, because nulls are interpreted as phase reset + points in Loris. If a null is encountered, the remainder of the + Partial (the front part) is fixed in the forward direction, + beginning at the start of the Partial. + */ + +void fixPhaseBetween( PartialList * partials, double tbeg, double tend ); +/* + Fix the phase travel between two times by adjusting the + frequency and phase of Breakpoints between those two times. + + This algorithm assumes that there is nothing interesting + about the phases of the intervening Breakpoints, and modifies + their frequencies as little as possible to achieve the correct + amount of phase travel such that the frequencies and phases at + the specified times match the stored values. The phases of all + the Breakpoints between the specified times are recomputed. + */ + +void fixPhaseForward( PartialList * partials, double tbeg, double tend ); +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + Breakpoints later than tend are unmodified. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + corrected from its non-Null successor, if it has one, otherwise + it is unmodified. + */ + +int forEachBreakpoint( Partial * p, + int ( * func )( Breakpoint * p, double time, void * data ), + void * data ); +/* Apply a function to each Breakpoint in a Partial. The function + is called once for each Breakpoint in the source Partial. The + function may modify the Breakpoint (but should not otherwise attempt + to modify the Partial). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data is needed. + The function should return 0 if successful. If the function returns + a non-zero value, then forEachBreakpoint immediately returns that value + without applying the function to any other Breakpoints in the Partial. + forEachBreakpoint returns zero if all calls to func return zero. + */ + +int forEachPartial( PartialList * src, + int ( * func )( Partial * p, void * data ), + void * data ); +/* Apply a function to each Partial in a PartialList. The function + is called once for each Partial in the source PartialList. The + function may modify the Partial (but should not attempt to modify + the PartialList). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data + is needed. The function should return 0 if successful. If the + function returns a non-zero value, then forEachPartial immediately + returns that value without applying the function to any other + Partials in the PartialList. forEachPartial returns zero if all + calls to func return zero. + */ + +double peakAmplitude( const Partial * p ); +/* Return the maximum amplitude achieved by a Partial. + */ + +void removeIf( PartialList * src, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Remove from a PartialList all Partials satisfying the + specified predicate. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void removeLabeled( PartialList * src, long label ); +/* Remove from a PartialList all Partials having the specified label. + */ + +void scaleAmplitude( PartialList * partials, LinearEnvelope * ampEnv ); +/* Scale the amplitude of the Partials in a PartialList according + to an envelope representing a time-varying amplitude scale value. + */ + +void scaleAmp( PartialList * partials, LinearEnvelope * ampEnv ); +/* Bad old name for scaleAmplitude. + */ + +void scaleBandwidth( PartialList * partials, LinearEnvelope * bwEnv ); +/* Scale the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth scale value. + */ + +void scaleFrequency( PartialList * partials, LinearEnvelope * freqEnv ); +/* Scale the frequency of the Partials in a PartialList according + to an envelope representing a time-varying frequency scale value. + */ + +void scaleNoiseRatio( PartialList * partials, LinearEnvelope * noiseEnv ); +/* Scale the relative noise content of the Partials in a PartialList + according to an envelope representing a (time-varying) noise energy + scale value. + */ + +void setBandwidth( PartialList * partials, LinearEnvelope * bwEnv ); +/* Set the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth value. + */ + +void shiftPitch( PartialList * partials, LinearEnvelope * pitchEnv ); +/* Shift the pitch of all Partials in a PartialList according to + the given pitch envelope. The pitch envelope is assumed to have + units of cents (1/100 of a halfstep). + */ + +void shiftTime( PartialList * partials, double offset ); +/* Shift the time of all the Breakpoints in a Partial by a + constant amount. + */ + +void sortByLabel( PartialList * partials ); +/* Sort the Partials in a PartialList in order of increasing label. + The sort is stable; Partials having the same label are not + reordered. + */ + +void timeSpan( PartialList * partials, double * tmin, double * tmax ); +/* Return the minimum start time and maximum end time + in seconds of all Partials in this PartialList. The v + times are returned in the (non-null) pointers tmin + and tmax. + */ + +double weightedAvgFrequency( const Partial * p ); +/* Return the average frequency over all Breakpoints in this Partial, + weighted by the Breakpoint amplitudes. Return zero if the Partial + has no Breakpoints. + */ + +/* ---------------------------------------------------------------- */ +/* Notification and exception handlers +/* +/* An exception handler and a notifier may be specified. Both + are functions taking a const char * argument and returning + void. + */ + +void setExceptionHandler( void(*f)(const char *) ); +/* Specify a function to call when reporting exceptions. The + function takes a const char * argument, and returns void. + */ + +void setNotifier( void(*f)(const char *) ); +/* Specify a notification function. The function takes a + const char * argument, and returns void. + */ + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif /* ndef INCLUDE_LORIS_H */ diff --git a/src/loris/loris.h.in b/src/loris/loris.h.in new file mode 100644 index 0000000..9948da3 --- /dev/null +++ b/src/loris/loris.h.in @@ -0,0 +1,1020 @@ +#ifndef INCLUDE_LORIS_H +#define INCLUDE_LORIS_H +/* + * 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 + * + * + * loris.h + * + * Header specifying C-linkable procedural interface for Loris. + * + * Main components of this interface: + * - version identification symbols + * - type declarations + * - Analyzer configuration + * - LinearEnvelope (formerly BreakpointEnvelope) operations + * - PartialList operations + * - Partial operations + * - Breakpoint operations + * - sound modeling functions for preparing PartialLists + * - utility functions for manipulating PartialLists + * - notification and exception handlers (all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function. The default one in Loris uses printf()). + * + * loris.h is generated automatically from loris.h.in. Do not modify loris.h + * + * Kelly Fitz, 4 Feb 2002 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +/* ---------------------------------------------------------------- */ +/* Version +/* +/* Define symbols that facilitate version/release identification. + */ + +#define LORIS_MAJOR_VERSION @LORIS_MAJOR_VERSION@ +#define LORIS_MINOR_VERSION @LORIS_MINOR_VERSION@ +#define LORIS_SUBMINOR_VERSION @LORIS_SUBMINOR_VERSION@ +#define LORIS_VERSION_STR "@LORIS_VERSION_STR@" + +/* ---------------------------------------------------------------- */ +/* Types +/* +/* The (class) types Breakpoint, LinearEnvelope, Partial, + and PartialList are imported from the Loris namespace. + The first three are classes, the latter is a typedef + for std::list< Loris::Partial >. + */ +#if defined(__cplusplus) + // include std library list header, declaring templates + // is too painful and fragile: + #include <list> + + // declare Loris classes in Loris namespace: + namespace Loris + { + class Breakpoint; + class LinearEnvelope; + class Partial; + + // this typedef has to be copied from PartialList.h + typedef std::list< Loris::Partial > PartialList; + } + + // import those names into the global namespace + using Loris::Breakpoint; + using Loris::LinearEnvelope; + using Loris::Partial; + using Loris::PartialList; +#else + /* no classes, just declare types and use + opaque C pointers + */ + typedef struct Breakpoint Breakpoint; + typedef struct LinearEnvelope LinearEnvelope; + typedef struct PartialList PartialList; + typedef struct Partial Partial; +#endif + +/* + TODO + Maybe should also have loris_label_t and loris_size_t + defined, depending on configure. +*/ + +#if defined(__cplusplus) + extern "C" { +#endif + +/* ---------------------------------------------------------------- */ +/* Analyzer configuration +/* +/* An Analyzer represents a configuration of parameters for + performing Reassigned Bandwidth-Enhanced Additive Analysis + of sampled waveforms. This analysis process yields a collection + of Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ + +void analyze( const double * buffer, unsigned int bufferSize, + double srate, PartialList * partials ); +/* Analyze an array of bufferSize (mono) samples at the given sample rate + (in Hz) and append the extracted Partials to the given + PartialList. + */ + +void analyzer_configure( double resolution, double windowWidth ); +/* Configure the sole Analyzer instance with the specified + frequency resolution (minimum instantaneous frequency + difference between Partials). All other Analyzer parameters + are computed from the specified frequency resolution. + + Construct the Analyzer instance if necessary. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ + +double analyzer_getAmpFloor( void ); +/* Return the amplitude floor (lowest detected spectral amplitude), + in (negative) dB, for the Loris Analyzer. + */ + +double analyzer_getCropTime( void ); +/* Return the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for the Loris Analyzer. + */ + +double analyzer_getFreqDrift( void ); +/* Return the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for the Loris Analyzer. + */ + +double analyzer_getFreqFloor( void ); +/* Return the frequency floor (minimum instantaneous Partial + frequency), in Hz, for the Loris Analyzer. + */ + +double analyzer_getFreqResolution( void ); +/* Return the frequency resolution (minimum instantaneous frequency + difference between Partials) for the Loris Analyzer. + */ + +double analyzer_getHopTime( void ); +/* Return the hop time (which corresponds approximately to the + average density of Partial envelope Breakpoint data) for this + Analyzer. + */ + +double analyzer_getSidelobeLevel( void ); +/* Return the sidelobe attenutation level for the Kaiser analysis window in + positive dB. Higher numbers (e.g. 90) give very good sidelobe + rejection but cause the window to be longer in time. Smaller + numbers raise the level of the sidelobes, increasing the likelihood + of frequency-domain interference, but allow the window to be shorter + in time. + */ + +double analyzer_getWindowWidth( void ); +/* Return the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by the Loris Analyzer. + */ + +void analyzer_setAmpFloor( double x ); +/* Set the amplitude floor (lowest detected spectral amplitude), in + (negative) dB, for the Loris Analyzer. + */ + +void analyzer_setBwRegionWidth( double x ); +/* Deprecated, use analyzer_storeResidueBandwidth instead. + */ + +void analyzer_setCropTime( double x ); +/* Set the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for the Loris Analyzer. + */ + +void analyzer_setFreqDrift( double x ); +/* Set the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for the Loris Analyzer. + */ + +void analyzer_setFreqFloor( double x ); +/* Set the amplitude floor (minimum instantaneous Partial + frequency), in Hz, for the Loris Analyzer. + */ + +void analyzer_setFreqResolution( double x ); +/* Set the frequency resolution (minimum instantaneous frequency + difference between Partials) for the Loris Analyzer. (Does not cause + other parameters to be recomputed.) + */ + +void analyzer_setHopTime( double x ); +/* Set the hop time (which corresponds approximately to the average + density of Partial envelope Breakpoint data) for the Loris Analyzer. + */ + +void analyzer_setSidelobeLevel( double x ); +/* Set the sidelobe attenutation level for the Kaiser analysis window in + positive dB. Larger numbers (e.g. 90) give very good sidelobe + rejection but cause the window to be longer in time. Smaller + numbers raise the level of the sidelobes, increasing the likelihood + of frequency-domain interference, but allow the window to be shorter + in time. + */ + +void analyzer_setWindowWidth( double x ); +/* Set the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by the Loris Analyzer. + */ + +void analyzer_storeResidueBandwidth( double regionWidth ); +/* Construct Partial bandwidth envelopes during analysis + by associating residual energy in the spectrum (after + peak extraction) with the selected spectral peaks that + are used to construct Partials. + + regionWidth is the width (in Hz) of the bandwidth + association regions used by this process, must be positive. + */ + +void analyzer_storeConvergenceBandwidth( double tolerancePct ); +/* Construct Partial bandwidth envelopes during analysis + by storing the mixed derivative of short-time phase, + scaled and shifted so that a value of 0 corresponds + to a pure sinusoid, and a value of 1 corresponds to a + bandwidth-enhanced sinusoid with maximal energy spread + (minimum sinusoidal convergence). + + tolerance is the amount of range over which the + mixed derivative indicator should be allowed to drift away + from a pure sinusoid before saturating. This range is mapped + to bandwidth values on the range [0,1]. Must be positive and + not greater than 1. + */ + +void analyzer_storeNoBandwidth( void ); +/* Disable bandwidth envelope construction. Bandwidth + will be zero for all Breakpoints in all Partials. + */ + +double analyzer_getBwRegionWidth( void ); +/* Return the width (in Hz) of the Bandwidth Association regions + used by this Analyzer, only if the spectral residue method is + used to compute bandwidth envelopes. Return zero if the mixed + derivative method is used, or if no bandwidth is computed. + */ + +double analyzer_getBwConvergenceTolerance( void ); +/* Return the mixed derivative convergence tolerance + only if the convergence indicator is used to compute + bandwidth envelopes. Return zero if the spectral residue + method is used or if no bandwidth is computed. + */ + + +/* ---------------------------------------------------------------- */ +/* LinearEnvelope object interface +/* +/* A LinearEnvelope represents a linear segment breakpoint + function with infinite extension at each end (that is, the + values past either end of the breakpoint function have the + values at the nearest end). + + In C++, a LinearEnvelope is a Loris::LinearEnvelope. + */ + +LinearEnvelope * createLinearEnvelope( void ); +/* Construct and return a new LinearEnvelope having no + breakpoints and an implicit value of 0. everywhere, + until the first breakpoint is inserted. + */ + +LinearEnvelope * copyLinearEnvelope( const LinearEnvelope * ptr_this ); +/* Construct and return a new LinearEnvelope that is an + exact copy of the specified LinearEnvelopes, having + an identical set of breakpoints. + */ + +void destroyLinearEnvelope( LinearEnvelope * ptr_this ); +/* Destroy this LinearEnvelope. + */ + +void linearEnvelope_insertBreakpoint( LinearEnvelope * ptr_this, + double time, double val ); +/* Insert a breakpoint representing the specified (time, value) + pair into this LinearEnvelope. If there is already a + breakpoint at the specified time, it will be replaced with + the new breakpoint. + */ + +double linearEnvelope_valueAt( const LinearEnvelope * ptr_this, + double time ); +/* Return the interpolated value of this LinearEnvelope at the + specified time. + */ + +/* ---------------------------------------------------------------- */ +/* PartialList object interface +/* +/* A PartialList represents a collection of Bandwidth-Enhanced + Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Bandwidth-Enhanced Partials and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to + the Loris website: www.cerlsoundgroup.org/Loris/. + + In C++, a PartialList is a Loris::PartialList. + */ +PartialList * createPartialList( void ); +/* Return a new empty PartialList. + */ + +void destroyPartialList( PartialList * ptr_this ); +/* Destroy this PartialList. + */ + +void partialList_clear( PartialList * ptr_this ); +/* Remove (and destroy) all the Partials from this PartialList, + leaving it empty. + */ + +void partialList_copy( PartialList * ptr_this, + const PartialList * src ); +/* Make this PartialList a copy of the source PartialList by making + copies of all of the Partials in the source and adding them to + this PartialList. + */ + +unsigned long partialList_size( const PartialList * ptr_this ); +/* Return the number of Partials in this PartialList. + */ + +void partialList_splice( PartialList * ptr_this, + PartialList * src ); +/* Splice all the Partials in the source PartialList onto the end of + this PartialList, leaving the source empty. + */ + +/* ---------------------------------------------------------------- */ +/* Partial object interface +/* +/* A Partial represents a single component in the + reassigned bandwidth-enhanced additive model. A Partial consists of a + chain of Breakpoints describing the time-varying frequency, amplitude, + and bandwidth (or noisiness) envelopes of the component, and a 4-byte + label. The Breakpoints are non-uniformly distributed in time. For more + information about Reassigned Bandwidth-Enhanced Analysis and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to the Loris + website: www.cerlsoundgroup.org/Loris/. + */ + +double partial_startTime( const Partial * p ); +/* Return the start time (seconds) for the specified Partial. + */ + +double partial_endTime( const Partial * p ); +/* Return the end time (seconds) for the specified Partial. + */ + +double partial_duration( const Partial * p ); +/* Return the duration (seconds) for the specified Partial. + */ + +double partial_initialPhase( const Partial * p ); +/* Return the initial phase (radians) for the specified Partial. + */ + +int partial_label( const Partial * p ); +/* Return the integer label for the specified Partial. + */ + +unsigned long partial_numBreakpoints( const Partial * p ); +/* Return the number of Breakpoints in the specified Partial. + */ + +double partial_frequencyAt( const Partial * p, double t ); +/* Return the frequency (Hz) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_bandwidthAt( const Partial * p, double t ); +/* Return the bandwidth of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_phaseAt( const Partial * p, double t ); +/* Return the phase (radians) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ + +double partial_amplitudeAt( const Partial * p, double t ); +/* Return the (absolute) amplitude of the specified Partial interpolated + at a particular time. Partials are assumed to fade out + over 1 millisecond at the ends (rather than instantaneously). + It is an error to apply this function to a Partial having no Breakpoints. + */ + +void partial_setLabel( Partial * p, int label ); +/* Assign a new integer label to the specified Partial. + */ + +/* ---------------------------------------------------------------- */ +/* Breakpoint object interface +/* +/* A Breakpoint represents a single breakpoint in the + Partial parameter (frequency, amplitude, bandwidth) envelope. + Instantaneous phase is also stored, but is only used at the onset of + a partial, or when it makes a transition from zero to nonzero amplitude. + + Loris Partials represent reassigned bandwidth-enhanced model components. + A Partial consists of a chain of Breakpoints describing the time-varying + frequency, amplitude, and bandwidth (noisiness) of the component. + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: + www.cerlsoundgroup.org/Loris/. + */ + +double breakpoint_getAmplitude( const Breakpoint * bp ); +/* Return the (absolute) amplitude of the specified Breakpoint. + */ + +double breakpoint_getBandwidth( const Breakpoint * bp ); +/* Return the bandwidth coefficient of the specified Breakpoint. + */ + +double breakpoint_getFrequency( const Breakpoint * bp ); +/* Return the frequency (Hz) of the specified Breakpoint. + */ + +double breakpoint_getPhase( const Breakpoint * bp ); +/* Return the phase (radians) of the specified Breakpoint. + */ + +void breakpoint_setAmplitude( Breakpoint * bp, double a ); +/* Assign a new (absolute) amplitude to the specified Breakpoint. + */ + +void breakpoint_setBandwidth( Breakpoint * bp, double bw ); +/* Assign a new bandwidth coefficient to the specified Breakpoint. + */ + +void breakpoint_setFrequency( Breakpoint * bp, double f ); +/* Assign a new frequency (Hz) to the specified Breakpoint. + */ + +void breakpoint_setPhase( Breakpoint * bp, double phi ); +/* Assign a new phase (radians) to the specified Breakpoint. + */ + +/* ---------------------------------------------------------------- */ +/* non-object-based procedures +/* +/* Operations in Loris that need not be accessed though object + interfaces are represented as simple functions. + */ + +void channelize( PartialList * partials, + LinearEnvelope * refFreqEnvelope, int refLabel ); +/* Label Partials in a PartialList with the integer nearest to + the amplitude-weighted average ratio of their frequency envelope + to a reference frequency envelope. The frequency spectrum is + partitioned into non-overlapping channels whose time-varying + center frequencies track the reference frequency envelope. + The reference label indicates which channel's center frequency + is exactly equal to the reference envelope frequency, and other + channels' center frequencies are multiples of the reference + envelope frequency divided by the reference label. Each Partial + in the PartialList is labeled with the number of the channel + that best fits its frequency envelope. The quality of the fit + is evaluated at the breakpoints in the Partial envelope and + weighted by the amplitude at each breakpoint, so that high- + amplitude breakpoints contribute more to the channel decision. + Partials are labeled, but otherwise unmodified. In particular, + their frequencies are not modified in any way. + */ + +void collate( PartialList * partials ); +/* Collate unlabeled (zero-labeled) Partials into the smallest-possible + number of Partials that does not combine any overlapping Partials. + Collated Partials appear at the end of the sequence, after all + labeled Partials. + */ + +LinearEnvelope * +createFreqReference( PartialList * partials, + double minFreq, double maxFreq, long numSamps ); +/* Return a newly-constructed LinearEnvelope using the legacy + FrequencyReference class. The envelope will have approximately + the specified number of samples. The specified number of samples + must be greater than 1. Uses the FundamentalEstimator + (FundamentalFromPartials) class to construct an estimator of + fundamental frequency, configured to emulate the behavior of + the FrequencyReference class in Loris 1.4-1.5.2. If numSamps + is zero, construct the reference envelope from fundamental + estimates taken every five milliseconds. + + For simple sounds, this frequency reference may be a + good first approximation to a reference envelope for + channelization (see channelize()). + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ + +LinearEnvelope * +createF0Estimate( PartialList * partials, double minFreq, double maxFreq, + double interval ); +/* Return a newly-constructed LinearEnvelope that estimates + the time-varying fundamental frequency of the sound + represented by the Partials in a PartialList. This uses + the FundamentalEstimator (FundamentalFromPartials) + class to construct an estimator of fundamental frequency, + and returns a LinearEnvelope that samples the estimator at the + specified time interval (in seconds). Default values are used + to configure the estimator. Only estimates in the specified + frequency range will be considered valid, estimates outside this + range will be ignored. + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ + +void dilate( PartialList * partials, + const double * initial, const double * target, int npts ); +/* Dilate Partials in a PartialList according to the given + initial and target time points. Partial envelopes are + stretched and compressed so that temporal features at + the initial time points are aligned with the final time + points. Time points are sorted, so Partial envelopes are + are only stretched and compressed, but breakpoints are not + reordered. Duplicate time points are allowed. There must be + the same number of initial and target time points. + */ + +void distill( PartialList * partials ); +/* Distill labeled (channelized) Partials in a PartialList into a + PartialList containing at most one Partial per label. Unlabeled + (zero-labeled) Partials are left unmodified at the end of the + distilled Partials. + */ + +void exportAiff( const char * path, const double * buffer, + unsigned int bufferSize, double samplerate, int bitsPerSamp ); +/* Export mono audio samples stored in an array of size bufferSize to + an AIFF file having the specified sample rate at the given file path + (or name). The floating point samples in the buffer are clamped to the + range (-1.,1.) and converted to integers having bitsPerSamp bits. + */ + +void exportSdif( const char * path, PartialList * partials ); +/* Export Partials in a PartialList to a SDIF file at the specified + file path (or name). SDIF data is described by RBEM and RBEL + matrices. + For more information about SDIF, see the SDIF web site at: + www.ircam.fr/equipes/analyse-synthese/sdif/ + */ + +void exportSpc( const char * path, PartialList * partials, double midiPitch, + int enhanced, double endApproachTime ); +/* Export Partials in a PartialList to a Spc file at the specified file + path (or name). The fractional MIDI pitch must be specified. The + enhanced parameter defaults to true (for bandwidth-enhanced spc files), + but an be specified false for pure-sines spc files. The endApproachTime + parameter is in seconds. A nonzero endApproachTime indicates that the plist does + not include a release, but rather ends in a static spectrum corresponding + to the final breakpoint values of the partials. The endApproachTime + specifies how long before the end of the sound the amplitude, frequency, + and bandwidth values are to be modified to make a gradual transition to + the static spectrum. + */ + +/* Apply a reference Partial to fix the frequencies of Breakpoints + whose amplitude is below threshold_dB. 0 harmonifies full-amplitude + Partials, to apply only to quiet Partials, specify a lower + threshold like -90). The reference Partial is the first Partial + in the PartialList labeled refLabel (usually 1). The LinearEnvelope + is a time-varying weighting on the harmonifing process. When 1, + harmonic frequencies are used, when 0, breakpoint frequencies are + unmodified. + */ +void harmonify( PartialList * partials, long refLabel, + const LinearEnvelope * env, double threshold_dB ); + +unsigned int importAiff( const char * path, double * buffer, unsigned int bufferSize, + double * samplerate ); +/* Import audio samples stored in an AIFF file at the given file + path (or name). The samples are converted to floating point + values on the range (-1.,1.) and stored in an array of doubles. + The value returned is the number of samples in buffer, and it is at + most bufferSize. If samplerate is not a NULL pointer, + then, on return, it points to the value of the sample rate (in + Hz) of the AIFF samples. The AIFF file must contain only a single + channel of audio data. The prior contents of buffer, if any, are + overwritten. + */ + +void importSdif( const char * path, PartialList * partials ); +/* Import Partials from an SDIF file at the given file path (or + name), and append them to a PartialList. + */ + +void importSpc( const char * path, PartialList * partials ); +/* Import Partials from an Spc file at the given file path (or + name), and return them in a PartialList. + */ + +void morph( const PartialList * src0, const PartialList * src1, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ); +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ + +void morphWithReference( const PartialList * src0, + const PartialList * src1, + long src0RefLabel, + long src1RefLabel, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ); +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Specify the labels of the Partials to be used as + reference Partial for the two morph sources. The reference + partial is used to compute frequencies for very low-amplitude + Partials whose frequency estimates are not considered reliable. + The reference Partial is considered to have good frequency + estimates throughout. A reference label of 0 indicates that + no reference Partial should be used for the corresponding + morph source. + + Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ + +void morpher_setAmplitudeShape( double shape ); +/* Set the shaping parameter for the amplitude morphing + function. This shaping parameter controls the slope of + the amplitude morphing function, for values greater than + 1, this function gets nearly linear (like the old + amplitude morphing function), for values much less than + 1 (e.g. 1E-5) the slope is gently curved and sounds + pretty "linear", for very small values (e.g. 1E-12) the + curve is very steep and sounds un-natural because of the + huge jump from zero amplitude to very small amplitude. + + Use LORIS_DEFAULT_AMPMORPHSHAPE to obtain the default + amplitude morphing shape for Loris, (equal to 1E-5, + which works well for many musical instrument morphs, + unless Loris was compiled with the symbol + LINEAR_AMP_MORPHS defined, in which case + LORIS_DEFAULT_AMPMORPHSHAPE is equal to + LORIS_LINEAR_AMPMORPHSHAPE). + + Use LORIS_LINEAR_AMPMORPHSHAPE to approximate the linear + amplitude morphs performed by older versions of Loris. + + The amplitude shape must be positive. + */ + +extern const double LORIS_DEFAULT_AMPMORPHSHAPE; +extern const double LORIS_LINEAR_AMPMORPHSHAPE; + +void resample( PartialList * partials, double interval ); +/* Resample all Partials in a PartialList using the specified + sampling interval, so that the Breakpoints in the Partial + envelopes will all lie on a common temporal grid. + The Breakpoint times in resampled Partials will comprise a + contiguous sequence of integer multiples of the sampling interval, + beginning with the multiple nearest to the Partial's start time and + ending with the multiple nearest to the Partial's end time. Resampling + is performed in-place. + + */ + +void shapeSpectrum( PartialList * partials, PartialList * surface, + double stretchFreq, double stretchTime ); +/* Scale the amplitudes of a set of Partials by applying + a spectral suface constructed from another set. + Stretch the spectral surface in time and frequency + using the specified stretch factors. Set the stretch + factors to one for no stretching. + */ + +void sift( PartialList * partials ); +/* Identify overlapping Partials having the same (nonzero) + label. If any two partials with same label + overlap in time, set the label of the weaker + (having less total energy) partial to zero. + + */ + +unsigned int +synthesize( const PartialList * partials, + double * buffer, unsigned int bufferSize, + double srate ); +/* Synthesize Partials in a PartialList at the given sample + rate, and store the (floating point) samples in a buffer of + size bufferSize. The buffer is neither resized nor + cleared before synthesis, so newly synthesized samples are + added to any previously computed samples in the buffer, and + samples beyond the end of the buffer are lost. Return the + number of samples synthesized, that is, the index of the + latest sample in the buffer that was modified. + */ + +/* ---------------------------------------------------------------- */ +/* utility functions +/* +/* Operations for transforming and manipulating collections + of Partials. + */ + +double avgAmplitude( const Partial * p ); +/* Return the average amplitude over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ + +double avgFrequency( const Partial * p ); +/* Return the average frequency over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ + +void copyIf( const PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Append copies of Partials in the source PartialList satisfying the + specified predicate to the destination PartialList. The source list + is unmodified. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void copyLabeled( const PartialList * src, long label, PartialList * dst ); +/* Append copies of Partials in the source PartialList having the + specified label to the destination PartialList. The source list + is unmodified. + */ + +void crop( PartialList * partials, double t1, double t2 ); +/* Trim Partials by removing Breakpoints outside a specified time span. + Insert a Breakpoint at the boundary when cropping occurs. Remove + any Partials that are left empty after cropping (Partials having no + Breakpoints between t1 and t2). + */ + +void extractIf( PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Remove Partials in the source PartialList satisfying the + specified predicate from the source list and append them to + the destination PartialList. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void extractLabeled( PartialList * src, long label, PartialList * dst ); +/* Remove Partials in the source PartialList having the specified + label from the source list and append them to the destination + PartialList. + */ + +void fixPhaseAfter( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + corrected from its non-Null successor, if it has one, otherwise + it is unmodified. + */ + +void fixPhaseAt( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints in a Partial + so that the synthesized phases match the stored phases, + and the synthesized phase at (nearest) the specified + time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) + Breakpoint is encountered, because nulls are interpreted as + phase reset points in Loris. If a null is encountered, the + remainder of the Partial (the front part) is fixed in the + forward direction, beginning at the start of the Partial. + Forward phase fixing is only applied to non-null + (nonzero-amplitude) Breakpoints. If a null is encountered, + its phase is corrected from its non-Null successor, if + it has one, otherwise it is unmodified. + */ + +void fixPhaseBefore( PartialList * partials, double time ); +/* Recompute phases of all Breakpoints earlier than the specified + time so that the synthesized phases of those earlier Breakpoints + matches the stored phase, and the synthesized phase at the + specified time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) Breakpoint + is encountered, because nulls are interpreted as phase reset + points in Loris. If a null is encountered, the remainder of the + Partial (the front part) is fixed in the forward direction, + beginning at the start of the Partial. + */ + +void fixPhaseBetween( PartialList * partials, double tbeg, double tend ); +/* + Fix the phase travel between two times by adjusting the + frequency and phase of Breakpoints between those two times. + + This algorithm assumes that there is nothing interesting + about the phases of the intervening Breakpoints, and modifies + their frequencies as little as possible to achieve the correct + amount of phase travel such that the frequencies and phases at + the specified times match the stored values. The phases of all + the Breakpoints between the specified times are recomputed. + */ + +void fixPhaseForward( PartialList * partials, double tbeg, double tend ); +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + Breakpoints later than tend are unmodified. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + corrected from its non-Null successor, if it has one, otherwise + it is unmodified. + */ + +int forEachBreakpoint( Partial * p, + int ( * func )( Breakpoint * p, double time, void * data ), + void * data ); +/* Apply a function to each Breakpoint in a Partial. The function + is called once for each Breakpoint in the source Partial. The + function may modify the Breakpoint (but should not otherwise attempt + to modify the Partial). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data is needed. + The function should return 0 if successful. If the function returns + a non-zero value, then forEachBreakpoint immediately returns that value + without applying the function to any other Breakpoints in the Partial. + forEachBreakpoint returns zero if all calls to func return zero. + */ + +int forEachPartial( PartialList * src, + int ( * func )( Partial * p, void * data ), + void * data ); +/* Apply a function to each Partial in a PartialList. The function + is called once for each Partial in the source PartialList. The + function may modify the Partial (but should not attempt to modify + the PartialList). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data + is needed. The function should return 0 if successful. If the + function returns a non-zero value, then forEachPartial immediately + returns that value without applying the function to any other + Partials in the PartialList. forEachPartial returns zero if all + calls to func return zero. + */ + +double peakAmplitude( const Partial * p ); +/* Return the maximum amplitude achieved by a Partial. + */ + +void removeIf( PartialList * src, + int ( * predicate )( const Partial * p, void * data ), + void * data ); +/* Remove from a PartialList all Partials satisfying the + specified predicate. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ + +void removeLabeled( PartialList * src, long label ); +/* Remove from a PartialList all Partials having the specified label. + */ + +void scaleAmplitude( PartialList * partials, LinearEnvelope * ampEnv ); +/* Scale the amplitude of the Partials in a PartialList according + to an envelope representing a time-varying amplitude scale value. + */ + +void scaleAmp( PartialList * partials, LinearEnvelope * ampEnv ); +/* Bad old name for scaleAmplitude. + */ + +void scaleBandwidth( PartialList * partials, LinearEnvelope * bwEnv ); +/* Scale the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth scale value. + */ + +void scaleFrequency( PartialList * partials, LinearEnvelope * freqEnv ); +/* Scale the frequency of the Partials in a PartialList according + to an envelope representing a time-varying frequency scale value. + */ + +void scaleNoiseRatio( PartialList * partials, LinearEnvelope * noiseEnv ); +/* Scale the relative noise content of the Partials in a PartialList + according to an envelope representing a (time-varying) noise energy + scale value. + */ + +void setBandwidth( PartialList * partials, LinearEnvelope * bwEnv ); +/* Set the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth value. + */ + +void shiftPitch( PartialList * partials, LinearEnvelope * pitchEnv ); +/* Shift the pitch of all Partials in a PartialList according to + the given pitch envelope. The pitch envelope is assumed to have + units of cents (1/100 of a halfstep). + */ + +void shiftTime( PartialList * partials, double offset ); +/* Shift the time of all the Breakpoints in a Partial by a + constant amount. + */ + +void sortByLabel( PartialList * partials ); +/* Sort the Partials in a PartialList in order of increasing label. + The sort is stable; Partials having the same label are not + reordered. + */ + +void timeSpan( PartialList * partials, double * tmin, double * tmax ); +/* Return the minimum start time and maximum end time + in seconds of all Partials in this PartialList. The v + times are returned in the (non-null) pointers tmin + and tmax. + */ + +double weightedAvgFrequency( const Partial * p ); +/* Return the average frequency over all Breakpoints in this Partial, + weighted by the Breakpoint amplitudes. Return zero if the Partial + has no Breakpoints. + */ + +/* ---------------------------------------------------------------- */ +/* Notification and exception handlers +/* +/* An exception handler and a notifier may be specified. Both + are functions taking a const char * argument and returning + void. + */ + +void setExceptionHandler( void(*f)(const char *) ); +/* Specify a function to call when reporting exceptions. The + function takes a const char * argument, and returns void. + */ + +void setNotifier( void(*f)(const char *) ); +/* Specify a notification function. The function takes a + const char * argument, and returns void. + */ + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif /* ndef INCLUDE_LORIS_H */ diff --git a/src/loris/lorisAnalyzer_pi.C b/src/loris/lorisAnalyzer_pi.C new file mode 100644 index 0000000..9cafb05 --- /dev/null +++ b/src/loris/lorisAnalyzer_pi.C @@ -0,0 +1,1014 @@ +/* + * 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 + * + * + * lorisAnalyzer_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of the Loris procedural interface: + * - object interfaces - Analyzer, Synthesizer, Partial, PartialIterator, + * PartialList, PartialListIterator, Breakpoint, BreakpointEnvelope, + * and SampleVector need to be (opaque) objects in the interface, + * either because they hold state (e.g. Analyzer) or because they are + * fundamental data types (e.g. Partial), so they need a procedural + * interface to their member functions. All these things need to be + * opaque pointers for the benefit of C. + * - non-object-based procedures - other classes in Loris are not so stateful, + * and have sufficiently narrow functionality that they need only + * procedures, and no object representation. + * - utility functions - some procedures that are generally useful but are + * not yet part of the Loris core are also defined. + * - notification and exception handlers - all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function (the default one in Loris uses printf()). + * + * This file contains the procedural interface for the Loris Analyzer class. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "loris.h" +#include "lorisException_pi.h" + +#include "Analyzer.h" +#include "Notifier.h" + +using namespace Loris; + +/* ---------------------------------------------------------------- */ +/* Analyzer object interface +/* +/* An Analyzer represents a configuration of parameters for + performing Reassigned Bandwidth-Enhanced Additive Analysis + of sampled waveforms. This analysis process yields a collection + of Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: www.cerlsoundgroup.org/Loris/. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ +static Analyzer * ptr_instance = 0; + +/* ---------------------------------------------------------------- */ +/* analyzer_configure +/* +/* Configure the sole Analyzer instance with the specified + frequency resolution (minimum instantaneous frequency + difference between Partials). All other Analyzer parameters + are computed from the specified frequency resolution. + + Construct the Analyzer instance if necessary. + + In the procedural interface, there is only one Analyzer. + It must be configured by calling analyzer_configure before + any of the other analyzer operations can be performed. + */ +extern "C" +void analyzer_configure( double resolution, double windowWidth ) +{ + try + { + if ( 0 == ptr_instance ) + { + debugger << "creating Analyzer" << endl; + ptr_instance = new Analyzer( resolution, windowWidth ); + } + else + { + debugger << "configuring Analyzer" << endl; + ptr_instance->configure( resolution, windowWidth ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in createAnalyzer(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in createAnalyzer(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyze +/* +/* Analyze an array of bufferSize (mono) samples at the given + sample rate (in Hz) and append the extracted Partials to the + given PartialList. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyze( const double * buffer, unsigned int bufferSize, + double srate, PartialList * partials ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ThrowIfNull((double *) buffer); + ThrowIfNull((PartialList *) partials); + + // perform analysis: + notifier << "analyzing " << bufferSize << " samples at " << + srate << " Hz with frequency resolution " << + ptr_instance->freqResolution() << endl; + if ( bufferSize > 0 ) + { + ptr_instance->analyze( buffer, buffer + bufferSize, srate ); + + // splice the Partials into the destination list: + partials->splice( partials->end(), ptr_instance->partials() ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyze(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyze(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getFreqResolution +/* +/* Return the frequency resolution (minimum instantaneous frequency + difference between Partials) for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getFreqResolution( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->freqResolution(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getFreqResolution(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getFreqResolution(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setFreqResolution +/* +/* Set the frequency resolution (minimum instantaneous frequency + difference between Partials) for this Analyzer. (Does not cause + other parameters to be recomputed.) + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setFreqResolution( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setFreqResolution( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setFreqResolution(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setFreqResolution(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getAmpFloor +/* +/* Return the amplitude floor (lowest detected spectral amplitude), + in (negative) dB, for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getAmpFloor( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->ampFloor(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getAmpFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getAmpFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setAmpFloor +/* +/* Set the amplitude floor (lowest detected spectral amplitude), in + (negative) dB, for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setAmpFloor( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setAmpFloor( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setAmpFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setAmpFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getWindowWidth +/* +/* Return the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getWindowWidth( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->windowWidth(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getWindowWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getWindowWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setWindowWidth +/* +/* Set the frequency-domain main lobe width (measured between + zero-crossings) of the analysis window used by this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setWindowWidth( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setWindowWidth( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setWindowWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setWindowWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getSidelobeLevel +/* +/* Return the sidelobe attenutation level for the Kaiser analysis window in + negative dB. More negative numbers (e.g. -90) give very good sidelobe + rejection but cause the window to be longer in time. Less negative + numbers raise the level of the sidelobes, increasing the liklihood + of frequency-domain interference, but allow the window to be shorter + in time. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getSidelobeLevel( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->sidelobeLevel(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getSidelobeLevel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getSidelobeLevel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setSidelobeLevel +/* +/* Set the sidelobe attenutation level for the Kaiser analysis window in + negative dB. More negative numbers (e.g. -90) give very good sidelobe + rejection but cause the window to be longer in time. Less negative + numbers raise the level of the sidelobes, increasing the liklihood + of frequency-domain interference, but allow the window to be shorter + in time. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setSidelobeLevel( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setSidelobeLevel( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setSidelobeLevel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setSidelobeLevel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getFreqFloor +/* +/* Return the frequency floor (minimum instantaneous Partial + frequency), in Hz, for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getFreqFloor( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->freqFloor(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getFreqFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getFreqFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setFreqFloor +/* +/* Set the amplitude floor (minimum instantaneous Partial + frequency), in Hz, for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setFreqFloor( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setFreqFloor( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setFreqFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setFreqFloor(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getFreqDrift +/* +/* Return the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getFreqDrift( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->freqDrift(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getFreqDrift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getFreqDrift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setFreqDrift +/* +/* Set the maximum allowable frequency difference between + consecutive Breakpoints in a Partial envelope for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setFreqDrift( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setFreqDrift( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setFreqDrift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setFreqDrift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getHopTime +/* +/* Return the hop time (which corresponds approximately to the + average density of Partial envelope Breakpoint data) for this + Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getHopTime( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->hopTime(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getHopTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + + { + std::string s("std C++ exception in analyzer_getHopTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setHopTime +/* +/* Set the hop time (which corresponds approximately to the average + density of Partial envelope Breakpoint data) for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setHopTime( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setHopTime( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setHopTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setHopTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} +/* ---------------------------------------------------------------- */ +/* analyzer_getCropTime +/* +/* Return the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getCropTime( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->cropTime(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getCropTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getCropTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setCropTime +/* +/* Set the crop time (maximum temporal displacement of a time- + frequency data point from the time-domain center of the analysis + window, beyond which data points are considered "unreliable") + for this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setCropTime( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setCropTime( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setCropTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setCropTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getBwRegionWidth +/* +/* Return the width (in Hz) of the Bandwidth Association regions + used by this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +double analyzer_getBwRegionWidth( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->bwRegionWidth(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getBwRegionWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getBwRegionWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* analyzer_setBwRegionWidth +/* +/* Set the width (in Hz) of the Bandwidth Association regions + used by this Analyzer. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_setBwRegionWidth( double x ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->setBwRegionWidth( x ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_setBwRegionWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_setBwRegionWidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_storeResidueBandwidth +/* +/* Construct Partial bandwidth envelopes during analysis + by associating residual energy in the spectrum (after + peak extraction) with the selected spectral peaks that + are used to construct Partials. + + regionWidth is the width (in Hz) of the bandwidth + association regions used by this process, must be positive. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_storeResidueBandwidth( double regionWidth ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->storeResidueBandwidth( regionWidth ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_storeResidueBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_storeResidueBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_storeConvergenceBandwidth +/* +/* Construct Partial bandwidth envelopes during analysis + by storing the mixed derivative of short-time phase, + scaled and shifted so that a value of 0 corresponds + to a pure sinusoid, and a value of 1 corresponds to a + bandwidth-enhanced sinusoid with maximal energy spread + (minimum sinusoidal convergence). + + tolerance is the amount of range over which the + mixed derivative indicator should be allowed to drift away + from a pure sinusoid before saturating. This range is mapped + to bandwidth values on the range [0,1]. Must be positive and + not greater than 1. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_storeConvergenceBandwidth( double tolerance ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->storeConvergenceBandwidth( tolerance ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_storeConvergenceBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_storeConvergenceBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_storeNoBandwidth +/* +/* Disable bandwidth envelope construction. Bandwidth + will be zero for all Breakpoints in all Partials. + + analyzer_configure must be called before any other analyzer + function. + */ +extern "C" +void analyzer_storeNoBandwidth( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return; + } + + try + { + ptr_instance->storeNoBandwidth(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_storeNoBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_storeNoBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* analyzer_getBwConvergenceTolerance +/* +/* Return the mixed derivative convergence tolerance + only if the convergence indicator is used to compute + bandwidth envelopes. Return zero if the spectral residue + method is used or if no bandwidth is computed. + */ +extern "C" +double analyzer_getBwConvergenceTolerance( void ) +{ + if ( 0 == ptr_instance ) + { + handleException( "analyzer_configure must be called before any other analyzer function." ); + return 0; + } + + try + { + return ptr_instance->bwConvergenceTolerance(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in analyzer_getBwConvergenceTolerance(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in analyzer_getBwConvergenceTolerance(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return 0; +} + + + + + diff --git a/src/loris/lorisBpEnvelope_pi.C b/src/loris/lorisBpEnvelope_pi.C new file mode 100644 index 0000000..8015006 --- /dev/null +++ b/src/loris/lorisBpEnvelope_pi.C @@ -0,0 +1,224 @@ +/* + * 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 + * + * + * lorisBpEnvelope_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of this interface: + * - version identification symbols + * - type declarations + * - Analyzer configuration + * - LinearEnvelope (formerly BreakpointEnvelope) operations + * - PartialList operations + * - Partial operations + * - Breakpoint operations + * - sound modeling functions for preparing PartialLists + * - utility functions for manipulating PartialLists + * - notification and exception handlers (all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function. The default one in Loris uses printf()). + * + * This file defines the procedural interface for the Loris + * BreakpointEnvelope class. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "loris.h" +#include "lorisException_pi.h" + +#include "LinearEnvelope.h" +#include "Notifier.h" + +using namespace Loris; + +/* ---------------------------------------------------------------- */ +/* LinearEnvelope object interface +/* +/* A LinearEnvelope represents a linear segment breakpoint + function with infinite extension at each end (that is, the + values past either end of the breakpoint function have the + values at the nearest end). + */ + +/* ---------------------------------------------------------------- */ +/* createLinearEnvelope +/* +/* Construct and return a new LinearEnvelope having no + breakpoints and an implicit value of 0. everywhere, + until the first breakpoint is inserted. + */ +extern "C" +LinearEnvelope * createLinearEnvelope( void ) +{ + try + { + debugger << "creating LinearEnvelope" << endl; + return new LinearEnvelope(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in createLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in createLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return NULL; +} + +/* ---------------------------------------------------------------- */ +/* copyLinearEnvelope +/* +/* Construct and return a new LinearEnvelope that is an + exact copy of the specified LinearEnvelopes, having + an identical set of breakpoints. + */ +extern "C" +LinearEnvelope * copyLinearEnvelope( const LinearEnvelope * ptr_this ) +{ + try + { + debugger << "copying LinearEnvelope" << endl; + return new LinearEnvelope( *ptr_this ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in copyLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in copyLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return NULL; +} + +/* ---------------------------------------------------------------- */ +/* destroyLinearEnvelope +/* +/* Destroy this LinearEnvelope. + */ +extern "C" +void destroyLinearEnvelope( LinearEnvelope * ptr_this ) +{ + try + { + ThrowIfNull((LinearEnvelope *) ptr_this); + + debugger << "deleting LinearEnvelope" << endl; + delete ptr_this; + } + catch( Exception & ex ) + { + std::string s("Loris exception in destroyLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in destroyLinearEnvelope(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* linearEnvelope_insertBreakpoint +/* +/* Insert a breakpoint representing the specified (time, value) + pair into this LinearEnvelope. If there is already a + breakpoint at the specified time, it will be replaced with + the new breakpoint. + */ +extern "C" +void linearEnvelope_insertBreakpoint( LinearEnvelope * ptr_this, + double time, double val ) +{ + try + { + ThrowIfNull((LinearEnvelope *) ptr_this); + + debugger << "inserting point (" << time << ", " << val + << ") into LinearEnvelope" << endl; + ptr_this->insertBreakpoint(time, val); + } + catch( Exception & ex ) + { + std::string s("Loris exception in linearEnvelope_insertBreakpoint(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in linearEnvelope_insertBreakpoint(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* linearEnvelope_valueAt +/* +/* Return the interpolated value of this LinearEnvelope at the + specified time. + */ +extern "C" +double linearEnvelope_valueAt( const LinearEnvelope * ptr_this, + double time ) +{ + try + { + ThrowIfNull((LinearEnvelope *) ptr_this); + return ptr_this->valueAt(time); + } + catch( Exception & ex ) + { + std::string s("Loris exception in linearEnvelope_valueAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in linearEnvelope_valueAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + diff --git a/src/loris/lorisException_pi.C b/src/loris/lorisException_pi.C new file mode 100644 index 0000000..86a6398 --- /dev/null +++ b/src/loris/lorisException_pi.C @@ -0,0 +1,115 @@ +/* + * 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 + * + * + * lorisException_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of the Loris procedural interface: + * - object interfaces - Analyzer, Synthesizer, Partial, PartialIterator, + * PartialList, PartialListIterator, Breakpoint, BreakpointEnvelope, + * and SampleVector need to be (opaque) objects in the interface, + * either because they hold state (e.g. Analyzer) or because they are + * fundamental data types (e.g. Partial), so they need a procedural + * interface to their member functions. All these things need to be + * opaque pointers for the benefit of C. + * - non-object-based procedures - other classes in Loris are not so stateful, + * and have sufficiently narrow functionality that they need only + * procedures, and no object representation. + * - utility functions - some procedures that are generally useful but are + * not yet part of the Loris core are also defined. + * - notification and exception handlers - all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function (the default one in Loris uses printf()). + * + * This file defines the exception and notification handling functions + * used in the Loris procedural interface. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "lorisException_pi.h" + + +#include "loris.h" +#include "Notifier.h" + +using namespace Loris; + +/* ---------------------------------------------------------------- */ +/* notification and exception handlers +/* +/* + An exception handler and a notifier may be specified. Both + are functions taking a const char * argument and returning + void. + */ + +/* ---------------------------------------------------------------- */ +/* handleException +/* +/* Report exceptions thrown out of Loris. If no handler is + specified, report them using Loris' notifier and return + without further incident. + */ +static void(*ex_handler)(const char *) = NULL; +void handleException( const char * s ) +{ + if ( ex_handler ) + ex_handler( s ); + else + notifier << s << endl; +} + +/* ---------------------------------------------------------------- */ +/* setExceptionHandler +/* +/* Specify a function to call when reporting exceptions. The + function takes a const char * argument, and returns void. + */ +extern "C" +void setExceptionHandler( void(*f)(const char *) ) +{ + ex_handler = f; +} + +/* ---------------------------------------------------------------- */ +/* setNotifier +/* */ +/* Specify a notification function. The function takes a + const char * argument, and returns void. + */ +extern "C" +void setNotifier( void(*f)(const char *) ) +{ + // these are guaranteed not to throw: + setNotifierHandler( f ); + setDebuggerHandler( f ); +} + diff --git a/src/loris/lorisException_pi.h b/src/loris/lorisException_pi.h new file mode 100644 index 0000000..8dddc2a --- /dev/null +++ b/src/loris/lorisException_pi.h @@ -0,0 +1,77 @@ +#ifndef INCLUDE_LORISEXCEPTION_PI_H +#define INCLUDE_LORISEXCEPTION_PI_H +/* + * 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 + * + * + * lorisException_pi.h + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of the Loris procedural interface: + * - object interfaces - Analyzer, Synthesizer, Partial, PartialIterator, + * PartialList, PartialListIterator, Breakpoint, BreakpointEnvelope, + * and SampleVector need to be (opaque) objects in the interface, + * either because they hold state (e.g. Analyzer) or because they are + * fundamental data types (e.g. Partial), so they need a procedural + * interface to their member functions. All these things need to be + * opaque pointers for the benefit of C. + * - non-object-based procedures - other classes in Loris are not so stateful, + * and have sufficiently narrow functionality that they need only + * procedures, and no object representation. + * - utility functions - some procedures that are generally useful but are + * not yet part of the Loris core are also defined. + * - notification and exception handlers - all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function (the default one in Loris uses printf()). + * + * This file declares the exception handling functions used in the + * Loris procedural interface. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ + +#include "LorisExceptions.h" +#include <string> + +// forward-declare local exception handler: +void handleException( const char * s ); + +/* ---------------------------------------------------------------- */ +/* class NullPointer +/* +/* Exception subclass for catching NULL pointers: + */ +class NullPointer : public Loris::Exception +{ +public: + NullPointer( const std::string & str, const std::string & where = "" ) : + Exception( std::string("NULL pointer exception -- ").append( str ), where ) {} +}; // end of class NullPointer + +#define ThrowIfNull(ptr) if ((ptr)==NULL) Throw( NullPointer, #ptr ); + + +#endif /* ndef INCLUDE_LORISEXCEPTION_PI_H */ diff --git a/src/loris/lorisNonObj_pi.C b/src/loris/lorisNonObj_pi.C new file mode 100644 index 0000000..605d19c --- /dev/null +++ b/src/loris/lorisNonObj_pi.C @@ -0,0 +1,1090 @@ +/* + * 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 + * + * + * lorisNonObj_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of this interface: + * - version identification symbols + * - type declarations + * - Analyzer configuration + * - LinearEnvelope (formerly BreakpointEnvelope) operations + * - PartialList operations + * - Partial operations + * - Breakpoint operations + * - sound modeling functions for preparing PartialLists + * - utility functions for manipulating PartialLists + * - notification and exception handlers (all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function. The default one in Loris uses printf()). + * + * This file defines the non-object-based component of the Loris + * procedural interface. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "loris.h" +#include "lorisException_pi.h" + +#include "AiffFile.h" +#include "Analyzer.h" +#include "BreakpointEnvelope.h" +#include "Channelizer.h" +#include "Collator.h" +#include "Dilator.h" +#include "Distiller.h" +#include "LorisExceptions.h" +#include "FrequencyReference.h" +#include "Fundamental.h" +#include "Harmonifier.h" +#include "ImportLemur.h" +#include "Morpher.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialUtils.h" +#include "Resampler.h" +#include "SdifFile.h" +#include "Sieve.h" +#include "SpcFile.h" +#include "SpectralSurface.h" +#include "Synthesizer.h" + +#include <cmath> +#include <functional> +#include <list> +#include <string> +#include <vector> +#include <algorithm> +#include <fstream> +#include <set> + + +using namespace Loris; + +/* ---------------------------------------------------------------- */ +/* non-object-based procedures +/* +/* Operations in Loris that need not be accessed though object + interfaces are represented as simple functions. + */ + +/* ---------------------------------------------------------------- */ +/* channelize +/* +/* Label Partials in a PartialList with the integer nearest to + the amplitude-weighted average ratio of their frequency envelope + to a reference frequency envelope. The frequency spectrum is + partitioned into non-overlapping channels whose time-varying + center frequencies track the reference frequency envelope. + The reference label indicates which channel's center frequency + is exactly equal to the reference envelope frequency, and other + channels' center frequencies are multiples of the reference + envelope frequency divided by the reference label. Each Partial + in the PartialList is labeled with the number of the channel + that best fits its frequency envelope. The quality of the fit + is evaluated at the breakpoints in the Partial envelope and + weighted by the amplitude at each breakpoint, so that high- + amplitude breakpoints contribute more to the channel decision. + Partials are labeled, but otherwise unmodified. In particular, + their frequencies are not modified in any way. + */ +extern "C" +void channelize( PartialList * partials, + LinearEnvelope * refFreqEnvelope, int refLabel ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((LinearEnvelope *) refFreqEnvelope); + + if ( refLabel <= 0 ) + { + Throw( InvalidArgument, "Channelization reference label must be positive." ); + } + notifier << "channelizing " << partials->size() << " Partials" << endl; + + Channelizer::channelize( *partials, *refFreqEnvelope, refLabel ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in channelize(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in channelize(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* collate +/* +/* Collate unlabeled (zero-labeled) Partials into the smallest-possible + number of Partials that does not combine any overlapping Partials. + Collated Partials assigned labels higher than any label in the original + list, and appear at the end of the sequence, after all previously-labeled + Partials. + */ +extern "C" +void collate( PartialList * partials ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "collating " << partials->size() << " Partials" << endl; + + // Uses default fade time of 1 ms, and .1 ms gap, + // should be parameters. + Collator::collate( *partials, 0.001, 0.0001 ); + + } + catch( Exception & ex ) + { + std::string s("Loris exception in collate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in collate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* createFreqReference +/* +/* Return a newly-constructed LinearEnvelope using the legacy + FrequencyReference class. The envelope will have approximately + the specified number of samples. The specified number of samples + must be greater than 1. Uses the FundamentalEstimator + (FundamentalFromPartials) class to construct an estimator of + fundamental frequency, configured to emulate the behavior of + the FrequencyReference class in Loris 1.4-1.5.2. If numSamps + is zero, construct the reference envelope from fundamental + estimates taken every five milliseconds. + + + For simple sounds, this frequency reference may be a + good first approximation to a reference envelope for + channelization (see channelize()). + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ +extern "C" +LinearEnvelope * +createFreqReference( PartialList * partials, double minFreq, double maxFreq, + long numSamps ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + // use auto_ptr to manage memory in case + // an exception is generated (hard to imagine): + std::auto_ptr< LinearEnvelope > env_ptr; + if ( numSamps != 0 ) + { + env_ptr.reset( new LinearEnvelope( + FrequencyReference( partials->begin(), partials->end(), + minFreq, maxFreq, numSamps ).envelope() ) ); + } + else + { + env_ptr.reset( new LinearEnvelope( + FrequencyReference( partials->begin(), partials->end(), + minFreq, maxFreq ).envelope() ) ); + } + + return env_ptr.release(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in createFreqReference(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in createFreqReference(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return NULL; +} + +/* ---------------------------------------------------------------- */ +/* createF0Estimate +/* +/* Return a newly-constructed LinearEnvelope that estimates + the time-varying fundamental frequency of the sound + represented by the Partials in a PartialList. This uses + the FundamentalEstimator (FundamentalFromPartials) + class to construct an estimator of fundamental frequency, + and returns a LinearEnvelope that samples the estimator at the + specified time interval (in seconds). Default values are used + to configure the estimator. Only estimates in the specified + frequency range will be considered valid, estimates outside this + range will be ignored. + + Clients are responsible for disposing of the newly-constructed + LinearEnvelope. + */ +extern "C" +LinearEnvelope * +createF0Estimate( PartialList * partials, double minFreq, double maxFreq, + double interval ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + const double Precision = 0.1; + const double Confidence = 0.9; + FundamentalFromPartials est( Precision ); + + std::pair< double, double > span = + PartialUtils::timeSpan( partials->begin(), partials->end() ); + + LinearEnvelope * env_ptr = + new LinearEnvelope( est.buildEnvelope( partials->begin(), + partials->end(), + span.first, span.second, interval, + minFreq, maxFreq, + Confidence ) ); + return env_ptr; + } + catch( Exception & ex ) + { + std::string s("Loris exception in createF0Estimate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in createF0Estimate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return NULL; +} + +/* ---------------------------------------------------------------- */ +/* dilate +/* +/* Dilate Partials in a PartialList according to the given + initial and target time points. Partial envelopes are + stretched and compressed so that temporal features at + the initial time points are aligned with the final time + points. Time points are sorted, so Partial envelopes are + are only stretched and compressed, but breakpoints are not + reordered. Duplicate time points are allowed. There must be + the same number of initial and target time points. + */ +extern "C" +void dilate( PartialList * partials, + const double * initial, const double * target, int npts ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((double *) initial); + ThrowIfNull((double *) target); + + notifier << "dilating " << partials->size() << " Partials" << endl; + Dilator::dilate( partials->begin(), partials->end(), + initial, initial + npts, target ); + + } + catch( Exception & ex ) + { + std::string s("Loris exception in dilate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in dilate(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* distill +/* +/* Distill labeled (channelized) Partials in a PartialList into a + PartialList containing at most one Partial per label. Unlabeled + (zero-labeled) Partials are left unmodified at the end of the + distilled Partials. + */ +extern "C" +void distill( PartialList * partials ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "distilling " << partials->size() << " Partials" << endl; + + // uses default fade time of 1 ms, should be parameter + Distiller::distill( *partials, 0.001 ); + + } + catch( Exception & ex ) + { + std::string s("Loris exception in distill(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in distill(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* exportAiff +/* +/* Export mono audio samples stored in an array of size bufferSize to + an AIFF file having the specified sample rate at the given file path + (or name). The floating point samples in the buffer are clamped to the + range (-1.,1.) and converted to integers having bitsPerSamp bits. + */ +extern "C" +void exportAiff( const char * path, const double * buffer, + unsigned int bufferSize, double samplerate, int bitsPerSamp ) +{ + try + { + ThrowIfNull((double *) buffer); + + // do nothing if there are no samples: + if ( bufferSize == 0 ) + { + notifier << "no samples to write to " << path << endl; + return; + } + + // write out samples: + notifier << "writing " << bufferSize << " samples to " << path << endl; + AiffFile fout( buffer, bufferSize, samplerate ); + fout.write( path, bitsPerSamp ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in exportAiff(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in exportAiff(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + +} + +/* ---------------------------------------------------------------- */ +/* exportSdif +/* +/* Export Partials in a PartialList to a SDIF file at the specified + file path (or name). SDIF data is written in the 1TRC format. + For more information about SDIF, see the SDIF web site at: + www.ircam.fr/equipes/analyse-synthese/sdif/ + */ +extern "C" +void exportSdif( const char * path, PartialList * partials ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + if ( partials->size() == 0 ) + { + Throw( Loris::InvalidObject, "No Partials in PartialList to export to sdif file." ); + } + + notifier << "exporting sdif partial data to " << path << endl; + + SdifFile fout( partials->begin(), partials->end() ); + fout.write( path ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in exportSdif(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in exportSdif(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + +} + +/* ---------------------------------------------------------------- */ +/* exportSpc +/* +/* Export Partials in a PartialList to a Spc file at the specified file + path (or name). The fractional MIDI pitch must be specified. The + enhanced parameter defaults to true (for bandwidth-enhanced spc files), + but an be specified false for pure-sines spc files. The endApproachTime + parameter is in seconds. A nonzero endApproachTime indicates that the plist does + not include a release, but rather ends in a static spectrum corresponding + to the final breakpoint values of the partials. The endApproachTime + specifies how long before the end of the sound the amplitude, frequency, + and bandwidth values are to be modified to make a gradual transition to + the static spectrum. + */ +extern "C" +void exportSpc( const char * path, PartialList * partials, double midiPitch, + int enhanced, double endApproachTime ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + if ( partials->size() == 0 ) + { + Throw( InvalidObject, "No Partials in PartialList to export to Spc file." ); + } + + notifier << "exporting Spc partial data to " << path << endl; + + SpcFile fout( midiPitch ); + PartialList::size_type countPartials = 0; + for ( PartialList::iterator iter = partials->begin(); iter != partials->end(); ++iter ) + { + if ( iter->label() > 0 && iter->label() < 512 ) + // should have a symbol defined for 512!!! + { + fout.addPartial( *iter ); + ++countPartials; + } + } + + if ( countPartials != partials->size() ) + { + notifier << "exporting " << countPartials << " of " + << partials->size() << " Partials having labels less than 512." << endl; + } + if ( countPartials == 0 ) + { + Throw( InvalidObject, "No Partials in PartialList have valid Spc labels (1-511)." ); + } + + if ( 0 == enhanced ) + { + fout.writeSinusoidal( path, endApproachTime ); + } + else + { + fout.write( path, endApproachTime ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in exportSpc(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in exportSdif(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + +} + +/* ---------------------------------------------------------------- */ +/* harmonify +/* +/* Apply a reference Partial to fix the frequencies of Breakpoints + whose amplitude is below threshold_dB. 0 harmonifies full-amplitude + Partials, to apply only to quiet Partials, specify a lower + threshold like -90). The reference Partial is the first Partial + in the PartialList labeled refLabel (usually 1). The Envelope + is a time-varying weighting on the harmonifing process. When 1, + harmonic frequencies are used, when 0, breakpoint frequencies are + unmodified. + */ +extern "C" +void harmonify( PartialList * partials, long refLabel, + const LinearEnvelope * env, double threshold_dB ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((LinearEnvelope *) env); + + if ( partials->size() == 0 ) + { + Throw( InvalidObject, "No Partials in PartialList to harmonify." ); + } + + notifier << "harmonifying " << partials->size() << " Partials" << endl; + + Harmonifier::harmonify( partials->begin(), partials->end(), refLabel, + *env, threshold_dB ); + + } + catch( Exception & ex ) + { + std::string s("Loris exception in harmonify(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in harmonify(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + +} + +/* ---------------------------------------------------------------- */ +/* importAiff +/* +/* Import audio samples stored in an AIFF file at the given file + path (or name). The samples are converted to floating point + values on the range (-1.,1.) and stored in an array of doubles. + The value returned is the number of samples in buffer, and it is at + most bufferSize. If samplerate is not a NULL pointer, + then, on return, it points to the value of the sample rate (in + Hz) of the AIFF samples. The AIFF file must contain only a single + channel of audio data. The prior contents of buffer, if any, are + overwritten. + */ +extern "C" +unsigned int +importAiff( const char * path, double * buffer, unsigned int bufferSize, + double * samplerate ) +{ + unsigned int howMany = 0; + try + { + // read samples: + notifier << "reading samples from " << path << endl; + AiffFile f( path ); + notifier << "read " << f.samples().size() << " frames at " + << f.sampleRate() << " Hz" << endl; + + howMany = std::min( f.samples().size(), + std::vector< double >::size_type( bufferSize ) ); + if ( howMany < f.samples().size() ) + { + notifier << "returning " << howMany << " samples" << endl; + } + + std::copy( f.samples().begin(), f.samples().begin() + howMany, + buffer ); + std::fill( buffer + howMany, buffer + bufferSize, 0. ); + + + if ( samplerate ) + { + *samplerate = f.sampleRate(); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in importAiff(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in importAiff(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return howMany; +} + +/* ---------------------------------------------------------------- */ +/* importSdif +/* +/* Import Partials from an SDIF file at the given file path (or + name), and append them to a PartialList. Loris reads SDIF + files in the 1TRC format. For more information about SDIF, + see the SDIF web site at: + www.ircam.fr/equipes/analyse-synthese/sdif/ + */ +extern "C" +void importSdif( const char * path, PartialList * partials ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "importing Partials from " << path << endl; + SdifFile imp( path ); + partials->splice( partials->end(), imp.partials() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in importSdif(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in importSdif(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* importSpc +/* +/* Import Partials from an Spc file at the given file path (or + name), and return them in a PartialList. + */ +extern "C" +void importSpc( const char * path, PartialList * partials ) +{ + try + { + Loris::notifier << "importing Partials from " << path << Loris::endl; + Loris::SpcFile imp( path ); + partials->insert( partials->end(), imp.partials().begin(), imp.partials().end() ); + + } + catch( Exception & ex ) + { + std::string s("Loris exception in importSpc(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in importSpc(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* morpher_setAmplitudeShape +/* +/* Set the shaping parameter for the amplitude morphing + function. This shaping parameter controls the + slope of the amplitude morphing function, + for values greater than 1, this function + gets nearly linear (like the old amplitude + morphing function), for values much less + than 1 (e.g. 1E-5) the slope is gently + curved and sounds pretty "linear", for + very small values (e.g. 1E-12) the curve + is very steep and sounds un-natural because + of the huge jump from zero amplitude to + very small amplitude. + + Use LORIS_DEFAULT_AMPMORPHSHAPE to obtain the + default amplitude morphing shape for Loris, + (equal to 1E-5, which works well for many musical + instrument morphs, unless Loris was compiled + with the symbol LINEAR_AMP_MORPHS defined, in + which case LORIS_DEFAULT_AMPMORPHSHAPE is equal + to LORIS_LINEAR_AMPMORPHSHAPE). + + Use LORIS_LINEAR_AMPMORPHSHAPE to approximate + the linear amplitude morphs performed by older + versions of Loris. + + The amplitude shape must be positive. + */ +#if !defined(LINEAR_AMP_MORPHS) || !LINEAR_AMP_MORPHS + const double LORIS_DEFAULT_AMPMORPHSHAPE = 1E-5; +#else + const double LORIS_DEFAULT_AMPMORPHSHAPE = 1E5; +#endif + +const double LORIS_LINEAR_AMPMORPHSHAPE = 1E5; + +static double PI_ampMorphShape = LORIS_DEFAULT_AMPMORPHSHAPE; +extern "C" +void morpher_setAmplitudeShape( double x ) +{ + if ( x <= 0. ) + { + std::string s("Loris exception in morpher_setAmplitudeShape(): " ); + s.append( "Invalid Argument: the amplitude morph shaping parameter must be positive" ); + handleException( s.c_str() ); + } + PI_ampMorphShape = x; +} + +/* ---------------------------------------------------------------- */ +/* morph +/* +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ +extern "C" +void morph( const PartialList * src0, const PartialList * src1, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ) +{ + try + { + ThrowIfNull((PartialList *) src0); + ThrowIfNull((PartialList *) src1); + ThrowIfNull((PartialList *) dst); + ThrowIfNull((LinearEnvelope *) ffreq); + ThrowIfNull((LinearEnvelope *) famp); + ThrowIfNull((LinearEnvelope *) fbw); + + notifier << "morphing " << src0->size() << " Partials with " << + src1->size() << " Partials" << endl; + + // make a Morpher object and do it: + Morpher m( *ffreq, *famp, *fbw ); + m.morph( src0->begin(), src0->end(), src1->begin(), src1->end() ); + + // splice the morphed Partials into dst: + dst->splice( dst->end(), m.partials() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in morph(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in morph(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* morphWithReference +/* +/* Morph labeled Partials in two PartialLists according to the + given frequency, amplitude, and bandwidth (noisiness) morphing + envelopes, and append the morphed Partials to the destination + PartialList. Specify the labels of the Partials to be used as + reference Partial for the two morph sources. The reference + partial is used to compute frequencies for very low-amplitude + Partials whose frequency estimates are not considered reliable. + The reference Partial is considered to have good frequency + estimates throughout. A reference label of 0 indicates that + no reference Partial should be used for the corresponding + morph source. + + Loris morphs Partials by interpolating frequency, + amplitude, and bandwidth envelopes of corresponding Partials in + the source PartialLists. For more information about the Loris + morphing algorithm, see the Loris website: + www.cerlsoundgroup.org/Loris/ + */ +extern "C" +void morphWithReference( const PartialList * src0, + const PartialList * src1, + long src0RefLabel, + long src1RefLabel, + const LinearEnvelope * ffreq, + const LinearEnvelope * famp, + const LinearEnvelope * fbw, + PartialList * dst ) +{ + try + { + ThrowIfNull((PartialList *) src0); + ThrowIfNull((PartialList *) src1); + ThrowIfNull((PartialList *) dst); + ThrowIfNull((LinearEnvelope *) ffreq); + ThrowIfNull((LinearEnvelope *) famp); + ThrowIfNull((LinearEnvelope *) fbw); + + notifier << "morphing " << src0->size() << " Partials with " + << src1->size() << " Partials" << endl; + + // make a Morpher object and do it: + Morpher m( *ffreq, *famp, *fbw ); + + if ( src0RefLabel != 0 ) + { + notifier << "using Partial labeled " << src0RefLabel; + notifier << " as reference Partial for first morph source" << endl; + m.setSourceReferencePartial( *src0, src0RefLabel ); + } + else + { + notifier << "using no reference Partial for first morph source" << endl; + } + + if ( src1RefLabel != 0 ) + { + notifier << "using Partial labeled " << src1RefLabel; + notifier << " as reference Partial for second morph source" << endl; + m.setTargetReferencePartial( *src1, src1RefLabel ); + } + else + { + notifier << "using no reference Partial for second morph source" << endl; + } + + m.morph( src0->begin(), src0->end(), src1->begin(), src1->end() ); + + // splice the morphed Partials into dst: + dst->splice( dst->end(), m.partials() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in morphWithReference(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in morphWithReference(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + + +/* ---------------------------------------------------------------- */ +/* resample +/* +/* Resample all Partials in a PartialList using the specified + sampling interval, so that the Breakpoints in the Partial + envelopes will all lie on a common temporal grid. + The Breakpoint times in resampled Partials will comprise a + contiguous sequence of integer multiples of the sampling interval, + beginning with the multiple nearest to the Partial's start time and + ending with the multiple nearest to the Partial's end time. Resampling + is performed in-place. + + */ +extern "C" +void resample( PartialList * partials, double interval ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "resampling " << partials->size() << " Partials" << Loris::endl; + + Resampler::resample( partials->begin(), partials->end(), interval ); + + // remove any resulting empty Partials + PartialList::iterator it = partials->begin(); + while ( it != partials->end() ) + { + if ( 0 == it->numBreakpoints() ) + { + it = partials->erase( it ); + } + else + { + ++it; + } + } + + } + catch( Exception & ex ) + { + std::string s( "Loris exception in resample(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s( "std C++ exception in resample(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* shapeSpectrum +/* Scale the amplitudes of a set of Partials by applying + a spectral suface constructed from another set. + Strecth the spectral surface in time and frequency + using the specified stretch factors. + */ +extern "C" +void shapeSpectrum( PartialList * partials, PartialList * surface, + double stretchFreq, double stretchTime ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((PartialList *) surface); + + notifier << "shaping " << partials->size() << " Partials using " + << "spectral surface created from " << surface->size() + << " Partials" << Loris::endl; + + // uses default fade time of 1 ms, should be parameter + SpectralSurface surf( surface->begin(), surface->end() ); + surf.setFrequencyStretch( stretchFreq ); + surf.setTimeStretch( stretchTime ); + surf.setEffect( 1.0 ); // should this be a parameter? + surf.scaleAmplitudes( partials->begin(), partials->end() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in shapeSpectrum(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in shapeSpectrum(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} +/* ---------------------------------------------------------------- */ +/* sift +/* Eliminate overlapping Partials having the same label + (except zero). If any two partials with same label + overlap in time, keep only the longer of the two. + Set the label of the shorter duration partial to zero. + + */ +extern "C" +void sift( PartialList * partials ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "sifting " << partials->size() << " Partials" << Loris::endl; + + // uses default fade time of 1 ms, should be parameter + Sieve::sift( partials->begin(), partials->end(), 0.001 ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in sift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in sift(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* synthesize +/* +/* Synthesize Partials in a PartialList at the given sample + rate, and store the (floating point) samples in a buffer of + size bufferSize. The buffer is neither resized nor + cleared before synthesis, so newly synthesized samples are + added to any previously computed samples in the buffer, and + samples beyond the end of the buffer are lost. Return the + number of samples synthesized, that is, the index of the + latest sample in the buffer that was modified. + */ +extern "C" +unsigned int synthesize( const PartialList * partials, + double * buffer, unsigned int bufferSize, + double srate ) +{ + unsigned int howMany = 0; + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((double *) buffer); + + notifier << "synthesizing " << partials->size() + << " Partials at " << srate << " Hz" << endl; + + // synthesize: + std::vector< double > vec; + Synthesizer synth( srate, vec ); + synth.synthesize( partials->begin(), partials->end() ); + + // determine the number of synthesized samples + // that will be stored: + howMany = vec.size(); + if ( howMany > bufferSize ) + { + howMany = bufferSize; + } + + // accumulate into the buffer: + std::transform( buffer, buffer + howMany, vec.begin(), + buffer, std::plus< double >() ); + + + } + catch( Exception & ex ) + { + std::string s("Loris exception in synthesize(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in synthesize(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return howMany; +} + + + + diff --git a/src/loris/lorisPartialList_pi.C b/src/loris/lorisPartialList_pi.C new file mode 100644 index 0000000..c99c77c --- /dev/null +++ b/src/loris/lorisPartialList_pi.C @@ -0,0 +1,839 @@ +/* + * 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 + * + * + * lorisPartialList_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of the Loris procedural interface: + * - object interfaces - Analyzer, Synthesizer, Partial, PartialIterator, + * PartialList, PartialListIterator, Breakpoint, BreakpointEnvelope, + * and SampleVector need to be (opaque) objects in the interface, + * either because they hold state (e.g. Analyzer) or because they are + * fundamental data types (e.g. Partial), so they need a procedural + * interface to their member functions. All these things need to be + * opaque pointers for the benefit of C. + * - non-object-based procedures - other classes in Loris are not so stateful, + * and have sufficiently narrow functionality that they need only + * procedures, and no object representation. + * - utility functions - some procedures that are generally useful but are + * not yet part of the Loris core are also defined. + * - notification and exception handlers - all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function (the default one in Loris uses printf()). + * + * This file contains the procedural interface for the Loris + * PartialList (std::list< Partial >) class. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "loris.h" +#include "lorisException_pi.h" + +#include "Partial.h" +#include "Notifier.h" + +#include <list> + +using namespace Loris; + + +/* ---------------------------------------------------------------- */ +/* PartialList object interface +/* +/* A PartialList represents a collection of Bandwidth-Enhanced + Partials, each having a trio of synchronous, non-uniformly- + sampled breakpoint envelopes representing the time-varying + frequency, amplitude, and noisiness of a single bandwidth- + enhanced sinusoid. + + For more information about Bandwidth-Enhanced Partials and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to + the Loris website: www.cerlsoundgroup.org/Loris/. + + In C++, a PartialList * is a Loris::PartialList *. + */ + +/* ---------------------------------------------------------------- */ +/* createPartialList +/* +/* Return a new empty PartialList. + */ +extern "C" +PartialList * createPartialList( void ) +{ + try + { + debugger << "creating empty PartialList" << endl; + return new std::list< Partial >; + } + catch( Exception & ex ) + { + std::string s("Loris exception in createPartialList(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in createPartialList(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return NULL; +} + +/* ---------------------------------------------------------------- */ +/* destroyPartialList +/* +/* Destroy this PartialList. + */ +extern "C" +void destroyPartialList( PartialList * ptr_this ) +{ + try + { + ThrowIfNull((PartialList *) ptr_this); + + debugger << "deleting PartialList containing " << ptr_this->size() << " Partials" << endl; + delete ptr_this; + } + catch( Exception & ex ) + { + std::string s("Loris exception in destroyPartialList(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in destroyPartialList(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* partialList_clear +/* +/* Remove (and destroy) all the Partials from this PartialList, + leaving it empty. + */ +extern "C" +void partialList_clear( PartialList * ptr_this ) +{ + try + { + ThrowIfNull((PartialList *) ptr_this); + ptr_this->clear(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partialList_clear(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partialList_clear(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* partialList_copy +/* +/* Make this PartialList a copy of the source PartialList by making + copies of all of the Partials in the source and adding them to + this PartialList. + */ +extern "C" +void partialList_copy( PartialList * dst, const PartialList * src ) +{ + try + { + ThrowIfNull((PartialList *) dst); + ThrowIfNull((PartialList *) src); + + debugger << "copying PartialList containing " << src->size() << " Partials" << endl; + *dst = *src; + } + catch( Exception & ex ) + { + std::string s("Loris exception in partialList_copy(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partialList_copy(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* partialList_size +/* +/* Return the number of Partials in this PartialList. + */ +extern "C" +unsigned long partialList_size( const PartialList * ptr_this ) +{ + try + { + ThrowIfNull((PartialList *) ptr_this); + return ptr_this->size(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partialList_size(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partialList_size(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return 0; +} + +/* ---------------------------------------------------------------- */ +/* partialList_splice +/* +/* Splice all the Partials in the source PartialList onto the end of + this PartialList, leaving the source empty. + */ +extern "C" +void partialList_splice( PartialList * dst, PartialList * src ) +{ + try + { + ThrowIfNull((PartialList *) dst); + ThrowIfNull((PartialList *) src); + + debugger << "splicing PartialList containing " << src->size() << " Partials" + << " into PartialList containing " << dst->size() << " Partials"<< endl; + dst->splice( dst->end(), *src ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partialList_splice(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partialList_splice(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* Partial object interface +/* +/* A Partial represents a single component in the + reassigned bandwidth-enhanced additive model. A Partial consists of a + chain of Breakpoints describing the time-varying frequency, amplitude, + and bandwidth (or noisiness) envelopes of the component, and a 4-byte + label. The Breakpoints are non-uniformly distributed in time. For more + information about Reassigned Bandwidth-Enhanced Analysis and the + Reassigned Bandwidth-Enhanced Additive Sound Model, refer to the Loris + website: www.cerlsoundgroup.org/Loris/. + */ + +/* ---------------------------------------------------------------- */ +/* partial_startTime +/* +/* Return the start time (seconds) for the specified Partial. + */ +double partial_startTime( const Partial * p ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->startTime(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_startTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_startTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_endTime +/* +/* Return the end time (seconds) for the specified Partial. + */ +double partial_endTime( const Partial * p ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->endTime(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_endTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_endTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_duration +/* +/* Return the duration (seconds) for the specified Partial. + */ +double partial_duration( const Partial * p ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->duration(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_duration(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_duration(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_initialPhase +/* +/* Return the initial phase (radians) for the specified Partial. + */ +double partial_initialPhase( const Partial * p ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->initialPhase(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_initialPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_initialPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_label +/* +/* Return the integer label for the specified Partial. + */ +int partial_label( const Partial * p ) +{ + int ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->label(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_label(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_label(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_numBreakpoints +/* +/* Return the number of Breakpoints in the specified Partial. + */ +unsigned long partial_numBreakpoints( const Partial * p ) +{ + unsigned long ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->size(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_numBreakpoints(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_numBreakpoints(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_frequencyAt +/* +/* Return the frequency (Hz) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ +double partial_frequencyAt( const Partial * p, double t ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->frequencyAt( t ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_frequencyAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_frequencyAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_bandwidthAt +/* +/* Return the bandwidth of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ +double partial_bandwidthAt( const Partial * p, double t ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->bandwidthAt( t ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_bandwidthAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_bandwidthAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_phaseAt +/* +/* Return the phase (radians) of the specified Partial interpolated + at a particular time. It is an error to apply this function to + a Partial having no Breakpoints. + */ +double partial_phaseAt( const Partial * p, double t ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->phaseAt( t ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_phaseAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_phaseAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_amplitudeAt +/* +/* Return the (absolute) amplitude of the specified Partial interpolated + at a particular time. Partials are assumed to fade out + over 1 millisecond at the ends (rather than instantaneously). + It is an error to apply this function to a Partial having no Breakpoints. + */ +double partial_amplitudeAt( const Partial * p, double t ) +{ + double ret = 0; + try + { + ThrowIfNull((Partial *) p); + + ret = p->amplitudeAt( t, 0.001 ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_amplitudeAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_amplitudeAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* partial_setLabel +/* +/* Assign a new integer label to the specified Partial. + */ +void partial_setLabel( Partial * p, int label ) +{ + try + { + ThrowIfNull((Partial *) p); + + p->setLabel( label ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in partial_setLabel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in partial_setLabel(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* Breakpoint object interface +/* +/* A Breakpoint represents a single breakpoint in the + Partial parameter (frequency, amplitude, bandwidth) envelope. + Instantaneous phase is also stored, but is only used at the onset of + a partial, or when it makes a transition from zero to nonzero amplitude. + + Loris Partials represent reassigned bandwidth-enhanced model components. + A Partial consists of a chain of Breakpoints describing the time-varying + frequency, amplitude, and bandwidth (noisiness) of the component. + For more information about Reassigned Bandwidth-Enhanced + Analysis and the Reassigned Bandwidth-Enhanced Additive Sound + Model, refer to the Loris website: + www.cerlsoundgroup.org/Loris/. + */ + +/* ---------------------------------------------------------------- */ +/* breakpoint_getFrequency +/* +/* Return the frequency (Hz) of the specified Breakpoint. + */ +double breakpoint_getFrequency( const Breakpoint * bp ) +{ + double ret = 0; + try + { + ThrowIfNull((Breakpoint *) bp); + + ret = bp->frequency(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_getFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_getFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_getAmplitude +/* +/* Return the (absolute) amplitude of the specified Breakpoint. + */ +double breakpoint_getAmplitude( const Breakpoint * bp ) +{ + double ret = 0; + try + { + ThrowIfNull((Breakpoint *) bp); + + ret = bp->amplitude(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_getAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_getAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_getBandwidth +/* +/* Return the bandwidth coefficient of the specified Breakpoint. + */ +double breakpoint_getBandwidth( const Breakpoint * bp ) +{ + double ret = 0; + try + { + ThrowIfNull((Breakpoint *) bp); + + ret = bp->bandwidth(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_getBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_getBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_getPhase +/* +/* Return the phase (radians) of the specified Breakpoint. + */ +double breakpoint_getPhase( const Breakpoint * bp ) +{ + double ret = 0; + try + { + ThrowIfNull((Breakpoint *) bp); + + ret = bp->phase(); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_getPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_getPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + return ret; +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_setFrequency +/* +/* Assign a new frequency (Hz) to the specified Breakpoint. + */ +void breakpoint_setFrequency( Breakpoint * bp, double f ) +{ + try + { + ThrowIfNull((Breakpoint *) bp); + + bp->setFrequency( f ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_setFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_setFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_setAmplitude +/* +/* Assign a new (absolute) amplitude to the specified Breakpoint. + */ +void breakpoint_setAmplitude( Breakpoint * bp, double a ) +{ + try + { + ThrowIfNull((Breakpoint *) bp); + + bp->setAmplitude( a ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_setAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_setAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_setBandwidth +/* +/* Assign a new bandwidth coefficient to the specified Breakpoint. + */ +void breakpoint_setBandwidth( Breakpoint * bp, double bw ) +{ + try + { + ThrowIfNull((Breakpoint *) bp); + + bp->setBandwidth( bw ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_setBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_setBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* breakpoint_setPhase +/* +/* Assign a new phase (radians) to the specified Breakpoint. + */ +void breakpoint_setPhase( Breakpoint * bp, double phi ) +{ + try + { + ThrowIfNull((Breakpoint *) bp); + + bp->setPhase( phi ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in breakpoint_setPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in breakpoint_setPhase(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} diff --git a/src/loris/lorisUtilities_pi.C b/src/loris/lorisUtilities_pi.C new file mode 100644 index 0000000..0b401a4 --- /dev/null +++ b/src/loris/lorisUtilities_pi.C @@ -0,0 +1,1075 @@ +/* + * 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 + * + * + * lorisUtilities_pi.C + * + * A component of the C-linkable procedural interface for Loris. + * + * Main components of the Loris procedural interface: + * - object interfaces - Analyzer, Synthesizer, Partial, PartialIterator, + * PartialList, PartialListIterator, Breakpoint, BreakpointEnvelope, + * and SampleVector need to be (opaque) objects in the interface, + * either because they hold state (e.g. Analyzer) or because they are + * fundamental data types (e.g. Partial), so they need a procedural + * interface to their member functions. All these things need to be + * opaque pointers for the benefit of C. + * - non-object-based procedures - other classes in Loris are not so stateful, + * and have sufficiently narrow functionality that they need only + * procedures, and no object representation. + * - utility functions - some procedures that are generally useful but are + * not yet part of the Loris core are also defined. + * - notification and exception handlers - all exceptions must be caught and + * handled internally, clients can specify an exception handler and + * a notification function (the default one in Loris uses printf()). + * + * This file defines the utility functions that are useful, and in many + * cases trivial in C++ (usign the STL for example) but are not represented + * by classes in the Loris core. + * + * Kelly Fitz, 10 Nov 2000 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "loris.h" +#include "lorisException_pi.h" + +#include "BreakpointEnvelope.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" +#include "PartialUtils.h" + +#include <algorithm> +#include <cmath> +#include <functional> +#include <iterator> +#include <list> +#include <memory> + +using namespace Loris; + +// --------------------------------------------------------------------------- +// Functors to help apply C-callbacks to lists of Partials +// by converting Partial references to pointer arguments. +// + +struct CallWithPointer : public std::unary_function< Partial, void > +{ + typedef void (* Func)( Partial *, void * ); + Func func; + void * data; + + CallWithPointer( Func f, void * d ) : func( f ), data( d ) {} + + void operator()( Partial & partial ) const + { + func( &partial, data ); + } +}; + +struct PredWithPointer : public std::unary_function< const Partial, bool > +{ + typedef int (* Pred)( const Partial *, void * ); + Pred pred; + void * data; + + PredWithPointer( Pred p, void * d ) : pred( p ), data( d ) {} + + bool operator()( const Partial & partial ) const + { + return 0 != pred( &partial, data ); + } +}; + +/* ---------------------------------------------------------------- */ +/* utility functions +/* +/* Operations for transforming and manipulating collections + of Partials. + */ + +/* ---------------------------------------------------------------- */ +/* avgAmplitude +/* +/* Return the average amplitude over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ +extern "C" +double avgAmplitude( const Partial * p ) +{ + try + { + ThrowIfNull((Partial *) p); + return PartialUtils::avgAmplitude( *p ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in avgAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in avgAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return 0; +} + +/* ---------------------------------------------------------------- */ +/* avgFrequency +/* +/* Return the average frequency over all Breakpoints in this Partial. + Return zero if the Partial has no Breakpoints. + */ +extern "C" +double avgFrequency( const Partial * p ) +{ + try + { + ThrowIfNull((Partial *) p); + return PartialUtils::avgFrequency( *p ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in avgFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in avgFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return 0;} + + +/* ---------------------------------------------------------------- */ +/* copyIf +/* +/* Append copies of Partials in the source PartialList satisfying the + specified predicate to the destination PartialList. The source list + is unmodified. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ +extern "C" +void copyIf( const PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ) +{ + try + { + ThrowIfNull((PartialList *) src); + ThrowIfNull((PartialList *) dst); + + std::remove_copy_if( src->begin(), src->end(), std::back_inserter( *dst ), + std::not1( PredWithPointer( predicate, data ) ) ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in copyIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in copyIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* copyLabeled +/* +/* Append copies of Partials in the source PartialList having the + specified label to the destination PartialList. The source list + is unmodified. + */ +extern "C" +void copyLabeled( const PartialList * src, long label, PartialList * dst ) +{ + try + { + ThrowIfNull((PartialList *) src); + ThrowIfNull((PartialList *) dst); + + std::remove_copy_if( src->begin(), src->end(), std::back_inserter( *dst ), + std::not1( PartialUtils::isLabelEqual(label) ) ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in copyLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in copyLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* crop +/* +/* Trim Partials by removing Breakpoints outside a specified time span. + Insert a Breakpoint at the boundary when cropping occurs. Remove + any Partials that are left empty after cropping (Partials having no + Breakpoints between t1 and t2). + */ +extern "C" +void crop( PartialList * partials, double t1, double t2 ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "cropping " << partials->size() << " Partials" << endl; + + PartialUtils::crop( partials->begin(), partials->end(), t1, t2 ); + + // remove empty Partials: + PartialList::iterator it = partials->begin(); + while ( it != partials->end() ) + { + if ( 0 == it->numBreakpoints() ) + { + it = partials->erase( it ); + } + else + { + ++it; + } + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in crop(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in crop(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* extractIf +/* +/* Remove Partials in the source PartialList satisfying the + specified predicate from the source list and append them to + the destination PartialList. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ +extern "C" +void extractIf( PartialList * src, PartialList * dst, + int ( * predicate )( const Partial * p, void * data ), + void * data ) +{ + try + { + ThrowIfNull((PartialList *) src); + ThrowIfNull((PartialList *) dst); + + /* + std::list< Partial >::iterator it = + std::stable_partition( src->begin(), src->end(), + std::not1( PredWithPointer( predicate, data ) ) ); + + stable_partition should work, but seems sometimes to hang, + especially when there are lots and lots of Partials. Do it + efficiently by hand instead. + + dst->splice( dst->end(), *src, it, src->end() ); + */ + std::list< Partial >::iterator it; + for ( it = std::find_if( src->begin(), src->end(), PredWithPointer( predicate, data ) ); + it != src->end(); + it = std::find_if( it, src->end(), PredWithPointer( predicate, data ) ) ) + { + // the iterator passed to splice is a copy of it + // before the increment, so it is not corrupted + // by the splice, it is advanced to the next + // position before the splice is performed. + dst->splice( dst->end(), *src, it++ ); + } + + } + catch( Exception & ex ) + { + std::string s("Loris exception in extractIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in extractIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* extractLabeled +/* +/* Remove Partials in the source PartialList having the specified + label from the source list and append them to the destination + PartialList. + */ +extern "C" +void extractLabeled( PartialList * src, long label, PartialList * dst ) +{ + try + { + ThrowIfNull((PartialList *) src); + ThrowIfNull((PartialList *) dst); + + /* + std::list< Partial >::iterator it = + std::stable_partition( src->begin(), src->end(), + std::not1( PartialUtils::isLabelEqual(label) ) ); + + stable_partition should work, but seems sometimes to hang, + especially when there are lots and lots of Partials. Do it + efficiently by hand instead. + + dst->splice( dst->end(), *src, it, src->end() ); + */ + std::list< Partial >::iterator it; + for ( it = std::find_if( src->begin(), src->end(), PartialUtils::isLabelEqual(label) ); + it != src->end(); + it = std::find_if( it, src->end(), PartialUtils::isLabelEqual(label) ) ) + { + // the iterator passed to splice is a copy of it + // before the increment, so it is not corrupted + // by the splice, it is advanced to the next + // position before the splice is performed. + dst->splice( dst->end(), *src, it++ ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in extractLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in extractLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* fixPhaseAfter +/* +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + simply left unmodified, and future phases wil be recomputed + from that one. + */ +extern "C" +void fixPhaseAfter( PartialList * partials, double time ) +{ + try + { + ThrowIfNull((PartialList *) partials); + PartialUtils::fixPhaseAfter( partials->begin(), partials->end(), time ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in fixPhaseAfter(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in fixPhaseAfter(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* fixPhaseAt +/* +/* Recompute phases of all Breakpoints in a Partial + so that the synthesized phases match the stored phases, + and the synthesized phase at (nearest) the specified + time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) + Breakpoint is encountered, because nulls are interpreted as + phase reset points in Loris. If a null is encountered, the + remainder of the Partial (the front part) is fixed in the + forward direction, beginning at the start of the Partial. + Forward phase fixing is only applied to non-null + (nonzero-amplitude) Breakpoints. If a null is encountered, + its phase is simply left unmodified, and future phases wil be + recomputed from that one. + */ +extern "C" +void fixPhaseAt( PartialList * partials, double time ) +{ + try + { + ThrowIfNull((PartialList *) partials); + PartialUtils::fixPhaseAt( partials->begin(), partials->end(), time ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in fixPhaseAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in fixPhaseAt(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* fixPhaseBefore +/* +/* Recompute phases of all Breakpoints earlier than the specified + time so that the synthesized phases of those earlier Breakpoints + matches the stored phase, and the synthesized phase at the + specified time matches the stored (not recomputed) phase. + + Backward phase-fixing stops if a null (zero-amplitude) Breakpoint + is encountered, because nulls are interpreted as phase reset + points in Loris. If a null is encountered, the remainder of the + Partial (the front part) is fixed in the forward direction, + beginning at the start of the Partial. + */ +extern "C" +void fixPhaseBefore( PartialList * partials, double time ) +{ + try + { + ThrowIfNull((PartialList *) partials); + PartialUtils::fixPhaseBefore( partials->begin(), partials->end(), time ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in fixPhaseBefore(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in fixPhaseBefore(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* fixPhaseBetween +/* + Fix the phase travel between two times by adjusting the + frequency and phase of Breakpoints between those two times. + + This algorithm assumes that there is nothing interesting + about the phases of the intervening Breakpoints, and modifies + their frequencies as little as possible to achieve the correct + amount of phase travel such that the frequencies and phases at + the specified times match the stored values. The phases of all + the Breakpoints between the specified times are recomputed. + */ +extern "C" +void fixPhaseBetween( PartialList * partials, double tbeg, double tend ) +{ + try + { + ThrowIfNull((PartialList *) partials); + PartialUtils::fixPhaseBetween( partials->begin(), partials->end(), + tbeg, tend ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in fixPhaseBetween(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in fixPhaseBetween(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + + +/* ---------------------------------------------------------------- */ +/* fixPhaseForward +/* +/* Recompute phases of all Breakpoints later than the specified + time so that the synthesized phases of those later Breakpoints + matches the stored phase, as long as the synthesized phase at + the specified time matches the stored (not recomputed) phase. + Breakpoints later than tend are unmodified. + + Phase fixing is only applied to non-null (nonzero-amplitude) + Breakpoints, because null Breakpoints are interpreted as phase + reset points in Loris. If a null is encountered, its phase is + simply left unmodified, and future phases wil be recomputed + from that one. + */ +extern "C" +void fixPhaseForward( PartialList * partials, double tbeg, double tend ) +{ + try + { + ThrowIfNull((PartialList *) partials); + PartialUtils::fixPhaseForward( partials->begin(), partials->end(), + tbeg, tend ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in fixPhaseForward(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in fixPhaseForward(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + + +/* ---------------------------------------------------------------- */ +/* forEachBreakpoint +/* +/* Apply a function to each Breakpoint in a Partial. The function + is called once for each Breakpoint in the source Partial. The + function may modify the Breakpoint (but should not otherwise attempt + to modify the Partial). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data is needed. + The function should return 0 if successful. If the function returns + a non-zero value, then forEachBreakpoint immediately returns that value + without applying the function to any other Breakpoints in the Partial. + forEachBreakpoint returns zero if all calls to func return zero. + */ +int forEachBreakpoint( Partial * p, + int ( * func )( Breakpoint * p, double time, void * data ), + void * data ) +{ + int result = 0; + try + { + ThrowIfNull((Partial *) p); + + Partial::iterator it; + for ( it = p->begin(); 0 == result && it != p->end(); ++it ) + { + result = func( &(it.breakpoint()), it.time(), data ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in forEachBreakpoint(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in forEachBreakpoint(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return result; +} + + +/* ---------------------------------------------------------------- */ +/* forEachPartial +/* +/* Apply a function to each Partial in a PartialList. The function + is called once for each Partial in the source PartialList. The + function may modify the Partial (but should not attempt to modify + the PartialList). The data parameter can be used to supply extra + user-defined data to the function. Pass 0 if no additional data + is needed. The function should return 0 if successful. If the + function returns a non-zero value, then forEachPartial immediately + returns that value without applying the function to any other + Partials in the PartialList. forEachPartial returns zero if all + calls to func return zero. + */ +int forEachPartial( PartialList * src, + int ( * func )( Partial * p, void * data ), + void * data ) +{ + int result = 0; + try + { + ThrowIfNull((PartialList *) src); + + PartialList::iterator it; + for ( it = src->begin(); 0 == result && it != src->end(); ++it ) + { + result = func( &(*it), data ); + } + } + catch( Exception & ex ) + { + std::string s("Loris exception in forEachPartial(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in forEachPartial(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return result; +} + + +/* ---------------------------------------------------------------- */ +/* peakAmplitude +/* +/* Return the maximum amplitude achieved by a Partial. + */ +extern "C" +double peakAmplitude( const Partial * p ) +{ + try + { + ThrowIfNull((Partial *) p); + return PartialUtils::peakAmplitude( *p ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in peakAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in peakAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return 0; +} + +/* ---------------------------------------------------------------- */ +/* removeIf +/* +/* Remove from a PartialList all Partials satisfying the + specified predicate. The data parameter can be used to + supply extra user-defined data to the function. Pass 0 if no + additional data is needed. + */ +extern "C" +void removeIf( PartialList * src, + int ( * predicate )( const Partial * p, void * data ), + void * data ) +{ + try + { + ThrowIfNull((PartialList *) src); + std::list< Partial >::iterator it = + std::remove_if( src->begin(), src->end(), + PredWithPointer( predicate, data ) ); + src->erase( it, src->end() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in removeIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in removeIf(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + + +/* ---------------------------------------------------------------- */ +/* removeLabeled +/* +/* Remove from a PartialList all Partials having the specified label. + */ +extern "C" +void removeLabeled( PartialList * src, long label ) +{ + try + { + ThrowIfNull((PartialList *) src); + std::list< Partial >::iterator it = + std::remove_if( src->begin(), src->end(), + PartialUtils::isLabelEqual( label ) ); + src->erase( it, src->end() ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in removeLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in removeLabeled(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* scaleAmp +/* +/* Bad old name for scaleAmplitude. + */ +extern "C" +void scaleAmp( PartialList * partials, BreakpointEnvelope * ampEnv ) +{ + scaleAmplitude( partials, ampEnv ); +} + +/* ---------------------------------------------------------------- */ +/* scaleAmplitude +/* +/* Scale the amplitude of the Partials in a PartialList according + to an envelope representing a time-varying amplitude scale value. + */ +extern "C" +void scaleAmplitude( PartialList * partials, BreakpointEnvelope * ampEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) ampEnv); + + notifier << "scaling amplitude of " << partials->size() << " Partials" << endl; + + PartialUtils::scaleAmplitude( partials->begin(), partials->end(), *ampEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in scaleAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in scaleAmplitude(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* scaleBandwidth +/* +/* Scale the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth scale value. + */ +extern "C" +void scaleBandwidth( PartialList * partials, BreakpointEnvelope * bwEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) bwEnv); + + notifier << "scaling bandwidth of " << partials->size() << " Partials" << endl; + + PartialUtils::scaleBandwidth( partials->begin(), partials->end(), *bwEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in scaleBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in scaleBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* setBandwidth +/* +/* Assign the bandwidth of the Partials in a PartialList according + to an envelope representing a time-varying bandwidth scale value. + */ +extern "C" +void setBandwidth( PartialList * partials, BreakpointEnvelope * bwEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) bwEnv); + + notifier << "setting bandwidth of " << partials->size() << " Partials" << endl; + + PartialUtils::setBandwidth( partials->begin(), partials->end(), *bwEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in setBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in setBandwidth(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* scaleFrequency +/* +/* Scale the frequency of the Partials in a PartialList according + to an envelope representing a time-varying frequency scale value. + */ +extern "C" +void scaleFrequency( PartialList * partials, BreakpointEnvelope * freqEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) freqEnv); + + notifier << "scaling frequency of " << partials->size() << " Partials" << endl; + + PartialUtils::scaleFrequency( partials->begin(), partials->end(), *freqEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in scaleFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in scaleFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* scaleNoiseRatio +/* +/* Scale the relative noise content of the Partials in a PartialList + according to an envelope representing a (time-varying) noise energy + scale value. + */ +extern "C" +void scaleNoiseRatio( PartialList * partials, BreakpointEnvelope * noiseEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) noiseEnv); + + notifier << "scaling noise ratio of " << partials->size() << " Partials" << endl; + + PartialUtils::scaleNoiseRatio( partials->begin(), partials->end(), *noiseEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in scaleNoiseRatio(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in scaleNoiseRatio(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* shiftPitch +/* +/* Shift the pitch of all Partials in a PartialList according to + the given pitch envelope. The pitch envelope is assumed to have + units of cents (1/100 of a halfstep). + */ +extern "C" +void shiftPitch( PartialList * partials, BreakpointEnvelope * pitchEnv ) +{ + try + { + ThrowIfNull((PartialList *) partials); + ThrowIfNull((BreakpointEnvelope *) pitchEnv); + + notifier << "shifting pitch of " << partials->size() << " Partials" << endl; + + PartialUtils::shiftPitch( partials->begin(), partials->end(), *pitchEnv ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in shiftPitch(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in shiftPitch(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* shiftTime +/* +/* Shift the time of all the Breakpoints in all Partials in a + PartialList by a constant amount. + */ +extern "C" +void shiftTime( PartialList * partials, double offset ) +{ + try + { + ThrowIfNull((PartialList *) partials); + + notifier << "shifting time of " << partials->size() << " Partials" << endl; + + PartialUtils::shiftTime( partials->begin(), partials->end(), offset ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in shiftTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in shiftTime(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } +} + +/* ---------------------------------------------------------------- */ +/* sortByLabel +/* +/* Sort the Partials in a PartialList in order of increasing label. + * The sort is stable; Partials having the same label are not + * reordered. + */ +extern "C" +void sortByLabel( PartialList * partials ) +{ + partials->sort( PartialUtils::compareLabelLess() ); +} + +/* ---------------------------------------------------------------- */ +/* timeSpan +/* +/* Return the minimum start time and maximum end time + * in seconds of all Partials in this PartialList. The v + * times are returned in the (non-null) pointers tmin + * and tmax. + */ +extern "C" +void timeSpan( PartialList * partials, double * tmin, double * tmax ) +{ + std::pair< double, double > times = + PartialUtils::timeSpan( partials->begin(), partials->end() ); + if ( 0 != tmin ) + { + *tmin = times.first; + } + if ( 0 != tmax ) + { + *tmax = times.second; + } +} + +/* ---------------------------------------------------------------- */ +/* weightedAvgFrequency +/* +/* Return the average frequency over all Breakpoints in this Partial, + weighted by the Breakpoint amplitudes. Return zero if the Partial + has no Breakpoints. + */ +extern "C" +double weightedAvgFrequency( const Partial * p ) +{ + try + { + ThrowIfNull((Partial *) p); + return PartialUtils::weightedAvgFrequency( *p ); + } + catch( Exception & ex ) + { + std::string s("Loris exception in weightedAvgFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + catch( std::exception & ex ) + { + std::string s("std C++ exception in weightedAvgFrequency(): " ); + s.append( ex.what() ); + handleException( s.c_str() ); + } + + return 0; +} diff --git a/src/loris/phasefix.C b/src/loris/phasefix.C new file mode 100644 index 0000000..b26deab --- /dev/null +++ b/src/loris/phasefix.C @@ -0,0 +1,470 @@ +/* + * 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 + * + * + * phasefix.C + * + * Implements a phase correction algorithm that perturbs slightly the + * frequencies or Breakpoints in a Partial so that the rendered Partial + * will achieve (or be closer to) the analyzed Breakpoint phases. + * + * Kelly Fitz, 23 Sept 04 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#if HAVE_CONFIG_H + #include "config.h" +#endif + +#include "phasefix.h" + +#include "Breakpoint.h" +#include "BreakpointUtils.h" +#include "LorisExceptions.h" +#include "Notifier.h" +#include "Partial.h" + + +#include <algorithm> +#include <cmath> +#include <iostream> +#include <utility> + +#if defined(HAVE_M_PI) && (HAVE_M_PI) + const double Pi = M_PI; +#else + const double Pi = 3.14159265358979324; +#endif + + +// begin namespace +namespace Loris { + +// -- local helpers -- + + +// --------------------------------------------------------------------------- +// wrapPi +// Wrap an unwrapped phase value to the range [-pi,pi] using +// O'Donnell's phase wrapping function. +// +double wrapPi( double x ) +{ + using namespace std; // floor should be in std + #define ROUND(x) (floor(.5 + (x))) + const double TwoPi = 2.0*Pi; + return x + ( TwoPi * ROUND(-x/TwoPi) ); +} + + + +// --------------------------------------------------------------------------- +// phaseTravel +// +// Compute the sinusoidal phase travel between two Breakpoints. +// Return the total unwrapped phase travel. +// +double phaseTravel( const Breakpoint & bp0, const Breakpoint & bp1, + double dt ) +{ + double f0 = bp0.frequency(); + double f1 = bp1.frequency(); + double favg = .5 * ( f0 + f1 ); + return 2 * Pi * favg * dt; +} + +// --------------------------------------------------------------------------- +// phaseTravel +// +// Compute the sinusoidal phase travel between two Breakpoints. +// Return the total unwrapped phase travel. +// +static double phaseTravel( Partial::const_iterator bp0, Partial::const_iterator bp1 ) +{ + return phaseTravel( bp0.breakpoint(), bp1.breakpoint(), + bp1.time() - bp0.time() ); +} + +// -- phase correction -- + +// --------------------------------------------------------------------------- +// fixPhaseBackward +// +//! Recompute phases of all Breakpoints on the half-open range [stopHere, pos) +//! so that the synthesized phases of those Breakpoints matches +//! the stored phase, as long as the synthesized phase at stopHere +//! matches the stored (not recomputed) phase. +//! +//! The phase is corrected beginning at the end of the range, maintaining +//! the stored phase in the Breakpoint at pos. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the range +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the stopHere. +//! +//! \pre pos and stopHere are iterators on the same Partial, and +//! pos must be not later than stopHere. +//! \pre pos cannot be end of the Partial, it must be the postion +//! of a valid Breakpoint. +//! \param stopHere the position of the earliest Breakpoint whose phase might be +//! recomputed. +//! \param pos the position of a (later) Breakpoint whose phase is to be matched. +//! The phase at pos is not modified. +// +void fixPhaseBackward( Partial::iterator stopHere, Partial::iterator pos ) +{ + while ( pos != stopHere && + BreakpointUtils::isNonNull( pos.breakpoint() ) ) + { + // pos is not the first Breakpoint in the Partial, + // and pos is not a Null Breakpoint. + // Compute the correct phase for the + // predecessor of pos. + Partial::iterator posFwd = pos--; + double travel = phaseTravel( pos, posFwd ); + pos.breakpoint().setPhase( wrapPi( posFwd.breakpoint().phase() - travel ) ); + } + + // if a null was encountered, then stop fixing backwards, + // and fix the front of the Partial in the forward direction: + if ( pos != stopHere ) + { + // pos is not the first Breakpoint in the Partial, + // and it is a Null Breakpoint (zero amplitude), + // so it will be used to reset the phase during + // synthesis. + // The phase of all Breakpoints starting with pos + // and ending with the Breakpoint nearest to time t + // has been corrected. + // Fix phases before pos starting at the beginning + // of the Partial. + // + // Dont fix pos, it has already been fixed. + fixPhaseForward( stopHere, --pos ); + } +} + +// ----------------------------------------------------------------------- ---- +// fixPhaseForward +// +//! Recompute phases of all Breakpoints on the closed range [pos, stopHere] +//! so that the synthesized phases of those Breakpoints matches +//! the stored phase, as long as the synthesized phase at pos +//! matches the stored (not recomputed) phase. The phase at pos +//! is modified only if pos is the position of a null Breakpoint +//! and the Breakpoint that follows is non-null. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is corrected from its non-Null +//! successor, if it has one, otherwise it is unmodified. +//! +//! \pre pos and stopHere are iterators on the same Partial, and +//! pos must be not later than stopHere. +//! \pre stopHere cannot be end of the Partial, it must be the postion +//! of a valid Breakpoint. +//! \param pos the position of the first Breakpoint whose phase might be +//! recomputed. +//! \param stopHere the position of the last Breakpoint whose phase might +//! be modified. +// +void fixPhaseForward( Partial::iterator pos, Partial::iterator stopHere ) +{ + while ( pos != stopHere ) + { + Partial::iterator posPrev = pos++; + + // update phase based on the phase travel between + // posPrev and pos UNLESS pos is Null: + if ( BreakpointUtils::isNonNull( pos.breakpoint() ) ) + { + // pos is the position of a non-Null Breakpoint, + // posPrev is its predecessor: + double travel = phaseTravel( posPrev, pos ); + + if ( BreakpointUtils::isNonNull( posPrev.breakpoint() ) ) + { + // if its predecessor of pos is non-Null, then fix + // the phase of the Breakpoint at pos. + pos.breakpoint().setPhase( wrapPi( posPrev.breakpoint().phase() + travel ) ); + } + else + { + // if the predecessor of pos is Null, then + // correct the predecessor's phase so that + // it correctly resets the synthesis phase + // so that the phase of the Breakpoint at + // pos is achieved in synthesis. + posPrev.breakpoint().setPhase( wrapPi( pos.breakpoint().phase() - travel ) ); + } + } + } +} + + +// --------------------------------------------------------------------------- +// fixPhaseBetween +// +//! Fix the phase travel between two Breakpoints by adjusting the +//! frequency and phase of Breakpoints between those two. +//! +//! This algorithm assumes that there is nothing interesting about the +//! phases of the intervening Breakpoints, and modifies their frequencies +//! as little as possible to achieve the correct amount of phase travel +//! such that the frequencies and phases at the specified times +//! match the stored values. The phases of all the Breakpoints between +//! the specified times are recomputed. +//! +//! Null Breakpoints are treated the same as non-null Breakpoints. +//! +//! \pre b and e are iterators on the same Partials, and +//! e must not preceed b in that Partial. +//! \pre There must be at least one Breakpoint in the +//! Partial between b and e. +//! \post The phases and frequencies of the Breakpoints in the +//! range have been recomputed such that an oscillator +//! initialized to the parameters of the first Breakpoint +//! will arrive at the parameters of the last Breakpoint, +//! and all the intervening Breakpoints will be matched. +//! \param b The phases and frequencies of Breakpoints later than +//! this one may be modified. +//! \param e The phases and frequencies of Breakpoints earlier than +//! this one may be modified. +// +void fixPhaseBetween( Partial::iterator b, Partial::iterator e ) +{ + if ( 1 < std::distance( b, e ) ) + { + // Accumulate the actual phase travel over the Breakpoint + // span, and count the envelope segments. + double travel = 0; + Partial::iterator next = b; + do + { + Partial::iterator prev = next++; + travel += phaseTravel( prev, next ); + } while( next != e ); + + // Compute the desired amount of phase travel: + double deviation = wrapPi( e.breakpoint().phase() - ( b.breakpoint().phase() + travel ) ); + double desired = travel + deviation; + + // Compute the amount by which to perturb the frequencies of + // all the null Breakpoints between b and e. + // + // The accumulated phase travel is the sum of the average frequency + // (in radians) of each segment times the duration of each segment + // (the actual phase travel is computed this way). If this sum is + // computed with each Breakpoint frequency perturbed (additively) + // by delta, and set equal to the desired phase travel, then it + // can be simplified to: + // delta = 2 * ( phase error ) / ( tN + tN-1 - t1 - t0 ) + // where tN is the time of e, tN-1 is the time of its predecessor, + // t0 is the time of b, and t1 is the time of b's successor. + // + Partial::iterator iter = b; + double t0 = iter.time(); + ++iter; + double t1 = iter.time(); + iter = e; + double tN = iter.time(); + --iter; + double tNm1 = iter.time(); + + Assert( t1 < tN ); // else there were no Breakpoints in between + + double delta = ( 2 * ( desired - travel ) / ( tN + tNm1 - t1 - t0 ) ) / ( 2 * Pi ); + + // Perturb the Breakpoint frequencies. + next = b; + Partial::iterator prev = next++; + while ( next != e ) + { + next.breakpoint().setFrequency( next.breakpoint().frequency() + delta ); + + double newtravel = phaseTravel( prev, next ); + next.breakpoint().setPhase( wrapPi( prev.breakpoint().phase() + newtravel ) ); + + prev = next++; + } + } + else + { + // Preconditions not met, cannot fix the phase travel. + // Should raise exception? + debugger << "cannot fix phase between " << b.time() << " and " << e.time() + << ", there are no Breakpoints between those times" << endl; + } + +} + +// --------------------------------------------------------------------------- +// matchPhaseFwd +// +//! Compute the target frequency that will affect the +//! predicted (by the Breakpoint phases) amount of +//! sinusoidal phase travel between two breakpoints, +//! and assign that frequency to the target Breakpoint. +//! After computing the new frequency, update the phase of +//! the later Breakpoint. +//! +//! If the earlier Breakpoint is null and the later one +//! is non-null, then update the phase of the earlier +//! Breakpoint, and do not modify its frequency or the +//! later Breakpoint. +//! +//! The most common kinds of errors are local (or burst) errors in +//! frequency and phase. These errors are best corrected by correcting +//! less than half the detected error at any time. Correcting more +//! than that will produce frequency oscillations for the remainder of +//! the Partial, in the case of a single bad frequency (as is common +//! at the onset of a tone). Any damping factor less then one will +//! converge eventually, .5 or less will converge without oscillating. +//! Use the damping argument to control the damping of the correction. +//! Specify 1 for no damping. +//! +//! \pre The two Breakpoints are assumed to be consecutive in +//! a Partial. +//! \param bp0 The earlier Breakpoint. +//! \param bp1 The later Breakpoint. +//! \param dt The time (in seconds) between bp0 and bp1. +//! \param damping The fraction of the amount of phase error that will +//! be corrected (.5 or less will prevent frequency oscilation +//! due to burst errors in phase). +//! \param maxFixPct The maximum amount of frequency adjustment +//! that can be made to the frequency of bp1, expressed +//! as a precentage of the unmodified frequency of bp1. +//! If the necessary amount of frequency adjustment exceeds +//! this amount, then the phase will not be matched, +//! but will be updated as well to be consistent with +//! the frequencies. (default is 0.2%) +// +void matchPhaseFwd( Breakpoint & bp0, Breakpoint & bp1, + double dt, double damping, double maxFixPct ) +{ + double travel = phaseTravel( bp0, bp1, dt ); + + if ( ! BreakpointUtils::isNonNull( bp1 ) ) + { + // if bp1 is null, DON'T compute a new phase, + // because Nulls are phase reset points. + + // bp1.setPhase( wrapPi( bp0.phase() + travel ) ); + } + else if ( ! BreakpointUtils::isNonNull( bp0 ) ) + { + // if bp0 is null, and bp1 is not, then bp0 + // should be a phase reset Breakpoint during + // rendering, so compute a new phase for + // bp0 that achieves bp1's phase. + bp0.setPhase( wrapPi( bp1.phase() - travel ) ) ; + } + else + { + // invariant: + // neither bp0 nor bp1 is null + // + // modify frequecies to match phases as nearly as possible + double err = wrapPi( bp1.phase() - ( bp0.phase() + travel ) ); + + // The most common kinds of errors are local (or burst) errors in + // frequency and phase. These errors are best corrected by correcting + // less than half the detected error at any time. Correcting more + // than that will produce frequency oscillations for the remainder of + // the Partial, in the case of a single bad frequency (as is common + // at the onset of a tone). Any damping factor less then one will + // converge eventually, .5 or less will converge without oscillating. + // #define DAMPING .5 + travel += damping * err; + + double f0 = bp0.frequency(); + double ftgt = ( travel / ( Pi * dt ) ) - f0; + + #ifdef Loris_Debug + debugger << "matchPhaseFwd: correcting " << bp1.frequency() << " to " << ftgt + << " (phase " << wrapPi( bp1.phase() ) << "), "; + #endif + + // If the target is not a null breakpoint, may need to + // clamp the amount of frequency modification. + if ( ftgt > bp1.frequency() * ( 1 + (maxFixPct*.01) ) ) + { + ftgt = bp1.frequency() * ( 1 + (maxFixPct*.01) ); + } + else if ( ftgt < bp1.frequency() * ( 1 - (maxFixPct*.01) ) ) + { + ftgt = bp1.frequency() * ( 1 - (maxFixPct*.01) ); + } + + bp1.setFrequency( ftgt ); + + // Recompute the phase according to the new frequency. + double phi = wrapPi( bp0.phase() + phaseTravel( bp0, bp1, dt ) ); + bp1.setPhase( phi ); + + #ifdef Loris_Debug + debugger << "achieved " << ftgt << " (phase " << phi << ")" << endl; + #endif + } +} + +// --------------------------------------------------------------------------- +// fixFrequency +// +//! Adjust frequencies of the Breakpoints in the +//! specified Partial such that the rendered Partial +//! achieves (or matches as nearly as possible, within +//! the constraint of the maximum allowable frequency +//! alteration) the analyzed phases. +//! +//! This just iterates over the Partial calling +//! matchPhaseFwd, should probably name those similarly. +//! +//! \param partial The Partial whose frequencies, +//! and possibly phases (if the frequencies +//! cannot be sufficiently altered to match +//! the phases), will be recomputed. +//! \param maxFixPct The maximum allowable frequency +//! alteration, default is 0.2%. +// +void fixFrequency( Partial & partial, double maxFixPct ) +{ + if ( partial.numBreakpoints() > 1 ) + { + Partial::iterator next = partial.begin(); + Partial::iterator prev = next++; + while ( next != partial.end() ) + { + if ( BreakpointUtils::isNonNull( next.breakpoint() ) ) + { + matchPhaseFwd( prev.breakpoint(), next.breakpoint(), + next.time() - prev.time(), 0.5, maxFixPct ); + } + prev = next++; + } + } +} + +} // end of namespace Loris diff --git a/src/loris/phasefix.h b/src/loris/phasefix.h new file mode 100644 index 0000000..016983d --- /dev/null +++ b/src/loris/phasefix.h @@ -0,0 +1,237 @@ +#ifndef PHASEFIX_H +#define PHASEFIX_H +/* + * 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 + * + * + * phasefix.h + * + * Functions for correcting Breakpoint phases and frequencies so that + * stored phases match the phases that would be synthesized using the + * Loris Synthesizer. + * + * Kelly Fitz, 23 Sept 04 + * loris@cerlsoundgroup.org + * + * http://www.cerlsoundgroup.org/Loris/ + * + */ +#include "Partial.h" + +// begin namespace +namespace Loris { + +// FUNCTION PROTOYPES + +// fixPhaseBackward +// +//! Recompute phases of all Breakpoints on the half-open range [stopHere, pos) +//! so that the synthesized phases of those Breakpoints matches +//! the stored phase, as long as the synthesized phase at stopHere +//! matches the stored (not recomputed) phase. +//! +//! The phase is corrected beginning at the end of the range, maintaining +//! the stored phase in the Breakpoint at pos. +//! +//! Backward phase-fixing stops if a null (zero-amplitude) Breakpoint +//! is encountered, because nulls are interpreted as phase reset points +//! in Loris. If a null is encountered, the remainder of the range +//! (the front part) is fixed in the forward direction, beginning at +//! the start of the stopHere. +//! +//! \pre pos and stopHere are iterators on the same Partial, and +//! pos must be not later than stopHere. +//! \pre pos cannot be end of the Partial, it must be the postion +//! of a valid Breakpoint. +//! \param stopHere the position of the earliest Breakpoint whose phase might be +//! recomputed. +//! \param pos the position of a (later) Breakpoint whose phase is to be matched. +//! The phase at pos is not modified. +// +void fixPhaseBackward( Partial::iterator stopHere, Partial::iterator pos ); + +// fixPhaseForward +// +//! Recompute phases of all Breakpoints on the closed range [pos, stopHere] +//! so that the synthesized phases of those Breakpoints matches +//! the stored phase, as long as the synthesized phase at pos +//! matches the stored (not recomputed) phase. The phase at pos +//! is modified only if pos is the position of a null Breakpoint +//! and the Breakpoint that follows is non-null. +//! +//! Phase fixing is only applied to non-null (nonzero-amplitude) Breakpoints, +//! because null Breakpoints are interpreted as phase reset points in +//! Loris. If a null is encountered, its phase is corrected from its non-Null +//! successor, if it has one, otherwise it is unmodified. +//! +//! \pre pos and stopHere are iterators on the same Partial, and +//! pos must be not later than stopHere. +//! \pre stopHere cannot be end of the Partial, it must be the postion +//! of a valid Breakpoint. +//! \param pos the position of the first Breakpoint whose phase might be +//! recomputed. +//! \param stopHere the position of the last Breakpoint whose phase might +//! be modified. +// +void fixPhaseForward( Partial::iterator pos, Partial::iterator stopHere ); + +// --------------------------------------------------------------------------- +// fixPhaseBetween +// +//! Fix the phase travel between two Breakpoints by adjusting the +//! frequency and phase of Breakpoints between those two. +//! +//! This algorithm assumes that there is nothing interesting about the +//! phases of the intervening Breakpoints, and modifies their frequencies +//! as little as possible to achieve the correct amount of phase travel +//! such that the frequencies and phases at the specified times +//! match the stored values. The phases of all the Breakpoints between +//! the specified times are recomputed. +//! +//! Null Breakpoints are treated the same as non-null Breakpoints. +//! +//! \pre b and e are iterators on the same Partials, and +//! e must not preceed b in that Partial. +//! \pre There must be at least one Breakpoint in the +//! Partial between b and e. +//! \post The phases and frequencies of the Breakpoints in the +//! range have been recomputed such that an oscillator +//! initialized to the parameters of the first Breakpoint +//! will arrive at the parameters of the last Breakpoint, +//! and all the intervening Breakpoints will be matched. +//! \param b The phases and frequencies of Breakpoints later than +//! this one may be modified. +//! \param e The phases and frequencies of Breakpoints earlier than +//! this one may be modified. +// +void fixPhaseBetween( Partial::iterator b, Partial::iterator e ); + +// fixFrequency +// +//! Adjust frequencies of the Breakpoints in the +//! specified Partial such that the rendered Partial +//! achieves (or matches as nearly as possible, within +//! the constraint of the maximum allowable frequency +//! alteration) the analyzed phases. +//! +//! \param partial The Partial whose frequencies, +//! and possibly phases (if the frequencies +//! cannot be sufficiently altered to match +//! the phases), will be recomputed. +//! \param maxFixPct The maximum allowable frequency +//! alteration, default is 0.2%. +// +void fixFrequency( Partial & partial, double maxFixPct = 0.2 ); + +// fixFrequency +// +//! Adjust frequencies of the Breakpoints in the +//! specified Partials such that the rendered Partial +//! achieves (or matches as nearly as possible, within +//! the constraint of the maximum allowable frequency +//! alteration) the analyzed phases. +//! +//! \param b The beginning of a range of Partials whose +//! frequencies should be fixed. +//! \param e The end of a range of Partials whose frequencies +//! should be fixed. +//! \param maxFixPct The maximum allowable frequency +//! alteration, default is 0.2%. +// +template < class Iter > +void fixFrequency( Iter b, Iter e, double maxFixPct = 0.2 ) +{ + while ( b != e ) + { + fixFrequency( *b, maxFixPct ); + ++b; + } +} + +// --------------------- useful phase maintenance utilities --------------------- + +// matchPhaseFwd +// +//! Compute the target frequency that will affect the +//! predicted (by the Breakpoint phases) amount of +//! sinusoidal phase travel between two breakpoints, +//! and assign that frequency to the target Breakpoint. +//! After computing the new frequency, update the phase of +//! the later Breakpoint. +//! +//! The most common kinds of errors are local (or burst) errors in +//! frequency and phase. These errors are best corrected by correcting +//! less than half the detected error at any time. Correcting more +//! than that will produce frequency oscillations for the remainder of +//! the Partial, in the case of a single bad frequency (as is common +//! at the onset of a tone). Any damping factor less then one will +//! converge eventually, .5 or less will converge without oscillating. +//! Use the damping argument to control the damping of the correction. +//! Specify 1 for no damping. +//! +//! +//! \pre The two Breakpoints are assumed to be consecutive in +//! a Partial. +//! \param bp0 The earlier Breakpoint. +//! \param bp1 The later Breakpoint. +//! \param dt The time (in seconds) between bp0 and bp1. +//! \param damping The fraction of the amount of phase error that will +//! be corrected (.5 or less will prevent frequency oscilation +//! due to burst errors in phase). +//! \param maxFixPct The maximum amount of frequency adjustment +//! that can be made to the frequency of bp1, expressed +//! as a precentage of the unmodified frequency of bp1. +//! If the necessary amount of frequency adjustment exceeds +//! this amount, then the phase will not be matched, +//! but will be updated as well to be consistent with +//! the frequencies. (default is 0.2%) +// +void matchPhaseFwd( Breakpoint & bp0, Breakpoint & bp1, + double dt, double damping, double maxFixPct = 0.2 ); + +// phaseTravel +// +//! Compute the sinusoidal phase travel between two Breakpoints. +//! Return the total unwrapped phase travel. +//! +//! \pre The two Breakpoints are assumed to be consecutive in +//! a Partial. +//! \param bp0 The earlier Breakpoint. +//! \param bp1 The later Breakpoint. +//! \param dt The time (in seconds) between bp0 and bp1. +//! \return The total unwrapped phase travel in radians. +// +double phaseTravel( const Breakpoint & bp0, const Breakpoint & bp1, double dt ); + +// wrapPi +// +//! Wrap an unwrapped phase value to the range (-pi,pi]. +//! +//! \param x The unwrapped phase in radians. +//! \return The phase (in radians) wrapped to the range (-Pi,Pi]. +// +double wrapPi( double x ); + + +} // end of namespace Loris + +#endif // ndef PHASEFIX_H + |