Treat peakfiles as a multilevel cache.

Always generated all levels when building peaks, instead of
trying to be clever about it.
This commit is contained in:
Jonathan Moore Liles 2008-05-10 12:02:21 -05:00
parent bff8d98078
commit 45a660d98a
8 changed files with 355 additions and 173 deletions

View File

@ -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 <std::string, Audio_File*> 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;
}

View File

@ -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 );
};

View File

@ -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 );

View File

@ -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() ) );
}

View File

@ -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 <stdio.h>
#include <string.h>
#include <sndfile.h>
#include "Audio_File.H"
#include "assert.h"
@ -40,12 +74,23 @@
#include <FL/Fl.H> // 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;
int
Peaks::read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, int chunksize ) const
static nframes_t
nearest_cached_chunksize ( nframes_t chunksize )
{
FILE *fp;
nframes_t r = nearest_power_of_two( chunksize );
int best_fit = nearest_power_of_two( chunksize );
for ( int i = Peaks::cache_levels; i--; r >>= Peaks::cache_step )
if ( chunksize >= r )
return r;
int pfchunksize;
// 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;
if ( ! fp )
{
printf( "failed to open peak file!\n" );
return 0;
}
int channels = _clip->channels();
const unsigned int ratio = chunksize / pfchunksize;
/* locate to start position */
if ( fseek( fp, (s * channels / pfchunksize) * sizeof( Peak ), SEEK_CUR ) )
/* failed to seek... peaks not ready? */
return 0;
if ( ratio == 1 )
{
int len = fread( peaks, sizeof( Peak ) * channels, npeaks, fp );
fclose( fp );
return len;
}
Peak *pbuf = new Peak[ ratio * channels ];
size_t len = 0;
int i;
for ( i = 0; i < npeaks; ++i )
{
/* 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;
fclose( fp );
return i;
return 0;
}
class Peakfile
{
FILE *_fp;
nframes_t _chunksize;
int _channels;
const char *_name;
public:
Peakfile ( )
{
_fp = NULL;
_chunksize = 0;
_channels = 0;
_name =NULL;
}
~Peakfile ( )
{
if ( _fp )
close();
}
/** 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.*/
return frame * _channels / _chunksize;
}
/** return the number of peaks in already open peakfile /fp/ */
nframes_t
npeaks ( void ) const
{
struct stat st;
fstat( fileno( _fp ), &st );
return st.st_size / sizeof( Peak );
}
/** 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();
}
/** 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;
for ( _chunksize = nearest_cached_chunksize( chunksize );
_chunksize >= Peaks::cache_minimum; _chunksize >>= Peaks::cache_step )
if ( ( _fp = fopen( peakname( name, _chunksize ), "r" ) ) )
break;
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 )
{
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;
}
Peak *pbuf = new Peak[ ratio * _channels ];
size_t len = 0;
int i;
for ( i = 0; i < npeaks; ++i )
{
/* 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;
}
};
int
Peaks::read_source_peaks ( Peak *peaks, int npeaks, int chunksize ) const
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;
}
return _peakfile.read_peaks( peaks, s, npeaks, chunksize );
// _peakfile.close();
}
int
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;
/* /\* if ( ! current( chunksize ) ) *\/ */
/* /\* /\\* Build peaks asyncronously *\\/ *\/ */
/* /\* if ( ! fork() ) *\/ */
/* /\* exit( make_peaks( chunksize ) ); *\/ */
{
struct stat st;
/* return true; */
fstat( fd, &st );
_len = st.st_size;
}
::close( fd );
_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 ];
if ( ! fp )
Peak buf[ _clip->channels() ];
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;

View File

@ -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;
// Peaks ( );
Peak_Writer *_peak_writer;
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 );
};

View File

@ -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 );

View File

@ -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 );