mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-04 20:55:48 +01:00
redesign of declicking and fades around loop boundaries
This commit is contained in:
parent
c456006f82
commit
1a2665e25f
16 changed files with 453 additions and 39 deletions
|
|
@ -3176,8 +3176,24 @@ RCOptionEditor::RCOptionEditor ()
|
|||
Gtkmm2ext::UI::instance()->set_tip (bo->tip_widget(),
|
||||
(_("<b>When enabled</b> the loop button does not start playback but forces playback to always play the loop\n\n"
|
||||
"<b>When disabled</b> the loop button starts playing the loop, but stop then cancels loop playback")));
|
||||
|
||||
|
||||
add_option (_("Transport"), bo);
|
||||
|
||||
|
||||
ComboOption<LoopFadeChoice>* lca = new ComboOption<LoopFadeChoice> (
|
||||
"loop-fade-choice",
|
||||
_("Loop Fades"),
|
||||
sigc::mem_fun (*_rc_config, &RCConfiguration::get_loop_fade_choice),
|
||||
sigc::mem_fun (*_rc_config, &RCConfiguration::set_loop_fade_choice)
|
||||
);
|
||||
lca->add (NoLoopFade, _("No fades at loop boundaries"));
|
||||
lca->add (EndLoopFade, _("Fade out at loop end"));
|
||||
lca->add (BothLoopFade, _("Fade in at loop start & Fade out at loop end"));
|
||||
lca->add (XFadeLoop, _("Cross-fade loop end and start"));
|
||||
add_option (_("Transport"), lca);
|
||||
Gtkmm2ext::UI::instance()->set_tip (lca->tip_widget(), _("Options for fades/crossfades at loop boundaries"));
|
||||
|
||||
add_option (_("Transport"), new OptionEditorHeading (_("Dropout (xrun) Handling")));
|
||||
bo = new BoolOption (
|
||||
"stop-recording-on-xrun",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
#include "evoral/Curve.h"
|
||||
|
||||
#include "ardour/disk_io.h"
|
||||
#include "ardour/midi_buffer.h"
|
||||
#include "ardour/midi_state_tracker.h"
|
||||
|
|
@ -53,6 +55,7 @@ public:
|
|||
void realtime_locate (bool);
|
||||
bool overwrite_existing_buffers ();
|
||||
void set_pending_overwrite ();
|
||||
void set_loop (Location *);
|
||||
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
|
|
@ -96,6 +99,7 @@ public:
|
|||
void reset_tracker ();
|
||||
|
||||
bool declick_in_progress () const;
|
||||
void reload_loop ();
|
||||
|
||||
static void set_midi_readahead_samples (samplecnt_t samples_ahead) { midi_readahead = samples_ahead; }
|
||||
|
||||
|
|
@ -110,18 +114,30 @@ public:
|
|||
static void inc_no_disk_output ();
|
||||
static void dec_no_disk_output();
|
||||
static bool no_disk_output () { return g_atomic_int_get (&_no_disk_output); }
|
||||
static void reset_loop_declick (Location*, samplecnt_t sample_rate);
|
||||
static void alloc_loop_declick (samplecnt_t sample_rate);
|
||||
|
||||
protected:
|
||||
friend class Track;
|
||||
friend class MidiTrack;
|
||||
|
||||
struct ReaderChannelInfo : public DiskIOProcessor::ChannelInfo {
|
||||
ReaderChannelInfo (samplecnt_t buffer_size)
|
||||
struct ReaderChannelInfo : public DiskIOProcessor::ChannelInfo
|
||||
{
|
||||
ReaderChannelInfo (samplecnt_t buffer_size, samplecnt_t preloop_size)
|
||||
: DiskIOProcessor::ChannelInfo (buffer_size)
|
||||
, pre_loop_buffer (0)
|
||||
, pre_loop_buffer_size (0)
|
||||
{
|
||||
resize (buffer_size);
|
||||
resize_preloop (preloop_size);
|
||||
}
|
||||
~ReaderChannelInfo() { delete [] pre_loop_buffer; }
|
||||
|
||||
void resize (samplecnt_t);
|
||||
void resize_preloop (samplecnt_t);
|
||||
|
||||
Sample* pre_loop_buffer;
|
||||
samplecnt_t pre_loop_buffer_size;
|
||||
};
|
||||
|
||||
XMLNode& state ();
|
||||
|
|
@ -138,7 +154,7 @@ protected:
|
|||
public:
|
||||
DeclickAmp (samplecnt_t sample_rate);
|
||||
|
||||
void apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target);
|
||||
void apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target, sampleoffset_t buffer_offset = 0);
|
||||
|
||||
float gain () const { return _g; }
|
||||
void set_gain (float g) { _g = g; }
|
||||
|
|
@ -149,6 +165,22 @@ protected:
|
|||
float _g;
|
||||
};
|
||||
|
||||
class Declicker {
|
||||
public:
|
||||
Declicker ();
|
||||
~Declicker ();
|
||||
|
||||
void alloc (samplecnt_t sr, bool fadein);
|
||||
|
||||
void run (Sample* buf, samplepos_t start, samplepos_t end);
|
||||
void reset (samplepos_t start, samplepos_t end, bool fadein, samplecnt_t sr);
|
||||
|
||||
samplepos_t fade_start;
|
||||
samplepos_t fade_end;
|
||||
samplecnt_t fade_length;
|
||||
Sample* vec;
|
||||
};
|
||||
|
||||
private:
|
||||
/** The number of samples by which this diskstream's output should be delayed
|
||||
with respect to the transport sample. This is used for latency compensation.
|
||||
|
|
@ -170,12 +202,18 @@ private:
|
|||
static samplecnt_t midi_readahead;
|
||||
static gint _no_disk_output;
|
||||
|
||||
static Declicker loop_declick_in;
|
||||
static Declicker loop_declick_out;
|
||||
static samplecnt_t loop_fade_length;
|
||||
|
||||
int audio_read (PBD::PlaybackBuffer<Sample>*,
|
||||
Sample* sum_buffer,
|
||||
Sample* mixdown_buffer,
|
||||
float* gain_buffer,
|
||||
samplepos_t& start, samplecnt_t cnt,
|
||||
int channel, bool reversed);
|
||||
ReaderChannelInfo* rci,
|
||||
int channel,
|
||||
bool reversed);
|
||||
|
||||
static Sample* _sum_buffer;
|
||||
static Sample* _mixdown_buffer;
|
||||
|
|
@ -189,6 +227,7 @@ private:
|
|||
RTMidiBuffer* rt_midibuffer();
|
||||
|
||||
void get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, samplepos_t end_sample, MonitorState, BufferSet&, double speed, samplecnt_t distance);
|
||||
void maybe_xfade_loop (Sample*, samplepos_t read_start, samplepos_t read_end, ReaderChannelInfo*);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ CONFIG_VARIABLE (bool, create_xrun_marker, "create-xrun-marker", true)
|
|||
CONFIG_VARIABLE (bool, stop_at_session_end, "stop-at-session-end", false)
|
||||
CONFIG_VARIABLE (float, preroll_seconds, "preroll-seconds", -2.0f)
|
||||
CONFIG_VARIABLE (bool, loop_is_mode, "loop-is-mode", false)
|
||||
CONFIG_VARIABLE (LoopFadeChoice, loop_fade_choice, "loop-fade-choice", XFadeLoop)
|
||||
CONFIG_VARIABLE (samplecnt_t, preroll, "preroll", 0)
|
||||
CONFIG_VARIABLE (samplecnt_t, postroll, "postroll", 0)
|
||||
CONFIG_VARIABLE (float, shuttle_speed_factor, "shuttle-speed-factor", 1.0f) // used for MMC shuttle
|
||||
|
|
|
|||
|
|
@ -360,6 +360,8 @@ public:
|
|||
PBD::Signal0<void> denormal_protection_changed;
|
||||
PBD::Signal0<void> comment_changed;
|
||||
|
||||
virtual void reload_loop();
|
||||
|
||||
bool is_track();
|
||||
|
||||
/** track numbers - assigned by session
|
||||
|
|
|
|||
|
|
@ -1486,7 +1486,6 @@ private:
|
|||
|
||||
PBD::ScopedConnectionList loop_connections;
|
||||
void auto_loop_changed (Location *);
|
||||
void auto_loop_declick_range (Location *, samplepos_t &, samplepos_t &);
|
||||
|
||||
void pre_engine_init (std::string path);
|
||||
int post_engine_init ();
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ public:
|
|||
}
|
||||
void adjust_playback_buffering ();
|
||||
void adjust_capture_buffering ();
|
||||
void reload_loop ();
|
||||
|
||||
PBD::Signal0<void> FreezeChange;
|
||||
PBD::Signal0<void> PlaylistChanged;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ struct TransportFSM
|
|||
};
|
||||
/* for locate */
|
||||
samplepos_t target;
|
||||
bool with_loop;
|
||||
bool for_loop_end;
|
||||
bool force;
|
||||
|
||||
Event (EventType t)
|
||||
|
|
@ -62,7 +62,7 @@ struct TransportFSM
|
|||
, with_roll (false)
|
||||
, with_flush (false)
|
||||
, target (0)
|
||||
, with_loop (false)
|
||||
, for_loop_end (false)
|
||||
, force (false)
|
||||
{}
|
||||
Event (EventType t, bool ab, bool cl)
|
||||
|
|
@ -77,7 +77,7 @@ struct TransportFSM
|
|||
, with_roll (r)
|
||||
, with_flush (fl)
|
||||
, target (pos)
|
||||
, with_loop (lp)
|
||||
, for_loop_end (lp)
|
||||
, force (f4c)
|
||||
{
|
||||
assert (t == Locate);
|
||||
|
|
|
|||
|
|
@ -785,6 +785,13 @@ struct CaptureInfo {
|
|||
samplecnt_t samples;
|
||||
};
|
||||
|
||||
enum LoopFadeChoice {
|
||||
NoLoopFade,
|
||||
EndLoopFade,
|
||||
BothLoopFade,
|
||||
XFadeLoop,
|
||||
};
|
||||
|
||||
typedef std::vector<CaptureInfo*> CaptureInfos;
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ DEFINE_ENUM_CONVERT(ARDOUR::VUMeterStandard)
|
|||
DEFINE_ENUM_CONVERT(ARDOUR::MeterLineUp)
|
||||
DEFINE_ENUM_CONVERT(ARDOUR::MidiPortFlags)
|
||||
DEFINE_ENUM_CONVERT(ARDOUR::TransportRequestType)
|
||||
DEFINE_ENUM_CONVERT(ARDOUR::LoopFadeChoice)
|
||||
|
||||
DEFINE_ENUM_CONVERT(MusicalMode::Type)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
#include "pbd/memento_command.h"
|
||||
#include "pbd/playback_buffer.h"
|
||||
|
||||
#include "evoral/Range.h"
|
||||
|
||||
#include "ardour/amp.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/audioplaylist.h"
|
||||
|
|
@ -52,6 +54,9 @@ Sample* DiskReader::_mixdown_buffer = 0;
|
|||
gain_t* DiskReader::_gain_buffer = 0;
|
||||
samplecnt_t DiskReader::midi_readahead = 4096;
|
||||
gint DiskReader::_no_disk_output (0);
|
||||
DiskReader::Declicker DiskReader::loop_declick_in;
|
||||
DiskReader::Declicker DiskReader::loop_declick_out;
|
||||
samplecnt_t DiskReader::loop_fade_length (0);
|
||||
|
||||
DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
|
||||
: DiskIOProcessor (s, str, f)
|
||||
|
|
@ -80,11 +85,25 @@ DiskReader::ReaderChannelInfo::resize (samplecnt_t bufsize)
|
|||
memset (rbuf->buffer(), 0, sizeof (Sample) * rbuf->bufsize());
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::ReaderChannelInfo::resize_preloop (samplecnt_t bufsize)
|
||||
{
|
||||
if (bufsize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bufsize > pre_loop_buffer_size) {
|
||||
delete [] pre_loop_buffer;
|
||||
pre_loop_buffer = new Sample[bufsize];
|
||||
pre_loop_buffer_size = bufsize;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
DiskReader::add_channel_to (boost::shared_ptr<ChannelList> c, uint32_t how_many)
|
||||
{
|
||||
while (how_many--) {
|
||||
c->push_back (new ReaderChannelInfo (_session.butler()->audio_diskstream_playback_buffer_size()));
|
||||
c->push_back (new ReaderChannelInfo (_session.butler()->audio_diskstream_playback_buffer_size(), loop_fade_length));
|
||||
DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: new reader channel, write space = %2 read = %3\n",
|
||||
name(),
|
||||
c->back()->rbuf->write_space(),
|
||||
|
|
@ -540,7 +559,9 @@ DiskReader::overwrite_existing_buffers ()
|
|||
samplepos_t start = overwrite_sample;
|
||||
samplecnt_t to_read = size;
|
||||
|
||||
if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) {
|
||||
ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (*chan);
|
||||
|
||||
if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, rci, n, reversed)) {
|
||||
error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), size, overwrite_sample) << endmsg;
|
||||
goto midi;
|
||||
}
|
||||
|
|
@ -689,6 +710,7 @@ DiskReader::audio_read (PBD::PlaybackBuffer<Sample>*rb,
|
|||
Sample* mixdown_buffer,
|
||||
float* gain_buffer,
|
||||
samplepos_t& start, samplecnt_t cnt,
|
||||
ReaderChannelInfo* rci,
|
||||
int channel, bool reversed)
|
||||
{
|
||||
samplecnt_t this_read = 0;
|
||||
|
|
@ -758,11 +780,36 @@ DiskReader::audio_read (PBD::PlaybackBuffer<Sample>*rb,
|
|||
|
||||
this_read = min (cnt, this_read);
|
||||
|
||||
/* 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()
|
||||
*/
|
||||
|
||||
if (audio_playlist()->read (sum_buffer, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
|
||||
error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read, start) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (loc) {
|
||||
|
||||
/* Looping: do something (maybe) about the loop boundaries */
|
||||
|
||||
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:
|
||||
maybe_xfade_loop (sum_buffer, start, start + this_read, rci);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reversed) {
|
||||
|
||||
swap_by_ptr (sum_buffer, sum_buffer + this_read - 1);
|
||||
|
|
@ -968,7 +1015,8 @@ DiskReader::refill_audio (Sample* sum_buffer, Sample* mixdown_buffer, float* gai
|
|||
// cerr << owner()->name() << " to-read: " << to_read << endl;
|
||||
|
||||
if (to_read) {
|
||||
if (audio_read (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) {
|
||||
ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (chan);
|
||||
if (audio_read (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, rci, chan_n, reversed)) {
|
||||
error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), to_read, ffa) << endmsg;
|
||||
ret = -1;
|
||||
goto out;
|
||||
|
|
@ -1236,7 +1284,7 @@ DiskReader::DeclickAmp::DeclickAmp (samplecnt_t sample_rate)
|
|||
}
|
||||
|
||||
void
|
||||
DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target)
|
||||
DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target, sampleoffset_t buffer_offset)
|
||||
{
|
||||
if (n_samples == 0) {
|
||||
return;
|
||||
|
|
@ -1244,6 +1292,7 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
|
|||
float g = _g;
|
||||
|
||||
if (g == target) {
|
||||
assert (buffer_offset == 0);
|
||||
Amp::apply_simple_gain (buf, n_samples, target, 0);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1253,7 +1302,7 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
|
|||
|
||||
const int max_nproc = 16;
|
||||
uint32_t remain = n_samples;
|
||||
uint32_t offset = 0;
|
||||
uint32_t offset = buffer_offset;
|
||||
|
||||
while (remain > 0) {
|
||||
uint32_t n_proc = remain > max_nproc ? max_nproc : remain;
|
||||
|
|
@ -1280,6 +1329,232 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
|
|||
}
|
||||
}
|
||||
|
||||
DiskReader::Declicker::Declicker ()
|
||||
: fade_start (0)
|
||||
, fade_end (0)
|
||||
, fade_length (0)
|
||||
, vec (0)
|
||||
{
|
||||
}
|
||||
|
||||
DiskReader::Declicker::~Declicker ()
|
||||
{
|
||||
delete vec;
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::Declicker::alloc (samplecnt_t sr, bool fadein)
|
||||
{
|
||||
delete [] vec;
|
||||
vec = new Sample[loop_fade_length];
|
||||
|
||||
const float a = 1024.0f / sr;
|
||||
|
||||
/* build a psuedo-exponential (linear-volume) shape for the fade */
|
||||
|
||||
samplecnt_t n;
|
||||
|
||||
#define GAIN_COEFF_DELTA (1e-5)
|
||||
|
||||
if (fadein) {
|
||||
gain_t g = 0.0;
|
||||
for (n = 0; (n < sr) && ((1.0 - g) > GAIN_COEFF_DELTA); ++n) {
|
||||
vec[n] = g;
|
||||
g += a * (1.0 - g);
|
||||
}
|
||||
} else {
|
||||
gain_t g = 1.0;
|
||||
for (n = 0; (n < sr) && (g > GAIN_COEFF_DELTA); ++n) {
|
||||
vec[n] = g;
|
||||
g += a * -g;
|
||||
}
|
||||
}
|
||||
|
||||
fade_length = n;
|
||||
|
||||
/* zero out the rest just to be safe */
|
||||
|
||||
memset (&vec[n], 0, sizeof (gain_t) * (loop_fade_length - n));
|
||||
|
||||
#undef GAIN_COEFF_DELTA
|
||||
}
|
||||
|
||||
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;
|
||||
fade_end = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* adjust the position of the fade (this is absolute (global) timeline units) */
|
||||
|
||||
if (fadein) {
|
||||
fade_start = loop_start;
|
||||
fade_end = loop_start + fade_length;
|
||||
} else {
|
||||
fade_start = loop_end - fade_length;
|
||||
fade_end = loop_end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::Declicker::run (Sample* buf, samplepos_t read_start, samplepos_t read_end)
|
||||
{
|
||||
samplecnt_t n; /* how many samples to process */
|
||||
sampleoffset_t bo; /* offset into buffer */
|
||||
sampleoffset_t vo; /* offset into gain vector */
|
||||
|
||||
if (fade_start == fade_end) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
switch (Evoral::coverage (fade_start, fade_end, read_start, read_end)) {
|
||||
|
||||
case Evoral::OverlapInternal:
|
||||
/* note: start and end points cannot coincide (see evoral/Range.h)
|
||||
*
|
||||
* read range is entirely within fade range
|
||||
*/
|
||||
bo = 0;
|
||||
vo = read_start - fade_start;
|
||||
n = read_end - read_start;
|
||||
break;
|
||||
|
||||
case Evoral::OverlapExternal:
|
||||
/* 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;
|
||||
|
||||
case Evoral::OverlapStart:
|
||||
/* read range starts before and ends within fade or at same end as fade */
|
||||
n = fade_end - read_start;
|
||||
vo = 0;
|
||||
bo = fade_start - read_start;
|
||||
break;
|
||||
|
||||
case Evoral::OverlapEnd:
|
||||
/* 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;
|
||||
|
||||
case Evoral::OverlapNone:
|
||||
/* no overlap ... nothing to do */
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
samplecnt_t n; /* how many samples to process */
|
||||
sampleoffset_t bo; /* offset into buffer */
|
||||
sampleoffset_t vo; /* offset into gain vector */
|
||||
|
||||
const samplepos_t fade_start = loop_declick_out.fade_start;
|
||||
const samplepos_t fade_end = loop_declick_out.fade_end;
|
||||
|
||||
if (fade_start == fade_end) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
switch (Evoral::coverage (fade_start, fade_end, read_start, read_end)) {
|
||||
|
||||
case Evoral::OverlapInternal:
|
||||
/* note: start and end points cannot coincide (see evoral/Range.h)
|
||||
*
|
||||
* read range is entirely within fade range
|
||||
*/
|
||||
bo = 0;
|
||||
vo = read_start - fade_start;
|
||||
n = read_end - read_start;
|
||||
break;
|
||||
|
||||
case Evoral::OverlapExternal:
|
||||
/* 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;
|
||||
|
||||
case Evoral::OverlapStart:
|
||||
/* read range starts before and ends within fade or at same end as fade */
|
||||
n = fade_end - read_start;
|
||||
vo = 0;
|
||||
bo = fade_start - read_start;
|
||||
break;
|
||||
|
||||
case Evoral::OverlapEnd:
|
||||
/* 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;
|
||||
|
||||
case Evoral::OverlapNone:
|
||||
/* no overlap ... nothing to do */
|
||||
return;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
RTMidiBuffer*
|
||||
DiskReader::rt_midibuffer ()
|
||||
{
|
||||
|
|
@ -1298,3 +1573,60 @@ DiskReader::rt_midibuffer ()
|
|||
|
||||
return mpl->rendered();
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::alloc_loop_declick (samplecnt_t sr)
|
||||
{
|
||||
loop_fade_length = lrintf (ceil (-log (1e-5) / (1024.f/sr)));
|
||||
loop_declick_in.alloc (sr, true);
|
||||
loop_declick_out.alloc (sr, false);
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::reset_loop_declick (Location* loc, samplecnt_t sr)
|
||||
{
|
||||
if (loc) {
|
||||
loop_declick_in.reset (loc->start(), loc->end(), true, sr);
|
||||
loop_declick_out.reset (loc->start(), loc->end(), false, sr);
|
||||
} 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;
|
||||
}
|
||||
|
||||
reload_loop ();
|
||||
}
|
||||
|
||||
void
|
||||
DiskReader::reload_loop ()
|
||||
{
|
||||
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]);
|
||||
|
||||
boost::shared_ptr<ChannelList> c = channels.reader();
|
||||
uint32_t channel = 0;
|
||||
|
||||
for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
|
||||
|
||||
ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (*chan);
|
||||
|
||||
rci->resize_preloop (loop_fade_length);
|
||||
|
||||
if (loc->start() > loop_fade_length) {
|
||||
audio_playlist()->read (rci->pre_loop_buffer, mix_buf.get(), gain_buf.get(), loc->start() - loop_declick_out.fade_length, loop_declick_out.fade_length, channel);
|
||||
} else {
|
||||
memset (rci->pre_loop_buffer, 0, sizeof (Sample) * loop_fade_length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,8 @@ setup_enum_writer ()
|
|||
TransportFSM::EventType _TransportFSM_EventType;
|
||||
TransportFSM::MotionState _TransportFSM_MotionState;
|
||||
TransportFSM::ButlerState _TransportFSM_ButlerState;
|
||||
|
||||
LoopFadeChoice _LoopFadeChooice;
|
||||
|
||||
#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
|
||||
#define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
|
||||
#define REGISTER_ENUM(e) i.push_back (e); s.push_back (#e)
|
||||
|
|
@ -819,6 +820,12 @@ setup_enum_writer ()
|
|||
REGISTER_CLASS_ENUM (TransportFSM, NotWaitingForButler);
|
||||
REGISTER_CLASS_ENUM (TransportFSM, WaitingForButler);
|
||||
REGISTER (_TransportFSM_ButlerState);
|
||||
|
||||
REGISTER_ENUM (NoLoopFade);
|
||||
REGISTER_ENUM (EndLoopFade);
|
||||
REGISTER_ENUM (BothLoopFade);
|
||||
REGISTER_ENUM (XFadeLoop);
|
||||
REGISTER (_LoopFadeChooice);
|
||||
}
|
||||
|
||||
} /* namespace ARDOUR */
|
||||
|
|
|
|||
|
|
@ -6051,3 +6051,8 @@ Route::monitoring_state () const
|
|||
|
||||
return get_auto_monitoring_state();
|
||||
}
|
||||
|
||||
void
|
||||
Route::reload_loop ()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1408,17 +1408,6 @@ Session::auto_punch_changed (Location* location)
|
|||
auto_punch_end_changed (location);
|
||||
}
|
||||
|
||||
/** @param loc A loop location.
|
||||
* @param pos Filled in with the start time of the required fade-out (in session samples).
|
||||
* @param length Filled in with the length of the required fade-out.
|
||||
*/
|
||||
void
|
||||
Session::auto_loop_declick_range (Location* loc, samplepos_t & pos, samplepos_t & length)
|
||||
{
|
||||
pos = max (loc->start(), loc->end() - 64);
|
||||
length = loc->end() - pos;
|
||||
}
|
||||
|
||||
void
|
||||
Session::auto_loop_changed (Location* location)
|
||||
{
|
||||
|
|
@ -1430,6 +1419,12 @@ Session::auto_loop_changed (Location* location)
|
|||
|
||||
if (play_loop) {
|
||||
|
||||
boost::shared_ptr<RouteList> r = routes.reader ();
|
||||
|
||||
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
||||
(*i)->reload_loop ();
|
||||
}
|
||||
|
||||
if (_transport_sample < location->start() || _transport_sample > location->end()) {
|
||||
// new loop range excludes current transport
|
||||
// sample => relocate to beginning of loop and roll.
|
||||
|
|
@ -1534,9 +1529,6 @@ Session::set_auto_loop_location (Location* location)
|
|||
loop_connections.drop_connections ();
|
||||
existing->set_auto_loop (false, this);
|
||||
remove_event (existing->end(), SessionEvent::AutoLoop);
|
||||
samplepos_t dcp;
|
||||
samplecnt_t dcl;
|
||||
auto_loop_declick_range (existing, dcp, dcl);
|
||||
auto_loop_location_changed (0);
|
||||
}
|
||||
|
||||
|
|
@ -1972,6 +1964,10 @@ Session::set_sample_rate (samplecnt_t frames_per_second)
|
|||
clear_clicks ();
|
||||
reset_write_sources (false);
|
||||
|
||||
DiskReader::alloc_loop_declick (nominal_sample_rate());
|
||||
Location* loc = _locations->auto_loop_location ();
|
||||
DiskReader::reset_loop_declick (loc, nominal_sample_rate());
|
||||
|
||||
// XXX we need some equivalent to this, somehow
|
||||
// SndFileSource::setup_standard_crossfades (frames_per_second);
|
||||
|
||||
|
|
|
|||
|
|
@ -1675,6 +1675,8 @@ Session::set_track_loop (bool yn)
|
|||
(*i)->set_loop (yn ? loc : 0);
|
||||
}
|
||||
}
|
||||
|
||||
DiskReader::reset_loop_declick (loc, nominal_sample_rate());
|
||||
}
|
||||
|
||||
samplecnt_t
|
||||
|
|
|
|||
|
|
@ -572,6 +572,12 @@ Track::set_slaved (bool s)
|
|||
_disk_writer->set_slaved (s);
|
||||
}
|
||||
|
||||
void
|
||||
Track::reload_loop ()
|
||||
{
|
||||
_disk_reader->reload_loop ();
|
||||
}
|
||||
|
||||
ChanCount
|
||||
Track::n_channels ()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ a_row < Stopped, locate, WaitingForLocate, &T::start_locat
|
|||
g_row < WaitingForLocate, locate_done, Stopped, &T::should_not_roll_after_locate >,
|
||||
_row < Rolling, butler_done, Rolling >,
|
||||
_row < Rolling, start_transport, Rolling >,
|
||||
a_row < Rolling, stop_transport, DeclickToStop, &T::start_declick_for_stop >,
|
||||
a_row < DeclickToStop, declick_done, Stopped, &T::stop_playback >,
|
||||
a_row < Rolling, stop_transport, DeclickToStop, &T::stop_playback >,
|
||||
a_row < DeclickToStop, declick_done, Stopped, >,
|
||||
a_row < Rolling, locate, DeclickToLocate, &T::start_declick_for_locate >,
|
||||
a_row < DeclickToLocate, declick_done, WaitingForLocate, &T::start_locate_after_declick >,
|
||||
row < WaitingForLocate, locate_done, Rolling, &T::roll_after_locate, &T::should_roll_after_locate >,
|
||||
|
|
@ -253,7 +253,7 @@ TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
|
|||
ev.with_roll,
|
||||
ev.with_flush,
|
||||
ev.target,
|
||||
ev.with_loop,
|
||||
ev.for_loop_end,
|
||||
ev.force));
|
||||
switch (_motion_state) {
|
||||
case Stopped:
|
||||
|
|
@ -261,7 +261,7 @@ TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
|
|||
start_locate_while_stopped (ev);
|
||||
break;
|
||||
case Rolling:
|
||||
if (ev.with_loop) {
|
||||
if (ev.for_loop_end) {
|
||||
/* we will finish the locate synchronously, so
|
||||
* that after returning from
|
||||
* ::locate_for_loop() we will already have
|
||||
|
|
@ -407,17 +407,17 @@ TransportFSM::start_locate_while_stopped (Event const & l) const
|
|||
|
||||
set_roll_after (l.with_roll);
|
||||
|
||||
api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.with_loop, l.force);
|
||||
api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.for_loop_end, l.force);
|
||||
}
|
||||
|
||||
void
|
||||
TransportFSM::locate_for_loop (Event const & l)
|
||||
{
|
||||
assert (l.type == Locate);
|
||||
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.with_loop));
|
||||
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
|
||||
set_roll_after (l.with_roll);
|
||||
_last_locate = l;
|
||||
api->locate (l.target, l.with_roll, l.with_flush, l.with_loop, l.force);
|
||||
api->locate (l.target, l.with_roll, l.with_flush, l.for_loop_end, l.force);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -427,7 +427,7 @@ TransportFSM::start_locate_after_declick () const
|
|||
current_roll_after_locate_status ? current_roll_after_locate_status.get() : _last_locate.with_roll));
|
||||
|
||||
const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : _last_locate.with_roll;
|
||||
api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.with_loop, _last_locate.force);
|
||||
api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.for_loop_end, _last_locate.force);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -455,7 +455,7 @@ TransportFSM::interrupt_locate (Event const & l) const
|
|||
/* maintain original "with-roll" choice of initial locate, even though
|
||||
* we are interrupting the locate to start a new one.
|
||||
*/
|
||||
api->locate (l.target, false, l.with_flush, l.with_loop, l.force);
|
||||
api->locate (l.target, false, l.with_flush, l.for_loop_end, l.force);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -482,9 +482,9 @@ TransportFSM::should_roll_after_locate () const
|
|||
void
|
||||
TransportFSM::roll_after_locate () const
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.with_loop));
|
||||
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.for_loop_end));
|
||||
current_roll_after_locate_status = boost::none;
|
||||
if (!_last_locate.with_loop) {
|
||||
if (!_last_locate.for_loop_end) {
|
||||
api->start_transport ();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue