13
0

get the data collection and initial setup part of audio clip recording working

Not yet implemented: writing the data to disk and creating a new Region
This commit is contained in:
Paul Davis 2024-09-26 16:08:44 -06:00
parent fa6da8818f
commit 74132b60a0
2 changed files with 178 additions and 44 deletions

View File

@ -295,7 +295,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
timepos_t current_pos() const; timepos_t current_pos() const;
double position_as_fraction() const; double position_as_fraction() const;
virtual void captured (SlotArmInfo&, BufferSet&, samplepos_t capture_start) {} virtual void captured (SlotArmInfo&, BufferSet&) {}
virtual void arm(); virtual void arm();
virtual void disarm (); virtual void disarm ();
bool armed() const { return _armed; } bool armed() const { return _armed; }
@ -529,8 +529,10 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
void io_change (); void io_change ();
bool probably_oneshot () const; bool probably_oneshot () const;
void captured (SlotArmInfo&, BufferSet&);
int set_region_in_worker_thread (std::shared_ptr<Region>); int set_region_in_worker_thread (std::shared_ptr<Region>);
int set_region_in_worker_thread_from_capture (std::shared_ptr<Region>) { return 0; } int set_region_in_worker_thread_from_capture (std::shared_ptr<Region>);
void jump_start (); void jump_start ();
void jump_stop (BufferSet& bufs, pframes_t dest_offset); void jump_stop (BufferSet& bufs, pframes_t dest_offset);
@ -544,6 +546,9 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
void start_and_roll_to (samplepos_t start, samplepos_t position, uint32_t cnt); void start_and_roll_to (samplepos_t start, samplepos_t position, uint32_t cnt);
bool stretching () const; bool stretching () const;
uint32_t channels () const { return data.size(); }
RubberBand::RubberBandStretcher* alloc_stretcher () const;
protected: protected:
void retrigger (); void retrigger ();
@ -577,6 +582,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
void estimate_tempo (); void estimate_tempo ();
void reset_stretcher (); void reset_stretcher ();
void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &); void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &);
int set_region_in_worker_thread_internal (std::shared_ptr<Region> r, bool from_capture);
}; };
@ -585,7 +591,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
MIDITrigger (uint32_t index, TriggerBox&); MIDITrigger (uint32_t index, TriggerBox&);
~MIDITrigger (); ~MIDITrigger ();
void captured (SlotArmInfo&, BufferSet&, samplepos_t capture_start); void captured (SlotArmInfo&, BufferSet&);
void arm(); void arm();
void disarm (); void disarm ();
@ -759,9 +765,11 @@ struct SlotArmInfo {
Trigger& slot; Trigger& slot;
Temporal::timepos_t start; Temporal::timepos_t start;
Temporal::timepos_t end; Temporal::timepos_t end;
samplecnt_t capture_length;
RTMidiBuffer* midi_buf; /* assumed large enough */ RTMidiBuffer* midi_buf; /* assumed large enough */
RTMidiBufferBeats* beats; /* will take over data allocated for midi_but */ RTMidiBufferBeats* beats; /* will take over data allocated for midi_but */
std::vector<Sample*> audio_buf; /* assumed large enough */ std::vector<Sample*> audio_buf; /* assumed large enough */
RubberBand::RubberBandStretcher* stretcher;
}; };
class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_from_this<TriggerBox> class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_from_this<TriggerBox>
@ -786,7 +794,7 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
bool record_enabled() const { return _record_enabled; } bool record_enabled() const { return _record_enabled; }
PBD::Signal0<void> RecEnableChanged; PBD::Signal0<void> RecEnableChanged;
void arm_from_another_thread (Trigger& slot, samplepos_t, timecnt_t const & expected_duration, uint32_t chans); void arm_from_another_thread (Trigger& slot, samplepos_t, uint32_t chans);
void disarm(); void disarm();
bool armed() const { return (bool) _arm_info.load(); } bool armed() const { return (bool) _arm_info.load(); }
PBD::Signal0<void> ArmedChanged; PBD::Signal0<void> ArmedChanged;
@ -940,7 +948,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
bool _cancel_locate_armed; bool _cancel_locate_armed;
bool _fast_forwarding; bool _fast_forwarding;
bool _record_enabled; bool _record_enabled;
samplepos_t capture_start;
PBD::PCGRand _pcg; PBD::PCGRand _pcg;

View File

@ -37,9 +37,10 @@
#include "temporal/tempo.h" #include "temporal/tempo.h"
#include "ardour/async_midi_port.h" #include "ardour/async_midi_port.h"
#include "ardour/auditioner.h" #include "ardour/audio_track.h"
#include "ardour/audioengine.h" #include "ardour/audioengine.h"
#include "ardour/audioregion.h" #include "ardour/audioregion.h"
#include "ardour/auditioner.h"
#include "ardour/audio_buffer.h" #include "ardour/audio_buffer.h"
#include "ardour/debug.h" #include "ardour/debug.h"
#include "ardour/import_status.h" #include "ardour/import_status.h"
@ -287,7 +288,8 @@ void
Trigger::arm () Trigger::arm ()
{ {
std::cerr << "T::arm\n"; std::cerr << "T::arm\n";
_box.arm_from_another_thread (*this, _box.session().transport_sample(), timecnt_t::max (Temporal::AudioTime), 0); /* XXX get audio channel count somehow */
_box.arm_from_another_thread (*this, _box.session().transport_sample(), 2);
_armed = true; _armed = true;
ArmChanged(); /* EMIT SIGNAL */ ArmChanged(); /* EMIT SIGNAL */
} }
@ -1578,11 +1580,25 @@ AudioTrigger::natural_length() const
} }
return timepos_t (Temporal::BeatTime); return timepos_t (Temporal::BeatTime);
} }
int
AudioTrigger::set_region_in_worker_thread_from_capture (std::shared_ptr<Region> r)
{
return set_region_in_worker_thread_internal (r, true);
}
int int
AudioTrigger::set_region_in_worker_thread (std::shared_ptr<Region> r) AudioTrigger::set_region_in_worker_thread (std::shared_ptr<Region> r)
{ {
assert (!active()); return set_region_in_worker_thread_internal (r, false);
}
int
AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr<Region> r, bool from_capture)
{
if (!from_capture) {
assert (!active());
}
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion> (r); std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion> (r);
@ -1597,7 +1613,9 @@ AudioTrigger::set_region_in_worker_thread (std::shared_ptr<Region> r)
return 0; return 0;
} }
load_data (ar); if (!from_capture) {
load_data (ar);
}
estimate_tempo (); /* NOTE: if this is an existing clip (D+D copy) then it will likely have a SD tempo, and that short-circuits minibpm for us */ estimate_tempo (); /* NOTE: if this is an existing clip (D+D copy) then it will likely have a SD tempo, and that short-circuits minibpm for us */
@ -1795,18 +1813,16 @@ AudioTrigger::reset_stretcher ()
to_drop = 0; to_drop = 0;
} }
void RubberBand::RubberBandStretcher*
AudioTrigger::setup_stretcher () AudioTrigger::alloc_stretcher () const
{ {
using namespace RubberBand; using namespace RubberBand;
using namespace Temporal; using namespace Temporal;
if (!_region) { AudioTrack const * trk = static_cast<AudioTrack*> (_box.owner());
return; assert (trk);
}
std::shared_ptr<AudioRegion> ar (std::dynamic_pointer_cast<AudioRegion> (_region)); const uint32_t nchans = trk->input()->n_ports().n_audio();
const uint32_t nchans = std::min (_box.input_streams().n_audio(), ar->n_channels());
//map our internal enum to a rubberband option //map our internal enum to a rubberband option
RubberBandStretcher::Option ro = RubberBandStretcher::Option (0); RubberBandStretcher::Option ro = RubberBandStretcher::Option (0);
@ -1816,11 +1832,15 @@ AudioTrigger::setup_stretcher ()
case Trigger::Smooth : ro = RubberBandStretcher::OptionTransientsSmooth; break; case Trigger::Smooth : ro = RubberBandStretcher::OptionTransientsSmooth; break;
} }
RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessRealTime | RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessRealTime | ro);
ro); return new RubberBandStretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0);
}
void
AudioTrigger::setup_stretcher ()
{
delete _stretcher; delete _stretcher;
_stretcher = new RubberBandStretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0); _stretcher = alloc_stretcher ();
_stretcher->setMaxProcessSize (rb_blocksize); _stretcher->setMaxProcessSize (rb_blocksize);
} }
@ -1833,6 +1853,41 @@ AudioTrigger::drop_data ()
data.clear (); data.clear ();
} }
void
AudioTrigger::captured (SlotArmInfo& ai, BufferSet&)
{
data.clear ();
data.length = ai.capture_length;
for (auto & s : ai.audio_buf) {
data.push_back (s);
}
ai.audio_buf.clear (); /* data now owned by us, not SlotArmInfo */
/* follow length will get set when we build the region, and
call estimate_tempo(), which hopefully happens before
this finishes playing.
*/
set_length (timecnt_t (data.length));
/* adopt the previously allocated stretcher, with a ratio of 1.0 (no stretch) */
delete _stretcher;
_stretcher = ai.stretcher;
_stretcher->setMaxProcessSize (rb_blocksize);
_stretcher->setTimeRatio (1.0);
ai.stretcher = nullptr;
delete &ai; // XXX delete is not RT-safe
_box.queue_explict (index());
TriggerBox::worker->request_build_source (this);
_armed = false;
ArmChanged(); /* EMIT SIGNAL */
}
int int
AudioTrigger::load_data (std::shared_ptr<AudioRegion> ar) AudioTrigger::load_data (std::shared_ptr<AudioRegion> ar)
{ {
@ -1878,9 +1933,11 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & start, Temporal::Beats const & end,
pframes_t nframes, pframes_t dest_offset, double bpm, pframes_t& quantize_offset) pframes_t nframes, pframes_t dest_offset, double bpm, pframes_t& quantize_offset)
{ {
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion>(_region); AudioTrack const * trk = static_cast<AudioTrack*> (_box.owner());
/* We do not modify the I/O of our parent route, so we process only min (bufs.n_audio(),region.channels()) */ assert (trk);
const uint32_t nchans = (in_process_context ? std::min (bufs.count().n_audio(), ar->n_channels()) : ar->n_channels()); uint32_t nchans = trk->input()->n_ports().n_audio();
/* We do not modify the I/O of our parent route, so we process only min * (bufs.n_audio(), input_channels) */
nchans = (in_process_context ? std::min (bufs.count().n_audio(), nchans) : nchans);
int avail = 0; int avail = 0;
BufferSet* scratch; BufferSet* scratch;
std::unique_ptr<BufferSet> scratchp; std::unique_ptr<BufferSet> scratchp;
@ -2294,7 +2351,7 @@ MIDITrigger::disarm ()
} }
void void
MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs, samplepos_t capture_start) MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs)
{ {
if (ai.midi_buf->size() == 0) { if (ai.midi_buf->size() == 0) {
_armed = false; _armed = false;
@ -2303,7 +2360,7 @@ MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs, samplepos_t capture_sta
return; return;
} }
ai.midi_buf->shift (-capture_start); ai.midi_buf->shift (-ai.start.samples());
ai.midi_buf->convert (*ai.beats); ai.midi_buf->convert (*ai.beats);
/* Note: the original MIDI buffer in ai is now invalid, all data has /* Note: the original MIDI buffer in ai is now invalid, all data has
@ -3301,8 +3358,10 @@ SlotArmInfo::SlotArmInfo (Trigger& s)
: slot (s) : slot (s)
, start (0) , start (0)
, end (0) , end (0)
, capture_length (0)
, midi_buf (nullptr) , midi_buf (nullptr)
, beats (nullptr) , beats (nullptr)
, stretcher (nullptr)
{ {
} }
@ -3310,9 +3369,10 @@ SlotArmInfo::~SlotArmInfo()
{ {
delete midi_buf; delete midi_buf;
delete beats; delete beats;
delete stretcher;
for (auto & ab : audio_buf) { for (auto & ab : audio_buf) {
delete ab; delete [] ab;
} }
} }
@ -3383,7 +3443,6 @@ TriggerBox::TriggerBox (Session& s, DataType dt)
, _cancel_locate_armed (false) , _cancel_locate_armed (false)
, _fast_forwarding (false) , _fast_forwarding (false)
, _record_enabled (false) , _record_enabled (false)
, capture_start (0)
, requests (1024) , requests (1024)
, _arm_info (nullptr) , _arm_info (nullptr)
{ {
@ -3410,7 +3469,7 @@ TriggerBox::TriggerBox (Session& s, DataType dt)
} }
void void
TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, timecnt_t const & expected_duration, uint32_t chans) TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, uint32_t chans)
{ {
using namespace Temporal; using namespace Temporal;
@ -3424,6 +3483,9 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, timecnt_t c
for (uint32_t n = 0; n < chans; ++n) { for (uint32_t n = 0; n < chans; ++n) {
ai->audio_buf.push_back (new Sample[_session.sample_rate() * 30]); // XXX Config->max_slot_audio_duration ai->audio_buf.push_back (new Sample[_session.sample_rate() * 30]); // XXX Config->max_slot_audio_duration
} }
AudioTrigger* at = dynamic_cast<AudioTrigger*> (&slot);
assert (at);
ai->stretcher = at->alloc_stretcher ();
} }
Beats start_b; Beats start_b;
@ -3438,14 +3500,12 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, timecnt_t c
t_bbt, t_beats, t_samples, tmap, slot.quantization()); t_bbt, t_beats, t_samples, tmap, slot.quantization());
ai->start = t_samples; ai->start = t_samples;
ai->end = tmap->sample_at (now_beats + Beats (16, 0)); // XXX slot duration/length
if (currently_recording) { if (currently_recording) {
currently_recording->disarm (); currently_recording->disarm ();
currently_recording = nullptr; currently_recording = nullptr;
} }
currently_recording = this;
_arm_info = ai; _arm_info = ai;
} }
@ -3460,40 +3520,86 @@ TriggerBox::finish_recording (BufferSet& bufs)
{ {
SlotArmInfo* ai = _arm_info.load(); SlotArmInfo* ai = _arm_info.load();
assert (ai); assert (ai);
ai->slot.captured (*ai, bufs, capture_start); ai->slot.captured (*ai, bufs);
_arm_info = nullptr; _arm_info = nullptr;
currently_recording = nullptr; currently_recording = nullptr;
capture_start = 0;
} }
void void
TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes) TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes)
{ {
using namespace Temporal;
const size_t n_buffers = bufs.count().n_audio(); const size_t n_buffers = bufs.count().n_audio();
SlotArmInfo* ai = _arm_info.load(); SlotArmInfo* ai = _arm_info.load();
pframes_t offset = 0;
bool reached_end = false;
if (!ai) { if (!ai) {
return; return;
} }
std::cerr << "slot armed " << ai->slot.armed() << " CR " << currently_recording << " ss " << start_sample << " es " << end_sample << " our end " << ai->end.samples() << std::endl;
if (!ai->slot.armed() && (currently_recording == this)) { if (!ai->slot.armed() && (currently_recording == this)) {
if (!ai->end) {
/* disarmed: compute end */
Beats start_b;
Beats end_b;
BBT_Argument t_bbt;
Beats t_beats;
samplepos_t t_samples;
TempoMap::SharedPtr tmap (TempoMap::use());
Beats now_beats = tmap->quarters_at (timepos_t (start_sample));
ai->slot.compute_quantized_transition (start_sample, now_beats, std::numeric_limits<Beats>::max(),
t_bbt, t_beats, t_samples, tmap, ai->slot.quantization());
std::cerr << "disarm noticed at " << start_sample << " quantized by " << ai->slot.quantization() << " to " << t_samples << std::endl;
ai->end = t_samples;
return;
}
}
if (speed == 0. && currently_recording == this) {
/* We stopped the transport, so just stop immediately (no quantization) */
std::cerr << "stopped, wrap it up\n";
finish_recording (bufs); finish_recording (bufs);
return; return;
} }
if (speed <= 0.) { if (speed <= 0.) {
std::cerr << "not moving\n"; /* we stopped or reversed, but were not recording. Nothing to do here */
std::cerr << "less than stopped\n";
return; return;
} }
if (start_sample < ai->start.samples()) { if (ai->end.samples() != 0 && (start_sample > ai->end.samples())) {
std::cerr << "waiting to capture\n"; std::cerr << "passed us by!\n";
return; return;
} }
capture_start = ai->start.samples(); if (start_sample < ai->start.samples() && end_sample < ai->start.samples() ) {
std::cerr << "not at " << ai->start.samples() << std::endl;
/* Have not yet reached the start of capture */
return;
}
std::cerr << "capturing from " << capture_start << std::endl; std::cerr << "start at " << ai->start.samples() << " vs " << start_sample << " .. " << end_sample << std::endl;
if (ai->start.samples() >= start_sample && ai->start.samples() < end_sample) {
/* Let's get going */
offset = ai->start.samples() - start_sample;
nframes -= offset;
currently_recording = this;
std::cerr << "start capturing, offset " << offset << " nf " << nframes << std::endl;
}
if ((ai->end.samples() != 0) && (start_sample <= ai->end.samples() && ai->end.samples() < end_sample)) {
/* we're going to stop */
nframes -= (end_sample - ai->end.samples());
std::cerr << "going to stop after handling " << nframes << std::endl;
reached_end = true;
}
/* Audio */ /* Audio */
@ -3504,8 +3610,11 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_
for (size_t n = 0; n < n_buffers; ++n) { for (size_t n = 0; n < n_buffers; ++n) {
assert (ai->audio_buf.size() >= n); assert (ai->audio_buf.size() >= n);
AudioBuffer& buf (bufs.get_audio (n%n_buffers)); AudioBuffer& buf (bufs.get_audio (n%n_buffers));
memcpy (buf.data(), ai->audio_buf[n], sizeof (Sample) * nframes); memcpy (ai->audio_buf[n], buf.data() + offset, sizeof (Sample) * nframes);
} }
/* This count is used only for audio */
ai->capture_length += nframes;
} }
/* MIDI */ /* MIDI */
@ -3539,10 +3648,15 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_
if (!skip_event && (!filter || !filter->filter(ev.buffer(), ev.size()))) { if (!skip_event && (!filter || !filter->filter(ev.buffer(), ev.size()))) {
const samplepos_t event_time = start_sample + ev.time(); const samplepos_t event_time = start_sample + ev.time();
std::cerr << "\twritten to midi rt buf\n"; if (!ai->end || (event_time < ai->end.samples())) {
ai->midi_buf->write (event_time, ev.event_type(), ev.size(), ev.buffer()); ai->midi_buf->write (event_time, ev.event_type(), ev.size(), ev.buffer());
}
} }
} }
if (reached_end) {
finish_recording (bufs);
}
} }
void void
@ -4884,9 +4998,9 @@ TriggerBox::run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
pframes_t frames_covered; pframes_t frames_covered;
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion> (_currently_playing->region()); AudioTrack const * trk = static_cast<AudioTrack*> (_owner);
if (ar) { if (trk) {
max_chans = std::max (ar->n_channels(), max_chans); max_chans = std::max (trk->input()->n_ports().n_audio(), max_chans);
} }
/* Quantize offset will generally be zero, but if non-zero, it /* Quantize offset will generally be zero, but if non-zero, it
@ -5437,9 +5551,24 @@ TriggerBoxThread::build_source (Trigger* t)
{ {
std::cerr << "Build source!\n"; std::cerr << "Build source!\n";
MIDITrigger* mt = dynamic_cast<MIDITrigger*> (t); MIDITrigger* mt = dynamic_cast<MIDITrigger*> (t);
AudioTrigger* at;
if (mt) { if (mt) {
build_midi_source (mt); build_midi_source (mt);
} else if ((mt = dynamic_cast<MIDITrigger*> (t))) {
build_audio_source (at);
}
}
void
TriggerBoxThread::build_audio_source (AudioTrigger* t)
{
Track* trk = static_cast<Track*> (t->box().owner());
SourceList sources;
for (uint32_t c = 0; c < t->channels(); ++c) {
std::shared_ptr<AudioSource> as = t->box().session().create_audio_source_for_session (t->channels(), trk->name(), c);
sources.push_back (as);
} }
} }
@ -5450,8 +5579,6 @@ TriggerBoxThread::build_midi_source (MIDITrigger* t)
std::shared_ptr<MidiSource> ms = t->box().session().create_midi_source_for_session (trk->name()); std::shared_ptr<MidiSource> ms = t->box().session().create_midi_source_for_session (trk->name());
assert (ms); assert (ms);
std::cerr << "Created new source from rtmb, called " << ms->name() << std::endl;
RTMidiBufferBeats const & rtmb = t->rt_midi_buffer(); RTMidiBufferBeats const & rtmb = t->rt_midi_buffer();
{ {