diff --git a/Timeline/Audio_File.C b/Timeline/Audio_File.C index 7eee64c..7c5a1fe 100644 --- a/Timeline/Audio_File.C +++ b/Timeline/Audio_File.C @@ -17,9 +17,13 @@ /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*******************************************************************************/ +/* FIXME: need locking for when disk thread and peak reader are + * interested in the same source? */ + #include "Audio_File.H" #include "Audio_File_SF.H" + std::map Audio_File::_open_files; Audio_File::~Audio_File ( ) @@ -72,22 +76,23 @@ done: bool Audio_File::read_peaks( float fpp, nframes_t start, nframes_t end, int *peaks, Peak **pbuf, int *channels ) { - Peaks pk; +// Peaks pk; *peaks = 0; *channels = 0; *pbuf = NULL; - pk.clip( this ); +// pk.clip( this ); - if ( ! pk.open() ) - return false; +/* /\* only open peaks (and potentially generate) when first requested *\/ */ +/* if ( ! _peaks.open() ) */ +/* return false; */ - *peaks = pk.fill_buffer( fpp, start, end ); + *peaks = _peaks.fill_buffer( fpp, start, end ); *channels = this->channels(); - *pbuf = pk.peakbuf(); + *pbuf = _peaks.peakbuf(); return true; } diff --git a/Timeline/Audio_File.H b/Timeline/Audio_File.H index 448f702..305609b 100644 --- a/Timeline/Audio_File.H +++ b/Timeline/Audio_File.H @@ -52,9 +52,9 @@ protected: nframes_t _samplerate; /* sample rate */ int _channels; - Peaks *_peaks; + Peaks _peaks; - Peak_Writer *_peak_writer; +// Peak_Writer *_peak_writer; static const format_desc * find_format ( const format_desc *fd, const char *name ) @@ -69,7 +69,7 @@ protected: public: - Audio_File ( ) + Audio_File ( ) : _peaks( this ) { _filename = NULL; _length = _channels = 0; @@ -81,7 +81,7 @@ public: static Audio_File *from_file ( const char *filename ); - Peaks const * peaks ( ) { return _peaks; } + Peaks const * peaks ( ) { return &_peaks; } const char *name ( void ) const { return _filename; } nframes_t length ( void ) const { return _length; } int channels ( void ) const { return _channels; } @@ -97,5 +97,4 @@ public: bool read_peaks( float fpp, nframes_t start, nframes_t end, int *peaks, Peak **pbuf, int *channels ); - }; diff --git a/Timeline/Audio_File_SF.C b/Timeline/Audio_File_SF.C index be3aec5..3e09c97 100644 --- a/Timeline/Audio_File_SF.C +++ b/Timeline/Audio_File_SF.C @@ -71,7 +71,7 @@ Audio_File_SF::from_file ( const char *filename ) c = new Audio_File_SF; - c->_peak_writer = NULL; +// c->_peak_writer = NULL; c->_current_read = 0; c->_filename = strdup( filename ); c->_length = si.frames; @@ -127,7 +127,7 @@ Audio_File_SF::create ( const char *filename, nframes_t samplerate, int channels c->_in = out; /* FIXME: 256 ? */ - c->_peak_writer = new Peak_Writer( name, 256, channels ); +// c->_peak_writer = new Peak_Writer( name, 256, channels ); return c; } @@ -159,8 +159,8 @@ Audio_File_SF::close ( void ) if ( _in ) sf_close( _in ); - if ( _peak_writer ) - delete _peak_writer; +/* if ( _peak_writer ) */ +/* delete _peak_writer; */ _in = NULL; } @@ -228,7 +228,7 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end nframes_t Audio_File_SF::write ( sample_t *buf, nframes_t nframes ) { - _peak_writer->write( buf, nframes ); +// _peak_writer->write( buf, nframes ); nframes_t l = sf_writef_float( _in, buf, nframes ); diff --git a/Timeline/Audio_Region.C b/Timeline/Audio_Region.C index 582ac2f..ee1b5a1 100644 --- a/Timeline/Audio_Region.C +++ b/Timeline/Audio_Region.C @@ -604,7 +604,8 @@ Audio_Region::draw ( void ) { /* couldn't read peaks--perhaps they're being generated. Try again later. */ - Fl::add_timeout( 0.1f, damager, new Rectangle( X, y(), W, h() ) ); + /* commented out for testing. */ +// Fl::add_timeout( 0.1f, damager, new Rectangle( X, y(), W, h() ) ); } diff --git a/Timeline/Peaks.C b/Timeline/Peaks.C index 12947d1..49ae711 100644 --- a/Timeline/Peaks.C +++ b/Timeline/Peaks.C @@ -17,6 +17,42 @@ /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*******************************************************************************/ + +/* + peakfile reading/writing. + + Here's how it works: + + Peakfiles are of the form 'name-[chunksize].peak', and contain + floating point min/max pairs for an entire soundfile at a certain + chunksize. + + It gets a little complicated because peakfiles are generated + asynchronously and streamed to disk when capturing. + + When the GUI requests the peaks for a range of samples at a certain + chunksize, a search is performed for a peakfile--starting with the + requested chunksize, and then moving to smaller chunksizes--that + exists and contains the peaks for the requested range (which it may + not--if the peaks are in the process of being written). The peaks, + if available, are then read from disk, downsampled to the exact + chunksize requested, and displayed. + + On the generation end, peaks are either streamed to disk while + recording (and therefore more or less in real time), or a process + is forked to generate the entire peak file (either directly from + the source, or from a higher-resolution peak file that exists and + is complete). + + The end result is that peaks are: + + * read synchronously + * generated asynchronously + * cached at many resolutions + + */ + + #include "Peaks.H" // #include "Timeline.H" @@ -30,8 +66,6 @@ #include #include -#include - #include "Audio_File.H" #include "assert.h" @@ -40,12 +74,23 @@ #include // for Fl::check(); +#include "debug.h" + Peaks::peakbuffer Peaks::_peakbuf; +/* chunksizes at which to generate peakfiles (on demand). This should + pretty much cover the usable range. Better performance can be + achieved at high zoom-levels and for compressed sources with a + minimum of 64, but those files are up into the megabytes. */ +const int Peaks::cache_minimum = 256; /* minimum chunksize to build peakfiles for */ +const int Peaks::cache_levels = 8; /* number of sampling levels in peak cache */ +// const int Peaks::cache_step = 2; /* powers of two between each level. 4 == 256, 2048, 16384, ... */ + +const int Peaks::cache_step = 1; /* powers of two between each level. 4 == 256, 2048, 16384, ... */ static const char * -peakname ( const char *filename, int chunksize ) +peakname ( const char *filename, nframes_t chunksize ) { static char file[512]; @@ -74,90 +119,213 @@ nearest_power_of_two ( int v ) return 1 << p; } -const int MAX_CHUNKSIZE = 4096; -const int MIN_CHUNKSIZE = 256; +static nframes_t +nearest_cached_chunksize ( nframes_t chunksize ) +{ + nframes_t r = nearest_power_of_two( chunksize ); + + for ( int i = Peaks::cache_levels; i--; r >>= Peaks::cache_step ) + if ( chunksize >= r ) + return r; -int -Peaks::read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) const + return 0; +} + +class Peakfile { - FILE *fp; - int best_fit = nearest_power_of_two( chunksize ); + FILE *_fp; + nframes_t _chunksize; + int _channels; + const char *_name; - int pfchunksize; +public: -// for ( pfchunksize = best_fit; pfchunksize < MAX_CHUNKSIZE; pfchunksize <<= 1 ) - for ( pfchunksize = best_fit; pfchunksize >= MIN_CHUNKSIZE; pfchunksize >>= 1 ) - if ( ( fp = fopen( peakname( _clip->name(), pfchunksize ), "r" ) ) ) - break; + Peakfile ( ) + { + _fp = NULL; + _chunksize = 0; + _channels = 0; + _name =NULL; + } - if ( ! fp ) - { - printf( "failed to open peak file!\n" ); + ~Peakfile ( ) + { + if ( _fp ) + close(); + } - return 0; - } + /** convert frame number of peak number */ + nframes_t frame_to_peak ( nframes_t frame ) + { + /* how many powers of two is chunksize > cache_minimum? + skip clip size of peaks at increasing powers of two until + we reach the closest one less than chunksize. Then + address normally.*/ - int channels = _clip->channels(); - const unsigned int ratio = chunksize / pfchunksize; + return frame * _channels / _chunksize; + } - /* locate to start position */ - if ( fseek( fp, (s * channels / pfchunksize) * sizeof( Peak ), SEEK_CUR ) ) - /* failed to seek... peaks not ready? */ - return 0; + /** return the number of peaks in already open peakfile /fp/ */ + nframes_t + npeaks ( void ) const + { + struct stat st; - if ( ratio == 1 ) - { - int len = fread( peaks, sizeof( Peak ) * channels, npeaks, fp ); - fclose( fp ); - return len; - } + fstat( fileno( _fp ), &st ); - Peak *pbuf = new Peak[ ratio * channels ]; + return st.st_size / sizeof( Peak ); + } - size_t len = 0; + /** returns true if the peakfile contains /npeaks/ peaks starting at sample /s/ */ + bool + contains ( nframes_t start, nframes_t npeaks ) + { + return frame_to_peak( start ) + npeaks <= this->npeaks(); + } - int i; - for ( i = 0; i < npeaks; ++i ) - { - /* read in a buffer */ - len = fread( pbuf, sizeof( Peak ) * channels, ratio, fp ); + /** given soundfile name /name/, try to open the best peakfile for /chunksize/ */ + bool + open ( const char *name, nframes_t chunksize, int channels ) + { + _channels = channels; + _name = name; - Peak *pk = peaks + (i * channels); + for ( _chunksize = nearest_cached_chunksize( chunksize ); + _chunksize >= Peaks::cache_minimum; _chunksize >>= Peaks::cache_step ) + if ( ( _fp = fopen( peakname( name, _chunksize ), "r" ) ) ) + break; - /* get the peak for each channel */ - for ( int j = 0; j < channels; ++j ) + return _fp != NULL; + } + + bool + open ( FILE *fp, int channels, nframes_t chunksize ) + { + _fp = fp; + _chunksize = chunksize; + _channels = channels; + } + + void + leave_open ( void ) + { + _fp = NULL; + } + + void + close ( void ) + { + fclose( _fp ); + _fp = NULL; + } + + /** read /npeaks/ peaks at /chunksize/ starting at sample /s/ + * assuming the peakfile contains data for /channels/ + * channels. Place the result in buffer /peaks/, which must be + * large enough to fit the entire request. Returns the number of + * peaks actually read, which may be fewer than were requested. */ + int + read_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) { - Peak *p = &pk[ j ]; + if ( ! _fp ) + return 0; + + const unsigned int ratio = chunksize / _chunksize; + + /* locate to start position */ + if ( fseek( _fp, frame_to_peak( s ) * sizeof( Peak ), SEEK_SET ) ) + /* failed to seek... peaks not ready? */ + return 0; + + if ( ratio == 1 ) + { + int len = fread( peaks, sizeof( Peak ) * _channels, npeaks, _fp ); + // close; + return len; + } - p->min = 0; - p->max = 0; + Peak *pbuf = new Peak[ ratio * _channels ]; - const Peak *pb = pbuf + j; + size_t len = 0; - for ( int k = len; k--; pb += channels ) + int i; + for ( i = 0; i < npeaks; ++i ) { - if ( pb->max > p->max ) - p->max = pb->max; - if ( pb->min < p->min ) - p->min = pb->min; + /* read in a buffer */ + len = fread( pbuf, sizeof( Peak ) * _channels, ratio, _fp ); + + Peak *pk = peaks + (i * _channels); + + /* get the peak for each channel */ + for ( int j = 0; j < _channels; ++j ) + { + Peak *p = &pk[ j ]; + + p->min = 0; + p->max = 0; + + const Peak *pb = pbuf + j; + + for ( int k = len; k--; pb += _channels ) + { + if ( pb->max > p->max ) + p->max = pb->max; + if ( pb->min < p->min ) + p->min = pb->min; + } + + } + + if ( len < ratio ) + break; } + delete[] pbuf; + +// close(); + + return i; } - if ( len < ratio ) - break; +}; + + +int +Peaks::read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const +{ + + nframes_t ncc = nearest_cached_chunksize( chunksize ); + + if ( ! current( cache_minimum ) ) + /* Build peaks asyncronously */ + if ( ! fork() ) + exit( make_peaks( ) ); + else + return 0; + + Peakfile _peakfile; + + if ( ! _peakfile.open( _clip->name(), chunksize, _clip->channels() ) ) + return 0; + else if ( ! _peakfile.contains( s, npeaks ) ) + { + /* the best peakfile for this chunksize doesn't have the + * peaks we need. Perhaps it's still being constructed, + * try the next best, then give up. */ + if ( ! _peakfile.open( _clip->name(), chunksize >> 1, _clip->channels() ) ) + return 0; } - delete[] pbuf; + return _peakfile.read_peaks( peaks, s, npeaks, chunksize ); - fclose( fp ); + // _peakfile.close(); - return i; } + int -Peaks::read_source_peaks ( Peak *peaks, int npeaks, int chunksize ) const +Peaks::read_source_peaks ( Peak *peaks, int npeaks, nframes_t chunksize ) const { int channels = _clip->channels(); @@ -201,7 +369,7 @@ Peaks::read_source_peaks ( Peak *peaks, int npeaks, int chunksize ) const } int -Peaks::read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) const +Peaks::read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const { // _clip->open(); _clip->seek( s ); @@ -214,9 +382,9 @@ Peaks::read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) } int -Peaks::read_peaks ( nframes_t s, nframes_t e, int npeaks, int chunksize ) const +Peaks::read_peaks ( nframes_t s, nframes_t e, int npeaks, nframes_t chunksize ) const { - printf( "reading peaks %d @ %d\n", npeaks, chunksize ); +// printf( "reading peaks %d @ %d\n", npeaks, chunksize ); if ( _peakbuf.size < (nframes_t)( npeaks * _clip->channels() ) ) { @@ -225,8 +393,6 @@ Peaks::read_peaks ( nframes_t s, nframes_t e, int npeaks, int chunksize ) const _peakbuf.buf = (peakdata*)realloc( _peakbuf.buf, sizeof( peakdata ) + (_peakbuf.size * sizeof( Peak )) ); } - assert( s >= 0 ); - _peakbuf.offset = s; _peakbuf.buf->chunksize = chunksize; @@ -239,48 +405,36 @@ Peaks::read_peaks ( nframes_t s, nframes_t e, int npeaks, int chunksize ) const return _peakbuf.len; } +/* FIXME: what purpose does this serve now? */ bool Peaks::open ( void ) { - const char *filename = _clip->name(); - int fd; +/* const char *filename = _clip->name(); */ - if ( ! current() ) - /* Build peaks asyncronously */ - if ( ! fork() ) - exit( make_peaks( 256 ) ); +/* /\* FIXME: determine this based on zoom level *\/ */ +/* const nframes_t chunksize = 256; */ - /* FIXME: 256 == bogus */ - if ( ( fd = ::open( peakname( filename, 256 ), O_RDONLY ) ) < 0 ) - return false; - - { - struct stat st; - - fstat( fd, &st ); +/* /\* if ( ! current( chunksize ) ) *\/ */ +/* /\* /\\* Build peaks asyncronously *\\/ *\/ */ +/* /\* if ( ! fork() ) *\/ */ +/* /\* exit( make_peaks( chunksize ) ); *\/ */ - _len = st.st_size; - } - - ::close( fd ); +/* return true; */ - _len = (_len - sizeof( int )) / sizeof( Peak ); - - return true; } + /** returns false if peak file for /filename/ is out of date */ bool -Peaks::current ( void ) const +Peaks::current ( nframes_t chunksize ) const { int sfd, pfd; if ( ( sfd = ::open( _clip->name(), O_RDONLY ) ) < 0 ) return true; - /* FIXME: 256 == bogus */ - if ( ( pfd = ::open( peakname( _clip->name(), 256 ), O_RDONLY ) ) < 0 ) + if ( ( pfd = ::open( peakname( _clip->name(), chunksize ), O_RDONLY ) ) < 0 ) return false; struct stat sst, pst; @@ -295,44 +449,77 @@ Peaks::current ( void ) const } -/* FIXME: we need to work out a way to run this in another thread and - possibly stream back the data to the GUI */ -/** build peaks file for /filename/ if necessary */ +/** build peaks file at /chunksize/. If higher-resolution peaks + already exist, downsample those rather than building from + scratch */ bool -Peaks::make_peaks ( int chunksize ) +Peaks::make_peaks ( void ) const { const char *filename = _clip->name(); - if ( current() ) - return true; - _clip->seek( 0 ); - FILE *fp = fopen( peakname( filename, chunksize ), "w" ); + FILE *fp[ cache_levels ]; + + Peak buf[ _clip->channels() ]; - if ( ! fp ) + if ( ! ( fp[ 0 ] = fopen( peakname( filename, cache_minimum ), "w+" ) ) ) return false; - Peak peaks[ _clip->channels() ]; + DMESSAGE( "building level 1 peak cache" ); + /* build first level from source */ size_t len; do { - len = read_source_peaks( peaks, 1, chunksize ); - fwrite( peaks, sizeof( peaks ), 1, fp ); + len = read_source_peaks( buf, 1, cache_minimum ); + + fwrite( buf, sizeof( buf ), len, fp[ 0 ] ); } while ( len ); - fclose( fp ); + /* now build the remaining peak levels, each based on the + * preceding level */ + + nframes_t cs = cache_minimum << cache_step; + for ( int i = 1; i < cache_levels; ++i, cs <<= cache_step ) + { + DMESSAGE( "building level %d peak cache", i + 1 ); + + Peakfile pf; + + if ( ! ( fp[ i ] = fopen( peakname( filename, cs ), "w+" ) ) ) + { + DWARNING( "could not open peakfile for writing" ); + return false; + } + + /* open the peakfile for the previous cache level */ + pf.open( fp[ i - 1 ], _clip->channels(), cs >> cache_step ); + + size_t len; + nframes_t s = 0; + do { + len = pf.read_peaks( buf, s, 1, cs ); + s += cs; + + fwrite( buf, sizeof( buf ), len, fp[ i ] ); + } + while ( len ); + + pf.leave_open(); + } + + /* all done */ + for ( int i = cache_levels; i--; ) + fclose( fp[ i ] ); return true; } -/** return normalization factor for range of samples from /start/ to - /end/ (uses known peak data if possible */ - +/** return normalization factor for a single peak, assuming the peak + * represents a downsampling of the entire range to be normalized. */ float -//Peaks::normalization_factor( float fpp, nframes_t start, nframes_t end ) const Peak::normalization_factor( void ) const { float s; @@ -345,9 +532,7 @@ Peak::normalization_factor( void ) const return s; } - - -Peak_Writer::Peak_Writer ( const char *filename, int chunksize, int channels ) +Peak_Writer::Peak_Writer ( const char *filename, nframes_t chunksize, int channels ) { _channels = channels; diff --git a/Timeline/Peaks.H b/Timeline/Peaks.H index 48a86cc..3288653 100644 --- a/Timeline/Peaks.H +++ b/Timeline/Peaks.H @@ -32,13 +32,14 @@ struct Peak { }; class Audio_File; +class Peak_Writer; class Peaks { struct peakdata { - int chunksize; /* should always be a power of 2 */ + nframes_t chunksize; /* should always be a power of 2 */ Peak data[]; }; @@ -61,31 +62,29 @@ class Peaks Audio_File *_clip; - size_t _len; - mutable float _fpp; - int read_peaks ( nframes_t s, nframes_t e, int npeaks, int chunksize ) const; - int read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) const; - int read_source_peaks ( Peak *peaks, int npeaks, int chunksize ) const; - int read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) const; + int read_peaks ( nframes_t s, nframes_t e, int npeaks, nframes_t chunksize ) const; + int read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const; + int read_source_peaks ( Peak *peaks, int npeaks, nframes_t chunksize ) const; + int read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const; -// const char *peakname ( const char *filename ) const; + Peak_Writer *_peak_writer; +public: - // Peaks ( ); -public: + static const int cache_minimum; + static const int cache_levels; + static const int cache_step; Peaks ( ) { - _len = 0; _clip = NULL; } Peaks ( Audio_File *c ) { - _len = 0; _clip = c; } @@ -99,8 +98,8 @@ public: bool open ( void ); // float normalization_factor( float fpp, nframes_t start, nframes_t end ) ; - bool current ( void ) const; - bool make_peaks ( int chunksize ); + bool current ( nframes_t chunksize ) const; + bool make_peaks ( void ) const; Peak & peak ( nframes_t start, nframes_t end ) const; @@ -112,10 +111,6 @@ public: class Peak_Writer { - - static const int VERSION_MAJOR = 0; - static const int VERSION_MINOR = 1; - FILE *_fp; Peak *_peak; int _chunksize; @@ -123,17 +118,15 @@ class Peak_Writer int _index; + /* not permitted */ Peak_Writer ( const Peak_Writer &rhs ); const Peak_Writer &operator= ( const Peak_Writer &rhs ); - public: - Peak_Writer ( const char *filename, int chunksize, int channels ); + Peak_Writer ( const char *filename, nframes_t chunksize, int channels ); ~Peak_Writer ( ); - void write_header ( void ); void write ( sample_t *buf, nframes_t nframes ); - }; diff --git a/Timeline/Sequence_Region.C b/Timeline/Sequence_Region.C index 0a9a09e..a489259 100644 --- a/Timeline/Sequence_Region.C +++ b/Timeline/Sequence_Region.C @@ -234,8 +234,6 @@ Sequence_Region::handle ( int m ) /* track jumping */ if ( Y > y() + h() || Y < y() ) { - printf( "wants to jump tracks\n" ); - Track *t = timeline->track_under( Y ); fl_cursor( (Fl_Cursor)1 ); diff --git a/Timeline/Waveform.C b/Timeline/Waveform.C index 89ba427..8610f40 100644 --- a/Timeline/Waveform.C +++ b/Timeline/Waveform.C @@ -78,7 +78,8 @@ Waveform::draw ( int X, int Y, int W, int H, fl_color( FL_RED ); else if ( Waveform::vary_color ) - fl_color( fl_color_average( FL_WHITE, color, diff / 2.0f ) ); +// fl_color( fl_color_average( FL_WHITE, color, diff / 2.0f ) ); + fl_color( fl_color_average( FL_WHITE, color, diff * 0.5f ) ); else fl_color( color );