@@ -41,10 +41,10 @@ Audio_File::from_file ( const char * filename ) | |||
done: | |||
a->_peaks = new Peaks; | |||
/* a->_peaks = new Peaks; */ | |||
a->_peaks->clip( a ); | |||
a->_peaks->open(); | |||
/* a->_peaks->clip( a ); */ | |||
/* a->_peaks->open(); */ | |||
_open_files[ string( filename ) ] = a; | |||
@@ -21,33 +21,42 @@ | |||
#include "Track_Header.H" | |||
#include "Audio_Track.H" | |||
#include "Port.H" | |||
#include "Engine.H" // for locking. | |||
// float Disk_Stream::seconds_to_buffer = 5.0f; | |||
float Disk_Stream::seconds_to_buffer = 5.0f; | |||
// size_t Disk_Stream::disk_block_frames = 2048; | |||
/**********/ | |||
/* Engine */ | |||
/**********/ | |||
/* A Disk_Stream uses a separate I/O thread to stream a track's | |||
regions from disk into a ringbuffer, to be processed by the RT | |||
thread (or vice-versa). */ | |||
thread (or vice-versa). The I/O thread syncronizes access with the | |||
user thread via the Timeline mutex. The size of the buffer (in | |||
seconds) must be set before any Disk_Stream objects are created; | |||
that is, at startup time. The default is 5 seconds, which may or | |||
may not be excessive depending on various external factors. */ | |||
/* FIXME: handle termination of IO thread in destructor */ | |||
/* FIXME: could all of this not simply be included in the Track_Header | |||
class? */ | |||
/* FIXME: deal with (jack) buffer size changes */ | |||
/* FIXME: can this be made to actually handle capture? */ | |||
/* FIXME: needs error handling everywhere! */ | |||
/* TODO: handle capture too */ | |||
float Disk_Stream::seconds_to_buffer = 1.0f; | |||
// size_t Disk_Stream::disk_block_frames = 2048; | |||
Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : _th( th ) | |||
{ | |||
_frame = 0; | |||
_thread = 0; | |||
_terminate = false; | |||
_pending_seek = -1; | |||
printf( "nframes %lu\n", nframes ); | |||
const int blocks = frame_rate * seconds_to_buffer / nframes; | |||
_total_blocks = frame_rate * seconds_to_buffer / nframes; | |||
_nframes = nframes; | |||
size_t bufsize = blocks * nframes * sizeof( sample_t ); | |||
size_t bufsize = _total_blocks * nframes * sizeof( sample_t ); | |||
/* const int blocks = 64; */ | |||
/* const size_t bufsize = (blocks * (nframes * sizeof( sample_t ))) + sizeof( sample_t ); */ | |||
@@ -55,19 +64,29 @@ Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes | |||
for ( int i = channels; i--; ) | |||
_rb.push_back( jack_ringbuffer_create( bufsize ) ); | |||
sem_init( &_blocks, 0, blocks ); | |||
sem_init( &_blocks, 0, _total_blocks ); | |||
run(); | |||
} | |||
Disk_Stream::~Disk_Stream ( ) | |||
{ | |||
/* stop the IO thread */ | |||
_terminate = true; | |||
pthread_join( _thread, NULL ); | |||
/* it isn't safe to do all this with the RT thread running */ | |||
engine->lock(); | |||
_th = NULL; | |||
sem_destroy( &_blocks ); | |||
for ( int i = channels(); i--; ) | |||
jack_ringbuffer_free( _rb[ i ] ); | |||
engine->unlock(); | |||
} | |||
Audio_Track * | |||
@@ -84,6 +103,56 @@ Disk_Stream::run ( void ) | |||
/* error */; | |||
} | |||
/* to be called when the JACK buffer size changes. */ | |||
void | |||
Disk_Stream::resize ( nframes_t nframes ) | |||
{ | |||
if ( nframes != _nframes ) | |||
/* FIXME: to something here! */; | |||
} | |||
bool | |||
Disk_Stream::seek_pending ( void ) | |||
{ | |||
return _pending_seek != (nframes_t)-1; | |||
} | |||
/* THREAD: RT */ | |||
/** request that the IO thread perform a seek and rebuffer. This is | |||
called for each Disk_Stream whenever the RT thread determines that | |||
the transport has jumped to a new position. This is called *before* | |||
process. */ | |||
void | |||
Disk_Stream::seek ( nframes_t frame ) | |||
{ | |||
printf( "requesting seek\n" ); | |||
if ( seek_pending() ) | |||
printf( "seek error, attempt to seek while seek is pending\n" ); | |||
_pending_seek = frame; | |||
/* flush buffers */ | |||
for ( int i = channels(); i--; ) | |||
jack_ringbuffer_read_advance( _rb[ i ], jack_ringbuffer_read_space( _rb[ i ] ) ); | |||
/* dirty hack... reset the semaphore. Should we just call sem_init | |||
* again instead? */ | |||
/* sem_init( &_blocks, 0, _total_blocks ); */ | |||
int n; | |||
sem_getvalue( &_blocks, &n ); | |||
n = _total_blocks - n; | |||
while ( n-- ) | |||
sem_post( &_blocks ); | |||
} | |||
/* void */ | |||
/* DIsk_Stream::shutdown ( void ) */ | |||
/* { */ | |||
@@ -100,9 +169,9 @@ Disk_Stream::io_thread ( void *arg ) | |||
} | |||
/* THREAD: IO */ | |||
/** read a block of data from the track into /buf/ */ | |||
/** read /nframes/ from the attached track into /buf/ */ | |||
void | |||
Disk_Stream::read_block ( sample_t *buf ) | |||
Disk_Stream::read_block ( sample_t *buf, nframes_t nframes ) | |||
{ | |||
/* stupid chicken/egg */ | |||
@@ -119,14 +188,24 @@ Disk_Stream::read_block ( sample_t *buf ) | |||
timeline->rdlock(); | |||
if ( track()->play( buf, _frame, _nframes, channels() ) ) | |||
_frame += _nframes; | |||
if ( track()->play( buf, _frame, nframes, channels() ) ) | |||
_frame += nframes; | |||
else | |||
/* error */; | |||
timeline->unlock(); | |||
} | |||
int | |||
Disk_Stream::output_buffer_percent ( void ) | |||
{ | |||
int n; | |||
sem_getvalue( &_blocks, &n ); | |||
return 100 - (n * 100 / _total_blocks); | |||
} | |||
/* THREAD: IO */ | |||
void | |||
Disk_Stream::io_thread ( void ) | |||
@@ -145,7 +224,26 @@ Disk_Stream::io_thread ( void ) | |||
{ | |||
// printf( "IO: RT thread is ready for more data...\n" ); | |||
read_block( buf ); | |||
printf( "IO: disk buffer is %3d%% full\r", output_buffer_percent() ); | |||
// lock(); // for seeking | |||
if ( seek_pending() ) | |||
{ | |||
printf( "performing seek\n" ); | |||
_frame = _pending_seek; | |||
_pending_seek = -1; | |||
/* finish flushing the buffer */ | |||
/* for ( int i = channels(); i-- ) */ | |||
/* jack_ringbuffer_write_advance( _rb[ i ], jack_ringbuffer_write_space( _rb[ i ] ) ); */ | |||
} | |||
/* FIXME: should we not read from disk in larger-than-JACK-buffer blocks? */ | |||
read_block( buf, _nframes ); | |||
// unlock(); // for seeking | |||
/* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | |||
@@ -158,6 +256,7 @@ Disk_Stream::io_thread ( void ) | |||
while ( jack_ringbuffer_write_space( _rb[ i ] ) < block_size ) | |||
{ | |||
printf( "IO: disk buffer overrun!\n" ); | |||
/* FIXME: is this *really* the right thing to do? */ | |||
usleep( 2000 ); | |||
} | |||
@@ -165,13 +264,15 @@ Disk_Stream::io_thread ( void ) | |||
} | |||
} | |||
printf( "IO thread terminating.\n" ); | |||
delete[] buf; | |||
delete[] cbuf; | |||
} | |||
/* THREAD: RT */ | |||
/** take a block from the ringbuffers and send it out the track's | |||
* ports */ | |||
/** take a single block from the ringbuffers and send it out the | |||
* attached track's ports */ | |||
nframes_t | |||
Disk_Stream::process ( nframes_t nframes ) | |||
{ | |||
@@ -184,19 +285,11 @@ Disk_Stream::process ( nframes_t nframes ) | |||
void *buf = _th->output[ i ].buffer( nframes ); | |||
/* FIXME: handle underrun */ | |||
/* if ( jack_ringbuffer_read_space( _rb[ i ] ) < block_size ) */ | |||
/* { */ | |||
/* printf( "disktream (rt): buffer underrun!\n" ); */ | |||
/* memset( buf, 0, block_size ); */ | |||
/* } */ | |||
/* else */ | |||
if ( jack_ringbuffer_read( _rb[ i ], (char*)buf, block_size ) < block_size ) | |||
{ | |||
printf( "RT: buffer underrun (disk can't keep up).\n" ); | |||
memset( buf, 0, block_size ); | |||
/* FIXME: we need to resync somehow */ | |||
} | |||
/* /\* testing. *\/ */ | |||
@@ -27,13 +27,15 @@ | |||
#include <pthread.h> | |||
#include "Mutex.H" | |||
#include <vector> | |||
using std::vector; | |||
class Track_Header; | |||
class Audio_Track; | |||
class Disk_Stream | |||
class Disk_Stream : public Mutex | |||
{ | |||
pthread_t _thread; | |||
@@ -49,7 +51,11 @@ class Disk_Stream | |||
sem_t _blocks; /* semaphore to wake the IO thread with */ | |||
// volatile nframes_t _seek_request; | |||
int _total_blocks; | |||
volatile nframes_t _pending_seek; /* absolute transport position to seek to */ | |||
volatile int _terminate; | |||
int channels ( void ) const { return _rb.size(); } | |||
@@ -60,7 +66,19 @@ class Disk_Stream | |||
protected: | |||
void block_processed ( void ) { sem_post( &_blocks ); } | |||
bool wait_for_block ( void ) { while ( sem_wait( &_blocks ) == EINTR ); return true; } | |||
bool wait_for_block ( void ) | |||
{ | |||
if ( _terminate ) | |||
return false; | |||
else | |||
{ | |||
while ( sem_wait( &_blocks ) == EINTR ); | |||
return true; | |||
} | |||
} | |||
void read_block ( sample_t *buf, nframes_t nframes ); | |||
void io_thread ( void ); | |||
public: | |||
@@ -71,23 +89,12 @@ public: | |||
virtual ~Disk_Stream ( ); | |||
void | |||
resize ( nframes_t nframes ) | |||
{ | |||
if ( nframes != _nframes ) | |||
/* FIXME: to something here! */; | |||
} | |||
void | |||
seek ( nframes_t frame ) | |||
{ | |||
_frame = frame; | |||
/* FIXME: need to signal the IO thread somehow? */ | |||
} | |||
void resize ( nframes_t nframes ); | |||
void seek ( nframes_t frame ); | |||
bool seek_pending ( void ); | |||
void run ( void ); | |||
void read_block ( sample_t *buf ); | |||
void io_thread ( void ); | |||
nframes_t process ( nframes_t nframes ); | |||
int output_buffer_percent ( void ); | |||
}; |
@@ -40,6 +40,63 @@ Engine::process ( nframes_t nframes, void *arg ) | |||
return ((Engine*)arg)->process( nframes ); | |||
} | |||
/* static wrapper */ | |||
int | |||
Engine::sync ( jack_transport_state_t state, jack_position_t *pos, void *arg ) | |||
{ | |||
return ((Engine*)arg)->sync( state, pos ); | |||
} | |||
void | |||
Engine::request_locate ( nframes_t frame ) | |||
{ | |||
if ( timeline ) | |||
timeline->seek( frame ); | |||
} | |||
/* THREAD: RT */ | |||
/** This is the jack slow-sync callback. */ | |||
int | |||
Engine::sync ( jack_transport_state_t state, jack_position_t *pos ) | |||
{ | |||
static bool seeking = false; | |||
switch ( state ) | |||
{ | |||
case JackTransportStopped: /* new position requested */ | |||
/* JACK docs lie. This is only called when the transport | |||
is *really* stopped, not when starting a slow-sync | |||
cycle */ | |||
request_locate( pos->frame ); | |||
return 1; | |||
case JackTransportStarting: /* this means JACK is polling slow-sync clients */ | |||
{ | |||
if ( ! seeking ) | |||
{ | |||
request_locate( pos->frame ); | |||
seeking = true; | |||
} | |||
int r = timeline->seek_pending(); | |||
if ( ! r ) | |||
seeking = false; | |||
return ! seeking; | |||
} | |||
case JackTransportRolling: /* JACK's timeout has expired */ | |||
/* FIXME: what's the right thing to do here? */ | |||
// request_locate( pos->frame ); | |||
return 1; | |||
// return transport.frame == pos->frame; | |||
break; | |||
default: | |||
printf( "unknown transport state.\n" ); | |||
} | |||
return 0; | |||
} | |||
/* THREAD: RT */ | |||
int | |||
Engine::process ( nframes_t nframes ) | |||
@@ -83,6 +140,10 @@ Engine::init ( void ) | |||
jack_set_process_callback( _client, &Engine::process, this ); | |||
/* FIXME: should we wait to register this until after the session | |||
has been loaded (and we have disk threads running)? */ | |||
jack_set_sync_callback( _client, &Engine::sync, this ); | |||
jack_activate( _client ); | |||
/* we don't need to create any ports until tracks are created */ | |||
@@ -44,6 +44,8 @@ class Engine : public Mutex | |||
static int process ( nframes_t nframes, void *arg ); | |||
int process ( nframes_t nframes ); | |||
static int sync ( jack_transport_state_t state, jack_position_t *pos, void *arg ); | |||
int sync ( jack_transport_state_t state, jack_position_t *pos ); | |||
private: | |||
@@ -52,12 +54,12 @@ private: | |||
public: | |||
Engine ( ); | |||
int init ( void ); | |||
void request_locate ( nframes_t frame ); | |||
nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); } | |||
float frame_rate ( void ) const { return jack_get_sample_rate( _client ); } | |||
@@ -38,6 +38,8 @@ | |||
#include <math.h> | |||
#include <FL/Fl.H> // for Fl::check(); | |||
Peaks::peakbuffer Peaks::_peakbuf; | |||
@@ -305,6 +307,8 @@ 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 */ | |||
bool | |||
Peaks::make_peaks ( int chunksize ) | |||
@@ -334,6 +338,8 @@ Peaks::make_peaks ( int chunksize ) | |||
do { | |||
len = read_source_peaks( peaks, 1, chunksize ); | |||
fwrite( peaks, sizeof( peaks ), 1, fp ); | |||
/* FIXME: GUI code shouldn't be here! */ | |||
Fl::check(); | |||
} | |||
while ( len ); | |||
@@ -29,6 +29,9 @@ | |||
#include "Track_Header.H" | |||
#include "Disk_Stream.H" | |||
void | |||
Timeline::cb_scroll ( Fl_Widget *w, void *v ) | |||
{ | |||
@@ -153,7 +156,7 @@ Timeline::Timeline ( int X, int Y, int W, int H, const char* L ) : Fl_Overlay_Wi | |||
o->type( Fl_Pack::VERTICAL ); | |||
o->spacing( 0 ); | |||
for ( int i = 1; i--; ) | |||
for ( int i = 2; i--; ) | |||
{ | |||
// Track_Header *t = new Track_Header( 0, 0, W, 75 ); | |||
Track_Header *t = new Track_Header( 0, 0, W, 30 ); | |||
@@ -616,3 +619,30 @@ Timeline::process ( nframes_t nframes ) | |||
/* FIXME: BOGUS */ | |||
return nframes; | |||
} | |||
/* THREAD: RT */ | |||
void | |||
Timeline::seek ( nframes_t frame ) | |||
{ | |||
for ( int i = tracks->children(); i-- ; ) | |||
{ | |||
Track_Header *t = (Track_Header*)tracks->child( i ); | |||
t->seek( frame ); | |||
} | |||
} | |||
/* THREAD: RT */ | |||
int | |||
Timeline::seek_pending ( void ) | |||
{ | |||
int r = 0; | |||
for ( int i = tracks->children(); i-- ; ) | |||
{ | |||
Track_Header *t = (Track_Header*)tracks->child( i ); | |||
if ( t->diskstream ) | |||
r += t->diskstream->output_buffer_percent() < 50; | |||
} | |||
} |
@@ -148,5 +148,8 @@ private: | |||
friend class Engine; // FIXME: only Engine::process() needs to be friended.x | |||
/* Engine */ | |||
nframes_t process ( nframes_t nframes ); | |||
void seek ( nframes_t frame ); | |||
int seek_pending ( void ); | |||
}; |
@@ -100,7 +100,13 @@ Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : | |||
_show_all_takes = false; | |||
_size = 1; | |||
output.push_back( Port( "foo" ) ); | |||
{ | |||
char pname[40]; | |||
static int n = 0; | |||
snprintf( pname, sizeof( pname ), "out-%d", n++ ); | |||
output.push_back( Port( strdup( pname ) ) ); | |||
} | |||
diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 ); | |||
@@ -299,3 +305,11 @@ Track_Header::process ( nframes_t nframes ) | |||
else | |||
return 0; | |||
} | |||
/* THREAD: RT */ | |||
void | |||
Track_Header::seek ( nframes_t frame ) | |||
{ | |||
if ( diskstream ) | |||
return diskstream->seek( frame ); | |||
} |
@@ -264,6 +264,8 @@ public: | |||
} | |||
/* Engine */ | |||
nframes_t process ( nframes_t nframes ); | |||
void seek ( nframes_t frame ); | |||
}; | |||
#endif |