non/Timeline/Peaks.C

825 lines
20 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*******************************************************************************/
/* Copyright (C) 2008 Jonathan Moore Liles */
/* */
/* 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; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
/*
peakfile reading/writing.
*/
#include "Peaks.H"
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Audio_File.H"
#include "assert.h"
#include "debug.h"
#include <errno.h>
#include "Transport.H" // for .recording
#include <list>
#include <algorithm>
using std::min;
using std::max;
/* whether to cache peaks at multiple resolutions on disk to
* drastically improve performance */
bool Peaks::mipmapped_peakfiles = true;
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 = 1; /* powers of two between each level. 4 == 256, 2048, 16384, ... */
Peaks::peakbuffer Peaks::_peakbuf;
static
const char *
peakname ( const char *filename )
{
static char file[512];
snprintf( file, 512, "%s.peak", filename );
return (const char*)&file;
}
/** update the modification time of file referred to by /fd/ */
static void
touch ( int fd )
{
struct stat st;
fstat( fd, &st );
fchmod( fd, st.st_mode );
}
Peaks::Peaks ( Audio_File *c )
{
_clip = c;
_peak_writer = NULL;
}
Peaks::~Peaks ( )
{
if ( _peak_writer )
delete _peak_writer;
}
/** Prepare a buffer of peaks from /s/ to /e/ for reading. Must be
* called before any calls to operator[] */
int
Peaks::fill_buffer ( float fpp, nframes_t s, nframes_t e ) const
{
_fpp = fpp;
return read_peaks( s, (e - s) / fpp, fpp );
}
struct peakfile_block_header
{
unsigned long chunksize;
unsigned long skip;
};
class Peakfile
{
FILE *_fp;
nframes_t _chunksize;
int _channels; /* number of channels this peakfile represents */
nframes_t _length; /* length, in frames, of the clip this peakfile represents */
size_t _offset;
int _blocks;
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:
Peakfile ( )
{
_blocks = 0;
_fp = NULL;
_offset = 0;
_chunksize = 0;
_channels = 0;
}
~Peakfile ( )
{
if ( _fp )
close();
}
int blocks ( void ) const { return _blocks; }
/** find the best block for /chunksize/ */
void
scan ( nframes_t chunksize )
{
rewind( _fp );
clearerr( _fp );
std::list <block_descriptor> blocks;
/* scan all blocks */
for ( ;; )
{
peakfile_block_header bh;
fread( &bh, sizeof( bh ), 1, _fp );
if ( feof( _fp ) )
break;
// printf( "chunksize=%lu, skip=%lu\n", (unsigned long)bh.chunksize, (unsigned long) bh.skip );
ASSERT( bh.chunksize, "Invalid peak file structure!" );
blocks.push_back( block_descriptor( bh.chunksize, ftell( _fp ) ) );
if ( ! bh.skip )
/* last block */
break;
if ( fseek( _fp, bh.skip, SEEK_CUR ) )
{
WARNING( "seek failed: %s (%lu)", strerror( errno ), bh.skip );
break;
}
}
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;
}
// DMESSAGE( "using peakfile block for chunksize %lu", _chunksize );
_blocks = blocks.size();
_offset = ftell( _fp );
}
/** convert frame number of peak number */
nframes_t frame_to_peak ( nframes_t frame )
{
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( peakfile_block_header ) ) / sizeof( Peak );
}
/** returns true if the peakfile contains /npeaks/ peaks starting at sample /s/ */
bool
ready ( nframes_t start, nframes_t npeaks )
{
if ( _blocks > 1 )
return true;
else
return this->npeaks() > frame_to_peak( start ) + npeaks;
}
/** given soundfile name /name/, try to open the best peakfile for /chunksize/ */
bool
open ( const char *name, int channels, nframes_t chunksize )
{
_chunksize = 0;
_channels = channels;
if ( ! ( _fp = fopen( peakname( name ), "r" ) ) )
return false;
scan( chunksize );
assert( _chunksize );
return true;
}
bool
open ( FILE *fp, int channels, nframes_t chunksize )
{
_fp = fp;
_chunksize = 0;
_channels = channels;
scan( chunksize );
assert( _chunksize );
return true;
}
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. */
nframes_t
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, _offset + ( frame_to_peak( s ) * sizeof( Peak ) ), SEEK_SET ) )
/* failed to seek... peaks not ready? */
return 0;
if ( ratio == 1 )
return fread( peaks, sizeof( Peak ) * _channels, npeaks, _fp );
Peak *pbuf = new Peak[ ratio * _channels ];
nframes_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;
return i;
}
};
bool
Peaks::ready ( nframes_t s, int npeaks, nframes_t chunksize ) const
{
Peakfile _peakfile;
if ( ! _peakfile.open( _clip->name(), _clip->channels(), chunksize ) )
return false;
return _peakfile.ready( s, npeaks );
}
int
Peaks::read_peakfile_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const
{
/* never try to build peaks while recording */
if ( ! transport->recording )
{
if ( ! current() )
/* Build peaks asyncronously */
if ( ! fork() )
exit( make_peaks() );
else
return 0;
}
Peakfile _peakfile;
if ( ! _peakfile.open( _clip->name(), _clip->channels(), chunksize ) )
return 0;
return _peakfile.read_peaks( peaks, s, npeaks, chunksize );
}
int
Peaks::read_source_peaks ( Peak *peaks, int npeaks, nframes_t chunksize ) const
{
int channels = _clip->channels();
sample_t *fbuf = new sample_t[ chunksize * channels ];
size_t len;
int i;
for ( i = 0; i < npeaks; ++i )
{
/* read in a buffer */
len = _clip->read( fbuf, -1, chunksize );
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;
for ( nframes_t k = j; k < len * channels; k += channels )
{
if ( fbuf[ k ] > p.max )
p.max = fbuf[ k ];
if ( fbuf[ k ] < p.min )
p.min = fbuf[ k ];
}
}
if ( len < (nframes_t)chunksize )
break;
}
delete[] fbuf;
return i;
}
int
Peaks::read_source_peaks ( Peak *peaks, nframes_t s, int npeaks, nframes_t chunksize ) const
{
_clip->seek( s );
int i = read_source_peaks( peaks, npeaks, chunksize );
return i;
}
int
Peaks::read_peaks ( nframes_t s, int npeaks, nframes_t chunksize ) const
{
// printf( "reading peaks %d @ %d\n", npeaks, chunksize );
if ( _peakbuf.size < (nframes_t)( npeaks * _clip->channels() ) )
{
_peakbuf.size = npeaks * _clip->channels();
// printf( "reallocating peak buffer %li\n", _peakbuf.size );
_peakbuf.buf = (peakdata*)realloc( _peakbuf.buf, sizeof( peakdata ) + (_peakbuf.size * sizeof( Peak )) );
}
_peakbuf.offset = s;
_peakbuf.buf->chunksize = chunksize;
/* FIXME: use actual minimum chunksize from peakfile! */
if ( chunksize < (nframes_t)cache_minimum )
_peakbuf.len = read_source_peaks( _peakbuf.buf->data, s, npeaks, chunksize );
else
_peakbuf.len = read_peakfile_peaks( _peakbuf.buf->data, s, npeaks, chunksize );
return _peakbuf.len;
}
/** returns false if peak file for /filename/ is out of date */
bool
Peaks::current ( void ) const
{
int sfd, pfd;
if ( ( sfd = ::open( _clip->name(), O_RDONLY ) ) < 0 )
return true;
if ( ( pfd = ::open( peakname( _clip->name() ), O_RDONLY ) ) < 0 )
return false;
struct stat sst, pst;
fstat( sfd, &sst );
fstat( pfd, &pst );
close( sfd );
close( pfd );
return sst.st_mtime <= pst.st_mtime;
}
bool
Peaks::make_peaks ( void ) const
{
Peaks::Builder pb( this );
return pb.make_peaks();
}
bool
Peaks::make_peaks_mipmap ( void ) const
{
Peaks::Builder pb( this );
return pb.make_peaks_mipmap();
}
/** return normalization factor for a single peak, assuming the peak
* represents a downsampling of the entire range to be normalized. */
float
Peak::normalization_factor( void ) const
{
float s;
s = 1.0f / fabs( this->max );
if ( s * this->min < -1.0 )
s = 1.0f / fabs( this->min );
return s;
}
/* THREAD: IO */
/* wrapper for peak writer */
void
Peaks::prepare_for_writing ( void )
{
assert( ! _peak_writer );
_peak_writer = new Peaks::Streamer( _clip->name(), _clip->channels(), cache_minimum );
}
/* THREAD: IO */
void
Peaks::finish_writing ( void )
{
assert( _peak_writer );
delete _peak_writer;
_peak_writer = NULL;
/* now fill in the rest of the cache */
make_peaks_mipmap();
/* if ( ! fork() ) */
/* exit( make_peaks_mipmap() ); */
}
/* THREAD: IO */
void
Peaks::write ( sample_t *buf, nframes_t nframes )
{
_peak_writer->write( buf, nframes );
}
/*
The Streamer is for streaming peaks from audio buffers to disk while
capturing. It works by accumulating a peak value across write()
calls. The Streamer can only generate peaks at a single
chunksize--additional cache levels must be appended after the
Streamer has finished.
*/
Peaks::Streamer::Streamer ( const char *filename, int channels, nframes_t chunksize )
{
_channels = channels;
_chunksize = chunksize;
_index = 0;
_fp = NULL;
_peak = new Peak[ channels ];
memset( _peak, 0, sizeof( Peak ) * channels );
if ( ! ( _fp = fopen( peakname( filename ), "w" ) ) )
{
WARNING( "could not open peakfile for streaming." );
}
peakfile_block_header bh;
bh.chunksize = chunksize;
bh.skip = 0;
fwrite( &bh, sizeof( bh ), 1, _fp );
fflush( _fp );
}
Peaks::Streamer::~Streamer ( )
{
/* fwrite( _peak, sizeof( Peak ) * _channels, 1, _fp ); */
touch( fileno( _fp ) );
fclose( _fp );
delete[] _peak;
}
/** append peaks for samples in /buf/ to peakfile */
void
Peaks::Streamer::write ( const sample_t *buf, nframes_t nframes )
{
while ( nframes )
{
const nframes_t remaining = _chunksize - _index;
if ( ! remaining )
{
fwrite( _peak, sizeof( Peak ) * _channels, 1, _fp );
/* FIXME: shouldn't we just use write() instead? */
fflush( _fp );
memset( _peak, 0, sizeof( Peak ) * _channels );
_index = 0;
}
int processed = min( nframes, remaining );
for ( int i = _channels; i--; )
{
Peak *p = _peak + i;
const sample_t *f = buf + i;
for ( int j = processed; j--; f += _channels )
{
if ( *f > p->max )
p->max = *f;
if ( *f < p->min )
p->min = *f;
}
}
_index += processed;
nframes -= processed;
}
}
/*
The Builder is for generating peaks from imported or updated
sources, or when the peakfile is simply missing.
*/
void
Peaks::Builder::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;
ASSERT( bh.skip, "Attempt to create empty block. pos=%lu, last_block_pos=%lu", 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 );
fflush( fp );
}
/** generate additional cache levels for a peakfile with only 1 block (ie. that of a new capture) */
bool
Peaks::Builder::make_peaks_mipmap ( void )
{
if ( ! Peaks::mipmapped_peakfiles )
return true;
Audio_File *_clip = _peaks->_clip;
const char *filename = _clip->name();
FILE *rfp;
rfp = fopen( peakname( filename ), "r" );
last_block_pos = sizeof( peakfile_block_header );
/* open for reading */
// rfp = fopen( peakname( filename ), "r" );
/* open the file again for appending */
if ( ! ( fp = fopen( peakname( filename ), "r+" ) ) )
{
WARNING( "could not open peakfile for appending." );
return false;
}
if ( fseek( fp, 0, SEEK_END ) )
FATAL( "error performing seek: %s", strerror( errno ) );
if ( ftell( fp ) == sizeof( peakfile_block_header ) )
{
DWARNING( "truncated peakfile. Programming error?" );
return false;
}
Peak buf[ _clip->channels() ];
/* now build the remaining peak levels, each based on the
* preceding level */
nframes_t cs = Peaks::cache_minimum << Peaks::cache_step;
for ( int i = 1; i < Peaks::cache_levels; ++i, cs <<= Peaks::cache_step )
{
DMESSAGE( "building level %d peak cache", i + 1 );
DMESSAGE( "%lu", _clip->length() / cs );
if ( _clip->length() / cs < 1 )
{
DMESSAGE( "source not long enough for any peaks at chunksize %lu", cs );
break;
}
Peakfile pf;
/* open the peakfile for the previous cache level */
pf.open( rfp, _clip->channels(), cs >> Peaks::cache_step );
// pf.open( _clip->name(), _clip->channels(), cs >> Peaks::cache_step );
write_block_header( cs );
size_t len;
nframes_t s = 0;
do {
len = pf.read_peaks( buf, s, 1, cs );
s += cs;
fwrite( buf, sizeof( buf ), len, fp );
}
while ( len );
pf.leave_open();
}
fclose( rfp );
fclose( fp );
return true;
}
bool
Peaks::Builder::make_peaks ( void )
{
Audio_File *_clip = _peaks->_clip;
const char *filename = _clip->name();
DMESSAGE( "building peaks for \"%s\"", filename );
if ( ! ( fp = fopen( peakname( filename ), "w+" ) ) )
return false;
_clip->seek( 0 );
Peak buf[ _clip->channels() ];
DMESSAGE( "building level 1 peak cache" );
write_block_header( Peaks::cache_minimum );
/* build first level from source */
size_t len;
do {
len = _peaks->read_source_peaks( buf, 1, Peaks::cache_minimum );
fwrite( buf, sizeof( buf ), len, fp );
}
while ( len );
/* reopen for reading */
fclose( fp );
make_peaks_mipmap();
DMESSAGE( "done building peaks" );
return true;
}
Peaks::Builder::Builder ( const Peaks *peaks ) : _peaks( peaks )
{
fp = NULL;
last_block_pos = 0;
}