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:
parent
fa6da8818f
commit
74132b60a0
@ -295,7 +295,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
||||
timepos_t current_pos() 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 disarm ();
|
||||
bool armed() const { return _armed; }
|
||||
@ -529,8 +529,10 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
||||
void io_change ();
|
||||
bool probably_oneshot () const;
|
||||
|
||||
void captured (SlotArmInfo&, BufferSet&);
|
||||
|
||||
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_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);
|
||||
|
||||
bool stretching () const;
|
||||
uint32_t channels () const { return data.size(); }
|
||||
|
||||
RubberBand::RubberBandStretcher* alloc_stretcher () const;
|
||||
|
||||
protected:
|
||||
void retrigger ();
|
||||
@ -577,6 +582,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
||||
void estimate_tempo ();
|
||||
void reset_stretcher ();
|
||||
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 ();
|
||||
|
||||
void captured (SlotArmInfo&, BufferSet&, samplepos_t capture_start);
|
||||
void captured (SlotArmInfo&, BufferSet&);
|
||||
void arm();
|
||||
void disarm ();
|
||||
|
||||
@ -759,9 +765,11 @@ struct SlotArmInfo {
|
||||
Trigger& slot;
|
||||
Temporal::timepos_t start;
|
||||
Temporal::timepos_t end;
|
||||
samplecnt_t capture_length;
|
||||
RTMidiBuffer* midi_buf; /* assumed large enough */
|
||||
RTMidiBufferBeats* beats; /* will take over data allocated for midi_but */
|
||||
std::vector<Sample*> audio_buf; /* assumed large enough */
|
||||
RubberBand::RubberBandStretcher* stretcher;
|
||||
};
|
||||
|
||||
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; }
|
||||
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();
|
||||
bool armed() const { return (bool) _arm_info.load(); }
|
||||
PBD::Signal0<void> ArmedChanged;
|
||||
@ -940,7 +948,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
|
||||
bool _cancel_locate_armed;
|
||||
bool _fast_forwarding;
|
||||
bool _record_enabled;
|
||||
samplepos_t capture_start;
|
||||
|
||||
PBD::PCGRand _pcg;
|
||||
|
||||
|
@ -37,9 +37,10 @@
|
||||
#include "temporal/tempo.h"
|
||||
|
||||
#include "ardour/async_midi_port.h"
|
||||
#include "ardour/auditioner.h"
|
||||
#include "ardour/audio_track.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/audioregion.h"
|
||||
#include "ardour/auditioner.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
#include "ardour/debug.h"
|
||||
#include "ardour/import_status.h"
|
||||
@ -287,7 +288,8 @@ void
|
||||
Trigger::arm ()
|
||||
{
|
||||
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;
|
||||
ArmChanged(); /* EMIT SIGNAL */
|
||||
}
|
||||
@ -1578,11 +1580,25 @@ AudioTrigger::natural_length() const
|
||||
}
|
||||
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
|
||||
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);
|
||||
|
||||
@ -1597,7 +1613,9 @@ AudioTrigger::set_region_in_worker_thread (std::shared_ptr<Region> r)
|
||||
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 */
|
||||
|
||||
@ -1795,18 +1813,16 @@ AudioTrigger::reset_stretcher ()
|
||||
to_drop = 0;
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrigger::setup_stretcher ()
|
||||
RubberBand::RubberBandStretcher*
|
||||
AudioTrigger::alloc_stretcher () const
|
||||
{
|
||||
using namespace RubberBand;
|
||||
using namespace Temporal;
|
||||
|
||||
if (!_region) {
|
||||
return;
|
||||
}
|
||||
AudioTrack const * trk = static_cast<AudioTrack*> (_box.owner());
|
||||
assert (trk);
|
||||
|
||||
std::shared_ptr<AudioRegion> ar (std::dynamic_pointer_cast<AudioRegion> (_region));
|
||||
const uint32_t nchans = std::min (_box.input_streams().n_audio(), ar->n_channels());
|
||||
const uint32_t nchans = trk->input()->n_ports().n_audio();
|
||||
|
||||
//map our internal enum to a rubberband option
|
||||
RubberBandStretcher::Option ro = RubberBandStretcher::Option (0);
|
||||
@ -1816,11 +1832,15 @@ AudioTrigger::setup_stretcher ()
|
||||
case Trigger::Smooth : ro = RubberBandStretcher::OptionTransientsSmooth; break;
|
||||
}
|
||||
|
||||
RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessRealTime |
|
||||
ro);
|
||||
RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessRealTime | ro);
|
||||
return new RubberBandStretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0);
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrigger::setup_stretcher ()
|
||||
{
|
||||
delete _stretcher;
|
||||
_stretcher = new RubberBandStretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0);
|
||||
_stretcher = alloc_stretcher ();
|
||||
_stretcher->setMaxProcessSize (rb_blocksize);
|
||||
}
|
||||
|
||||
@ -1833,6 +1853,41 @@ AudioTrigger::drop_data ()
|
||||
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
|
||||
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,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm, pframes_t& quantize_offset)
|
||||
{
|
||||
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion>(_region);
|
||||
/* We do not modify the I/O of our parent route, so we process only min (bufs.n_audio(),region.channels()) */
|
||||
const uint32_t nchans = (in_process_context ? std::min (bufs.count().n_audio(), ar->n_channels()) : ar->n_channels());
|
||||
AudioTrack const * trk = static_cast<AudioTrack*> (_box.owner());
|
||||
assert (trk);
|
||||
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;
|
||||
BufferSet* scratch;
|
||||
std::unique_ptr<BufferSet> scratchp;
|
||||
@ -2294,7 +2351,7 @@ MIDITrigger::disarm ()
|
||||
}
|
||||
|
||||
void
|
||||
MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs, samplepos_t capture_start)
|
||||
MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs)
|
||||
{
|
||||
if (ai.midi_buf->size() == 0) {
|
||||
_armed = false;
|
||||
@ -2303,7 +2360,7 @@ MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs, samplepos_t capture_sta
|
||||
return;
|
||||
}
|
||||
|
||||
ai.midi_buf->shift (-capture_start);
|
||||
ai.midi_buf->shift (-ai.start.samples());
|
||||
ai.midi_buf->convert (*ai.beats);
|
||||
|
||||
/* Note: the original MIDI buffer in ai is now invalid, all data has
|
||||
@ -3301,8 +3358,10 @@ SlotArmInfo::SlotArmInfo (Trigger& s)
|
||||
: slot (s)
|
||||
, start (0)
|
||||
, end (0)
|
||||
, capture_length (0)
|
||||
, midi_buf (nullptr)
|
||||
, beats (nullptr)
|
||||
, stretcher (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -3310,9 +3369,10 @@ SlotArmInfo::~SlotArmInfo()
|
||||
{
|
||||
delete midi_buf;
|
||||
delete beats;
|
||||
delete stretcher;
|
||||
|
||||
for (auto & ab : audio_buf) {
|
||||
delete ab;
|
||||
delete [] ab;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3383,7 +3443,6 @@ TriggerBox::TriggerBox (Session& s, DataType dt)
|
||||
, _cancel_locate_armed (false)
|
||||
, _fast_forwarding (false)
|
||||
, _record_enabled (false)
|
||||
, capture_start (0)
|
||||
, requests (1024)
|
||||
, _arm_info (nullptr)
|
||||
{
|
||||
@ -3410,7 +3469,7 @@ TriggerBox::TriggerBox (Session& s, DataType dt)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -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) {
|
||||
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;
|
||||
@ -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());
|
||||
|
||||
ai->start = t_samples;
|
||||
ai->end = tmap->sample_at (now_beats + Beats (16, 0)); // XXX slot duration/length
|
||||
|
||||
if (currently_recording) {
|
||||
currently_recording->disarm ();
|
||||
currently_recording = nullptr;
|
||||
}
|
||||
|
||||
currently_recording = this;
|
||||
_arm_info = ai;
|
||||
}
|
||||
|
||||
@ -3460,40 +3520,86 @@ TriggerBox::finish_recording (BufferSet& bufs)
|
||||
{
|
||||
SlotArmInfo* ai = _arm_info.load();
|
||||
assert (ai);
|
||||
ai->slot.captured (*ai, bufs, capture_start);
|
||||
ai->slot.captured (*ai, bufs);
|
||||
_arm_info = nullptr;
|
||||
currently_recording = nullptr;
|
||||
capture_start = 0;
|
||||
}
|
||||
|
||||
void
|
||||
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();
|
||||
SlotArmInfo* ai = _arm_info.load();
|
||||
pframes_t offset = 0;
|
||||
bool reached_end = false;
|
||||
|
||||
if (!ai) {
|
||||
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->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);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (start_sample < ai->start.samples()) {
|
||||
std::cerr << "waiting to capture\n";
|
||||
if (ai->end.samples() != 0 && (start_sample > ai->end.samples())) {
|
||||
std::cerr << "passed us by!\n";
|
||||
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 */
|
||||
|
||||
@ -3504,8 +3610,11 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_
|
||||
for (size_t n = 0; n < n_buffers; ++n) {
|
||||
assert (ai->audio_buf.size() >= n);
|
||||
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 */
|
||||
@ -3539,10 +3648,15 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_
|
||||
|
||||
if (!skip_event && (!filter || !filter->filter(ev.buffer(), ev.size()))) {
|
||||
const samplepos_t event_time = start_sample + ev.time();
|
||||
std::cerr << "\twritten to midi rt buf\n";
|
||||
ai->midi_buf->write (event_time, ev.event_type(), ev.size(), ev.buffer());
|
||||
if (!ai->end || (event_time < ai->end.samples())) {
|
||||
ai->midi_buf->write (event_time, ev.event_type(), ev.size(), ev.buffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reached_end) {
|
||||
finish_recording (bufs);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -4884,9 +4998,9 @@ TriggerBox::run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
|
||||
pframes_t frames_covered;
|
||||
|
||||
|
||||
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion> (_currently_playing->region());
|
||||
if (ar) {
|
||||
max_chans = std::max (ar->n_channels(), max_chans);
|
||||
AudioTrack const * trk = static_cast<AudioTrack*> (_owner);
|
||||
if (trk) {
|
||||
max_chans = std::max (trk->input()->n_ports().n_audio(), max_chans);
|
||||
}
|
||||
|
||||
/* 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";
|
||||
MIDITrigger* mt = dynamic_cast<MIDITrigger*> (t);
|
||||
AudioTrigger* at;
|
||||
|
||||
if (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());
|
||||
assert (ms);
|
||||
|
||||
std::cerr << "Created new source from rtmb, called " << ms->name() << std::endl;
|
||||
|
||||
RTMidiBufferBeats const & rtmb = t->rt_midi_buffer();
|
||||
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user