2017-03-03 10:14:07 -05:00
/*
2019-08-02 22:01:25 -04:00
* Copyright ( C ) 2017 - 2018 Paul Davis < paul @ linuxaudiosystems . com >
* Copyright ( C ) 2017 - 2019 Robin Gareus < robin @ gareus . org >
2019-02-05 14:51:33 -05:00
*
2019-08-02 22:01:25 -04:00
* 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 .
2019-02-05 14:51:33 -05:00
*
* 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 .
*
2019-08-02 22:01:25 -04:00
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
2019-02-05 14:51:33 -05:00
*/
2017-03-03 10:14:07 -05:00
2018-07-31 10:54:08 -04:00
# include <boost/smart_ptr/scoped_array.hpp>
2017-10-26 15:43:08 -04:00
2017-06-11 19:53:02 -04:00
# include "pbd/enumwriter.h"
2017-03-06 07:07:34 -05:00
# include "pbd/memento_command.h"
2019-02-05 14:51:33 -05:00
# include "pbd/playback_buffer.h"
2017-03-03 10:14:07 -05:00
2020-09-20 18:34:09 -04:00
# include "temporal/range.h"
2019-11-23 01:41:56 -05:00
2019-02-07 09:00:37 -05:00
# include "ardour/amp.h"
2020-04-07 10:20:53 -04:00
# include "ardour/audio_buffer.h"
2017-03-07 13:40:37 -05:00
# include "ardour/audioengine.h"
2017-03-06 06:47:46 -05:00
# include "ardour/audioplaylist.h"
# include "ardour/butler.h"
2017-03-03 10:14:07 -05:00
# include "ardour/debug.h"
# include "ardour/disk_reader.h"
2017-03-07 13:40:37 -05:00
# include "ardour/midi_playlist.h"
2020-04-07 10:20:53 -04:00
# include "ardour/midi_ring_buffer.h"
2017-07-25 12:58:23 -04:00
# include "ardour/midi_track.h"
2017-03-16 12:26:53 -04:00
# include "ardour/pannable.h"
2017-03-03 10:14:07 -05:00
# include "ardour/playlist.h"
2017-03-06 06:47:46 -05:00
# include "ardour/playlist_factory.h"
2017-03-03 10:14:07 -05:00
# include "ardour/session.h"
2017-03-06 06:47:46 -05:00
# include "ardour/session_playlists.h"
2017-03-03 10:14:07 -05:00
2017-09-21 13:38:13 -04:00
# include "pbd/i18n.h"
2017-03-03 10:14:07 -05:00
using namespace ARDOUR ;
using namespace PBD ;
using namespace std ;
2020-04-07 10:20:53 -04:00
ARDOUR : : samplecnt_t DiskReader : : _chunk_samples = default_chunk_samples ( ) ;
PBD : : Signal0 < void > DiskReader : : Underrun ;
2024-04-29 21:46:39 -04:00
thread_local Sample * DiskReader : : _sum_buffer = 0 ;
thread_local Sample * DiskReader : : _mixdown_buffer = 0 ;
thread_local gain_t * DiskReader : : _gain_buffer = 0 ;
2023-05-22 15:04:37 -04:00
std : : atomic < int > DiskReader : : _no_disk_output ( 0 ) ;
2019-11-23 01:41:56 -05:00
DiskReader : : Declicker DiskReader : : loop_declick_in ;
DiskReader : : Declicker DiskReader : : loop_declick_out ;
2020-04-07 10:20:53 -04:00
samplecnt_t DiskReader : : loop_fade_length ( 0 ) ;
2017-03-03 10:14:07 -05:00
2023-07-21 11:56:42 -04:00
DiskReader : : DiskReader ( Session & s , Track & t , string const & str , Temporal : : TimeDomainProvider const & tdp , DiskIOProcessor : : Flag f )
: DiskIOProcessor ( s , t , X_ ( " player: " ) + str , f , tdp )
2017-09-18 12:39:17 -04:00
, overwrite_sample ( 0 )
2019-10-17 19:32:56 -04:00
, run_must_resolve ( false )
2019-02-07 09:00:37 -05:00
, _declick_amp ( s . nominal_sample_rate ( ) )
, _declick_offs ( 0 )
2020-05-04 15:51:53 -04:00
, _declick_enabled ( false )
2020-05-12 12:06:36 -04:00
, last_refill_loop_start ( 0 )
2023-11-20 23:32:49 -05:00
, _midi_catchup ( false )
, _need_midi_catchup ( false )
2017-03-03 10:14:07 -05:00
{
2017-09-18 12:39:17 -04:00
file_sample [ DataType : : AUDIO ] = 0 ;
2020-04-07 10:20:53 -04:00
file_sample [ DataType : : MIDI ] = 0 ;
2023-02-17 02:31:21 -05:00
_pending_overwrite . store ( OverwriteReason ( 0 ) ) ;
2017-03-03 10:14:07 -05:00
}
DiskReader : : ~ DiskReader ( )
{
2017-06-21 12:57:41 -04:00
DEBUG_TRACE ( DEBUG : : Destruction , string_compose ( " DiskReader %1 @ %2 deleted \n " , _name , this ) ) ;
2018-05-22 15:35:27 -04:00
}
2017-03-06 06:47:46 -05:00
2020-08-19 11:39:32 -04:00
std : : string
DiskReader : : display_name ( ) const
{
2021-02-19 08:01:00 -05:00
return std : : string ( _ ( " Player " ) ) ;
2020-08-19 11:39:32 -04:00
}
2018-05-22 15:35:27 -04:00
void
DiskReader : : ReaderChannelInfo : : resize ( samplecnt_t bufsize )
{
2020-04-07 10:20:53 -04:00
delete rbuf ;
rbuf = 0 ;
2019-12-05 17:01:41 -05:00
2019-12-10 11:28:41 -05:00
rbuf = new PlaybackBuffer < Sample > ( bufsize ) ;
2019-12-06 16:21:40 -05:00
/* touch memory to lock it */
2020-04-07 10:20:53 -04:00
memset ( rbuf - > buffer ( ) , 0 , sizeof ( Sample ) * rbuf - > bufsize ( ) ) ;
2020-04-07 19:08:57 -04:00
initialized = false ;
2018-05-22 15:35:27 -04:00
}
2017-03-06 06:47:46 -05:00
2019-11-23 01:41:56 -05:00
void
DiskReader : : ReaderChannelInfo : : resize_preloop ( samplecnt_t bufsize )
{
if ( bufsize = = 0 ) {
return ;
}
if ( bufsize > pre_loop_buffer_size ) {
2020-04-07 10:20:53 -04:00
delete [ ] pre_loop_buffer ;
pre_loop_buffer = new Sample [ bufsize ] ;
2019-11-23 01:41:56 -05:00
pre_loop_buffer_size = bufsize ;
}
}
2018-05-22 15:35:27 -04:00
int
2023-02-16 18:33:28 -05:00
DiskReader : : add_channel_to ( std : : shared_ptr < ChannelList > c , uint32_t how_many )
2018-05-22 15:35:27 -04:00
{
while ( how_many - - ) {
2020-04-07 10:20:53 -04:00
c - > push_back ( new ReaderChannelInfo ( _session . butler ( ) - > audio_playback_buffer_size ( ) , loop_fade_length ) ) ;
2018-05-22 15:35:27 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1: new reader channel, write space = %2 read = %3 \n " ,
2020-04-07 10:20:53 -04:00
name ( ) ,
c - > back ( ) - > rbuf - > write_space ( ) ,
c - > back ( ) - > rbuf - > read_space ( ) ) ) ;
2017-03-06 06:47:46 -05:00
}
2018-05-22 15:35:27 -04:00
return 0 ;
2017-03-06 06:47:46 -05:00
}
void
2020-04-07 10:20:53 -04:00
DiskReader : : allocate_working_buffers ( )
2017-03-06 06:47:46 -05:00
{
/* with varifill buffer refilling, we compute the read size in bytes (to optimize
for disk i / o bandwidth ) and then convert back into samples . These buffers
need to reflect the maximum size we could use , which is 4 MB reads , or 2 M samples
using 16 bit samples .
*/
2020-04-07 10:20:53 -04:00
_sum_buffer = new Sample [ 2 * 1048576 ] ;
_mixdown_buffer = new Sample [ 2 * 1048576 ] ;
_gain_buffer = new gain_t [ 2 * 1048576 ] ;
2017-03-06 06:47:46 -05:00
}
void
2020-04-07 10:20:53 -04:00
DiskReader : : free_working_buffers ( )
2017-03-06 06:47:46 -05:00
{
2020-04-07 10:20:53 -04:00
delete [ ] _sum_buffer ;
delete [ ] _mixdown_buffer ;
delete [ ] _gain_buffer ;
2019-02-05 14:51:33 -05:00
_sum_buffer = 0 ;
_mixdown_buffer = 0 ;
_gain_buffer = 0 ;
2017-03-03 10:14:07 -05:00
}
2017-09-18 12:39:17 -04:00
samplecnt_t
2020-04-07 10:20:53 -04:00
DiskReader : : default_chunk_samples ( )
2017-03-03 10:14:07 -05:00
{
return 65536 ;
}
bool
2020-04-07 10:20:53 -04:00
DiskReader : : set_name ( string const & str )
2017-03-03 10:14:07 -05:00
{
2017-07-25 17:29:19 -04:00
string my_name = X_ ( " player: " ) ;
2017-04-17 06:12:38 -04:00
my_name + = str ;
if ( _name ! = my_name ) {
SessionObject : : set_name ( my_name ) ;
2017-03-03 10:14:07 -05:00
}
return true ;
}
2017-03-16 12:26:53 -04:00
XMLNode &
2022-04-06 23:56:32 -04:00
DiskReader : : state ( ) const
2017-03-16 12:26:53 -04:00
{
2017-10-03 18:35:29 -04:00
XMLNode & node ( DiskIOProcessor : : state ( ) ) ;
2020-04-07 10:20:53 -04:00
node . set_property ( X_ ( " type " ) , X_ ( " diskreader " ) ) ;
2017-03-16 12:26:53 -04:00
return node ;
}
2017-03-06 06:47:46 -05:00
int
DiskReader : : set_state ( const XMLNode & node , int version )
{
if ( DiskIOProcessor : : set_state ( node , version ) ) {
return - 1 ;
}
return 0 ;
}
void
DiskReader : : realtime_handle_transport_stopped ( )
{
2022-05-09 23:49:25 -04:00
if ( _session . exporting ( ) & & ! _session . realtime_export ( ) ) {
_declick_amp . set_gain ( 0 ) ;
}
2019-10-15 18:57:23 -04:00
/* can't do the resolve here because we don't have a place to put the
* note resolving data . Defer to
* MidiTrack : : realtime_handle_transport_stopped ( ) which will call
* : : resolve_tracker ( ) and put the output in its _immediate_events store .
*/
2017-03-06 06:47:46 -05:00
}
void
2019-11-06 18:00:00 -05:00
DiskReader : : realtime_locate ( bool for_loop_end )
2017-03-06 06:47:46 -05:00
{
2019-11-06 18:00:00 -05:00
if ( ! for_loop_end ) {
2021-02-14 15:43:06 -05:00
MidiTrack * mt = dynamic_cast < MidiTrack * > ( & _track ) ;
2020-04-07 10:20:53 -04:00
_tracker . resolve_notes ( mt - > immediate_events ( ) , 0 ) ;
2019-11-06 18:00:00 -05:00
}
2017-03-06 06:47:46 -05:00
}
float
DiskReader : : buffer_load ( ) const
{
2017-03-07 13:40:37 -05:00
/* Note: for MIDI it's not trivial to differentiate the following two cases:
2020-04-10 16:18:24 -04:00
*
2020-04-07 10:20:53 -04:00
* 1. The playback buffer is empty because the system has run out of time to fill it .
* 2. The playback buffer is empty because there is no more data on the playlist .
*
* If we use a simple buffer load computation , we will report that the MIDI diskstream
* cannot keep up when # 2 happens , when in fact it can . Since MIDI data rates
* are so low compared to audio , just use the audio value here .
*/
2017-03-07 13:40:37 -05:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2017-03-06 06:47:46 -05:00
if ( c - > empty ( ) ) {
2017-03-07 13:40:37 -05:00
/* no channels, so no buffers, so completely full and ready to playback, sir! */
2017-03-06 06:47:46 -05:00
return 1.0 ;
}
2020-04-07 10:20:53 -04:00
PBD : : PlaybackBuffer < Sample > * b = c - > front ( ) - > rbuf ;
return ( float ) ( ( double ) b - > read_space ( ) / ( double ) b - > bufsize ( ) ) ;
2017-03-06 06:47:46 -05:00
}
void
DiskReader : : adjust_buffering ( )
{
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2017-03-06 06:47:46 -05:00
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
chan - > resize ( _session . butler ( ) - > audio_playback_buffer_size ( ) ) ;
2017-03-06 06:47:46 -05:00
}
}
2017-03-03 10:14:07 -05:00
void
DiskReader : : playlist_modified ( )
{
2021-02-14 15:43:06 -05:00
_session . request_overwrite_buffer ( _track . shared_ptr ( ) , PlaylistModified ) ;
2017-03-03 10:14:07 -05:00
}
int
2023-02-16 18:33:28 -05:00
DiskReader : : use_playlist ( DataType dt , std : : shared_ptr < Playlist > playlist )
2017-03-03 10:14:07 -05:00
{
2020-04-07 09:59:18 -04:00
bool prior_playlist = false ;
2017-03-03 10:14:07 -05:00
2020-04-07 09:59:18 -04:00
if ( _playlists [ dt ] ) {
prior_playlist = true ;
}
2017-03-03 10:14:07 -05:00
2020-04-07 09:59:18 -04:00
if ( DiskIOProcessor : : use_playlist ( dt , playlist ) ) {
2017-03-16 12:26:53 -04:00
return - 1 ;
2017-03-03 10:14:07 -05:00
}
/* don't do this if we've already asked for it *or* if we are setting up
2020-04-07 09:59:18 -04:00
* the diskstream for the very first time - the input changed handling will
* take care of the buffer refill . */
2017-03-03 10:14:07 -05:00
2023-02-17 02:31:21 -05:00
if ( ! ( _pending_overwrite . load ( ) & PlaylistChanged ) | | prior_playlist ) {
2021-02-14 15:43:06 -05:00
_session . request_overwrite_buffer ( _track . shared_ptr ( ) , PlaylistChanged ) ;
2017-03-03 10:14:07 -05:00
}
return 0 ;
}
2017-03-06 06:47:46 -05:00
void
2019-11-18 22:47:47 -05:00
DiskReader : : run ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample , double speed , pframes_t nframes , bool result_required )
2017-03-06 06:47:46 -05:00
{
2023-04-07 17:33:13 -04:00
uint32_t n ;
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
ChannelList : : const_iterator chan ;
sampleoffset_t disk_samples_to_consume ;
2023-05-22 15:04:37 -04:00
MonitorState ms = _track . monitoring_state ( ) ;
const bool midi_only = ( c - > empty ( ) | | ! _playlists [ DataType : : AUDIO ] ) ;
2023-04-07 17:33:13 -04:00
bool no_disk_output = _no_disk_output . load ( ) ! = 0 ;
2017-03-07 13:40:37 -05:00
2023-05-22 15:04:37 -04:00
if ( ! check_active ( ) ) {
2021-11-24 12:28:16 -05:00
return ;
2017-06-11 19:53:02 -04:00
}
2017-04-18 07:28:44 -04:00
2019-11-18 22:47:47 -05:00
const gain_t target_gain = ( ( speed = = 0.0 ) | | ( ( ms & MonitoringDisk ) = = 0 ) ) ? 0.0 : 1.0 ;
2020-04-07 10:20:53 -04:00
bool declick_out = ( _declick_amp . gain ( ) ! = target_gain ) & & target_gain = = 0.0 ;
2020-03-31 22:53:09 -04:00
2020-05-04 15:51:53 -04:00
if ( declick_out & & _declick_amp . gain ( ) = = GAIN_COEFF_UNITY ) {
/* beginning a de-click, set de-click reason */
if ( speed = = 0 ) {
_declick_enabled = _session . cfg ( ) - > get_use_transport_fades ( ) ;
} else {
_declick_enabled = _session . cfg ( ) - > get_use_monitor_fades ( ) ;
}
2020-05-07 10:12:52 -04:00
} else if ( _declick_amp . gain ( ) = = GAIN_COEFF_ZERO & & speed = = 0 ) {
/* fade in */
_declick_enabled = _session . cfg ( ) - > get_use_transport_fades ( ) ;
2020-05-04 15:51:53 -04:00
}
if ( ! _declick_enabled | | ( _session . exporting ( ) & & ! _session . realtime_export ( ) ) ) {
2020-03-31 22:53:09 -04:00
/* no transport fades or exporting - no declick out logic */
2020-04-29 17:35:28 -04:00
if ( ! midi_only ) {
_declick_amp . set_gain ( target_gain ) ;
declick_out = false ;
}
2019-03-05 08:44:47 -05:00
2020-03-31 22:53:09 -04:00
} else {
/* using transport fades and not exporting - declick login in effect */
if ( ms = = MonitoringDisk ) {
/* Only monitoring from disk, so if we've finished a
* declick ( for stop / locate ) , do not accidentally pass
* any data from disk to our outputs .
*/
2020-04-07 10:20:53 -04:00
if ( ( target_gain = = 0.0 ) & & ( _declick_amp . gain ( ) = = target_gain ) ) {
2020-03-31 22:53:09 -04:00
/* we were heading for zero (declick out for
2020-04-07 10:20:53 -04:00
* stop ) , and we ' ve reached there . Done . */
2020-03-31 22:53:09 -04:00
return ;
}
}
2017-06-21 12:51:53 -04:00
}
2020-04-07 10:20:53 -04:00
BufferSet & scratch_bufs ( _session . get_scratch_buffers ( bufs . count ( ) ) ) ;
const bool still_locating = _session . global_locate_pending ( ) ;
2017-10-26 15:15:05 -04:00
2018-05-22 14:52:01 -04:00
assert ( speed = = - 1 | | speed = = 0 | | speed = = 1 ) ;
2019-02-06 14:22:22 -05:00
if ( speed = = 0 ) {
2018-05-22 14:52:01 -04:00
disk_samples_to_consume = 0 ;
2019-02-06 14:22:22 -05:00
} else {
disk_samples_to_consume = nframes ;
2017-04-18 07:28:44 -04:00
}
2017-03-06 06:47:46 -05:00
2020-04-29 17:35:28 -04:00
if ( midi_only ) {
2019-05-26 06:56:10 -04:00
/* do nothing with audio */
goto midi ;
}
2019-11-18 22:47:47 -05:00
if ( declick_out ) {
2019-03-05 08:44:47 -05:00
/* fade-out */
2019-11-18 22:47:47 -05:00
// printf ("DR fade-out speed=%.1f gain=%.3f off=%ld start=%ld playpos=%ld (%s)\n", speed, _declick_amp.gain (), _declick_offs, start_sample, playback_sample, owner()->name().c_str());
2019-03-05 08:44:47 -05:00
ms = MonitorState ( ms | MonitoringDisk ) ;
assert ( result_required ) ;
2023-05-22 15:04:37 -04:00
result_required = true ;
2020-05-04 15:48:47 -04:00
disk_samples_to_consume = 0 ; // non-committing read
2019-03-05 08:44:47 -05:00
} else {
_declick_offs = 0 ;
}
2021-03-19 01:12:11 -04:00
if ( ! result_required | | ( ( ms & MonitoringDisk ) = = 0 ) | | still_locating | | no_disk_output ) {
2019-11-03 11:20:00 -05:00
/* no need for actual disk data, just advance read pointer */
2017-03-06 06:47:46 -05:00
2021-03-19 01:12:11 -04:00
if ( ! still_locating | | no_disk_output ) {
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
assert ( chan - > rbuf ) ;
chan - > rbuf - > increment_read_ptr ( disk_samples_to_consume ) ;
2017-07-17 12:16:00 -04:00
}
2017-03-06 06:47:46 -05:00
}
2017-07-20 17:53:56 -04:00
/* if monitoring disk but locating put silence in the buffers */
2017-06-27 15:33:41 -04:00
2021-03-19 01:12:11 -04:00
if ( ( no_disk_output | | still_locating ) & & ( ms = = MonitoringDisk ) ) {
2017-07-27 12:32:10 -04:00
bufs . silence ( nframes , 0 ) ;
2017-06-27 15:33:41 -04:00
}
2017-03-16 12:26:53 -04:00
} else {
2017-06-11 19:53:02 -04:00
/* we need audio data from disk */
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
size_t n_buffers = bufs . count ( ) . n_audio ( ) ;
size_t n_chans = c - > size ( ) ;
2017-06-11 19:53:02 -04:00
gain_t scaling ;
2017-03-06 06:47:46 -05:00
2017-06-11 19:53:02 -04:00
if ( n_chans > n_buffers ) {
2020-04-07 10:20:53 -04:00
scaling = ( ( float ) n_buffers ) / n_chans ;
2017-06-11 19:53:02 -04:00
} else {
scaling = 1.0 ;
}
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
const float initial_declick_gain = _declick_amp . gain ( ) ;
const sampleoffset_t declick_offs = _declick_offs ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
for ( n = 0 , chan = c - > begin ( ) ; chan ! = c - > end ( ) ; + + chan , + + n ) {
2020-04-07 19:08:57 -04:00
ReaderChannelInfo * chaninfo = dynamic_cast < ReaderChannelInfo * > ( * chan ) ;
2023-05-22 15:04:37 -04:00
AudioBuffer & output ( bufs . get_audio ( n % n_buffers ) ) ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
AudioBuffer & disk_buf ( ( ms & MonitoringInput ) ? scratch_bufs . get_audio ( n ) : output ) ;
2017-03-06 06:47:46 -05:00
2019-03-05 08:44:47 -05:00
if ( start_sample ! = playback_sample & & target_gain ! = 0 ) {
2020-04-07 10:20:53 -04:00
samplepos_t ss = start_sample ;
Location * loc = _loop_location ;
2020-02-14 12:45:13 -05:00
if ( loc ) {
2020-09-20 18:34:09 -04:00
Temporal : : Range loop_range ( loc - > start ( ) , loc - > end ( ) ) ;
2023-05-22 15:04:37 -04:00
ss = loop_range . squish ( timepos_t ( playback_sample ) ) . samples ( ) ;
2020-09-20 18:34:09 -04:00
playback_sample = ss ;
2020-02-14 12:45:13 -05:00
}
2020-05-12 12:08:51 -04:00
if ( ss ! = playback_sample ) {
if ( can_internal_playback_seek ( ss - playback_sample ) ) {
internal_playback_seek ( ss - playback_sample ) ;
} else {
disk_samples_to_consume = 0 ; /* will force an underrun below */
}
2017-09-19 18:53:27 -04:00
}
}
2019-11-20 20:54:19 -05:00
/* reset _declick_amp to the correct gain before processing this channel. */
2020-02-19 19:41:53 -05:00
_declick_amp . set_gain ( initial_declick_gain ) ;
2019-12-06 16:21:40 -05:00
2019-09-17 20:26:03 -04:00
if ( ! declick_out ) {
2020-04-07 10:20:53 -04:00
const samplecnt_t available = chaninfo - > rbuf - > read ( disk_buf . data ( ) , disk_samples_to_consume ) ;
2019-11-18 22:47:47 -05:00
2020-04-07 19:08:57 -04:00
if ( available = = 0 & & ! chaninfo - > initialized ) {
disk_buf . silence ( disk_samples_to_consume ) ;
} else if ( disk_samples_to_consume > available ) {
2024-05-07 18:56:57 -04:00
# ifndef NDEBUG // not rt-safe to print here
2020-03-31 22:53:44 -04:00
cerr < < " underrun for " < < _name < < " Available samples: " < < available < < " required: " < < disk_samples_to_consume < < endl ;
2024-05-07 18:56:57 -04:00
# endif
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : Butler , string_compose ( " %1 underrun in %2, total space = %3 vs %4 \n " , DEBUG_THREAD_SELF , name ( ) , available , disk_samples_to_consume ) ) ;
2017-06-11 19:53:02 -04:00
Underrun ( ) ;
return ;
}
2019-11-18 22:47:47 -05:00
2020-04-07 10:20:53 -04:00
} else if ( _declick_amp . gain ( ) ! = target_gain ) {
2019-03-05 08:44:47 -05:00
assert ( target_gain = = 0 ) ;
2019-11-18 22:47:47 -05:00
/* note that this is a non-committing read: it
2020-04-07 10:20:53 -04:00
* retrieves data from the ringbuffer but does not
* advance the read pointer . As a result ,
* subsequent calls ( as we declick ) need to
* pass in an offset describing where to read
* from . We maintain _declick_offs across calls
* to : : run ( )
*/
2019-11-18 22:47:47 -05:00
2020-04-07 10:20:53 -04:00
const samplecnt_t total = chaninfo - > rbuf - > read ( disk_buf . data ( ) , nframes , false , declick_offs ) ;
2019-11-18 22:47:47 -05:00
2019-11-18 23:38:46 -05:00
if ( n = = 0 ) {
_declick_offs + = total ;
}
2017-03-06 06:47:46 -05:00
}
2019-03-05 08:44:47 -05:00
_declick_amp . apply_gain ( disk_buf , nframes , target_gain ) ;
2020-04-07 10:20:53 -04:00
/* _declick_amp is now left with the correct gain after processing nframes */
2019-11-18 23:38:25 -05:00
2019-03-05 08:44:47 -05:00
Amp : : apply_simple_gain ( disk_buf , nframes , scaling ) ;
2017-06-11 19:53:02 -04:00
2017-09-19 18:53:27 -04:00
if ( ms & MonitoringInput ) {
2017-06-11 19:53:02 -04:00
/* mix the disk signal into the input signal (already in bufs) */
2020-04-07 10:20:53 -04:00
mix_buffers_no_gain ( output . data ( ) , disk_buf . data ( ) , nframes ) ;
2017-06-11 19:53:02 -04:00
}
}
2017-03-06 06:47:46 -05:00
}
2020-04-07 10:20:53 -04:00
midi :
2020-01-03 18:12:45 -05:00
2017-03-16 12:26:53 -04:00
/* MIDI data handling */
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
const bool no_playlist_modification_pending = ! ( pending_overwrite ( ) & PlaylistModified ) ;
2017-07-27 12:32:10 -04:00
2020-04-07 10:20:53 -04:00
if ( bufs . count ( ) . n_midi ( ) ) {
2019-11-03 11:20:50 -05:00
MidiBuffer & dst ( bufs . get_midi ( 0 ) ) ;
if ( run_must_resolve ) {
resolve_tracker ( dst , 0 ) ;
run_must_resolve = false ;
2017-07-27 12:32:10 -04:00
}
2021-03-19 01:12:11 -04:00
if ( ! no_disk_output & & ! declick_in_progress ( ) & & ( ms & MonitoringDisk ) & & ! still_locating & & no_playlist_modification_pending & & speed ) {
2019-11-03 11:20:50 -05:00
get_midi_playback ( dst , start_sample , end_sample , ms , scratch_bufs , speed , disk_samples_to_consume ) ;
2017-03-06 06:47:46 -05:00
}
}
2019-11-03 11:20:50 -05:00
/* decide if we need the butler */
2020-02-20 02:15:32 -05:00
if ( ! still_locating & & no_playlist_modification_pending ) {
2017-07-25 12:59:31 -04:00
bool butler_required = false ;
2017-06-27 15:33:41 -04:00
if ( speed < 0.0 ) {
2017-07-27 12:32:10 -04:00
playback_sample - = disk_samples_to_consume ;
2017-06-27 15:33:41 -04:00
} else {
2017-07-27 12:32:10 -04:00
playback_sample + = disk_samples_to_consume ;
2017-06-27 15:33:41 -04:00
}
2019-11-01 15:24:37 -04:00
Location * loc = _loop_location ;
if ( loc ) {
2020-09-20 18:34:09 -04:00
Temporal : : Range loop_range ( loc - > start ( ) , loc - > end ( ) ) ;
2023-05-22 15:04:37 -04:00
playback_sample = loop_range . squish ( timepos_t ( playback_sample ) ) . samples ( ) ;
2019-11-01 15:24:37 -04:00
}
2017-06-27 15:33:41 -04:00
if ( _playlists [ DataType : : AUDIO ] ) {
2020-04-07 10:20:53 -04:00
if ( ! c - > empty ( ) ) {
2017-06-27 15:33:41 -04:00
if ( _slaved ) {
2020-04-07 10:20:53 -04:00
if ( c - > front ( ) - > rbuf - > write_space ( ) > = c - > front ( ) - > rbuf - > bufsize ( ) / 2 ) {
DEBUG_TRACE ( DEBUG : : Butler , string_compose ( " %1: slaved, write space = %2 of %3 \n " , name ( ) , c - > front ( ) - > rbuf - > write_space ( ) , c - > front ( ) - > rbuf - > bufsize ( ) ) ) ;
2017-07-25 12:59:31 -04:00
butler_required = true ;
2017-06-27 15:33:41 -04:00
}
} else {
2020-04-07 10:20:53 -04:00
if ( ( samplecnt_t ) c - > front ( ) - > rbuf - > write_space ( ) > = _chunk_samples ) {
DEBUG_TRACE ( DEBUG : : Butler , string_compose ( " %1: write space = %2 of %3 \n " , name ( ) , c - > front ( ) - > rbuf - > write_space ( ) ,
2017-09-18 12:39:17 -04:00
_chunk_samples ) ) ;
2017-07-25 12:59:31 -04:00
butler_required = true ;
2017-06-27 15:33:41 -04:00
}
2017-04-11 12:38:34 -04:00
}
2017-03-07 13:40:37 -05:00
}
2017-03-06 06:47:46 -05:00
}
2017-04-11 12:38:34 -04:00
2019-10-14 21:00:32 -04:00
/* All of MIDI is in RAM, no need to call the butler unless we
* have to overwrite buffers because of a playlist change .
*/
2017-07-25 12:59:31 -04:00
_need_butler = butler_required ;
2017-03-07 13:40:37 -05:00
}
2020-01-17 17:26:01 -05:00
if ( _need_butler ) {
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : Butler , string_compose ( " %1 reader run, needs butler = %2 \n " , name ( ) , _need_butler ) ) ;
2020-01-17 17:26:01 -05:00
}
2017-03-06 06:47:46 -05:00
}
2019-02-20 19:34:58 -05:00
bool
2019-09-17 20:26:03 -04:00
DiskReader : : declick_in_progress ( ) const
{
2020-05-04 15:51:53 -04:00
if ( ! _declick_enabled | | ( _session . exporting ( ) & & ! _session . realtime_export ( ) ) ) {
2020-03-06 17:55:35 -05:00
return false ;
}
2020-05-04 15:51:53 -04:00
return _declick_amp . gain ( ) ! = 0 ; // declick-out
2019-02-20 19:34:58 -05:00
}
2020-04-13 21:57:26 -04:00
void
DiskReader : : configuration_changed ( )
{
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2020-04-14 10:32:12 -04:00
if ( ! c - > empty ( ) ) {
ReaderChannelInfo * chaninfo = dynamic_cast < ReaderChannelInfo * > ( c - > front ( ) ) ;
if ( ! chaninfo - > initialized ) {
2023-05-22 15:04:37 -04:00
seek ( _session . transport_sample ( ) , true ) ;
2020-04-14 10:32:12 -04:00
return ;
}
}
2021-02-14 15:43:06 -05:00
_session . request_overwrite_buffer ( _track . shared_ptr ( ) , LoopDisabled ) ;
2020-04-13 21:57:26 -04:00
}
2019-02-08 11:42:14 -05:00
bool
2019-12-10 11:28:41 -05:00
DiskReader : : pending_overwrite ( ) const
{
2023-02-17 02:31:21 -05:00
return _pending_overwrite . load ( ) ! = 0 ;
2019-02-08 11:42:14 -05:00
}
2017-03-06 06:47:46 -05:00
void
2019-12-10 11:28:41 -05:00
DiskReader : : set_pending_overwrite ( OverwriteReason why )
2017-03-06 06:47:46 -05:00
{
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2020-01-03 18:12:45 -05:00
2017-03-06 06:47:46 -05:00
/* called from audio thread, so we can use the read ptr and playback sample as we wish */
2019-12-10 11:28:41 -05:00
if ( ! c - > empty ( ) ) {
2020-04-13 21:57:26 -04:00
if ( c - > size ( ) > 1 ) {
/* Align newly added buffers.
*
* overwrite_sample and file_sample [ ] are are maintained
* per DiskReader , not per channel .
* : : refill_audio ( ) and : : overwrite_existing_audio ( ) expect
* that read - pointers and fill_level of all buffers are in sync .
*/
2023-04-07 17:33:13 -04:00
ChannelList : : const_iterator chan = c - > begin ( ) ;
2020-04-13 21:57:26 -04:00
for ( + + chan ; chan ! = c - > end ( ) ; + + chan ) {
ReaderChannelInfo * chaninfo = dynamic_cast < ReaderChannelInfo * > ( * chan ) ;
if ( ! chaninfo - > initialized ) {
( * chan ) - > rbuf - > align_to ( * ( c - > front ( ) - > rbuf ) ) ;
}
}
}
2020-04-07 10:20:53 -04:00
const samplecnt_t reserved_size = c - > front ( ) - > rbuf - > reserved_size ( ) ;
const samplecnt_t bufsize = c - > front ( ) - > rbuf - > bufsize ( ) ;
2020-01-03 18:12:45 -05:00
2020-04-07 10:20:53 -04:00
overwrite_offset = c - > front ( ) - > rbuf - > read_ptr ( ) ;
2020-05-12 12:09:23 -04:00
overwrite_sample = playback_sample - reserved_size ;
2020-01-03 18:12:45 -05:00
if ( overwrite_offset > reserved_size ) {
/*
2020-04-07 10:20:53 -04:00
* | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
2020-05-12 12:09:23 -04:00
* ^ ^
* RRRRRRRRRRRRRRRRoverwrite_offset ( old read_ptr )
* | < - second - > | < - - - - - - - - - - - - - - - - - - first chunk - - - - - - - - - - - - - - - - - - - - - - - - > |
2020-04-07 10:20:53 -04:00
*
* Fill the the end of the buffer ( " first chunk " ) , above
2020-05-12 12:09:23 -04:00
*/
2020-01-03 18:12:45 -05:00
overwrite_offset - = reserved_size ;
} else {
/*
2020-04-07 10:20:53 -04:00
* | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
* RRRRRRRRE ^ RRRRRRRRR
* overwrite_offset ( old read_ptr )
* | < second chunk > | < first > |
*
* Fill the end of the buffer ( " R1R1R1 " aka " first " above )
*/
2020-01-03 18:12:45 -05:00
overwrite_offset = bufsize - ( reserved_size - overwrite_offset ) ;
}
2019-12-10 11:28:41 -05:00
}
2020-04-07 10:20:53 -04:00
if ( why & ( LoopChanged | PlaylistModified | PlaylistChanged ) ) {
2019-12-10 21:30:28 -05:00
run_must_resolve = true ;
}
2019-12-10 11:28:41 -05:00
while ( true ) {
2023-02-17 02:31:21 -05:00
OverwriteReason current = OverwriteReason ( _pending_overwrite . load ( ) ) ;
2020-04-07 10:20:53 -04:00
OverwriteReason next = OverwriteReason ( current | why ) ;
2023-02-17 02:31:21 -05:00
if ( _pending_overwrite . compare_exchange_strong ( current , next ) ) {
2019-12-10 11:28:41 -05:00
break ;
}
}
2017-03-06 06:47:46 -05:00
}
2019-02-08 11:35:05 -05:00
bool
2019-12-10 11:28:41 -05:00
DiskReader : : overwrite_existing_audio ( )
2017-03-06 06:47:46 -05:00
{
2020-01-03 20:41:24 -05:00
/* This is a tricky and/or clever little method. Let's try to describe
* precisely what it does .
*
* Our goal is to completely overwrite the playback buffers for each
* audio channel with new data . The wrinkle is that we want to preserve
* the EXACT mapping between a given timeline position and buffer
* offset that existed when we requested an overwrite . That is , if the
* Nth position in the buffer contained the sample corresponding to
* timeline position T , then once this is complete that condition
2020-05-06 15:52:38 -04:00
* should still hold . The actual value of the sample ( and even whether it
2020-01-03 20:41:24 -05:00
* corresponds to any actual material on disk - it may just be silence )
* may change , but this buffer_offset < - > timeline_position mapping must
* remain constant .
*
* Why do this ? There are many reasons . A trivial example is that the
* region gain level for one region has been changed , and the user
* should be able to hear the result .
*
* In : : set_pending_overwrite ( ) ( above ) we stored a sample and a buffer
* offset . These corresponded to the next sample to be played and the
* buffer position holding that sample . We were able to determine this
* pair atomically because : : set_pending_overwrite ( ) is called from
* within process context , and thus neither playback_sample nor the
* buffer read ptr can change while it runs . We computed the earliest
* sample / timeline position in the buffer ( at the start of the reserved
* zone , if any ) and its corresponding buffer offset .
*
* Here , we will refill the buffer , starting with the sample and buffer
* offset computed by : : set_pending_overwrite ( ) . Typically this will
* take two reads from the playlist , because our read will be " split "
* by the end of the buffer ( i . e . we fill from some mid - buffer point to
* the end , then fill from the start to the mid - buffer point , as is
* common with ring buffers ) .
*
* Note that the process thread may indeed access the buffer while we
* are doing this . There is a strong likelihood of colliding read / write
* between this thread ( the butler ) and a process thread . But we don ' t
* care : we know that the samples being read / written will correspond to
* the same timeline position , and that the user has just done
* something forcing us to update the value ( s ) . Given that a Sample is
* currently ( and likely forever ) a floating point value , and that on
* many / most architectures , a store for a floating point value is
* non - atomic , there is some chance of the process read reading a
* sample value while it is being written . This could theoretically
* cause a brief glitch , but no more or less than any other
* " discontinuity " in the sample ' s value will .
*
* It goes without saying that this relies on being serialized within
* the butler thread with respect any other buffer write operation
* ( e . g . via : : refill ( ) ) . It should also be noted that it has no effect
* at all on the write - related members of the playback buffer - we
* simply replace the contents of the buffer .
*/
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2017-03-06 06:47:46 -05:00
2020-02-19 19:17:14 -05:00
if ( c - > empty ( ) ) {
2019-12-10 11:28:41 -05:00
return true ;
}
2017-04-11 12:38:34 -04:00
2020-02-20 02:19:22 -05:00
const bool reversed = ! _session . transport_will_roll_forwards ( ) ;
2019-12-06 16:21:40 -05:00
2020-01-03 18:12:45 -05:00
sampleoffset_t chunk1_offset ;
2023-05-22 15:04:37 -04:00
size_t chunk1_cnt ;
size_t chunk2_cnt ;
2020-01-03 18:12:45 -05:00
2023-05-22 15:04:37 -04:00
const size_t to_overwrite = c - > front ( ) - > rbuf - > overwritable_at ( overwrite_offset ) ;
2020-01-03 18:12:45 -05:00
chunk1_offset = overwrite_offset ;
2023-05-22 15:04:37 -04:00
chunk1_cnt = min ( c - > front ( ) - > rbuf - > bufsize ( ) - ( size_t ) overwrite_offset , to_overwrite ) ;
2019-12-06 16:21:40 -05:00
2020-05-13 20:52:07 -04:00
/* note: because we are overwriting buffer contents but not moving the
2020-05-14 01:54:24 -04:00
* write / read pointers , we actually want to fill all the way to the
* write pointer ( the value returned by PlaybackBuffer : : overwritable_at ( ) .
2020-05-13 20:52:07 -04:00
*
* This differs from what happens during : : refill_audio ( ) where we are
* careful not to allow the read pointer to catch the write pointer
* ( that indicates an empty buffer )
*/
2020-05-12 13:32:56 -04:00
if ( chunk1_cnt = = to_overwrite ) {
2020-01-03 18:12:45 -05:00
chunk2_cnt = 0 ;
} else {
2020-05-12 13:32:56 -04:00
chunk2_cnt = to_overwrite - chunk1_cnt ;
2020-01-03 18:12:45 -05:00
}
2017-03-06 06:47:46 -05:00
2024-03-05 20:32:39 -05:00
boost : : scoped_array < Sample > sum_buffer ( new Sample [ to_overwrite ] ) ;
2020-05-12 13:32:56 -04:00
boost : : scoped_array < Sample > mixdown_buffer ( new Sample [ to_overwrite ] ) ;
boost : : scoped_array < float > gain_buffer ( new float [ to_overwrite ] ) ;
2020-04-07 10:20:53 -04:00
uint32_t n = 0 ;
bool ret = true ;
2024-03-07 06:14:34 -05:00
samplepos_t start = overwrite_sample ;
2019-12-06 16:21:40 -05:00
2024-03-07 06:14:34 -05:00
if ( chunk1_cnt ) {
for ( auto const & chan : * c ) {
Sample * buf = chan - > rbuf - > buffer ( ) ;
ReaderChannelInfo * rci = dynamic_cast < ReaderChannelInfo * > ( chan ) ;
2020-05-12 13:32:56 -04:00
2024-03-07 06:14:34 -05:00
/* Note that @p start is passed by reference and will be
* updated by the : : audio_read ( ) call
*/
start = overwrite_sample ;
2024-03-05 20:32:39 -05:00
if ( audio_read ( sum_buffer . get ( ) , mixdown_buffer . get ( ) , gain_buffer . get ( ) , start , chunk1_cnt , rci , n , reversed ) ! = ( samplecnt_t ) chunk1_cnt ) {
2020-04-07 10:20:53 -04:00
error < < string_compose ( _ ( " DiskReader %1: when overwriting(1), cannot read %2 from playlist at sample %3 " ) , id ( ) , chunk1_cnt , overwrite_sample ) < < endmsg ;
2020-01-03 18:12:45 -05:00
ret = false ;
2023-10-15 10:32:57 -04:00
+ + n ;
2020-01-03 18:12:45 -05:00
continue ;
}
2024-03-05 20:32:39 -05:00
memcpy ( buf + chunk1_offset , sum_buffer . get ( ) , sizeof ( float ) * chunk1_cnt ) ;
2024-03-07 06:14:34 -05:00
+ + n ;
2019-12-10 11:28:41 -05:00
}
2024-03-07 06:14:34 -05:00
}
overwrite_sample = start ;
/* sequence read chunks. first read data at same position for all channels */
n = 0 ;
for ( auto const & chan : * c ) {
Sample * buf = chan - > rbuf - > buffer ( ) ;
ReaderChannelInfo * rci = dynamic_cast < ReaderChannelInfo * > ( chan ) ;
2019-11-23 01:41:56 -05:00
2020-01-03 18:12:45 -05:00
if ( chunk2_cnt ) {
2024-03-07 06:14:34 -05:00
start = overwrite_sample ;
2024-03-05 20:32:39 -05:00
if ( audio_read ( sum_buffer . get ( ) , mixdown_buffer . get ( ) , gain_buffer . get ( ) , start , chunk2_cnt , rci , n , reversed ) ! = ( samplecnt_t ) chunk2_cnt ) {
2020-04-07 10:20:53 -04:00
error < < string_compose ( _ ( " DiskReader %1: when overwriting(2), cannot read %2 from playlist at sample %3 " ) , id ( ) , chunk2_cnt , overwrite_sample ) < < endmsg ;
2019-12-10 11:28:41 -05:00
ret = false ;
2017-03-07 13:40:37 -05:00
}
2024-03-05 20:32:39 -05:00
memcpy ( buf , sum_buffer . get ( ) , sizeof ( float ) * chunk2_cnt ) ;
2017-03-06 06:47:46 -05:00
}
2020-04-07 19:08:57 -04:00
2020-04-07 20:12:58 -04:00
if ( ! rci - > initialized ) {
2023-04-07 17:33:13 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " Init ReaderChannel '%1' overwriting at: %2, avail: %3 \n " , name ( ) , overwrite_sample , chan - > rbuf - > read_space ( ) ) ) ;
if ( chan - > rbuf - > read_space ( ) > 0 ) {
2020-04-07 20:12:58 -04:00
rci - > initialized = true ;
}
}
2023-10-15 10:32:57 -04:00
+ + n ;
2017-03-06 06:47:46 -05:00
}
2020-01-03 18:18:55 -05:00
2020-05-14 01:54:02 -04:00
file_sample [ DataType : : AUDIO ] = start ;
2020-05-12 13:31:07 -04:00
2019-12-10 11:28:41 -05:00
return ret ;
}
2017-03-07 13:40:37 -05:00
2019-12-10 11:28:41 -05:00
bool
DiskReader : : overwrite_existing_midi ( )
{
2019-10-18 01:15:53 -04:00
RTMidiBuffer * mbuf = rt_midibuffer ( ) ;
2017-03-07 13:40:37 -05:00
2019-10-18 01:15:53 -04:00
if ( mbuf ) {
2023-05-22 15:04:37 -04:00
MidiTrack * mt = dynamic_cast < MidiTrack * > ( & _track ) ;
2021-02-14 15:43:06 -05:00
MidiChannelFilter * filter = mt ? & mt - > playback_filter ( ) : 0 ;
2019-11-02 21:35:25 -04:00
2022-03-16 12:07:27 -04:00
# ifdef PROFILE_MIDI_IO
2019-10-17 19:32:56 -04:00
PBD : : Timing minsert ;
2020-04-07 10:20:53 -04:00
minsert . start ( ) ;
2022-03-16 12:07:27 -04:00
# endif
2019-11-02 21:35:25 -04:00
2020-04-07 10:20:53 -04:00
midi_playlist ( ) - > render ( filter ) ;
2022-03-16 12:07:27 -04:00
assert ( midi_playlist ( ) - > rendered ( ) ) ;
2019-11-02 21:35:25 -04:00
2022-03-16 12:07:27 -04:00
# ifdef PROFILE_MIDI_IO
2020-04-07 10:20:53 -04:00
minsert . update ( ) ;
cerr < < " Reading " < < name ( ) < < " took " < < minsert . elapsed ( ) < < " microseconds, final size = " < < midi_playlist ( ) - > rendered ( ) - > size ( ) < < endl ;
2022-03-16 12:07:27 -04:00
# endif
2017-03-07 13:40:37 -05:00
}
2017-03-06 06:47:46 -05:00
2019-02-08 11:35:05 -05:00
return true ;
2017-03-06 06:47:46 -05:00
}
2019-12-10 11:28:41 -05:00
bool
DiskReader : : overwrite_existing_buffers ( )
{
/* called from butler thread */
2023-02-17 02:31:21 -05:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1 overwriting existing buffers at %2 (because %3%4%5 \n " , owner ( ) - > name ( ) , overwrite_sample , std : : hex , _pending_overwrite . load ( ) , std : : dec ) ) ;
2019-12-12 13:24:32 -05:00
2019-12-10 11:28:41 -05:00
bool ret = true ;
2023-02-17 02:31:21 -05:00
if ( _pending_overwrite . load ( ) & ( PlaylistModified | LoopDisabled | LoopChanged | PlaylistChanged ) ) {
2020-02-19 19:17:23 -05:00
if ( _playlists [ DataType : : AUDIO ] & & ! overwrite_existing_audio ( ) ) {
2019-12-10 11:28:41 -05:00
ret = false ;
}
}
2023-02-17 02:31:21 -05:00
if ( _pending_overwrite . load ( ) & ( PlaylistModified | PlaylistChanged ) ) {
2020-02-19 19:17:23 -05:00
if ( _playlists [ DataType : : MIDI ] & & ! overwrite_existing_midi ( ) ) {
2019-12-10 11:28:41 -05:00
ret = false ;
}
}
2023-02-17 02:31:21 -05:00
_pending_overwrite . store ( OverwriteReason ( 0 ) ) ;
2019-12-10 11:28:41 -05:00
return ret ;
}
2017-03-06 06:47:46 -05:00
int
2017-09-18 12:39:17 -04:00
DiskReader : : seek ( samplepos_t sample , bool complete_refill )
2017-03-06 06:47:46 -05:00
{
2019-03-05 08:44:47 -05:00
/* called via non_realtime_locate() from butler thread */
2017-03-06 06:47:46 -05:00
int ret = - 1 ;
2020-04-07 10:20:53 -04:00
2020-02-20 02:19:22 -05:00
const bool read_reversed = ! _session . transport_will_roll_forwards ( ) ;
2020-04-07 10:20:53 -04:00
const bool read_loop = ( bool ) _loop_location ;
2017-03-06 06:47:46 -05:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2020-04-07 10:05:47 -04:00
2020-04-07 10:20:53 -04:00
if ( c - > empty ( ) ) {
2020-01-21 23:46:52 -05:00
return 0 ;
}
2020-04-01 09:54:44 -04:00
/* There are two possible shortcuts we can take that will completely
* skip reading from disk . However , they are invalid if we need to read
* data in the opposite direction than we did last time , or if our need
2022-05-08 20:20:45 -04:00
* for looped data has changed since the last read . Both of these change
2020-04-01 09:54:44 -04:00
* the semantics of a read from disk , even if the position we are
* reading from is the same .
*/
2020-01-21 23:46:52 -05:00
2020-04-01 09:54:44 -04:00
if ( ( _last_read_reversed . value_or ( read_reversed ) = = read_reversed ) & & ( _last_read_loop . value_or ( read_loop ) = = read_loop ) ) {
2020-02-20 02:21:31 -05:00
if ( sample = = playback_sample & & ! complete_refill ) {
return 0 ;
}
2023-05-22 15:04:37 -04:00
if ( ( size_t ) abs ( sample - playback_sample ) < ( c - > front ( ) - > rbuf - > reserved_size ( ) / 6 ) ) {
2020-02-20 02:21:31 -05:00
/* we're close enough. Note: this is a heuristic */
return 0 ;
}
2019-03-05 08:44:47 -05:00
}
2023-02-17 02:31:21 -05:00
_pending_overwrite . store ( OverwriteReason ( 0 ) ) ;
2019-02-08 11:42:14 -05:00
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " DiskReader::seek %1 %2 -> %3 refill=%4 \n " , owner ( ) - > name ( ) . c_str ( ) , playback_sample , sample , complete_refill ) ) ;
2017-09-28 00:31:12 -04:00
2019-09-20 16:11:07 -04:00
const samplecnt_t distance = sample - playback_sample ;
2019-11-01 23:13:39 -04:00
if ( ! complete_refill & & can_internal_playback_seek ( distance ) ) {
2019-09-20 16:11:07 -04:00
internal_playback_seek ( distance ) ;
return 0 ;
}
2019-03-05 08:44:47 -05:00
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
chan - > rbuf - > reset ( ) ;
assert ( chan - > rbuf - > reserved_size ( ) = = 0 ) ;
2017-03-06 06:47:46 -05:00
}
2020-02-20 02:22:09 -05:00
/* move the intended read target, so that after the refill is done,
2020-04-07 10:20:53 -04:00
* the intended read target is " reservation " from the start of the
* playback buffer . Then increment the read ptr , so that we can
* potentially do an internal seek backwards of up " reservation "
* samples .
*/
2020-02-20 02:22:09 -05:00
2023-05-22 15:04:37 -04:00
const samplecnt_t rsize = ( samplecnt_t ) c - > front ( ) - > rbuf - > reservation_size ( ) ;
samplecnt_t shift = ( sample > rsize ? rsize : sample ) ;
2020-02-21 13:53:53 -05:00
if ( read_reversed ) {
/* reading in reverse, so start at a later sample, and read
2020-04-07 10:20:53 -04:00
* " backwards " from there . */
2020-02-21 13:53:53 -05:00
shift = - shift ;
}
/* start the read at an earlier position (or later if reversed) */
2020-02-20 02:22:09 -05:00
sample - = shift ;
2020-04-07 10:20:53 -04:00
playback_sample = sample ;
2017-09-18 12:39:17 -04:00
file_sample [ DataType : : AUDIO ] = sample ;
2020-04-07 10:20:53 -04:00
file_sample [ DataType : : MIDI ] = sample ;
2017-03-06 06:47:46 -05:00
if ( complete_refill ) {
/* call _do_refill() to refill the entire buffer, using
2020-04-07 10:20:53 -04:00
* the largest reads possible . */
while ( ( ret = do_refill_with_alloc ( false , read_reversed ) ) > 0 )
;
2017-03-06 06:47:46 -05:00
} else {
2020-04-07 10:20:53 -04:00
/* call _do_refill() to refill just one chunk, and then return. */
2020-02-21 13:53:53 -05:00
ret = do_refill_with_alloc ( true , read_reversed ) ;
2017-03-06 06:47:46 -05:00
}
2020-02-21 13:53:53 -05:00
if ( shift ) {
/* now tell everyone where we really are, leaving the
* " reserved " data represented by " shift " available in the
* buffer for backwards - internal - seek
*/
2020-02-20 02:22:09 -05:00
2020-02-21 15:19:31 -05:00
playback_sample + = shift ;
2020-02-21 13:53:53 -05:00
/* we always move the read-ptr forwards, since even when in
* reverse , the data is placed in the buffer in normal read
* ( increment ) order .
*/
shift = abs ( shift ) ;
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
chan - > rbuf - > increment_read_ptr ( shift ) ;
2020-02-21 13:53:53 -05:00
}
2020-02-20 02:22:09 -05:00
}
2017-03-06 06:47:46 -05:00
return ret ;
}
2019-02-06 13:01:42 -05:00
bool
DiskReader : : can_internal_playback_seek ( sampleoffset_t distance )
2017-03-06 06:47:46 -05:00
{
2017-03-07 13:40:37 -05:00
/* 1. Audio */
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2017-03-06 06:47:46 -05:00
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
if ( ! chan - > rbuf - > can_seek ( distance ) ) {
2017-03-06 06:47:46 -05:00
return false ;
}
}
2017-03-07 13:40:37 -05:00
2019-10-14 21:00:32 -04:00
/* 2. MIDI can always seek any distance */
2017-03-07 13:40:37 -05:00
2019-10-14 21:00:32 -04:00
return true ;
2017-03-06 06:47:46 -05:00
}
2019-02-06 13:01:42 -05:00
void
DiskReader : : internal_playback_seek ( sampleoffset_t distance )
2017-03-06 06:47:46 -05:00
{
2019-02-06 13:01:42 -05:00
if ( distance = = 0 ) {
return ;
}
sampleoffset_t off = distance ;
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
for ( auto const & chan : * c ) {
2019-02-06 13:01:42 -05:00
if ( distance < 0 ) {
2023-05-22 15:04:37 -04:00
off = 0 - ( sampleoffset_t ) chan - > rbuf - > decrement_read_ptr ( : : llabs ( distance ) ) ;
2019-02-06 13:01:42 -05:00
} else {
2023-04-07 17:33:13 -04:00
off = chan - > rbuf - > increment_read_ptr ( distance ) ;
2019-02-06 13:01:42 -05:00
}
2017-03-06 06:47:46 -05:00
}
2019-02-06 13:01:42 -05:00
playback_sample + = off ;
2017-03-06 06:47:46 -05:00
}
2020-04-07 10:20:53 -04:00
static void
swap_by_ptr ( Sample * first , Sample * last )
2017-03-06 06:47:46 -05:00
{
while ( first < last ) {
Sample tmp = * first ;
2020-04-07 10:20:53 -04:00
* first + + = * last ;
* last - - = tmp ;
2017-03-06 06:47:46 -05:00
}
}
/** Read some data for 1 channel from our playlist into a buffer.
2020-05-12 13:31:36 -04:00
*
* @ param sum_buf sample - containing buffer to write to . Must be contiguous .
* @ param mixdown_buffer sample - containing buffer that will be used to mix layers
* @ param gain_buffer ptr to a buffer used to hold any necessary gain ( automation ) data
2017-09-18 12:39:17 -04:00
* @ param start Session sample to start reading from ; updated to where we end up
2020-05-12 13:31:36 -04:00
* after the read . Global timeline position .
2017-03-06 06:47:46 -05:00
* @ param cnt Count of samples to read .
2020-05-12 13:31:36 -04:00
* @ param rci ptr to ReaderChannelInfo for the channel we ' re reading
* @ param channel the number of the channel we ' re reading ( 0. . N )
2017-03-06 06:47:46 -05:00
* @ param reversed true if we are running backwards , otherwise false .
*/
2020-05-12 13:31:36 -04:00
2019-12-10 11:28:41 -05:00
samplecnt_t
2020-04-07 10:20:53 -04:00
DiskReader : : audio_read ( Sample * sum_buffer ,
Sample * mixdown_buffer ,
float * gain_buffer ,
samplepos_t & start ,
samplecnt_t cnt ,
2019-11-23 01:41:56 -05:00
ReaderChannelInfo * rci ,
2020-04-07 10:20:53 -04:00
int channel ,
bool reversed )
2017-03-06 06:47:46 -05:00
{
2020-04-07 10:20:53 -04:00
samplecnt_t this_read = 0 ;
bool reloop = false ;
2023-05-22 15:04:37 -04:00
samplepos_t loop_end = 0 ;
2020-04-07 10:20:53 -04:00
samplepos_t loop_start = 0 ;
Location * loc = 0 ;
const samplecnt_t rcnt = cnt ;
2017-04-11 12:38:34 -04:00
2017-03-06 06:47:46 -05:00
/* XXX we don't currently play loops in reverse. not sure why */
if ( ! reversed ) {
/* Make the use of a Location atomic for this read operation.
Note : Locations don ' t get deleted , so all we care about
when I say " atomic " is that we are always pointing to
the same one and using a start / length values obtained
just once .
*/
2017-10-31 13:32:26 -04:00
if ( ( loc = _loop_location ) ! = 0 ) {
2023-05-22 15:04:37 -04:00
loop_start = loc - > start_sample ( ) ;
loop_end = loc - > end_sample ( ) ;
2017-03-06 06:47:46 -05:00
2023-05-22 15:04:37 -04:00
const Temporal : : Range loop_range ( loc - > start ( ) , loc - > end ( ) ) ;
start = loop_range . squish ( timepos_t ( start ) ) . samples ( ) ;
2017-03-06 06:47:46 -05:00
}
2021-04-22 00:51:06 -04:00
} else {
2017-03-06 06:47:46 -05:00
start - = cnt ;
2021-04-22 00:50:26 -04:00
start = max ( samplepos_t ( 0 ) , start ) ;
2017-03-06 06:47:46 -05:00
}
/* We need this while loop in case we hit a loop boundary, in which case our read from
2020-04-07 10:20:53 -04:00
* the playlist must be split into more than one section . */
2017-03-06 06:47:46 -05:00
while ( cnt ) {
/* take any loop into account. we can't read past the end of the loop. */
if ( loc & & ( loop_end - start < cnt ) ) {
this_read = loop_end - start ;
2020-04-07 10:20:53 -04:00
reloop = true ;
2017-03-06 06:47:46 -05:00
} else {
2020-04-07 10:20:53 -04:00
reloop = false ;
2017-03-06 06:47:46 -05:00
this_read = cnt ;
}
if ( this_read = = 0 ) {
break ;
}
2019-02-05 14:51:33 -05:00
this_read = min ( cnt , this_read ) ;
2017-03-06 06:47:46 -05:00
2019-11-23 01:41:56 -05:00
/* note that the mixdown and gain buffers are purely for the
* internal use of the playlist , and cannot be considered
* useful after the return from AudioPlayback : : read ( )
*/
2020-09-20 18:34:09 -04:00
if ( audio_playlist ( ) - > read ( sum_buffer , mixdown_buffer , gain_buffer , timepos_t ( start ) , timecnt_t : : from_samples ( this_read ) , channel ) ! = this_read ) {
2020-04-07 10:20:53 -04:00
error < < string_compose ( _ ( " DiskReader %1: cannot read %2 from playlist at sample %3 " ) , id ( ) , this_read , start ) < < endmsg ;
2019-12-10 11:28:41 -05:00
return 0 ;
2017-03-06 06:47:46 -05:00
}
2019-11-23 01:41:56 -05:00
if ( loc ) {
/* Looping: do something (maybe) about the loop boundaries */
2020-04-07 10:20:53 -04:00
switch ( Config - > get_loop_fade_choice ( ) ) {
case NoLoopFade :
break ;
case BothLoopFade :
loop_declick_in . run ( sum_buffer , start , start + this_read ) ;
loop_declick_out . run ( sum_buffer , start , start + this_read ) ;
break ;
case EndLoopFade :
loop_declick_out . run ( sum_buffer , start , start + this_read ) ;
break ;
case XFadeLoop :
2020-09-20 18:34:09 -04:00
if ( last_refill_loop_start ! = loop_start | | rci - > pre_loop_buffer = = 0 ) {
2020-05-12 12:06:36 -04:00
setup_preloop_buffer ( ) ;
2020-09-20 18:34:09 -04:00
last_refill_loop_start = loop_start ;
2020-05-12 12:06:36 -04:00
}
2020-04-07 10:20:53 -04:00
maybe_xfade_loop ( sum_buffer , start , start + this_read , rci ) ;
break ;
2019-11-23 01:41:56 -05:00
}
}
2017-03-06 06:47:46 -05:00
if ( reversed ) {
2019-02-05 14:51:33 -05:00
swap_by_ptr ( sum_buffer , sum_buffer + this_read - 1 ) ;
2017-03-06 06:47:46 -05:00
} else {
/* if we read to the end of the loop, go back to the beginning */
if ( reloop ) {
start = loop_start ;
} else {
start + = this_read ;
}
}
cnt - = this_read ;
2019-12-10 11:28:41 -05:00
sum_buffer + = this_read ;
2017-03-06 06:47:46 -05:00
}
2017-03-03 10:14:07 -05:00
2020-02-20 02:19:22 -05:00
_last_read_reversed = reversed ;
2020-04-07 10:20:53 -04:00
_last_read_loop = ( bool ) loc ;
2020-03-31 22:51:13 -04:00
2019-12-10 11:28:41 -05:00
return rcnt ;
2017-03-03 10:14:07 -05:00
}
2017-03-06 06:47:46 -05:00
int
2020-02-21 13:53:53 -05:00
DiskReader : : do_refill ( )
{
const bool reversed = ! _session . transport_will_roll_forwards ( ) ;
return refill ( _sum_buffer , _mixdown_buffer , _gain_buffer , 0 , reversed ) ;
}
int
DiskReader : : do_refill_with_alloc ( bool partial_fill , bool reversed )
2017-03-06 06:47:46 -05:00
{
/* We limit disk reads to at most 4MB chunks, which with floating point
2020-04-07 10:20:53 -04:00
* samples would be 1 M samples . But we might use 16 or 14 bit samples ,
* in which case 4 MB is more samples than that . Therefore size this for
* the smallest sample value . . 4 MB = 2 M samples ( 16 bit ) .
*/
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
boost : : scoped_array < Sample > sum_buf ( new Sample [ 2 * 1048576 ] ) ;
boost : : scoped_array < Sample > mix_buf ( new Sample [ 2 * 1048576 ] ) ;
boost : : scoped_array < float > gain_buf ( new float [ 2 * 1048576 ] ) ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
return refill_audio ( sum_buf . get ( ) , mix_buf . get ( ) , gain_buf . get ( ) , ( partial_fill ? _chunk_samples : 0 ) , reversed ) ;
2017-03-06 06:47:46 -05:00
}
2017-03-07 13:40:37 -05:00
int
2020-02-21 13:53:53 -05:00
DiskReader : : refill ( Sample * sum_buffer , Sample * mixdown_buffer , float * gain_buffer , samplecnt_t fill_level , bool reversed )
2017-03-07 13:40:37 -05:00
{
2020-02-20 02:22:17 -05:00
/* NOTE: Audio refill MUST come first so that in contexts where ONLY it
2020-04-07 10:20:53 -04:00
* is called , _last_read_reversed is set correctly .
*/
2020-02-20 02:22:17 -05:00
2020-02-21 13:53:53 -05:00
if ( refill_audio ( sum_buffer , mixdown_buffer , gain_buffer , fill_level , reversed ) ) {
2019-11-18 14:01:26 -05:00
return - 1 ;
}
2020-04-07 10:20:53 -04:00
if ( rt_midibuffer ( ) & & ( reversed ! = rt_midibuffer ( ) - > reversed ( ) ) ) {
rt_midibuffer ( ) - > reverse ( ) ;
2019-11-18 14:01:26 -05:00
}
return 0 ;
2017-03-07 13:40:37 -05:00
}
2017-03-06 06:47:46 -05:00
/** Get some more data from disk and put it in our channels' bufs,
* if there is suitable space in them .
*
* If fill_level is non - zero , then we will refill the buffer so that there is
* still at least fill_level samples of space left to be filled . This is used
* after locates so that we do not need to wait to fill the entire buffer .
*
*/
int
2020-02-21 13:53:53 -05:00
DiskReader : : refill_audio ( Sample * sum_buffer , Sample * mixdown_buffer , float * gain_buffer , samplecnt_t fill_level , bool reversed )
2017-03-06 06:47:46 -05:00
{
2017-04-11 12:38:34 -04:00
/* do not read from disk while session is marked as Loading, to avoid
useless redundant I / O .
*/
2020-04-07 10:20:53 -04:00
if ( _session . loading ( ) ) {
2017-03-06 06:47:46 -05:00
return 0 ;
}
2023-04-07 17:33:13 -04:00
int32_t ret = 0 ;
samplecnt_t zero_fill ;
uint32_t chan_n ;
ChannelList : : const_iterator i ;
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2017-03-06 06:47:46 -05:00
2020-02-21 13:53:53 -05:00
_last_read_reversed = reversed ;
2020-02-20 02:19:22 -05:00
2020-04-07 10:20:53 -04:00
if ( c - > empty ( ) ) {
2017-03-06 06:47:46 -05:00
return 0 ;
}
2020-04-07 10:20:53 -04:00
assert ( mixdown_buffer ) ;
assert ( gain_buffer ) ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
samplecnt_t total_space = c - > front ( ) - > rbuf - > write_space ( ) ;
2017-03-06 06:47:46 -05:00
2019-02-05 14:51:33 -05:00
if ( total_space = = 0 ) {
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1: no space to refill \n " , name ( ) ) ) ;
2017-03-06 06:47:46 -05:00
/* nowhere to write to */
return 0 ;
}
if ( fill_level ) {
if ( fill_level < total_space ) {
total_space - = fill_level ;
} else {
/* we can't do anything with it */
fill_level = 0 ;
}
}
/* if we're running close to normal speed and there isn't enough
2020-04-07 10:20:53 -04:00
* space to do disk_read_chunk_samples of I / O , then don ' t bother .
*
* at higher speeds , just do it because the sync between butler
* and audio thread may not be good enough .
*
* Note : it is a design assumption that disk_read_chunk_samples is smaller
* than the playback buffer size , so this check should never trip when
* the playback buffer is empty .
*/
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1: space to refill %2 vs. chunk %3 (speed = %4) \n " , name ( ) , total_space , _chunk_samples , _session . transport_speed ( ) ) ) ;
if ( ( total_space < _chunk_samples ) & & fabs ( _session . transport_speed ( ) ) < 2.0f ) {
2017-03-06 06:47:46 -05:00
return 0 ;
}
/* when slaved, don't try to get too close to the read pointer. this
2020-04-07 10:20:53 -04:00
* leaves space for the buffer reversal to have something useful to
* work with .
*/
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
if ( _slaved & & total_space < ( samplecnt_t ) ( c - > front ( ) - > rbuf - > bufsize ( ) / 2 ) ) {
2017-04-11 12:38:34 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1: not enough to refill while slaved \n " , this ) ) ;
2017-03-06 06:47:46 -05:00
return 0 ;
}
2020-02-20 02:20:45 -05:00
samplepos_t fsa = file_sample [ DataType : : AUDIO ] ;
2017-07-25 12:46:05 -04:00
2017-03-06 06:47:46 -05:00
if ( reversed ) {
2020-02-20 02:20:45 -05:00
if ( fsa = = 0 ) {
2017-03-06 06:47:46 -05:00
/* at start: nothing to do but fill with silence */
2020-04-07 10:20:53 -04:00
for ( chan_n = 0 , i = c - > begin ( ) ; i ! = c - > end ( ) ; + + i , + + chan_n ) {
2017-03-06 06:47:46 -05:00
ChannelInfo * chan ( * i ) ;
2019-12-10 11:28:41 -05:00
chan - > rbuf - > write_zero ( chan - > rbuf - > write_space ( ) ) ;
2017-03-06 06:47:46 -05:00
}
return 0 ;
}
2020-02-20 02:20:45 -05:00
if ( fsa < total_space ) {
2019-02-05 14:51:33 -05:00
/* too close to the start: read what we can, and then zero fill the rest */
2020-04-07 10:20:53 -04:00
zero_fill = total_space - fsa ;
2020-02-20 02:20:45 -05:00
total_space = fsa ;
2017-03-06 06:47:46 -05:00
} else {
zero_fill = 0 ;
}
} else {
2020-02-20 02:20:45 -05:00
if ( fsa = = max_samplepos ) {
2017-03-06 06:47:46 -05:00
/* at end: nothing to do but fill with silence */
2020-04-07 10:20:53 -04:00
for ( chan_n = 0 , i = c - > begin ( ) ; i ! = c - > end ( ) ; + + i , + + chan_n ) {
2017-03-06 06:47:46 -05:00
ChannelInfo * chan ( * i ) ;
2019-12-10 11:28:41 -05:00
chan - > rbuf - > write_zero ( chan - > rbuf - > write_space ( ) ) ;
2017-03-06 06:47:46 -05:00
}
return 0 ;
}
2020-02-20 02:20:45 -05:00
if ( fsa > max_samplepos - total_space ) {
2017-03-06 06:47:46 -05:00
/* to close to the end: read what we can, and zero fill the rest */
2020-04-07 10:20:53 -04:00
zero_fill = total_space - ( max_samplepos - fsa ) ;
2020-02-20 02:20:45 -05:00
total_space = max_samplepos - fsa ;
2017-03-06 06:47:46 -05:00
} else {
zero_fill = 0 ;
}
}
/* total_space is in samples. We want to optimize read sizes in various sizes using bytes */
2020-04-07 10:20:53 -04:00
const size_t bits_per_sample = format_data_width ( _session . config . get_native_file_data_format ( ) ) ;
size_t total_bytes = total_space * bits_per_sample / 8 ;
2017-03-06 06:47:46 -05:00
2019-02-05 14:51:33 -05:00
/* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer */
2017-03-06 06:47:46 -05:00
size_t byte_size_for_read = max ( ( size_t ) ( 256 * 1024 ) , min ( ( size_t ) ( 4 * 1048576 ) , total_bytes ) ) ;
/* find nearest (lower) multiple of 16384 */
byte_size_for_read = ( byte_size_for_read / 16384 ) * 16384 ;
/* now back to samples */
2017-09-18 12:39:17 -04:00
samplecnt_t samples_to_read = byte_size_for_read / ( bits_per_sample / 8 ) ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " %1: will refill %2 channels with %3 samples \n " , name ( ) , c - > size ( ) , total_space ) ) ;
2017-03-06 06:47:46 -05:00
2020-02-20 02:20:45 -05:00
samplepos_t file_sample_tmp = fsa ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
#if 0
int64_t before = g_get_monotonic_time ( ) ;
int64_t elapsed ;
# endif
2019-12-10 11:28:41 -05:00
2020-04-07 10:20:53 -04:00
for ( chan_n = 0 , i = c - > begin ( ) ; i ! = c - > end ( ) ; + + i , + + chan_n ) {
2017-03-06 06:47:46 -05:00
ChannelInfo * chan ( * i ) ;
2020-02-20 02:20:45 -05:00
/* we want all channels to read from the same position, but
* audio_read ( ) will increment its position argument . So
* reinitialize this for every channel .
*/
file_sample_tmp = fsa ;
2020-04-07 10:20:53 -04:00
samplecnt_t ts = total_space ;
2017-03-06 06:47:46 -05:00
2022-08-11 13:03:00 -04:00
const guint wr_space = chan - > rbuf - > write_space ( ) ;
samplecnt_t to_read = min ( ts , ( samplecnt_t ) wr_space ) ;
2020-04-07 10:20:53 -04:00
to_read = min ( to_read , samples_to_read ) ;
2017-03-06 06:47:46 -05:00
assert ( to_read > = 0 ) ;
if ( to_read ) {
2019-11-23 01:41:56 -05:00
ReaderChannelInfo * rci = dynamic_cast < ReaderChannelInfo * > ( chan ) ;
2019-12-10 11:28:41 -05:00
if ( ! _playlists [ DataType : : AUDIO ] ) {
chan - > rbuf - > write_zero ( to_read ) ;
} else {
2022-08-11 13:03:00 -04:00
samplecnt_t nread , nwritten ;
2019-12-10 11:28:41 -05:00
if ( ( nread = audio_read ( sum_buffer , mixdown_buffer , gain_buffer , file_sample_tmp , to_read , rci , chan_n , reversed ) ) ! = to_read ) {
2022-08-11 13:03:00 -04:00
error < < string_compose ( _ ( " DiskReader %1: when refilling, cannot read %2 from playlist at sample %3 (rv: %4) " ) , name ( ) , to_read , fsa , nread ) < < endmsg ;
2019-12-10 11:28:41 -05:00
ret = - 1 ;
goto out ;
}
2022-08-11 13:03:00 -04:00
if ( ( nwritten = chan - > rbuf - > write ( sum_buffer , nread ) ) ! = nread ) {
error < < string_compose ( _ ( " DiskReader %1: when refilling, cannot write %2 into buffer (wrote %3, space %4) " ) , name ( ) , nread , nwritten , wr_space ) < < endmsg ;
2019-12-10 11:28:41 -05:00
ret = - 1 ;
}
2017-03-06 06:47:46 -05:00
}
2020-04-07 20:12:58 -04:00
if ( ! rci - > initialized ) {
2023-05-22 15:04:37 -04:00
DEBUG_TRACE ( DEBUG : : DiskIO , string_compose ( " -- Init ReaderChannel '%1' read: %2 samples, at: %4, avail: %5 \n " , name ( ) , to_read , file_sample_tmp , rci - > rbuf - > read_space ( ) ) ) ;
2020-04-07 20:12:58 -04:00
rci - > initialized = true ;
}
2017-03-06 06:47:46 -05:00
}
if ( zero_fill ) {
2019-02-06 10:59:41 -05:00
/* not sure if action is needed,
* we ' ll later hit the " to close to the end " case
*/
2019-12-10 11:28:41 -05:00
//chan->rbuf->write_zero (zero_fill);
2017-03-06 06:47:46 -05:00
}
}
2020-04-07 10:20:53 -04:00
#if 0
elapsed = g_get_monotonic_time ( ) - before ;
cerr < < ' \t ' < < name ( ) < < " : bandwidth = " < < ( byte_size_for_read / 1048576.0 ) / ( elapsed / 1000000.0 ) < < " MB/sec \n " ;
# endif
2017-03-06 06:47:46 -05:00
2017-09-18 12:39:17 -04:00
file_sample [ DataType : : AUDIO ] = file_sample_tmp ;
assert ( file_sample [ DataType : : AUDIO ] > = 0 ) ;
2017-03-06 06:47:46 -05:00
2017-09-18 12:39:17 -04:00
ret = ( ( total_space - samples_to_read ) > _chunk_samples ) ;
2017-03-06 06:47:46 -05:00
2020-04-07 10:20:53 -04:00
out :
2017-03-06 06:47:46 -05:00
return ret ;
}
2017-03-06 07:07:34 -05:00
void
2020-09-20 18:34:09 -04:00
DiskReader : : playlist_ranges_moved ( list < Temporal : : RangeMove > const & movements , bool from_undo_or_shift )
2017-03-06 07:07:34 -05:00
{
/* If we're coming from an undo, it will have handled
2018-12-21 11:15:38 -05:00
* automation undo ( it must , since automation - follows - regions
* can lose automation data ) . Hence we can do nothing here .
*
* Likewise when shifting regions ( insert / remove time )
* automation is taken care of separately ( busses with
* automation have no disk - reader ) .
*/
2017-03-06 07:07:34 -05:00
2018-12-21 11:15:38 -05:00
if ( from_undo_or_shift ) {
2017-03-06 07:07:34 -05:00
return ;
}
2021-02-14 15:43:06 -05:00
if ( Config - > get_automation_follows_regions ( ) = = false ) {
2017-03-06 07:07:34 -05:00
return ;
}
/* move panner automation */
2023-05-22 15:04:37 -04:00
std : : shared_ptr < Pannable > pannable = _track . pannable ( ) ;
2020-04-07 10:20:53 -04:00
Evoral : : ControlSet : : Controls & c ( pannable - > controls ( ) ) ;
for ( Evoral : : ControlSet : : Controls : : iterator ci = c . begin ( ) ; ci ! = c . end ( ) ; + + ci ) {
2023-02-16 18:33:28 -05:00
std : : shared_ptr < AutomationControl > ac = std : : dynamic_pointer_cast < AutomationControl > ( ci - > second ) ;
2020-04-07 10:20:53 -04:00
if ( ! ac ) {
continue ;
}
2023-02-16 18:33:28 -05:00
std : : shared_ptr < AutomationList > alist = ac - > alist ( ) ;
2020-04-07 10:20:53 -04:00
if ( ! alist - > size ( ) ) {
2017-03-06 07:07:34 -05:00
continue ;
}
2020-04-07 10:20:53 -04:00
XMLNode & before = alist - > get_state ( ) ;
bool const things_moved = alist - > move_ranges ( movements ) ;
if ( things_moved ) {
_session . add_command ( new MementoCommand < AutomationList > (
* alist . get ( ) , & before , & alist - > get_state ( ) ) ) ;
}
}
2017-03-06 07:07:34 -05:00
/* move processor automation */
2020-09-20 18:34:09 -04:00
_track . foreach_processor ( boost : : bind ( & DiskReader : : move_processor_automation , this , _1 , movements ) ) ;
2017-03-06 07:07:34 -05:00
}
void
2023-02-16 18:33:28 -05:00
DiskReader : : move_processor_automation ( std : : weak_ptr < Processor > p , list < Temporal : : RangeMove > const & movements )
2017-03-06 07:07:34 -05:00
{
2023-02-16 18:33:28 -05:00
std : : shared_ptr < Processor > processor ( p . lock ( ) ) ;
2017-03-06 07:07:34 -05:00
if ( ! processor ) {
return ;
}
set < Evoral : : Parameter > const a = processor - > what_can_be_automated ( ) ;
for ( set < Evoral : : Parameter > : : const_iterator i = a . begin ( ) ; i ! = a . end ( ) ; + + i ) {
2023-02-16 18:33:28 -05:00
std : : shared_ptr < AutomationList > al = processor - > automation_control ( * i ) - > alist ( ) ;
2020-04-07 10:20:53 -04:00
if ( ! al - > size ( ) ) {
2017-03-06 07:07:34 -05:00
continue ;
}
2020-04-07 10:20:53 -04:00
XMLNode & before = al - > get_state ( ) ;
2017-03-06 07:07:34 -05:00
bool const things_moved = al - > move_ranges ( movements ) ;
if ( things_moved ) {
_session . add_command (
2020-04-07 10:20:53 -04:00
new MementoCommand < AutomationList > (
* al . get ( ) , & before , & al - > get_state ( ) ) ) ;
2017-03-06 07:07:34 -05:00
}
}
}
2017-03-07 13:40:37 -05:00
void
DiskReader : : reset_tracker ( )
{
2019-10-15 18:57:23 -04:00
_tracker . reset ( ) ;
2017-03-07 13:40:37 -05:00
}
void
2017-09-18 12:39:17 -04:00
DiskReader : : resolve_tracker ( Evoral : : EventSink < samplepos_t > & buffer , samplepos_t time )
2017-03-07 13:40:37 -05:00
{
2019-10-15 18:57:23 -04:00
_tracker . resolve_notes ( buffer , time ) ;
2017-03-07 13:40:37 -05:00
}
/** Writes playback events from playback_sample for nframes to dst, translating time stamps
* so that an event at playback_sample has time = 0
*/
void
2017-09-30 17:30:52 -04:00
DiskReader : : get_midi_playback ( MidiBuffer & dst , samplepos_t start_sample , samplepos_t end_sample , MonitorState ms , BufferSet & scratch_bufs , double speed , samplecnt_t disk_samples_to_consume )
2017-03-07 13:40:37 -05:00
{
2020-04-07 10:20:53 -04:00
RTMidiBuffer * rtmb = rt_midibuffer ( ) ;
2019-10-18 01:15:53 -04:00
2020-04-07 10:20:53 -04:00
if ( ! rtmb | | ( rtmb - > size ( ) = = 0 ) ) {
2019-10-17 19:32:56 -04:00
/* no data to read, so do nothing */
2019-10-14 21:00:32 -04:00
return ;
}
2018-11-28 20:05:26 -05:00
2019-10-30 13:48:48 -04:00
MidiBuffer * target ;
2017-03-07 13:40:37 -05:00
2019-10-30 13:48:48 -04:00
if ( ms & MonitoringInput ) {
/* data from disk needs to be *merged* not written into the
2020-04-07 10:20:53 -04:00
* dst , because it may contain input data that we want to
* monitor . Since RTMidiBuffer currently ( Oct 2019 ) has no
* suitable method , put the disk data into a scratch buffer and
* then merge later .
*/
2017-03-07 13:40:37 -05:00
2019-10-30 13:48:48 -04:00
target = & scratch_bufs . get_midi ( 0 ) ;
} else {
/* No need to preserve the contents of the input buffer. But
* Route : : process_output_buffers ( ) clears the buffer as - needed
* so know we do not need to clear it .
*/
target = & dst ;
}
2017-03-07 13:40:37 -05:00
2023-02-17 02:31:21 -05:00
if ( _no_disk_output . load ( ) ) {
2022-06-25 15:36:50 -04:00
return ;
}
2017-03-07 13:40:37 -05:00
2022-06-25 15:36:50 -04:00
const samplecnt_t nframes = abs ( end_sample - start_sample ) ;
2017-06-12 15:39:09 -04:00
2022-06-25 15:36:50 -04:00
if ( ms & MonitoringDisk ) {
/* disk data needed */
Location * loc = _loop_location ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
if ( loc ) {
2022-07-15 13:54:21 -04:00
/* squish() operates in the location's time-domain. When the location was created
* using music - time , and later converted to audio - time , it can happen that the
* corresponding super - clock is " between samples " . e . g loop - end is at sample 1000.12 .
* if start_sample = 1000 ; squish ( ) does nothing because 1000 < 1000.12 .
* This is solved by creating the range using ( rounded ) sample - times .
*/
2023-05-22 15:04:37 -04:00
const Temporal : : Range loop_range ( loc - > start ( ) . samples ( ) , loc - > end ( ) . samples ( ) ) ;
2022-06-25 15:36:50 -04:00
samplepos_t effective_start = start_sample ;
samplecnt_t cnt = nframes ;
sampleoffset_t offset = 0 ;
2023-05-22 15:04:37 -04:00
const samplepos_t loop_end = loc - > end_sample ( ) ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " LOOP read, loop is %1..%2 range is %3..%4 nf %5 \n " , loc - > start ( ) , loc - > end ( ) , start_sample , end_sample , nframes ) ) ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
do {
samplepos_t effective_end ;
2019-11-06 10:58:09 -05:00
2023-05-22 15:04:37 -04:00
effective_start = loop_range . squish ( timepos_t ( effective_start ) ) . samples ( ) ;
2022-06-25 15:36:50 -04:00
effective_end = min ( effective_start + cnt , loop_end ) ;
assert ( effective_end > effective_start ) ;
2019-11-06 10:58:09 -05:00
2023-11-20 23:32:49 -05:00
if ( _midi_catchup & & _need_midi_catchup ) {
MidiStateTracker mst ;
rtmb - > track ( mst , effective_start , effective_end ) ;
mst . flush ( dst , 0 , false ) ;
_need_midi_catchup = false ;
}
2022-06-25 15:36:50 -04:00
const samplecnt_t this_read = effective_end - effective_start ;
2019-11-06 11:03:55 -05:00
2022-06-25 15:36:50 -04:00
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " playback buffer LOOP read, from %1 to %2 (%3) \n " , effective_start , effective_end , this_read ) ) ;
2019-11-06 10:58:09 -05:00
2022-06-21 20:35:24 -04:00
# ifndef NDEBUG
2022-06-25 15:36:50 -04:00
size_t events_read =
2022-06-21 20:35:24 -04:00
# endif
2023-11-20 23:32:49 -05:00
rtmb - > read ( * target , effective_start , effective_end , _tracker , offset ) ;
2022-06-21 20:35:24 -04:00
2022-06-25 15:36:50 -04:00
cnt - = this_read ;
effective_start + = this_read ;
offset + = this_read ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " %1 MDS events LOOP read %2 cnt now %3 \n " , _name , events_read , cnt ) ) ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
if ( cnt ) {
/* We re going to have to read across the loop end. Resolve any notes the extend across the loop end.
* Time is relative to start_sample .
*/
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " read crosses loop end, resolve @ %1 \n " , effective_end - start_sample ) ) ;
_tracker . resolve_notes ( * target , effective_end - start_sample ) ;
}
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
} while ( cnt ) ;
2019-11-06 10:58:09 -05:00
2022-06-25 15:36:50 -04:00
} else {
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " playback buffer read, from %1 to %2 (%3) \n " , start_sample , end_sample , nframes ) ) ;
2023-11-20 23:32:49 -05:00
if ( _midi_catchup & & _need_midi_catchup ) {
MidiStateTracker mst ;
rtmb - > track ( mst , start_sample , end_sample ) ;
mst . flush ( dst , 0 , false ) ;
_need_midi_catchup = false ;
}
2022-06-25 15:36:50 -04:00
DEBUG_RESULT ( size_t , events_read , rtmb - > read ( * target , start_sample , end_sample , _tracker ) ) ;
DEBUG_TRACE ( DEBUG : : MidiDiskIO , string_compose ( " %1 MDS events read %2 range %3 .. %4 \n " , _name , events_read , playback_sample , playback_sample + nframes ) ) ;
2017-03-07 13:40:37 -05:00
}
2022-06-25 15:36:50 -04:00
}
2017-03-07 13:40:37 -05:00
2022-06-25 15:36:50 -04:00
if ( ms & MonitoringInput ) {
/* merges data from disk (in "target", which is a scratch
* buffer in this case ) into the actual destination buffer
* ( which holds existing input data ) .
*/
dst . merge_from ( * target , nframes ) ;
2017-06-12 15:39:09 -04:00
}
2017-09-30 16:44:44 -04:00
#if 0
if ( ! target - > empty ( ) ) {
cerr < < " ======== MIDI OUT ======== \n " ;
for ( MidiBuffer : : iterator i = target - > begin ( ) ; i ! = target - > end ( ) ; + + i ) {
const Evoral : : Event < MidiBuffer : : TimeType > ev ( * i , false ) ;
cerr < < " MIDI EVENT (from disk) @ " < < ev . time ( ) ;
for ( size_t xx = 0 ; xx < ev . size ( ) ; + + xx ) {
cerr < < ' ' < < hex < < ( int ) ev . buffer ( ) [ xx ] ;
}
cerr < < dec < < endl ;
}
cerr < < " ---------------- \n " ;
}
# endif
2017-03-07 13:40:37 -05:00
}
2019-11-15 17:12:49 -05:00
void
DiskReader : : inc_no_disk_output ( )
{
2023-02-17 02:31:21 -05:00
_no_disk_output . fetch_add ( 1 ) ;
2019-11-15 17:12:49 -05:00
}
2017-03-07 13:40:37 -05:00
2017-07-20 17:53:56 -04:00
void
2019-09-17 20:26:03 -04:00
DiskReader : : dec_no_disk_output ( )
2017-07-20 17:53:56 -04:00
{
2019-09-17 20:26:03 -04:00
/* this is called unconditionally when things happen that ought to end
2020-04-07 10:20:53 -04:00
* a period of " no disk output " . It ' s OK for that to happen when there
* was no corresponding call to : : inc_no_disk_output ( ) , but we must
* stop the value from becoming negative .
*/
2019-09-17 20:26:03 -04:00
do {
2023-02-17 02:31:21 -05:00
gint v = _no_disk_output . load ( ) ;
2019-09-17 20:26:03 -04:00
if ( v > 0 ) {
2023-02-17 02:31:21 -05:00
if ( _no_disk_output . compare_exchange_strong ( v , v - 1 ) ) {
2019-09-17 20:26:03 -04:00
break ;
}
} else {
break ;
}
} while ( true ) ;
2017-07-20 17:53:56 -04:00
}
2019-02-07 09:00:37 -05:00
2020-02-12 11:06:41 -05:00
/* min gain difference for de-click and loop-fadess
* ( - 60 dB difference to target )
*/
# define GAIN_COEFF_DELTA (1e-5)
2019-02-07 09:00:37 -05:00
DiskReader : : DeclickAmp : : DeclickAmp ( samplecnt_t sample_rate )
{
2020-09-03 19:50:58 -04:00
_a = 800.f / ( gain_t ) sample_rate ; // ~ 1/50Hz to fade by 40dB
2019-02-07 09:00:37 -05:00
_l = - log1p ( _a ) ;
_g = 0 ;
}
void
2019-11-23 01:41:56 -05:00
DiskReader : : DeclickAmp : : apply_gain ( AudioBuffer & buf , samplecnt_t n_samples , const float target , sampleoffset_t buffer_offset )
2019-02-07 09:00:37 -05:00
{
if ( n_samples = = 0 ) {
return ;
}
float g = _g ;
if ( g = = target ) {
2019-11-23 01:41:56 -05:00
assert ( buffer_offset = = 0 ) ;
2019-02-07 09:00:37 -05:00
Amp : : apply_simple_gain ( buf , n_samples , target , 0 ) ;
return ;
}
2020-04-07 10:20:53 -04:00
const float a = _a ;
2019-02-07 09:00:37 -05:00
Sample * const buffer = buf . data ( ) ;
2020-09-03 19:50:58 -04:00
const int max_nproc = 4 ;
2020-04-07 10:20:53 -04:00
uint32_t remain = n_samples ;
uint32_t offset = buffer_offset ;
2019-11-18 22:47:47 -05:00
2019-02-07 09:00:37 -05:00
while ( remain > 0 ) {
2019-07-11 11:15:42 -04:00
uint32_t n_proc = remain > max_nproc ? max_nproc : remain ;
2019-02-07 09:00:37 -05:00
for ( uint32_t i = 0 ; i < n_proc ; + + i ) {
buffer [ offset + i ] * = g ;
}
# if 1
g + = a * ( target - g ) ;
# else /* accurate exponential fade */
2019-07-11 11:15:42 -04:00
if ( n_proc = = max_nproc ) {
2019-02-07 09:00:37 -05:00
g + = a * ( target - g ) ;
} else {
2019-07-11 11:15:42 -04:00
g = target - ( target - g ) * expf ( _l * n_proc / max_nproc ) ;
2019-02-07 09:00:37 -05:00
}
# endif
remain - = n_proc ;
offset + = n_proc ;
}
2020-02-12 11:06:41 -05:00
if ( fabsf ( g - target ) < GAIN_COEFF_DELTA ) {
2019-02-07 09:00:37 -05:00
_g = target ;
} else {
_g = g ;
}
}
2019-10-18 01:15:53 -04:00
2019-11-23 01:41:56 -05:00
DiskReader : : Declicker : : Declicker ( )
2020-04-07 10:20:53 -04:00
: fade_start ( 0 )
, fade_end ( 0 )
, fade_length ( 0 )
, vec ( 0 )
2019-11-23 01:41:56 -05:00
{
}
DiskReader : : Declicker : : ~ Declicker ( )
{
2020-04-07 10:20:53 -04:00
delete [ ] vec ;
2019-11-23 01:41:56 -05:00
}
void
2020-09-25 11:08:21 -04:00
DiskReader : : Declicker : : alloc ( samplecnt_t sr , bool fadein , bool linear )
2019-11-23 01:41:56 -05:00
{
2020-04-07 10:20:53 -04:00
delete [ ] vec ;
2019-11-23 01:41:56 -05:00
vec = new Sample [ loop_fade_length ] ;
2020-09-25 11:08:21 -04:00
if ( linear ) {
if ( fadein ) {
for ( samplecnt_t n = 0 ; n < loop_fade_length ; + + n ) {
2023-05-22 15:04:37 -04:00
vec [ n ] = n / ( float ) loop_fade_length ;
2020-09-25 11:08:21 -04:00
}
} else {
for ( samplecnt_t n = 0 ; n < loop_fade_length ; + + n ) {
2023-05-22 15:04:37 -04:00
vec [ n ] = 1.f - n / ( float ) loop_fade_length ;
2020-09-25 11:08:21 -04:00
}
}
fade_length = loop_fade_length - 1 ;
return ;
}
/* Exponential fade */
2020-09-22 10:44:17 -04:00
const float a = 390.f / sr ; // ~ 1/100Hz for 40dB
2019-11-23 01:41:56 -05:00
/* build a psuedo-exponential (linear-volume) shape for the fade */
samplecnt_t n ;
if ( fadein ) {
gain_t g = 0.0 ;
2020-02-12 11:04:44 -05:00
for ( n = 0 ; ( n < loop_fade_length ) & & ( ( 1.f - g ) > GAIN_COEFF_DELTA ) ; + + n ) {
2019-11-23 01:41:56 -05:00
vec [ n ] = g ;
g + = a * ( 1.0 - g ) ;
}
} else {
gain_t g = 1.0 ;
2020-02-12 11:04:44 -05:00
for ( n = 0 ; ( n < loop_fade_length ) & & ( g > GAIN_COEFF_DELTA ) ; + + n ) {
2019-11-23 01:41:56 -05:00
vec [ n ] = g ;
g + = a * - g ;
}
}
2020-09-25 10:33:55 -04:00
assert ( n > 0 & & n < = loop_fade_length ) ;
2020-02-12 11:19:57 -05:00
2020-09-25 10:33:55 -04:00
fade_length = n - 1 ;
2019-11-23 01:41:56 -05:00
2020-09-25 10:33:55 -04:00
/* Fill remaining fader-buffer with the target value.
*
* This is needed for loop x - fade . Due to float precision near 1.0 , fade - in length
* is can be one or two samples shorter than fade - out length ( depending on sample - rate ) .
* Summing the fade - in and fade - out curve over the complete fade - range ( fade - out ,
* as done by DiskReader : : maybe_xfade_loop ) must yield 1.0 + / - GAIN_COEFF_DELTA .
*/
for ( ; n < loop_fade_length ; + + n ) {
vec [ n ] = fadein ? 1.f : 0.f ;
}
2019-11-23 01:41:56 -05:00
}
void
DiskReader : : Declicker : : reset ( samplepos_t loop_start , samplepos_t loop_end , bool fadein , samplecnt_t sr )
{
if ( loop_start = = loop_end ) {
fade_start = 0 ;
2020-04-07 10:20:53 -04:00
fade_end = 0 ;
2019-11-23 01:41:56 -05:00
return ;
}
/* adjust the position of the fade (this is absolute (global) timeline units) */
if ( fadein ) {
fade_start = loop_start ;
2020-04-07 10:20:53 -04:00
fade_end = loop_start + fade_length ;
2019-11-23 01:41:56 -05:00
} else {
fade_start = loop_end - fade_length ;
2020-04-07 10:20:53 -04:00
fade_end = loop_end ;
2019-11-23 01:41:56 -05:00
}
}
void
DiskReader : : Declicker : : run ( Sample * buf , samplepos_t read_start , samplepos_t read_end )
{
2022-06-21 20:35:44 -04:00
samplecnt_t n = 0 ; /* how many samples to process */
sampleoffset_t bo = 0 ; /* offset into buffer */
sampleoffset_t vo = 0 ; /* offset into gain vector */
2019-11-23 01:41:56 -05:00
if ( fade_start = = fade_end ) {
return ;
}
2020-04-07 10:20:53 -04:00
/* Determine how the read range overlaps with the fade range, so we can determine
* which part of the fade gain vector to apply to which part of the buffer .
*
* see also DiskReader : : maybe_xfade_loop ( )
*/
2019-11-23 01:41:56 -05:00
2020-09-20 18:34:09 -04:00
switch ( Temporal : : coverage_exclusive_ends ( fade_start , fade_end , read_start , read_end ) ) {
case Temporal : : OverlapInternal :
2020-04-07 10:20:53 -04:00
/* note: start and end points cannot coincide (see evoral/Range.h)
*
* read range is entirely within fade range
*/
2019-11-23 01:41:56 -05:00
bo = 0 ;
2020-04-07 10:20:53 -04:00
vo = read_start - fade_start ;
n = read_end - read_start ;
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapExternal :
2020-04-07 10:20:53 -04:00
/* read range extends on either side of fade range
*
* External allows coincidental start & end points , so check for that
*/
if ( fade_start = = read_start & & fade_end = = read_end ) {
/* fade entire read ... this is SO unlikely ! */
bo = 0 ;
vo = 0 ;
n = fade_end - fade_start ;
} else {
bo = fade_start - read_start ;
vo = 0 ;
n = fade_end - fade_start ;
}
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapStart :
2020-04-07 10:20:53 -04:00
/* read range starts before and ends within fade or at same end as fade */
n = fade_end - read_start ;
2019-11-23 01:41:56 -05:00
vo = 0 ;
bo = fade_start - read_start ;
2020-04-07 10:20:53 -04:00
break ;
2019-11-23 01:41:56 -05:00
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapEnd :
2020-04-07 10:20:53 -04:00
/* read range starts within fade range, but possibly at it's end, so check */
if ( read_start = = fade_end ) {
/* nothing to do */
return ;
}
bo = 0 ;
vo = read_start - fade_start ;
n = fade_end - read_start ;
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapNone :
2020-04-07 10:20:53 -04:00
/* no overlap ... nothing to do */
return ;
2019-11-23 01:41:56 -05:00
}
Sample * b = & buf [ bo ] ;
gain_t * g = & vec [ vo ] ;
for ( sampleoffset_t i = 0 ; i < n ; + + i ) {
b [ i ] * = g [ i ] ;
}
}
void
DiskReader : : maybe_xfade_loop ( Sample * buf , samplepos_t read_start , samplepos_t read_end , ReaderChannelInfo * chan )
{
2023-05-22 15:04:37 -04:00
samplecnt_t n = 0 ; /* how many samples to process */
2022-06-21 20:35:44 -04:00
sampleoffset_t bo = 0 ; /* offset into buffer */
sampleoffset_t vo = 0 ; /* offset into gain vector */
2019-11-23 01:41:56 -05:00
const samplepos_t fade_start = loop_declick_out . fade_start ;
2020-04-07 10:20:53 -04:00
const samplepos_t fade_end = loop_declick_out . fade_end ;
2019-11-23 01:41:56 -05:00
if ( fade_start = = fade_end ) {
return ;
}
2020-04-07 10:20:53 -04:00
/* Determine how the read range overlaps with the fade range, so we can determine
* which part of the fade gain vector to apply to which part of the buffer .
*
* see also DiskReader : : Declicker : : run ( )
*/
2019-11-23 01:41:56 -05:00
2020-09-20 18:34:09 -04:00
switch ( Temporal : : coverage_exclusive_ends ( fade_start , fade_end , read_start , read_end ) ) {
case Temporal : : OverlapInternal :
2020-04-07 10:20:53 -04:00
/* note: start and end points cannot coincide (see evoral/Range.h)
*
* read range is entirely within fade range
*/
2019-11-23 01:41:56 -05:00
bo = 0 ;
2020-04-07 10:20:53 -04:00
vo = read_start - fade_start ;
n = read_end - read_start ;
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapExternal :
2020-04-07 10:20:53 -04:00
/* read range extends on either side of fade range
*
* External allows coincidental start & end points , so check for that
*/
if ( fade_start = = read_start & & fade_end = = read_end ) {
/* fade entire read ... this is SO unlikely ! */
bo = 0 ;
vo = 0 ;
n = fade_end - fade_start ;
} else {
bo = fade_start - read_start ;
vo = 0 ;
n = fade_end - fade_start ;
}
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapStart :
2020-04-07 10:20:53 -04:00
/* read range starts before and ends within fade or at same end as fade */
2020-04-10 18:07:31 -04:00
n = read_end - fade_start ;
2019-11-23 01:41:56 -05:00
vo = 0 ;
bo = fade_start - read_start ;
2020-04-07 10:20:53 -04:00
break ;
2019-11-23 01:41:56 -05:00
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapEnd :
2020-04-07 10:20:53 -04:00
/* read range starts within fade range, but possibly at it's end, so check */
if ( read_start = = fade_end ) {
/* nothing to do */
return ;
}
bo = 0 ;
vo = read_start - fade_start ;
n = fade_end - read_start ;
break ;
2020-09-20 18:34:09 -04:00
case Temporal : : OverlapNone :
2020-04-07 10:20:53 -04:00
/* no overlap ... nothing to do */
return ;
2019-11-23 01:41:56 -05:00
}
Sample * b = & buf [ bo ] ; /* data to be faded out */
Sample * sbuf = & chan - > pre_loop_buffer [ vo ] ; /* pre-loop (maybe silence) to be faded in */
gain_t * og = & loop_declick_out . vec [ vo ] ; /* fade out gain vector */
gain_t * ig = & loop_declick_in . vec [ vo ] ; /* fade in gain vector */
for ( sampleoffset_t i = 0 ; i < n ; + + i ) {
b [ i ] = ( b [ i ] * og [ i ] ) + ( sbuf [ i ] * ig [ i ] ) ;
}
}
2019-10-18 01:15:53 -04:00
RTMidiBuffer *
DiskReader : : rt_midibuffer ( )
{
2023-02-16 18:33:28 -05:00
std : : shared_ptr < Playlist > pl = _playlists [ DataType : : MIDI ] ;
2019-10-18 01:15:53 -04:00
if ( ! pl ) {
return 0 ;
}
2023-02-16 18:33:28 -05:00
std : : shared_ptr < MidiPlaylist > mpl = std : : dynamic_pointer_cast < MidiPlaylist > ( pl ) ;
2019-10-18 01:15:53 -04:00
if ( ! mpl ) {
/* error, but whatever ... */
return 0 ;
}
2020-04-07 10:20:53 -04:00
return mpl - > rendered ( ) ;
2019-10-18 01:15:53 -04:00
}
2019-11-23 01:41:56 -05:00
void
DiskReader : : alloc_loop_declick ( samplecnt_t sr )
{
2020-09-03 19:50:58 -04:00
loop_fade_length = lrintf ( ceil ( - log ( GAIN_COEFF_DELTA / 2. ) / ( 390. / sr ) ) ) ;
2020-09-25 11:08:21 -04:00
loop_declick_in . alloc ( sr , true , Config - > get_loop_fade_choice ( ) = = XFadeLoop ) ;
loop_declick_out . alloc ( sr , false , Config - > get_loop_fade_choice ( ) = = XFadeLoop ) ;
2019-11-23 01:41:56 -05:00
}
2020-02-12 11:06:41 -05:00
# undef GAIN_COEFF_DELTA
2019-11-23 01:41:56 -05:00
void
DiskReader : : reset_loop_declick ( Location * loc , samplecnt_t sr )
{
if ( loc ) {
2020-09-20 18:34:09 -04:00
loop_declick_in . reset ( loc - > start_sample ( ) , loc - > end_sample ( ) , true , sr ) ;
loop_declick_out . reset ( loc - > start_sample ( ) , loc - > end_sample ( ) , false , sr ) ;
2019-11-23 01:41:56 -05:00
} else {
loop_declick_in . reset ( 0 , 0 , true , sr ) ;
loop_declick_out . reset ( 0 , 0 , false , sr ) ;
}
}
void
DiskReader : : set_loop ( Location * loc )
{
Processor : : set_loop ( loc ) ;
if ( ! loc ) {
return ;
}
}
void
2020-05-12 12:00:45 -04:00
DiskReader : : setup_preloop_buffer ( )
2019-11-23 01:41:56 -05:00
{
2019-11-24 13:32:34 -05:00
if ( ! _loop_location ) {
return ;
}
2023-04-07 17:33:13 -04:00
std : : shared_ptr < ChannelList const > c = channels . reader ( ) ;
2019-12-18 17:05:35 -05:00
2020-04-07 10:20:53 -04:00
if ( c - > empty ( ) | | ! _playlists [ DataType : : AUDIO ] ) {
2019-12-18 17:05:35 -05:00
return ;
}
2020-04-07 10:20:53 -04:00
Location * loc = _loop_location ;
boost : : scoped_array < Sample > mix_buf ( new Sample [ loop_fade_length ] ) ;
boost : : scoped_array < Sample > gain_buf ( new Sample [ loop_fade_length ] ) ;
2023-05-22 15:04:37 -04:00
const timepos_t read_start = timepos_t ( loc - > start_sample ( ) - loop_declick_out . fade_length ) ;
const timecnt_t read_cnt = timecnt_t ( loop_declick_out . fade_length ) ;
2019-11-23 01:41:56 -05:00
uint32_t channel = 0 ;
2023-04-07 17:33:13 -04:00
for ( auto const & chan : * c ) {
ReaderChannelInfo * rci = dynamic_cast < ReaderChannelInfo * > ( chan ) ;
2019-11-23 01:41:56 -05:00
rci - > resize_preloop ( loop_fade_length ) ;
2020-04-07 10:20:53 -04:00
if ( loc - > start ( ) > loop_fade_length ) {
2020-09-20 18:34:09 -04:00
audio_playlist ( ) - > read ( rci - > pre_loop_buffer , mix_buf . get ( ) , gain_buf . get ( ) , read_start , read_cnt , channel ) ;
2019-11-23 01:41:56 -05:00
} else {
memset ( rci - > pre_loop_buffer , 0 , sizeof ( Sample ) * loop_fade_length ) ;
}
2023-04-07 17:33:13 -04:00
+ + channel ;
2019-11-23 01:41:56 -05:00
}
}
2023-11-20 23:32:49 -05:00
void
DiskReader : : set_need_midi_catchup ( bool yn )
{
_need_midi_catchup = yn ;
}