| @@ -0,0 +1,96 @@ | |||||
| /*******************************************************************************/ | |||||
| /* 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. */ | |||||
| /*******************************************************************************/ | |||||
| static float seconds_to_buffer = 5.0f; | |||||
| /* 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). */ | |||||
| /* FIXME: handle termination of IO thread in destructor */ | |||||
| /* FIXME: could all of this not simply be included in the Track_Header | |||||
| class? */ | |||||
| /** start Disk_Stream thread */ | |||||
| void | |||||
| Disk_Stream::run ( void ) | |||||
| { | |||||
| if ( pthread_create( 0, 0, &Disk_Stream::io_thread, this ) != 0 ) | |||||
| /* error */; | |||||
| } | |||||
| /* static wrapper */ | |||||
| void | |||||
| Disk_Stream::io_thread ( void *arg ) | |||||
| { | |||||
| ((Disk_Stream*)arg)->io_thread(); | |||||
| } | |||||
| /* THREAD: IO */ | |||||
| /** read a block of data from the track into /buf/ */ | |||||
| Disk_Stream::read_block ( sample_t *buf ) | |||||
| { | |||||
| if ( _th->track()->play( buf, _frame, _nframes, channels() ) ) | |||||
| _frame += nframes; | |||||
| else | |||||
| /* error */; | |||||
| } | |||||
| /* THREAD: IO */ | |||||
| void | |||||
| Disk_Stream::io_thread ( void ) | |||||
| { | |||||
| printf( "IO thread running...\n" ); | |||||
| /* buffer to hold the interleaved data returned by the track reader */ | |||||
| sample_t *buf = new sample_t[ _nframes * channels() ]; | |||||
| /* buffer for a single channel */ | |||||
| sample_t *cbuf = new sample_t[ _nframes ]; | |||||
| const size_t block_size = _nframes * sizeof( sample_t ); | |||||
| while ( wait_for_block() ) | |||||
| { | |||||
| read_block( buf ); | |||||
| /* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | |||||
| for ( int i = channels(); i-- ) | |||||
| { | |||||
| int k = 0; | |||||
| for ( int j = i; j < _nframes; j += channels() ) | |||||
| cbuf[ k++ ] = buf[ j ]; | |||||
| jack_ringbuffer_write( _rb[ i ], cbuf, block_size ); | |||||
| } | |||||
| } | |||||
| delete[] buf; | |||||
| delete[] cbuf; | |||||
| } | |||||
| /* THREAD: RT */ | |||||
| void | |||||
| Disk_Stream::process ( nframes_t nframes ) | |||||
| { | |||||
| _th->channels(); | |||||
| block_processed(); | |||||
| } | |||||
| @@ -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. */ | |||||
| /*******************************************************************************/ | |||||
| #pragma once | |||||
| #include <jack/ringbuffer.h> | |||||
| #include <semaphore.h> | |||||
| class Disk_Stream | |||||
| { | |||||
| const Track_Header *_th; /* Track_Header we whould be playing */ | |||||
| nframes_t _nframes; | |||||
| vector <jack_ringbuffer_t *> _rb; | |||||
| // jack_ringbuffer_t *_rb; /* One interleaved ringbuffer for all channels */ | |||||
| sem_t _blocks; /* semaphore to wake the IO thread with */ | |||||
| int channels ( void ) const { return _rb.size(); } | |||||
| protected: | |||||
| void block_processed ( void ) { sem_post( &_blocks ); } | |||||
| bool wait_for_block ( void ) { while ( sem_wait( &_work ) == EINTR ); return true; } | |||||
| public: | |||||
| static float seconds_to_buffer; | |||||
| Disk_Stream ( const Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : _th( th ) | |||||
| { | |||||
| _frame = 0; | |||||
| const int blocks = frame_rate * seconds_to_buffer / nframes; | |||||
| _nframes = nframes; | |||||
| size_t bufsize = blocks * nframes * sizeof( sample_t ); | |||||
| for ( int i = channels(); i-- ) | |||||
| _rb[ i ] = jack_ringbuffer_create( bufsize ); | |||||
| sem_init( &_blocks, 0, blocks ); | |||||
| run(); | |||||
| } | |||||
| virtual ~Disk_Stream ( ) | |||||
| { | |||||
| _th = NULL; | |||||
| sem_destroy( &_blocks ); | |||||
| for ( int i = channels(); i-- ) | |||||
| jack_ringbuffer_free( _rb[ i ] ); | |||||
| } | |||||
| void | |||||
| resize_buffer ( void ) | |||||
| { | |||||
| } | |||||
| void | |||||
| seek ( nframes_t frame ) | |||||
| { | |||||
| _frame = frame; | |||||
| } | |||||
| void run ( void ); | |||||
| }; | |||||
| @@ -0,0 +1,52 @@ | |||||
| /*******************************************************************************/ | |||||
| /* 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. */ | |||||
| /*******************************************************************************/ | |||||
| /* RT/thread-safe interface to a single jack port. */ | |||||
| /* nframes is the number of frames to buffer */ | |||||
| Port::Port ( jack_port_t *port, nframes_t nframes ) | |||||
| { | |||||
| _port = port; | |||||
| _rb = jack_ringbuffer_create( nframes * sizeof( stample_t ) ); | |||||
| _name = jack_port_name( _port ); | |||||
| } | |||||
| Port::~Port ( ) | |||||
| { | |||||
| jack_ringbuffer_free( _rb ); | |||||
| } | |||||
| nframes_t | |||||
| Port::write ( sample_t *buf, nframes_t nframes ) | |||||
| { | |||||
| const size_t size = nframes * sizeof( sample_t ); | |||||
| return jack_ringbuffer_write( _rb, buf, size ) / sizeof( sample_t ); | |||||
| } | |||||
| /* runs in the RT thread! */ | |||||
| void | |||||
| Port::process ( nframes_t nframes ) | |||||
| { | |||||
| sample_t *buf = jack_port_get_buffer( _port, nframes ); | |||||
| /* FIXME: check size */ | |||||
| jack_ringbuffer_read( _rb, buf, nframes ); | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| /*******************************************************************************/ | |||||
| /* 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. */ | |||||
| /*******************************************************************************/ | |||||
| class Port | |||||
| { | |||||
| jack_ringbuffer_t _rb; | |||||
| jack_port_t *_port; | |||||
| const char *_name; | |||||
| public: | |||||
| Port ( jack_port_t *port, nframes_t nframes ); | |||||
| ~Port ( ); | |||||
| bool connected ( void ) const { return jack_port_connected( _port ); } | |||||
| const char * name ( void ) const { return _name; } | |||||
| nframes_t write ( sample_t *buf, nframes_t nframes ); | |||||
| void process ( nframes_t nframes ); | |||||
| }; | |||||
| @@ -447,7 +447,7 @@ Region::draw ( int X, int Y, int W, int H ) | |||||
| int rw = timeline->ts_to_x( _r->end - _r->start ); | int rw = timeline->ts_to_x( _r->end - _r->start ); | ||||
| nframes_t end = _r->offset + ( _r->end - _r->start ); | |||||
| // nframes_t end = _r->offset + ( _r->end - _r->start ); | |||||
| /* calculate waveform offset due to scrolling */ | /* calculate waveform offset due to scrolling */ | ||||
| nframes_t offset = 0; | nframes_t offset = 0; | ||||
| @@ -537,3 +537,66 @@ Region::normalize ( void ) | |||||
| /* _scale = _clip->peaks( 0 )->normalization_factor( timeline->fpp(), _r->start, _r->end ); */ | /* _scale = _clip->peaks( 0 )->normalization_factor( timeline->fpp(), _r->start, _r->end ); */ | ||||
| } | } | ||||
| /** read the overlapping part of /channel/ at /pos/ for /nframes/ of | |||||
| this region into /buf/, where /pos/ is in timeline frames */ | |||||
| /* this runs in the diskstream thread. */ | |||||
| /* FIXME: it is far more efficient to read all the channels from a | |||||
| multichannel source at once... But how should we handle the case of a | |||||
| mismatch between the number of channels in this region's source and | |||||
| the number of channels on the track/buffer this data is being read | |||||
| for? Would it not be better to simply buffer and deinterlace the | |||||
| frames in the Audio_File class instead, so that sequential requests | |||||
| for different channels at the same position avoid hitting the disk | |||||
| again? */ | |||||
| /* FIXME: should fade-out/fade-ins not be handled here? */ | |||||
| nframes_t | |||||
| Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) | |||||
| { | |||||
| 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 ) ) | |||||
| return 0; | |||||
| /* calculate offsets into file and sample buffer */ | |||||
| nframes_t sofs, ofs, cnt; | |||||
| if ( pos < r.offset ) | |||||
| { | |||||
| sofs = 0; | |||||
| ofs = r.offset - pos; | |||||
| cnt = nframes - ofs; | |||||
| } | |||||
| else | |||||
| { | |||||
| ofs = 0; | |||||
| sofs = pos - r.offset; | |||||
| } | |||||
| if ( sofs > nframes ) | |||||
| return 0; | |||||
| const nframes_t start = ofs + r.start + sofs; | |||||
| const nframes_t len = min( cnt, nframes - sofs ); | |||||
| const nframes_t end = start + len; | |||||
| if ( len == 0 ) | |||||
| return 0; | |||||
| /* now that we know how much and where to read, get on with it */ | |||||
| /* FIXME: seeking can be very expensive. Esp. with compressed | |||||
| * formats. We should attempt to avoid it. But here or in the | |||||
| * Audio_File class? */ | |||||
| cnt = _clip->read( buf + ofs, channel, start, end ); | |||||
| /* apply gain */ | |||||
| for ( int i = cnt; i--; ) | |||||
| buf[i] *= _scale; | |||||
| return cnt; | |||||
| } | |||||
| @@ -206,5 +206,7 @@ public: | |||||
| void normalize ( void ); | void normalize ( void ); | ||||
| nframes_t read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ); | |||||
| }; | }; | ||||
| #endif | #endif | ||||