diff options
Diffstat (limited to 'src/loris/ImportLemur.C')
-rw-r--r-- | src/loris/ImportLemur.C | 519 |
1 files changed, 519 insertions, 0 deletions
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 |