diff --git a/Timeline/Audio_File_SF.C b/Timeline/Audio_File_SF.C index 7ef0bb4..79cfb4a 100644 --- a/Timeline/Audio_File_SF.C +++ b/Timeline/Audio_File_SF.C @@ -25,6 +25,8 @@ #include #include +#include + Audio_File_SF * Audio_File_SF::from_file ( const char *filename ) { @@ -100,6 +102,11 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t len ) return sf_readf_float( _in, buf, len ); else { + if ( len > 256 * 100 ) + printf( "warning: attempt to read an insane number of frames (%lu) from soundfile\n", len ); + + printf( "len = %lu, channels = %d\n", len, _channels ); + sample_t *tmp = new sample_t[ len * _channels ]; nframes_t rlen = sf_readf_float( _in, tmp, len ); @@ -118,6 +125,8 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t len ) nframes_t Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end ) { + assert( end > start ); + open(); seek( start ); diff --git a/Timeline/Audio_Track.C b/Timeline/Audio_Track.C index 6a37d7b..721a15d 100644 --- a/Timeline/Audio_Track.C +++ b/Timeline/Audio_Track.C @@ -99,7 +99,8 @@ Audio_Track::handle ( int m ) return 0; } - Region *r = new Region( c, this, timeline->xoffset + timeline->x_to_ts( Fl::event_x() - x() ) ); +// Region *r = + new Region( c, this, timeline->xoffset + timeline->x_to_ts( Fl::event_x() - x() ) ); redraw(); return 1; @@ -123,7 +124,7 @@ Audio_Track::play ( sample_t *buf, nframes_t frame, nframes_t nframes, int chann sample_t *cbuf = new sample_t[ nframes ]; /* quick and dirty--let the regions figure out coverage for themselves */ - for ( list ::const_iterator i = _widgets.begin(); i != _widgets.end(); i++ ) + for ( list ::const_iterator i = _widgets.begin(); i != _widgets.end(); ++i ) { const Region *r = (Region*)(*i); @@ -141,6 +142,15 @@ Audio_Track::play ( sample_t *buf, nframes_t frame, nframes_t nframes, int chann } } + delete[] cbuf; + /* FIXME: bogus */ return nframes; } + +/* /\* THREAD: RT *\/ */ +/* nframes_t */ +/* Audio_Track::process ( nframes_t nframes ) */ +/* { */ +/* return disktream->process( nframes ); */ +/* } */ diff --git a/Timeline/Disk_Stream.C b/Timeline/Disk_Stream.C index 2f303bb..867616b 100644 --- a/Timeline/Disk_Stream.C +++ b/Timeline/Disk_Stream.C @@ -46,7 +46,7 @@ Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes size_t bufsize = blocks * nframes * sizeof( sample_t ); for ( int i = channels; i--; ) - _rb[ i ] = jack_ringbuffer_create( bufsize ); + _rb.push_back( jack_ringbuffer_create( bufsize ) ); sem_init( &_blocks, 0, blocks ); @@ -97,10 +97,27 @@ Disk_Stream::io_thread ( void *arg ) void Disk_Stream::read_block ( sample_t *buf ) { + + /* stupid chicken/egg */ + if ( ! timeline ) + return; + + printf( "IO: attempting to read block @ %lu\n", _frame ); + + if ( ! track() ) + { +// _frame += _nframes; + return; + } + + timeline->rdlock(); + if ( track()->play( buf, _frame, _nframes, channels() ) ) _frame += _nframes; else /* error */; + + timeline->unlock(); } /* THREAD: IO */ @@ -119,6 +136,8 @@ Disk_Stream::io_thread ( void ) while ( wait_for_block() ) { +// printf( "IO: RT thread is ready for more data...\n" ); + read_block( buf ); /* deinterleave the buffer and stuff it into the per-channel ringbuffers */ @@ -140,8 +159,8 @@ Disk_Stream::io_thread ( void ) /* THREAD: RT */ /** take a block from the ringbuffers and send it out the track's * ports */ -void -Disk_Stream::process ( void ) +nframes_t +Disk_Stream::process ( nframes_t nframes ) { const size_t block_size = _nframes * sizeof( sample_t ); @@ -154,4 +173,7 @@ Disk_Stream::process ( void ) } block_processed(); + + /* FIXME: bogus */ + return nframes; } diff --git a/Timeline/Disk_Stream.H b/Timeline/Disk_Stream.H index e69657f..52d987a 100644 --- a/Timeline/Disk_Stream.H +++ b/Timeline/Disk_Stream.H @@ -86,6 +86,6 @@ public: void run ( void ); void read_block ( sample_t *buf ); void io_thread ( void ); - void process ( void ); + nframes_t process ( nframes_t nframes ); }; diff --git a/Timeline/Engine.C b/Timeline/Engine.C new file mode 100644 index 0000000..26f5ef4 --- /dev/null +++ b/Timeline/Engine.C @@ -0,0 +1,91 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +#include "Engine.H" + +#include "Timeline.H" // for process() + +#define APP_NAME "Non-DAW" // FIXME: wrong place for this! + +/* This is the home of the JACK process callback (does this *really* + need to be a class?) */ + +Engine::Engine ( ) +{ + _client = NULL; + _buffers_dropped = 0; +} + + +/* static wrapper */ +int +Engine::process ( nframes_t nframes, void *arg ) +{ + return ((Engine*)arg)->process( nframes ); +} + +/* THREAD: RT */ +int +Engine::process ( nframes_t nframes ) +{ + jack_position_t pos; + jack_transport_state_t ts; + + ts = jack_transport_query( _client, &pos ); + + if ( ts != JackTransportRolling ) + return 0; + + if ( ! trylock() ) + { + /* the data structures we need to access here (tracks and + * their ports, but not track contents) may be in an + * inconsistent state at the moment. Just punt and drop this + * buffer. */ + ++_buffers_dropped; + return 0; + } + + + /* handle chicken/egg problem */ + if ( timeline ) + /* this will initiate the process() call graph for the various + * number and types of tracks, which will in turn send data out + * the appropriate ports. */ + timeline->process( nframes ); + + unlock(); + + return 0; +} + + +int +Engine::init ( void ) +{ + if (( _client = jack_client_open ( APP_NAME, (jack_options_t)0, NULL )) == 0 ) + return 0; + + jack_set_process_callback( _client, &Engine::process, this ); + + jack_activate( _client ); + + /* we don't need to create any ports until tracks are created */ + return 1; +} diff --git a/Timeline/Engine.H b/Timeline/Engine.H new file mode 100644 index 0000000..ee4f624 --- /dev/null +++ b/Timeline/Engine.H @@ -0,0 +1,67 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +#pragma once + +#include "Mutex.H" + +#include + +typedef jack_nframes_t nframes_t; + +class Port; + +class Engine : public Mutex +{ + jack_client_t *_client; + + /* I know locking out the process callback is cheating, even + though we use trylock... The thing is, every other DAW does + this too and you can hear it in the glitches Ardour and friends + produce when reconfiguring I/O... Working out a message queue + system would obviously be better, but a DAW isn't a performance + instrument anyway, so I think these drop-outs are a reasonable + compromise. Obviously, this lock should never be held during + blocking operations. */ + + int _buffers_dropped; /* buffers dropped because of locking */ + + static int process ( nframes_t nframes, void *arg ); + int process ( nframes_t nframes ); + +private: + + friend class Port; + jack_client_t * client ( void ) { return _client; } + +public: + + + + Engine ( ); + + int init ( void ); + + nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); } + float frame_rate ( void ) const { return jack_get_sample_rate( _client ); } + +}; + + +extern Engine * engine; diff --git a/Timeline/Mutex.H b/Timeline/Mutex.H index 92c90a6..3d6eb3d 100644 --- a/Timeline/Mutex.H +++ b/Timeline/Mutex.H @@ -31,6 +31,12 @@ public: Mutex ( ) { // _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + pthread_mutex_init( &_lock, NULL ); + } + + virtual ~Mutex ( ) + { + pthread_mutex_destroy( &_lock ); } void diff --git a/Timeline/Port.C b/Timeline/Port.C index 5a9c7fe..6328cd8 100644 --- a/Timeline/Port.C +++ b/Timeline/Port.C @@ -21,6 +21,8 @@ #include +#include "Engine.H" + /* nframes is the number of frames to buffer */ Port::Port ( jack_port_t *port ) { @@ -28,9 +30,17 @@ Port::Port ( jack_port_t *port ) _name = jack_port_name( _port ); } +Port::Port ( const char *name ) +{ + _name = name; + + _port = jack_port_register( engine->client(), _name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); +} + Port::~Port ( ) { /* close port? */ +// jack_port_unregister( engine->client(), _port ); } void diff --git a/Timeline/Port.H b/Timeline/Port.H index 6fc631f..18ae299 100644 --- a/Timeline/Port.H +++ b/Timeline/Port.H @@ -33,6 +33,7 @@ class Port public: Port ( jack_port_t *port ); + Port ( const char *name ); ~Port ( ); bool connected ( void ) const { return jack_port_connected( _port ); } diff --git a/Timeline/RWLock.H b/Timeline/RWLock.H new file mode 100644 index 0000000..2bd4011 --- /dev/null +++ b/Timeline/RWLock.H @@ -0,0 +1,72 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +#pragma once + +#include + +class RWLock +{ + + pthread_rwlock_t _lock; + +public: + + RWLock ( ) + { +// _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + pthread_rwlock_init( &_lock, NULL ); + } + + virtual ~RWLock ( ) + { + pthread_rwlock_destroy( &_lock ); + } + + void + rdlock ( void ) + { + pthread_rwlock_rdlock( &_lock ); + } + + void + wrlock ( void ) + { + pthread_rwlock_wrlock( &_lock ); + } + + void + unlock ( void ) + { + pthread_rwlock_unlock( &_lock ); + } + + int + tryrdlock ( void ) + { + return pthread_rwlock_tryrdlock( &_lock ); + } + + int + trywrlock ( void ) + { + return pthread_rwlock_trywrlock( &_lock ); + } + +}; diff --git a/Timeline/Region.C b/Timeline/Region.C index 78b7206..d54a08c 100644 --- a/Timeline/Region.C +++ b/Timeline/Region.C @@ -555,9 +555,10 @@ Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) co { const Range &r = _range; - /* do nothing if we aren't covered by this frame range */ const nframes_t length = r.end - r.start; - if ( ! ( pos > r.offset + length || r.offset + length < pos ) ) + + /* do nothing if we aren't covered by this frame range */ + if ( pos > r.offset + length || pos + nframes < r.offset ) return 0; /* calculate offsets into file and sample buffer */ @@ -576,11 +577,11 @@ Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) co sofs = pos - r.offset; } - if ( sofs > nframes ) + if ( ofs > nframes ) return 0; const nframes_t start = ofs + r.start + sofs; - const nframes_t len = min( cnt, nframes - sofs ); + const nframes_t len = min( cnt, nframes - ofs ); const nframes_t end = start + len; if ( len == 0 ) diff --git a/Timeline/Timeline.C b/Timeline/Timeline.C index 34d5a12..b6c1912 100644 --- a/Timeline/Timeline.C +++ b/Timeline/Timeline.C @@ -24,8 +24,8 @@ #include "Control_Track.H" #include -#include -#include // needed for alpha blending +// #include +// #include // needed for alpha blending #include "Track_Header.H" @@ -153,7 +153,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 = 8; i--; ) + for ( int i = 1; i--; ) { // Track_Header *t = new Track_Header( 0, 0, W, 75 ); Track_Header *t = new Track_Header( 0, 0, W, 30 ); diff --git a/Timeline/Timeline.H b/Timeline/Timeline.H index 406fffb..0f42c60 100644 --- a/Timeline/Timeline.H +++ b/Timeline/Timeline.H @@ -79,9 +79,9 @@ struct Rectangle class Engine; -#include "Mutex.H" +#include "RWLock.H" -class Timeline : public Fl_Overlay_Window, public Mutex +class Timeline : public Fl_Overlay_Window, public RWLock { static void draw_clip ( void * v, int X, int Y, int W, int H ); diff --git a/Timeline/Track_Header.C b/Timeline/Track_Header.C index c73b2c7..cb73752 100644 --- a/Timeline/Track_Header.C +++ b/Timeline/Track_Header.C @@ -19,6 +19,9 @@ #include "Track_Header.H" +#include "Disk_Stream.H" +#include "Engine.H" + void Track_Header::cb_input_field ( Fl_Widget *w, void *v ) { @@ -85,6 +88,7 @@ Track_Header::cb_button ( Fl_Widget *w ) } } +#include "Port.H" Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : Fl_Group ( X, Y, W, H, L ) @@ -96,7 +100,9 @@ Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : _show_all_takes = false; _size = 1; -// diskstream = new Disk_Stream( this ); + output.push_back( Port( "foo" ) ); + + diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 ); Fl_Group::size( w(), height() ); @@ -284,8 +290,12 @@ Track_Header::add_control( Track *t ) /* Engine */ /**********/ +/* THREAD: RT */ nframes_t Track_Header::process ( nframes_t nframes ) { - return track()->process( nframes ); + if ( diskstream ) + return diskstream->process( nframes ); + else + return 0; } diff --git a/Timeline/main.C b/Timeline/main.C index ec96971..872f740 100644 --- a/Timeline/main.C +++ b/Timeline/main.C @@ -51,6 +51,9 @@ #include "Track_Header.H" // #include "const.h" +#include "Engine.H" + +Engine *engine; Timeline *timeline; void cb_undo ( Fl_Widget *w, void *v ) @@ -78,6 +81,10 @@ main ( int argc, char **argv ) Loggable::register_create( "Control_Point", &Control_Point::create ); Loggable::register_create( "Track_Header", &Track_Header::create ); + /* we don't really need a pointer for this */ + engine = new Engine; + engine->init(); + timeline = new Timeline( 0, 24, main_window->w(), main_window->h() - 24, "Timeline" ); Fl_Button *o = new Fl_Button( 0, 0, 50, 24, "undo" );