Peak all peak cache levels together in a single peakfile.

This commit is contained in:
Jonathan Moore Liles 2008-05-10 19:10:49 -05:00
parent e2e2241715
commit e27c0336fe
2 changed files with 263 additions and 129 deletions

View File

@ -20,36 +20,6 @@
/* /*
peakfile reading/writing. 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
*/ */
@ -75,6 +45,10 @@
#include <FL/Fl.H> // for Fl::check(); #include <FL/Fl.H> // for Fl::check();
#include "debug.h" #include "debug.h"
#include <errno.h>
#include <list>
Peaks::peakbuffer Peaks::_peakbuf; Peaks::peakbuffer Peaks::_peakbuf;
@ -111,11 +85,11 @@ Peaks::~Peaks ( )
static static
const char * const char *
peakname ( const char *filename, nframes_t chunksize ) peakname ( const char *filename )
{ {
static char file[512]; static char file[512];
snprintf( file, 512, "%s-[%d].peak", filename, chunksize ); snprintf( file, 512, "%s.peak", filename );
return (const char*)&file; return (const char*)&file;
} }
@ -152,21 +126,47 @@ nearest_cached_chunksize ( nframes_t chunksize )
return 0; return 0;
} }
struct peakfile_block_header
{
unsigned long chunksize;
unsigned long skip;
};
class Peakfile class Peakfile
{ {
FILE *_fp; FILE *_fp;
nframes_t _chunksize; nframes_t _chunksize;
int _channels; int _channels; /* number of channels this peakfile represents */
nframes_t _length; /* length, in frames, of the clip this peakfile represents */
const char *_name; const char *_name;
size_t _offset;
struct block_descriptor
{
nframes_t chunksize;
size_t pos;
block_descriptor ( nframes_t chunksize, size_t pos ) : chunksize( chunksize ), pos( pos )
{
}
bool operator< ( const block_descriptor &rhs )
{
return chunksize < rhs.chunksize;
}
};
public: public:
Peakfile ( ) Peakfile ( )
{ {
_fp = NULL; _fp = NULL;
_offset = 0;
_chunksize = 0; _chunksize = 0;
_channels = 0; _channels = 0;
_length = 0;
_name =NULL; _name =NULL;
} }
@ -176,14 +176,65 @@ public:
close(); close();
} }
/** find the best block for /chunksize/ */
void
find_block ( nframes_t chunksize )
{
rewind( _fp );
clearerr( _fp );
std::list <block_descriptor> blocks;
/* scan all blocks */
do
{
peakfile_block_header bh;
fread( &bh, sizeof( bh ), 1, _fp );
ASSERT( bh.chunksize, "Invalid peak file structure!" );
blocks.push_back( block_descriptor( bh.chunksize, ftell( _fp ) ) );
if ( ! bh.skip )
break;
if ( fseek( _fp, bh.skip, SEEK_CUR ) )
{
WARNING( "seek failed: %s (%lu)", strerror( errno ), bh.skip );
break;
}
}
while ( ! feof( _fp ) );
if ( ! blocks.size() )
FATAL( "invalid peak file?" );
// DMESSAGE( "peakfile has %d blocks.", blocks.size() );
blocks.sort();
/* fall back on the smallest chunksize */
fseek( _fp, blocks.front().pos, SEEK_SET );
_chunksize = blocks.front().chunksize;
/* search for the best-fit chunksize */
for ( std::list <block_descriptor>::const_reverse_iterator i = blocks.rbegin();
i != blocks.rend(); ++i )
if ( chunksize >= i->chunksize )
{
_chunksize = i->chunksize;
fseek( _fp, i->pos, SEEK_SET );
break;
}
_offset = ftell( _fp );
}
/** convert frame number of peak number */ /** convert frame number of peak number */
nframes_t frame_to_peak ( nframes_t frame ) 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 frame * _channels / _chunksize;
} }
@ -209,23 +260,30 @@ public:
bool bool
open ( const char *name, nframes_t chunksize, int channels ) open ( const char *name, nframes_t chunksize, int channels )
{ {
_chunksize = 0;
_channels = channels; _channels = channels;
_name = name; _name = name;
for ( _chunksize = nearest_cached_chunksize( chunksize ); if ( ! ( _fp = fopen( peakname( name ), "r" ) ) )
_chunksize >= Peaks::cache_minimum; _chunksize >>= Peaks::cache_step ) return false;
if ( ( _fp = fopen( peakname( name, _chunksize ), "r" ) ) )
break;
return _fp != NULL; find_block( chunksize );
assert( _chunksize );
return true;
} }
bool bool
open ( FILE *fp, int channels, nframes_t chunksize ) open ( FILE *fp, int channels, nframes_t chunksize )
{ {
_fp = fp; _fp = fp;
_chunksize = chunksize; _chunksize = 0;
_channels = channels; _channels = channels;
find_block( chunksize );
assert( _chunksize );
} }
void void
@ -255,7 +313,7 @@ public:
const unsigned int ratio = chunksize / _chunksize; const unsigned int ratio = chunksize / _chunksize;
/* locate to start position */ /* locate to start position */
if ( fseek( _fp, frame_to_peak( s ) * sizeof( Peak ), SEEK_SET ) ) if ( fseek( _fp, _offset + frame_to_peak( s ) * sizeof( Peak ), SEEK_SET ) )
/* failed to seek... peaks not ready? */ /* failed to seek... peaks not ready? */
return 0; return 0;
@ -430,19 +488,6 @@ Peaks::read_peaks ( nframes_t s, nframes_t e, int npeaks, nframes_t chunksize )
bool bool
Peaks::open ( void ) Peaks::open ( void )
{ {
/* const char *filename = _clip->name(); */
/* /\* FIXME: determine this based on zoom level *\/ */
/* const nframes_t chunksize = 256; */
/* /\* if ( ! current( chunksize ) ) *\/ */
/* /\* /\\* Build peaks asyncronously *\\/ *\/ */
/* /\* if ( ! fork() ) *\/ */
/* /\* exit( make_peaks( chunksize ) ); *\/ */
/* return true; */
} }
@ -455,7 +500,7 @@ Peaks::current ( nframes_t chunksize ) const
if ( ( sfd = ::open( _clip->name(), O_RDONLY ) ) < 0 ) if ( ( sfd = ::open( _clip->name(), O_RDONLY ) ) < 0 )
return true; return true;
if ( ( pfd = ::open( peakname( _clip->name(), chunksize ), O_RDONLY ) ) < 0 ) if ( ( pfd = ::open( peakname( _clip->name() ), O_RDONLY ) ) < 0 )
return false; return false;
struct stat sst, pst; struct stat sst, pst;
@ -470,52 +515,113 @@ Peaks::current ( nframes_t chunksize ) const
} }
/** build peaks file at /chunksize/. If higher-resolution peaks
already exist, downsample those rather than building from /* The Peak_Builder is for generating peaks from imported or updated sources, or when the
scratch */ peakfile is simply missing */
bool
Peaks::make_peaks ( void ) const class Peak_Builder
{ {
FILE *fp;
size_t last_block_pos;
const Peaks *_peaks;
void
write_block_header ( nframes_t chunksize )
{
if ( last_block_pos )
{
/* update previous block */
size_t pos = ftell( fp );
fseek( fp, last_block_pos - sizeof( peakfile_block_header ), SEEK_SET );
peakfile_block_header bh;
fread( &bh, sizeof( bh ), 1, fp );
fseek( fp, last_block_pos - sizeof( peakfile_block_header ), SEEK_SET );
// fseek( fp, 0 - sizeof( bh ), SEEK_CUR );
// DMESSAGE( "old block header: chunksize=%lu, skip=%lu", bh.chunksize, bh.skip );
bh.skip = pos - last_block_pos;
// DMESSAGE( "new block header: chunksize=%lu, skip=%lu", bh.chunksize, bh.skip );
fwrite( &bh, sizeof( bh ), 1, fp );
fseek( fp, pos, SEEK_SET );
}
peakfile_block_header bh;
bh.chunksize = chunksize;
bh.skip = 0;
fwrite( &bh, sizeof( bh ), 1, fp );
last_block_pos = ftell( fp );
}
public:
bool
run ( void )
{
Audio_File *_clip = _peaks->_clip;
const char *filename = _clip->name(); const char *filename = _clip->name();
DMESSAGE( "building peaks for \"%s\"", filename );
FILE *rfp;
if ( ! ( fp = fopen( peakname( filename ), "w+" ) ) )
return false;
_clip->seek( 0 ); _clip->seek( 0 );
FILE *fp[ cache_levels ];
Peak buf[ _clip->channels() ]; Peak buf[ _clip->channels() ];
if ( ! ( fp[ 0 ] = fopen( peakname( filename, cache_minimum ), "w+" ) ) )
return false;
DMESSAGE( "building level 1 peak cache" ); DMESSAGE( "building level 1 peak cache" );
write_block_header( Peaks::cache_minimum );
/* build first level from source */ /* build first level from source */
size_t len; size_t len;
do { do {
len = read_source_peaks( buf, 1, cache_minimum ); len = _peaks->read_source_peaks( buf, 1, Peaks::cache_minimum );
fwrite( buf, sizeof( buf ), len, fp[ 0 ] ); fwrite( buf, sizeof( buf ), len, fp );
} }
while ( len ); while ( len );
/* reopen for reading */
fclose( fp );
rfp = fopen( peakname( filename ), "r" );
// rfp = freopen( peakname( filename ), "r", fp );
/* open the file again for appending */
fp = fopen( peakname( filename ), "r+" );
fseek( fp, 0, SEEK_END );
/* now build the remaining peak levels, each based on the /* now build the remaining peak levels, each based on the
* preceding level */ * preceding level */
nframes_t cs = cache_minimum << cache_step; nframes_t cs = Peaks::cache_minimum << Peaks::cache_step;
for ( int i = 1; i < cache_levels; ++i, cs <<= cache_step ) for ( int i = 1; i < Peaks::cache_levels; ++i, cs <<= Peaks::cache_step )
{ {
DMESSAGE( "building level %d peak cache", i + 1 ); DMESSAGE( "building level %d peak cache", i + 1 );
Peakfile pf; 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 */ /* open the peakfile for the previous cache level */
pf.open( fp[ i - 1 ], _clip->channels(), cs >> cache_step ); pf.open( rfp, _clip->channels(), cs >> Peaks::cache_step );
write_block_header( cs );
fflush( fp );
size_t len; size_t len;
nframes_t s = 0; nframes_t s = 0;
@ -523,21 +629,38 @@ Peaks::make_peaks ( void ) const
len = pf.read_peaks( buf, s, 1, cs ); len = pf.read_peaks( buf, s, 1, cs );
s += cs; s += cs;
fwrite( buf, sizeof( buf ), len, fp[ i ] ); fwrite( buf, sizeof( buf ), len, fp );
} }
while ( len ); while ( len );
pf.leave_open(); pf.leave_open();
} }
/* all done */ fclose( rfp );
for ( int i = cache_levels; i--; ) fclose( fp );
fclose( fp[ i ] );
DMESSAGE( "done building peaks" );
return true; return true;
} }
Peak_Builder ( const Peaks *peaks ) : _peaks( peaks )
{
fp = NULL;
last_block_pos = 0;
}
};
bool
Peaks::make_peaks ( void ) const
{
Peak_Builder pb( this );
return pb.run();
}
/** return normalization factor for a single peak, assuming the peak /** return normalization factor for a single peak, assuming the peak
* represents a downsampling of the entire range to be normalized. */ * represents a downsampling of the entire range to be normalized. */
float float
@ -575,7 +698,6 @@ Peaks::write ( sample_t *buf, nframes_t nframes )
Peak_Writer::Peak_Writer ( const char *filename, nframes_t chunksize, int channels ) Peak_Writer::Peak_Writer ( const char *filename, nframes_t chunksize, int channels )
{ {
_channels = channels; _channels = channels;
_chunksize = chunksize; _chunksize = chunksize;
_index = 0; _index = 0;
@ -583,8 +705,17 @@ Peak_Writer::Peak_Writer ( const char *filename, nframes_t chunksize, int channe
_peak = new Peak[ channels ]; _peak = new Peak[ channels ];
memset( _peak, 0, sizeof( Peak ) * channels ); memset( _peak, 0, sizeof( Peak ) * channels );
if ( ! ( _fp = fopen( peakname( filename, chunksize ), "w" ) ) ) if ( ! ( _fp = fopen( peakname( filename ), "w" ) ) )
/* error! */; /* error! */;
peakfile_block_header bh;
bh.chunksize = chunksize;
bh.skip = 0;
fwrite( &bh, sizeof( bh ), 1, _fp );
fflush( _fp );
} }
Peak_Writer::~Peak_Writer ( ) Peak_Writer::~Peak_Writer ( )

View File

@ -68,13 +68,16 @@ class Peaks
int read_source_peaks ( Peak *peaks, 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; int read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const;
Peak_Writer *_peak_writer; /* exists when streaming peaks to disk */ Peak_Writer * volatile _peak_writer; /* exists when streaming peaks to disk */
size_t last_block_pos; /* FIXME: only needed in make_peaks! */
/* not permitted */ /* not permitted */
Peaks ( const Peaks &rhs ); Peaks ( const Peaks &rhs );
const Peaks &operator= ( const Peaks &rhs ); const Peaks &operator= ( const Peaks &rhs );
friend class Peak_Builder;
public: public:
static bool mipmapped_peakfiles; static bool mipmapped_peakfiles;