/*******************************************************************************/ /* 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. */ /*******************************************************************************/ /* Handles streaming from track inputs to disk */ /* FIXME: we shouldn't depend on these */ #include "../Timeline.H" // for locking #include "../Audio_Sequence.H" #include "../Track.H" // #include "Port.H" #include "Record_DS.H" #include "Engine.H" #include "dsp.h" #include "const.h" #include "debug.h" #include "Thread.H" const Audio_Region * Record_DS::capture_region ( void ) const { if ( _capture ) return _capture->region; else return NULL; } Track::Capture * Record_DS::capture ( void ) { return _capture; } /** write /nframes/ from buf to the capture file of the attached track */ void Record_DS::write_block ( sample_t *buf, nframes_t nframes ) { THREAD_ASSERT( Capture ); /* stupid chicken/egg */ if ( ! ( timeline && sequence() ) ) return; track()->write( _capture, buf, nframes ); _frames_written += nframes; } #define AVOID_UNNECESSARY_COPYING 1 void Record_DS::disk_thread ( void ) { _thread.name( "Capture" ); DMESSAGE( "capture thread running..." ); track()->record( _capture, _frame ); const nframes_t nframes = _nframes * _disk_io_blocks; /* buffer to hold the interleaved data returned by the track reader */ sample_t *buf = new sample_t[ nframes * channels() ]; #ifndef AVOID_UNNECESSARY_COPYING sample_t *cbuf = new sample_t[ nframes ]; #endif const size_t block_size = nframes * sizeof( sample_t ); int blocks_ready = 0; while ( wait_for_block() ) { if ( ++blocks_ready < _disk_io_blocks ) continue; else blocks_ready = 0; /* pull data from the per-channel ringbuffers and interlace it */ for ( int i = channels(); i--; ) { #ifdef AVOID_UNNECESSARY_COPYING /* interleave direcectly from the ringbuffer to avoid * unnecessary copying */ jack_ringbuffer_data_t rbd[2]; memset( rbd, 0, sizeof( rbd ) ); jack_ringbuffer_get_read_vector( _rb[ i ], rbd ); if ( rbd[ 0 ].len >= block_size ) { /* it'll all fit in one go */ buffer_interleave_one_channel( buf, (sample_t*)rbd[ 0 ].buf, i, channels(), nframes ); } else if ( rbd[ 0 ].len + rbd[ 1 ].len >= block_size ) { /* there's enough space in the ringbuffer, but it's not contiguous */ assert( ! ( rbd[ 0 ].len % sizeof( sample_t ) ) ); const nframes_t f = rbd[ 0 ].len / sizeof( sample_t ); /* do the first half */ buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), f ); buffer_interleave_one_channel( buf, (sample_t*)rbd[ 0 ].buf, i, channels(), f ); assert( rbd[ 1 ].len >= ( nframes - f ) * sizeof( sample_t ) ); /* do the second half */ buffer_interleave_one_channel( buf + f, (sample_t*)rbd[ 0 ].buf, i, channels(), nframes - f ); } else ++_xruns; jack_ringbuffer_read_advance( _rb[ i ], block_size ); #else if ( jack_ringbuffer_read( _rb[ i ], (char*)cbuf, block_size ) < block_size ) ++_xruns; buffer_interleave_one_channel( buf, cbuf, i, channels(), nframes ); #endif } write_block( buf, nframes ); } DMESSAGE( "capture thread terminating" ); /* flush what remains in the buffer out to disk */ { /* use JACk sized blocks for this last bit */ const nframes_t nframes = _nframes; const size_t block_size = _nframes * sizeof( sample_t ); #ifdef AVOID_UNNECESSARY_COPYING sample_t *cbuf = new sample_t[ nframes ]; #endif while ( blocks_ready-- > 0 || ( ! sem_trywait( &_blocks ) && errno != EAGAIN ) ) { for ( int i = channels(); i--; ) { jack_ringbuffer_read( _rb[ i ], (char*)cbuf, block_size ); buffer_interleave_one_channel( buf, cbuf, i, channels(), nframes ); } const nframes_t frames_remaining = (_stop_frame - _frame ) - _frames_written; if ( frames_remaining < nframes ) { /* this is the last block, might be partial */ write_block( buf, frames_remaining ); break; } else write_block( buf, nframes ); } #ifdef AVOID_UNNECESSARY_COPYING delete[] cbuf; #endif } delete[] buf; #ifndef AVOID_UNNECESSARY_COPYING delete[] cbuf; #endif DMESSAGE( "finalzing capture" ); Track::Capture *c = _capture; _capture = NULL; /* now finalize the recording */ track()->finalize( c, _stop_frame ); delete c; _terminate = false; DMESSAGE( "capture thread gone" ); _thread.exit(); } /** begin recording */ void Record_DS::start ( nframes_t frame ) { THREAD_ASSERT( UI ); if ( _recording ) { WARNING( "programming error: attempt to start recording while recording is still in progress" ); return; } /* /\* FIXME: safe to do this here? *\/ */ /* flush(); */ DMESSAGE( "recording started at frame %lu", (unsigned long)frame); _frame = frame; _capture = new Track::Capture; run(); _recording = true; } /** finalize the recording process. */ void Record_DS::stop ( nframes_t frame ) { THREAD_ASSERT( UI ); if ( ! _recording ) { WARNING( "programming error: attempt to stop recording when no recording is being made" ); return; } _recording = false; _stop_frame = frame; // detach(); DMESSAGE( "recording finished" ); } #include "../Transport.H" extern Transport *transport; /** read from the attached track's ports and stuff the ringbuffers */ nframes_t Record_DS::process ( nframes_t nframes ) { THREAD_ASSERT( RT ); if ( ! _recording ) return 0; if ( transport->frame < _frame ) return 0; /* DMESSAGE( "recording actually happening at %lu (start frame %lu)", (unsigned long)transport->frame, (unsigned long)_frame); */ nframes_t offset = 0; if ( _frame > transport->frame && _frame < transport->frame + nframes ) { /* The record start frame falls somewhere within the current buffer. We must discard the unneeded portion and only stuff the part requested into the ringbuffer. */ offset = _frame - transport->frame; /* DMESSAGE( "offset = %lu", (unsigned long)offset ); */ } const size_t offset_size = offset * sizeof( sample_t ); const size_t block_size = ( nframes * sizeof( sample_t ) ) - offset_size; for ( int i = channels(); i--; ) { /* read the entire input buffer */ void *buf = track()->input[ i ].buffer( nframes ); /* if ( buffer_is_digital_black( (sample_t*)buf, nframes ) ) */ /* DWARNING( "recording an entirely blank buffer" ); */ /* FIXME: this results in a ringbuffer size that is no longer necessarily a multiple of nframes... how will the other side handle that? */ if ( jack_ringbuffer_write( _rb[ i ], (char*)buf + offset, block_size ) < block_size ) { ++_xruns; memset( buf, 0, block_size ); /* FIXME: we need to resync somehow */ } } block_processed(); /* FIXME: bogus */ return nframes; }