| @@ -618,13 +618,12 @@ Audio_Region::draw ( void ) | |||
| oend = end; | |||
| } | |||
| } | |||
| else | |||
| if ( _clip->peaks()->needs_more_peaks() && ! transport->rolling ) | |||
| { | |||
| if ( ! transport->rolling ) | |||
| { | |||
| /* create a thread to make the peaks */ | |||
| /* maybe create a thread to make the peaks */ | |||
| /* this function will just return if there's nothing to do. */ | |||
| _clip->peaks()->make_peaks_asynchronously( Audio_Region::peaks_ready_callback, this ); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| @@ -74,7 +74,7 @@ Disk_Stream::~Disk_Stream ( ) | |||
| { | |||
| /* it isn't safe to do all this with the RT thread running */ | |||
| timeline->wrlock(); | |||
| // timeline->wrlock(); | |||
| shutdown(); | |||
| @@ -85,7 +85,7 @@ Disk_Stream::~Disk_Stream ( ) | |||
| for ( int i = channels(); i--; ) | |||
| jack_ringbuffer_free( _rb[ i ] ); | |||
| timeline->unlock(); | |||
| // timeline->unlock(); | |||
| } | |||
| @@ -144,13 +144,15 @@ Disk_Stream::shutdown ( void ) | |||
| { | |||
| if ( _thread.running() ) | |||
| { | |||
| DMESSAGE( "Sending terminate signal to diskthread." ); | |||
| _terminate = true; | |||
| /* try to wake the thread so it'll see that it's time to die */ | |||
| while ( _terminate ) | |||
| { | |||
| usleep( 100 ); | |||
| block_processed(); | |||
| usleep( 1000 ); | |||
| } | |||
| _thread.join(); | |||
| @@ -104,9 +104,7 @@ 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 */ | |||
| off_t _offset; | |||
| // int _blocks; | |||
| struct block_descriptor | |||
| { | |||
| @@ -127,14 +125,17 @@ class Peakfile | |||
| public: | |||
| int nblocks ( void ) const | |||
| { | |||
| return blocks.size(); | |||
| } | |||
| Peakfile ( ) | |||
| { | |||
| // _blocks = 0; | |||
| _fp = NULL; | |||
| _offset = 0; | |||
| _chunksize = 0; | |||
| _channels = 0; | |||
| // _length = 0; | |||
| } | |||
| ~Peakfile ( ) | |||
| @@ -143,6 +144,11 @@ public: | |||
| close(); | |||
| } | |||
| void rescan ( void ) | |||
| { | |||
| blocks.clear(); | |||
| } | |||
| /* int blocks ( void ) const { return blocks.size(); } */ | |||
| /** find the best block for /chunksize/ */ | |||
| void | |||
| @@ -184,9 +190,6 @@ public: | |||
| if ( ! blocks.size() ) | |||
| FATAL( "Peak file contains no blocks!" ); | |||
| // DMESSAGE( "peakfile has %d blocks.", blocks.size() ); | |||
| blocks.sort(); | |||
| /* fall back on the smallest chunksize */ | |||
| @@ -204,7 +207,6 @@ public: | |||
| } | |||
| // DMESSAGE( "using peakfile block for chunksize %lu", _chunksize ); | |||
| // _blocks = blocks.size(); | |||
| _offset = ftello( _fp ); | |||
| } | |||
| @@ -239,6 +241,7 @@ public: | |||
| bool | |||
| open ( const char *name, int channels, nframes_t chunksize ) | |||
| { | |||
| assert( ! _fp ); | |||
| // _chunksize = 0; | |||
| _channels = channels; | |||
| @@ -263,6 +266,8 @@ public: | |||
| bool | |||
| open ( FILE *fp, int channels, nframes_t chunksize ) | |||
| { | |||
| assert( ! _fp ); | |||
| _fp = fp; | |||
| _chunksize = 0; | |||
| _channels = channels; | |||
| @@ -364,7 +369,9 @@ public: | |||
| Peaks::Peaks ( Audio_File *c ) | |||
| { | |||
| _pending = false; | |||
| _rescan_needed = false; | |||
| _first_block_pending = false; | |||
| _mipmaps_pending = false; | |||
| _clip = c; | |||
| _peak_writer = NULL; | |||
| _peakfile = new Peakfile(); | |||
| @@ -412,39 +419,50 @@ Peaks::ready ( nframes_t s, nframes_t npeaks, nframes_t chunksize ) const | |||
| bool | |||
| Peaks::peakfile_ready ( void ) const | |||
| { | |||
| return current() && ! _pending; | |||
| if ( _rescan_needed ) | |||
| { | |||
| DMESSAGE( "Rescanning peakfile" ); | |||
| _peakfile->rescan(); | |||
| if ( _peakfile->open( _clip->filename(), _clip->channels(), 256 ) ) | |||
| _peakfile->close(); | |||
| _rescan_needed = false; | |||
| } | |||
| return current() && ! _first_block_pending; | |||
| } | |||
| /** start building peaks and/or peak mipmap in another thread. It is | |||
| * safe to call this again before the thread finishes. /callback/ will | |||
| * be called with /userdata/ FROM THE PEAK BUILDING THREAD when the | |||
| * peaks are finished. */ | |||
| void | |||
| Peaks::make_peaks_asynchronously ( void(*callback)(void*), void *userdata ) const | |||
| { | |||
| /* already working on it... */ | |||
| if( _pending ) | |||
| if( _first_block_pending || _mipmaps_pending ) | |||
| return; | |||
| // make_peaks(); | |||
| _pending = true; | |||
| /* maybe still building mipmaps... */ | |||
| _first_block_pending = _peakfile->nblocks() < 1; | |||
| _mipmaps_pending = _peakfile->nblocks() <= 1; | |||
| peak_thread_data *pd = new peak_thread_data(); | |||
| pd->callback = callback; | |||
| pd->userdata = userdata; | |||
| pd->peaks = const_cast<Peaks*>(this); | |||
| _make_peaks_thread.clone( &Peaks::make_peaks, pd ); | |||
| _make_peaks_thread.detach(); | |||
| DMESSAGE( "Starting new peak building thread" ); | |||
| } | |||
| nframes_t | |||
| Peaks::read_peakfile_peaks ( Peak *peaks, nframes_t s, nframes_t npeaks, nframes_t chunksize ) const | |||
| { | |||
| /* if ( _pending ) */ | |||
| /* return 0; */ | |||
| // Peakfile _peakfile; | |||
| if ( ! _peakfile->open( _clip->filename(), _clip->channels(), chunksize ) ) | |||
| if ( ! _peakfile->open( _clip->filename(), _clip->channels(), chunksize ) ) | |||
| { | |||
| DMESSAGE( "Failed to open peakfile!" ); | |||
| return 0; | |||
| @@ -545,7 +563,7 @@ Peaks::current ( void ) const | |||
| { | |||
| char *pn = peakname( _clip->filename() ); | |||
| bool b = ! newer( _clip->filename(), pn ); | |||
| bool b = newer( pn, _clip->filename() ); | |||
| free( pn ); | |||
| @@ -557,11 +575,14 @@ void * | |||
| Peaks::make_peaks ( void *v ) | |||
| { | |||
| peak_thread_data *pd = (peak_thread_data*)v; | |||
| pd->peaks->make_peaks(); | |||
| if ( pd->callback ) | |||
| pd->callback( pd->userdata ); | |||
| if ( pd->peaks->make_peaks() ) | |||
| { | |||
| if ( pd->callback ) | |||
| pd->callback( pd->userdata ); | |||
| pd->peaks->_rescan_needed = true; | |||
| } | |||
| delete pd; | |||
| @@ -569,35 +590,25 @@ Peaks::make_peaks ( void *v ) | |||
| } | |||
| bool | |||
| Peaks::make_peaks ( void ) const | |||
| Peaks::needs_more_peaks ( void ) const | |||
| { | |||
| Peaks::Builder pb( this ); | |||
| int b = pb.make_peaks(); | |||
| _pending = false; | |||
| return b; | |||
| } | |||
| /* thread entry point */ | |||
| void * | |||
| Peaks::make_peaks_mipmap ( void *v ) | |||
| { | |||
| ((Peaks*)v)->make_peaks_mipmap(); | |||
| return NULL; | |||
| return _peakfile->nblocks() <= 1 && ! ( _first_block_pending || _mipmaps_pending ); | |||
| } | |||
| bool | |||
| Peaks::make_peaks_mipmap ( void ) const | |||
| Peaks::make_peaks ( void ) const | |||
| { | |||
| Peaks::Builder pb( this ); | |||
| bool b = pb.make_peaks_mipmap(); | |||
| _pending = false; | |||
| /* make the first block */ | |||
| int b = pb.make_peaks(); | |||
| _first_block_pending = false; | |||
| b = pb.make_peaks_mipmap(); | |||
| _mipmaps_pending = false; | |||
| return b; | |||
| } | |||
| @@ -638,8 +649,6 @@ Peaks::finish_writing ( void ) | |||
| delete _peak_writer; | |||
| _peak_writer = NULL; | |||
| make_peaks_mipmap(); | |||
| } | |||
| void | |||
| @@ -792,15 +801,12 @@ Peaks::Builder::write_block_header ( nframes_t chunksize ) | |||
| 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; | |||
| return false; | |||
| Audio_File *_clip = _peaks->_clip; | |||
| @@ -895,10 +901,7 @@ Peaks::Builder::make_peaks_mipmap ( void ) | |||
| } | |||
| while ( len > 0 && s < _clip->length() ); | |||
| DMESSAGE( "Last sample was %lu", s ); | |||
| /* fflush( fp ); */ | |||
| /* fsync( fileno( fp ) ); */ | |||
| DMESSAGE( "Last sample was %lu", (unsigned long)s ); | |||
| pf.leave_open(); | |||
| } | |||
| @@ -918,43 +921,46 @@ Peaks::Builder::make_peaks ( void ) | |||
| const char *filename = _clip->filename(); | |||
| DMESSAGE( "building peaks for \"%s\"", filename ); | |||
| char *pn = peakname( filename ); | |||
| if ( ! ( fp = fopen( pn, "w+" ) ) ) | |||
| if ( _peaks->_peakfile && _peaks->_peakfile->nblocks() > 1 ) | |||
| { | |||
| free( pn ); | |||
| /* this peakfile already has enough blocks */ | |||
| return false; | |||
| } | |||
| else | |||
| { | |||
| DMESSAGE( "building peaks for \"%s\"", filename ); | |||
| char *pn = peakname( filename ); | |||
| if ( ! ( fp = fopen( pn, "w+" ) ) ) | |||
| { | |||
| free( pn ); | |||
| return false; | |||
| } | |||
| free( pn ); | |||
| _clip->seek( 0 ); | |||
| Peak buf[ _clip->channels() ]; | |||
| DMESSAGE( "building level 1 peak cache" ); | |||
| write_block_header( Peaks::cache_minimum ); | |||
| /* build first level from source */ | |||
| off_t len; | |||
| do { | |||
| len = _peaks->read_source_peaks( buf, 1, Peaks::cache_minimum ); | |||
| fwrite( buf, sizeof( buf ), len, fp ); | |||
| } | |||
| while ( len ); | |||
| fclose( fp ); | |||
| free( pn ); | |||
| _clip->seek( 0 ); | |||
| Peak buf[ _clip->channels() ]; | |||
| DMESSAGE( "building level 1 peak cache" ); | |||
| write_block_header( Peaks::cache_minimum ); | |||
| /* build first level from source */ | |||
| off_t len; | |||
| do { | |||
| len = _peaks->read_source_peaks( buf, 1, Peaks::cache_minimum ); | |||
| fwrite( buf, sizeof( buf ), len, fp ); | |||
| DMESSAGE( "done building peaks" ); | |||
| } | |||
| while ( len ); | |||
| /* reopen for reading */ | |||
| /* fflush( fp ); */ | |||
| /* fsync( fileno( fp ) ); */ | |||
| fclose( fp ); | |||
| make_peaks_mipmap(); | |||
| DMESSAGE( "done building peaks" ); | |||
| return true; | |||
| } | |||
| @@ -35,7 +35,9 @@ class Peakfile; | |||
| class Peaks | |||
| { | |||
| mutable volatile bool _pending; | |||
| /* true if first block is still being built */ | |||
| mutable volatile bool _first_block_pending; | |||
| mutable volatile bool _mipmaps_pending; | |||
| mutable Thread _make_peaks_thread; | |||
| mutable Thread _make_peaks_mipmap_thread; | |||
| @@ -110,6 +112,8 @@ class Peaks | |||
| mutable float _fpp; | |||
| volatile mutable bool _rescan_needed; | |||
| nframes_t read_peaks ( nframes_t s, nframes_t npeaks, nframes_t chunksize ) const; | |||
| nframes_t read_source_peaks ( Peak *peaks, nframes_t s, nframes_t npeaks, nframes_t chunksize ) const; | |||
| nframes_t read_source_peaks ( Peak *peaks, nframes_t npeaks, nframes_t chunksize ) const; | |||
| @@ -145,11 +149,11 @@ public: | |||
| bool ready ( nframes_t s, nframes_t npeaks, nframes_t chunksize ) const; | |||
| bool make_peaks ( void ) const; | |||
| bool make_peaks_mipmap ( void ) const; | |||
| void make_peaks_asynchronously ( void(*callback)(void*), void *userdata ) const; | |||
| void prepare_for_writing ( void ); | |||
| void finish_writing ( void ); | |||
| void write ( sample_t *buf, nframes_t nframes ); | |||
| bool needs_more_peaks ( void ) const; | |||
| }; | |||
| @@ -34,11 +34,16 @@ | |||
| bool | |||
| Timeline::record ( void ) | |||
| { | |||
| THREAD_ASSERT( UI ); | |||
| DMESSAGE( "Initiating recording." ); | |||
| /* FIXME: right place for this? */ | |||
| if ( transport->automatically_create_takes() && | |||
| ! _created_new_takes ) | |||
| { | |||
| DMESSAGE( "Creating new takes." ); | |||
| add_take_for_armed_tracks(); | |||
| _created_new_takes = true; | |||
| } | |||
| @@ -53,12 +58,16 @@ Timeline::record ( void ) | |||
| if ( transport->punch_enabled() ) | |||
| { | |||
| DMESSAGE( "Finding next punch region following frame %lu...", (unsigned long)frame); | |||
| const Sequence_Widget *w = punch_cursor_track->next( frame ); | |||
| if ( w && w->start() >= frame ) | |||
| { | |||
| frame = w->start(); | |||
| _punch_out_frame = w->start() + w->length(); | |||
| DMESSAGE( "Punch enabled... Will punch in at frame %lu.", (unsigned long)frame ); | |||
| } | |||
| } | |||
| @@ -94,6 +103,8 @@ Timeline::punch_in ( nframes_t frame ) | |||
| void | |||
| Timeline::punch_out ( nframes_t frame ) | |||
| { | |||
| THREAD_ASSERT( UI ); | |||
| for ( int i = tracks->children(); i-- ; ) | |||
| { | |||
| Track *t = (Track*)tracks->child( i ); | |||
| @@ -102,9 +113,11 @@ Timeline::punch_out ( nframes_t frame ) | |||
| t->record_ds->stop( frame ); | |||
| } | |||
| /* wait until finalization is complete before continuing */ | |||
| DMESSAGE( "Waiting for record threads to shutdown." ); | |||
| /* none of the record threads need to call Fl::lock, because we're | |||
| * holding up the UI thread waiting for them to join.*/ | |||
| DMESSAGE( "Waiting for record threads to shutdown" ); | |||
| for ( int i = tracks->children(); i-- ; ) | |||
| { | |||
| Track *t = (Track*)tracks->child( i ); | |||
| @@ -112,6 +125,8 @@ Timeline::punch_out ( nframes_t frame ) | |||
| if ( t->armed() && t->record_ds ) | |||
| t->record_ds->shutdown(); | |||
| } | |||
| DMESSAGE( "All record threads stopped." ); | |||
| _punched_in = false; | |||
| _punch_in_frame = 0; | |||
| @@ -122,6 +137,8 @@ Timeline::punch_out ( nframes_t frame ) | |||
| void | |||
| Timeline::stop ( void ) | |||
| { | |||
| THREAD_ASSERT( UI ); | |||
| nframes_t frame = transport->frame; | |||
| if ( transport->punch_enabled() ) | |||
| @@ -272,15 +272,19 @@ Track::record ( Capture *c, nframes_t frame ) | |||
| /* open it again for reading in the GUI thread */ | |||
| // Audio_File *af = Audio_File::from_file( c->audio_file->name() ); | |||
| /* must acquire the FLTK lock because adding a widget might interfere with drawing */ | |||
| Fl::lock(); | |||
| /* must acquire a write lock because the Audio_Region constructor | |||
| * will add the region to the specified sequence */ | |||
| * will add the region to the specified sequence, which might affect playback */ | |||
| timeline->wrlock(); | |||
| c->region = new Audio_Region( c->audio_file, sequence(), frame ); | |||
| timeline->unlock(); | |||
| Fl::unlock(); | |||
| c->region->prepare(); | |||
| } | |||
| @@ -305,14 +309,11 @@ Track::finalize ( Capture *c, nframes_t frame ) | |||
| /* adjust region start for latency */ | |||
| /* FIXME: is just looking at the first channel good enough? */ | |||
| timeline->wrlock(); | |||
| DMESSAGE( "finalizing audio file" ); | |||
| /* must finalize audio before peaks file, otherwise another thread | |||
| * might think the peaks are out of date and attempt to regenerate | |||
| * them */ | |||
| c->audio_file->finalize(); | |||
| timeline->wrlock(); | |||
| c->region->finalize( frame ); | |||
| nframes_t capture_offset = 0; | |||
| @@ -333,6 +334,4 @@ Track::finalize ( Capture *c, nframes_t frame ) | |||
| c->region->offset( capture_offset ); | |||
| timeline->unlock(); | |||
| // delete c->audio_file; | |||
| } | |||
| @@ -131,9 +131,7 @@ Project::write_info ( void ) | |||
| void | |||
| Project::undo ( void ) | |||
| { | |||
| timeline->wrlock(); | |||
| Loggable::undo(); | |||
| timeline->unlock(); | |||
| } | |||
| bool | |||
| @@ -203,12 +201,8 @@ Project::close ( void ) | |||
| if ( ! save() ) | |||
| return false; | |||
| timeline->wrlock(); | |||
| Loggable::close(); | |||
| timeline->unlock(); | |||
| // write_info(); | |||
| _is_open = false; | |||
| @@ -188,14 +188,11 @@ Sequence::add ( Sequence_Widget *r ) | |||
| { | |||
| // Logger _log( this ); | |||
| if ( r->sequence() ) | |||
| if ( r->sequence() && r->sequence() != this ) | |||
| { | |||
| /* This method can be called from the Capture thread as well as the GUI thread, so we must lock FLTK before redraw */ | |||
| Fl::lock(); | |||
| r->redraw(); | |||
| r->sequence()->remove( r ); | |||
| Fl::unlock(); | |||
| // r->track()->redraw(); | |||
| } | |||
| r->sequence( this ); | |||
| @@ -481,28 +478,30 @@ Sequence::handle ( int m ) | |||
| Sequence_Widget::pushed( NULL ); | |||
| } | |||
| Loggable::block_start(); | |||
| timeline->wrlock(); | |||
| while ( _delete_queue.size() ) | |||
| if ( _delete_queue.size() ) | |||
| { | |||
| Sequence_Widget *t = _delete_queue.front(); | |||
| _delete_queue.pop(); | |||
| if ( Sequence_Widget::pushed() == t ) | |||
| Sequence_Widget::pushed( NULL ); | |||
| if ( Sequence_Widget::belowmouse() == t ) | |||
| Loggable::block_start(); | |||
| while ( _delete_queue.size() ) | |||
| { | |||
| Sequence_Widget::belowmouse()->handle( FL_LEAVE ); | |||
| Sequence_Widget::belowmouse( NULL ); | |||
| } | |||
| Sequence_Widget *t = _delete_queue.front(); | |||
| _delete_queue.pop(); | |||
| if ( Sequence_Widget::pushed() == t ) | |||
| Sequence_Widget::pushed( NULL ); | |||
| if ( Sequence_Widget::belowmouse() == t ) | |||
| { | |||
| Sequence_Widget::belowmouse()->handle( FL_LEAVE ); | |||
| Sequence_Widget::belowmouse( NULL ); | |||
| } | |||
| delete t; | |||
| timeline->wrlock(); | |||
| delete t; | |||
| timeline->unlock(); | |||
| } | |||
| timeline->unlock(); | |||
| Loggable::block_end(); | |||
| } | |||
| if ( m == FL_PUSH ) | |||
| return 1; | |||
| @@ -550,6 +549,7 @@ const Sequence_Widget * | |||
| Sequence::next ( nframes_t from ) const | |||
| { | |||
| for ( list <Sequence_Widget*>::const_iterator i = _widgets.begin(); i != _widgets.end(); i++ ) | |||
| // if ( (*i)->start() >= from ) | |||
| if ( (*i)->start() > from ) | |||
| return *i; | |||
| @@ -396,7 +396,7 @@ Project::compact();} | |||
| } { | |||
| MenuItem {} { | |||
| label Undo | |||
| callback {Project::undo();} | |||
| callback {timeline->command_undo();} | |||
| xywh {5 5 40 25} shortcut 0x4007a divider | |||
| } | |||
| MenuItem {} { | |||
| @@ -1253,6 +1253,8 @@ Timeline::draw_cursors ( void ) const | |||
| void | |||
| Timeline::draw ( void ) | |||
| { | |||
| /* Any code that might affect the structures used for drawing from | |||
| * another thread must use Fl::lock()/unlock()! */ | |||
| THREAD_ASSERT( UI ); | |||
| int X, Y, W, H; | |||
| @@ -1260,8 +1262,6 @@ Timeline::draw ( void ) | |||
| int bdx = 0; | |||
| int bdw = 0; | |||
| rdlock(); | |||
| X = tracks->x() + bdx + 1; | |||
| Y = tracks->y(); | |||
| W = tracks->w() - bdw - 1; | |||
| @@ -1363,8 +1363,6 @@ done: | |||
| _old_xposition = xoffset; | |||
| _old_yposition = panzoomer->y_value(); | |||
| unlock(); | |||
| } | |||
| void | |||
| @@ -1438,6 +1436,7 @@ Timeline::redraw_playhead ( void ) | |||
| /* we've passed one or more punch regions... punch in for the next, if available. */ | |||
| const Sequence_Widget *w = punch_cursor_track->next( transport->frame ); | |||
| DMESSAGE( "Delayed punch in" ); | |||
| if ( w && | |||
| w->start() > transport->frame ) | |||
| { | |||
| @@ -1993,15 +1992,27 @@ Timeline::command_remove_track ( Track *track ) | |||
| } | |||
| void | |||
| Timeline::command_quit ( ) | |||
| Timeline::command_quit ( void ) | |||
| { | |||
| timeline->wrlock(); | |||
| Project::close(); | |||
| timeline->unlock(); | |||
| command_save(); | |||
| while ( Fl::first_window() ) Fl::first_window()->hide(); | |||
| } | |||
| void | |||
| Timeline::command_undo ( void ) | |||
| { | |||
| wrlock(); | |||
| Project::undo(); | |||
| unlock(); | |||
| } | |||
| bool | |||
| Timeline::command_load ( const char *name, const char *display_name ) | |||
| { | |||
| @@ -257,6 +257,7 @@ public: | |||
| void command_move_track_up ( Track *track ); | |||
| void command_move_track_down ( Track *track ); | |||
| void command_undo ( void ); | |||
| int find_track ( const Track * track ) const; | |||