| @@ -25,6 +25,8 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <assert.h> | |||
| 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 ); | |||
| @@ -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 <Track_Widget *>::const_iterator i = _widgets.begin(); i != _widgets.end(); i++ ) | |||
| for ( list <Track_Widget *>::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 ); */ | |||
| /* } */ | |||
| @@ -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; | |||
| } | |||
| @@ -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 ); | |||
| }; | |||
| @@ -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; | |||
| } | |||
| @@ -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 <jack/jack.h> | |||
| 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; | |||
| @@ -31,6 +31,12 @@ public: | |||
| Mutex ( ) | |||
| { | |||
| // _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; | |||
| pthread_mutex_init( &_lock, NULL ); | |||
| } | |||
| virtual ~Mutex ( ) | |||
| { | |||
| pthread_mutex_destroy( &_lock ); | |||
| } | |||
| void | |||
| @@ -21,6 +21,8 @@ | |||
| #include <string.h> | |||
| #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 | |||
| @@ -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 ); } | |||
| @@ -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 <pthread.h> | |||
| 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 ); | |||
| } | |||
| }; | |||
| @@ -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 ) | |||
| @@ -24,8 +24,8 @@ | |||
| #include "Control_Track.H" | |||
| #include <FL/Fl_Scrollbar.H> | |||
| #include <FL/Fl_Image.H> | |||
| #include <FL/Fl_RGB_Image.H> // needed for alpha blending | |||
| // #include <FL/Fl_Image.H> | |||
| // #include <FL/Fl_RGB_Image.H> // 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 ); | |||
| @@ -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 ); | |||
| @@ -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; | |||
| } | |||
| @@ -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" ); | |||