From ac4bb55f1314d3652bc7c0dcfebeaae28ed50a32 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 25 Sep 2024 18:34:28 -0600 Subject: [PATCH] first functioning MIDI clip recording (libs) --- libs/ardour/ardour/route.h | 2 - libs/ardour/ardour/rt_midibuffer.h | 18 +- libs/ardour/ardour/triggerbox.h | 54 ++++- libs/ardour/route.cc | 16 +- libs/ardour/rt_midibuffer.cc | 20 +- libs/ardour/track.cc | 14 -- libs/ardour/triggerbox.cc | 344 ++++++++++++++++++++++++++++- libs/ardour/wscript | 1 - 8 files changed, 392 insertions(+), 77 deletions(-) diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h index da47b6544f..2e1be45087 100644 --- a/libs/ardour/ardour/route.h +++ b/libs/ardour/ardour/route.h @@ -72,7 +72,6 @@ namespace ARDOUR { class Amp; class BeatBox; class DelayLine; -class ClipRecProcessor; class Delivery; class DiskReader; class DiskWriter; @@ -628,7 +627,6 @@ protected: std::shared_ptr _pannable; std::shared_ptr _disk_reader; std::shared_ptr _disk_writer; - std::shared_ptr _clip_recorder; #ifdef HAVE_BEATBOX std::shared_ptr _beatbox; #endif diff --git a/libs/ardour/ardour/rt_midibuffer.h b/libs/ardour/ardour/rt_midibuffer.h index a317c6f4cf..92bf5d80ae 100644 --- a/libs/ardour/ardour/rt_midibuffer.h +++ b/libs/ardour/ardour/rt_midibuffer.h @@ -56,7 +56,7 @@ class LIBARDOUR_API RTMidiBufferBase : public Evoral::EventSink a reference to any data. The data is all "moved" to the returned RTMidiBufferBase and timestamps modified to its time domain if nececssary. */ - RTMidiBufferBase* convert (); + void convert (RTMidiBufferBase&); void clear(); void resize(size_t); @@ -106,9 +106,6 @@ class LIBARDOUR_API RTMidiBufferBase : public Evoral::EventSink */ void shift (DistanceType distance) { - if (_size == 0) { - return; - } for (size_t n = 0; n < _size; ++n) { _data[n].timestamp += distance; } @@ -152,18 +149,5 @@ class LIBARDOUR_API RTMidiBufferBase : public Evoral::EventSink typedef RTMidiBufferBase RTMidiBuffer; -class Trigger; - -struct SlotArmInfo { - SlotArmInfo (Trigger& s); - ~SlotArmInfo(); - - Trigger& slot; - Temporal::timepos_t start; - Temporal::timepos_t end; - std::shared_ptr midi_buf; /* assumed large enough */ - std::vector audio_buf; /* assumed large enough */ -}; - } // namespace ARDOUR diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 00bfb118e7..aba0a5c299 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -82,6 +82,7 @@ LIBARDOUR_API std::string cue_marker_name (int32_t); class Trigger; typedef std::shared_ptr TriggerPtr; +typedef RTMidiBufferBase RTMidiBufferBeats; class LIBARDOUR_API Trigger : public PBD::Stateful { public: @@ -294,9 +295,11 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { timepos_t current_pos() const; double position_as_fraction() const; - virtual void captured (SlotArmInfo&) {} - virtual void arm() {} - virtual void disarm () {} + virtual void captured (SlotArmInfo&, BufferSet&, samplepos_t capture_start) {} + virtual void arm(); + virtual void disarm (); + bool armed() const { return _armed; } + PBD::Signal0 ArmChanged; Temporal::BBT_Argument compute_start (Temporal::TempoMap::SharedPtr const &, samplepos_t start, samplepos_t end, Temporal::BBT_Offset const & q, samplepos_t& start_samples, bool& will_start); virtual timepos_t compute_end (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t, Temporal::Beats &) = 0; @@ -324,6 +327,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { void set_region (std::shared_ptr, bool use_thread = true); void clear_region (); virtual int set_region_in_worker_thread (std::shared_ptr) = 0; + virtual int set_region_in_worker_thread_from_capture (std::shared_ptr) = 0; std::shared_ptr region() const { return _region; } uint32_t index() const { return _index; } @@ -450,6 +454,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { gain_t _pending_velocity_gain; gain_t _velocity_gain; bool _cue_launched; + bool _armed; /* these are only used by midi triggers but the ui_state API needs them */ Evoral::SMF::UsedChannels _used_channels; @@ -525,6 +530,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger { bool probably_oneshot () const; int set_region_in_worker_thread (std::shared_ptr); + int set_region_in_worker_thread_from_capture (std::shared_ptr) { return 0; } void jump_start (); void jump_stop (BufferSet& bufs, pframes_t dest_offset); @@ -579,7 +585,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { MIDITrigger (uint32_t index, TriggerBox&); ~MIDITrigger (); - void captured (SlotArmInfo&); + void captured (SlotArmInfo&, BufferSet&, samplepos_t capture_start); void arm(); void disarm (); @@ -605,6 +611,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { void estimate_midi_patches (); int set_region_in_worker_thread (std::shared_ptr); + int set_region_in_worker_thread_from_capture (std::shared_ptr); void jump_start (); void shutdown (BufferSet& bufs, pframes_t dest_offset); void jump_stop (BufferSet& bufs, pframes_t dest_offset); @@ -638,6 +645,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { std::vector const & channel_map() const { return _channel_map; } void check_edit_swap (timepos_t const &, bool playing, BufferSet&); + RTMidiBufferBeats const & rt_midi_buffer() const { return *rt_midibuffer.load(); } protected: void retrigger (); @@ -663,8 +671,6 @@ class LIBARDOUR_API MIDITrigger : public Trigger { uint32_t first_event_index; uint32_t last_event_index; - typedef RTMidiBufferBase RTMidiBufferBeats; - std::atomic rt_midibuffer; std::atomic pending_rt_midibuffer; std::atomic old_rt_midibuffer; @@ -687,6 +693,7 @@ class LIBARDOUR_API TriggerBoxThread void set_region (TriggerBox&, uint32_t slot, std::shared_ptr); void request_delete_trigger (Trigger* t); + void request_build_source (Trigger* t); void summon(); void stop(); @@ -699,7 +706,8 @@ class LIBARDOUR_API TriggerBoxThread enum RequestType { Quit, SetRegion, - DeleteTrigger + DeleteTrigger, + BuildSourceAndRegion }; struct Request { @@ -711,7 +719,7 @@ class LIBARDOUR_API TriggerBoxThread TriggerBox* box; uint32_t slot; std::shared_ptr region; - /* for DeleteTrigger */ + /* for DeleteTrigger and BuildSourceAndRegion */ Trigger* trigger; void* operator new (size_t); @@ -727,6 +735,9 @@ class LIBARDOUR_API TriggerBoxThread CrossThreadChannel _xthread; void queue_request (Request*); void delete_trigger (Trigger*); + void build_source (Trigger*); + void build_midi_source (MIDITrigger*); + void build_audio_source (AudioTrigger*); }; struct CueRecord { @@ -741,6 +752,18 @@ struct CueRecord { typedef PBD::RingBuffer CueRecords; +struct SlotArmInfo { + SlotArmInfo (Trigger& s); + ~SlotArmInfo(); + + Trigger& slot; + Temporal::timepos_t start; + Temporal::timepos_t end; + RTMidiBuffer* midi_buf; /* assumed large enough */ + RTMidiBufferBeats* beats; /* will take over data allocated for midi_but */ + std::vector audio_buf; /* assumed large enough */ +}; + class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_from_this { public: @@ -763,6 +786,11 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro bool record_enabled() const { return _record_enabled; } PBD::Signal0 RecEnableChanged; + void arm_from_another_thread (Trigger& slot, samplepos_t, timecnt_t const & expected_duration, uint32_t chans); + void disarm(); + bool armed() const { return (bool) _arm_info.load(); } + PBD::Signal0 ArmedChanged; + void run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool result_required); void run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes); bool can_support_io_configuration (const ChanCount& in, ChanCount& out); @@ -912,9 +940,14 @@ 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; + void maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes); + void finish_recording (BufferSet& bufs); + void set_armed (SlotArmInfo*); + /* These four are accessed (read/write) only from process() context */ void drop_triggers (); @@ -969,6 +1002,9 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro int handle_stopped_trigger (BufferSet& bufs, pframes_t dest_offset); PBD::ScopedConnection stop_all_connection; + std::atomic _arm_info; + + static TriggerBox* currently_recording; typedef std::map,std::pair > CustomMidiMap; static CustomMidiMap _custom_midi_map; @@ -1042,5 +1078,3 @@ DEFINE_ENUM_CONVERT(ARDOUR::FollowAction::Type); DEFINE_ENUM_CONVERT(ARDOUR::Trigger::LaunchStyle); DEFINE_ENUM_CONVERT(ARDOUR::Trigger::StretchMode); } /* namespace PBD */ - - diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 045801befa..ca4dc2f87c 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -59,7 +59,6 @@ #include "ardour/buffer.h" #include "ardour/buffer_set.h" #include "ardour/capturing_processor.h" -#include "ardour/cliprec.h" #include "ardour/debug.h" #include "ardour/delivery.h" #include "ardour/disk_reader.h" @@ -2804,9 +2803,6 @@ Route::set_state (const XMLNode& node, int version) DiskIOPoint diop; if (node.get_property (X_("disk-io-point"), diop)) { - if (_clip_recorder) { - _clip_recorder->set_display_to_user (diop == DiskIOCustom); - } if (_disk_writer) { _disk_writer->set_display_to_user (diop == DiskIOCustom); } @@ -3237,9 +3233,6 @@ Route::set_processor_state (const XMLNode& node, int version) } else if (prop->value() == "diskwriter" && _disk_writer) { _disk_writer->set_state (**niter, version); new_order.push_back (_disk_writer); - } else if (prop->value() == "cliprec" && _clip_recorder) { - _clip_recorder->set_state (**niter, version); - new_order.push_back (_clip_recorder); } else if (prop->value() == "triggerbox") { if (!_triggerbox) { _triggerbox.reset (new TriggerBox (_session, _default_type)); @@ -5428,7 +5421,7 @@ Route::setup_invisible_processors () /* DISK READER & WRITER (for Track objects) */ - if (_disk_reader || _disk_writer || _clip_recorder) { + if (_disk_reader || _disk_writer) { switch (_disk_io_point) { case DiskIOPreFader: if (trim != new_processors.end()) { @@ -5476,9 +5469,6 @@ Route::setup_invisible_processors () assert (writer_pos == find (new_processors.begin(), new_processors.end(), _disk_writer)); reader_pos = new_processors.insert (++writer_pos, _disk_reader); } - if (_clip_recorder) { - new_processors.insert (reader_pos, _clip_recorder); - } } /* Polarity Invert */ @@ -6098,10 +6088,6 @@ Route::set_disk_io_point (DiskIOPoint diop) display = false; } - if (_clip_recorder) { - _clip_recorder->set_display_to_user (display); - } - if (_disk_writer) { _disk_writer->set_display_to_user (display); } diff --git a/libs/ardour/rt_midibuffer.cc b/libs/ardour/rt_midibuffer.cc index 1b96ca8e17..5194b7519c 100644 --- a/libs/ardour/rt_midibuffer.cc +++ b/libs/ardour/rt_midibuffer.cc @@ -534,11 +534,9 @@ RTMidiBufferBase::track_state (TimeType when, MidiStateTr } template -RTMidiBufferBase* -RTMidiBufferBase::convert() +void +RTMidiBufferBase::convert (RTMidiBufferBase& beats) { - RTMidiBufferBase* beats = new RTMidiBufferBase(); - /* Convert timestamps, taking advantage of the fact that beats and * samples are both 64 bit integers, and thus Item::timestamp is the * same size and type for both. @@ -552,19 +550,19 @@ RTMidiBufferBase::convert() /* Hand over all the data */ - beats->_data = reinterpret_cast::Item*> (_data); - beats->_size = _size; - beats->_pool = _pool; - beats->_pool_size = _pool_size; - beats->_pool_size = _pool_size; + beats._data = reinterpret_cast::Item*> (_data); + beats._size = _size; + beats._capacity = _capacity; + beats._reversed = _reversed; + beats._pool = _pool; + beats._pool_size = _pool_size; + beats._pool_size = _pool_size; _data = nullptr; _pool = nullptr; _size = 0; _pool_size = 0; _pool_capacity = 0; - - return beats; } diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index 40b30fe9d7..e2336e7a6f 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -28,7 +28,6 @@ #include "ardour/audiofilesource.h" #include "ardour/audioplaylist.h" #include "ardour/audioregion.h" -#include "ardour/cliprec.h" #include "ardour/debug.h" #include "ardour/delivery.h" #include "ardour/disk_reader.h" @@ -90,10 +89,6 @@ Track::~Track () if (_disk_writer) { _disk_writer.reset (); } - - if (_clip_recorder) { - _clip_recorder.reset (); - } } int @@ -118,9 +113,6 @@ Track::init () _disk_writer->set_block_size (_session.get_block_size ()); _disk_writer->set_owner (this); - _clip_recorder.reset (new ClipRecProcessor (_session, *this, name(), data_type(), *this)); - _clip_recorder->set_owner (this); - /* no triggerbox for the auditioner, to avoid visual clutter in * patchbays and elsewhere (or special-case code in those places) */ @@ -1000,12 +992,6 @@ Track::set_processor_state (XMLNode const& node, int version, XMLProperty const* new_order.push_back (_disk_writer); return true; } - } else if (prop->value() == "cliprec") { - if (_clip_recorder) { - _clip_recorder->set_state (node, version); - new_order.push_back (_clip_recorder); - return true; - } } error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index cdd30963b3..ddfb1854b6 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -44,10 +44,12 @@ #include "ardour/debug.h" #include "ardour/import_status.h" #include "ardour/midi_buffer.h" +#include "ardour/midi_channel_filter.h" #include "ardour/midi_cursor.h" #include "ardour/midi_model.h" #include "ardour/midi_state_tracker.h" #include "ardour/midi_region.h" +#include "ardour/midi_track.h" #include "ardour/minibpm.h" #include "ardour/port.h" #include "ardour/region_factory.h" @@ -238,6 +240,7 @@ Trigger::Trigger (uint32_t n, TriggerBox& b) , _pending_velocity_gain (1.0) , _velocity_gain (1.0) , _cue_launched (false) + , _armed (false) , _used_channels (Evoral::SMF::UsedChannels()) , _estimated_tempo (0.) , _segment_tempo (0.) @@ -280,6 +283,24 @@ Trigger::request_trigger_delete (Trigger* t) TriggerBox::worker->request_delete_trigger (t); } +void +Trigger::arm () +{ + std::cerr << "T::arm\n"; + _box.arm_from_another_thread (*this, _box.session().transport_sample(), timecnt_t::max (Temporal::AudioTime), 0); + _armed = true; + ArmChanged(); /* EMIT SIGNAL */ +} + +void +Trigger::disarm () +{ + std::cerr << "T::disarm\n"; + _box.disarm (); + _armed = false; + ArmChanged(); /* EMIT SIGNAL */ +} + void Trigger::get_ui_state (Trigger::UIState &state) const { @@ -2182,6 +2203,8 @@ MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b) , last_event_samples (0) , _start_offset (0, 0, 0) , _legato_offset (0, 0, 0) + , first_event_index (0) + , last_event_index (0) , rt_midibuffer (nullptr) , pending_rt_midibuffer (nullptr) , old_rt_midibuffer (nullptr) @@ -2261,20 +2284,56 @@ MIDITrigger::check_edit_swap (timepos_t const & time, bool playing, BufferSet& b void MIDITrigger::arm () { + Trigger::arm (); } void MIDITrigger::disarm () { + Trigger::disarm (); } void -MIDITrigger::captured (SlotArmInfo& ai) +MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs, samplepos_t capture_start) { - RTMidiBufferBeats* rtmb = ai.midi_buf->convert (); + if (ai.midi_buf->size() == 0) { + _armed = false; + ArmChanged(); /* EMIT SIGNAL */ + delete &ai; + return; + } + + ai.midi_buf->shift (-capture_start); + ai.midi_buf->convert (*ai.beats); + /* Note: the original MIDI buffer in ai is now invalid, all data has * been moved to rtmb. */ + + old_rt_midibuffer = rt_midibuffer.exchange (ai.beats); + + loop_start = Temporal::Beats(); + loop_end = (*ai.beats)[ai.beats->size()-1].timestamp; + data_length = loop_end; + _follow_length = Temporal::BBT_Offset (0, data_length.get_beats(), 0); + set_length (timecnt_t (data_length)); + iter = 0; + first_event_index = 0; + last_event_index = ai.beats->size(); + _follow_action0 = FollowAction::Again; + + /* Mark ai.beats as null so that it is not deleted */ + ai.beats = nullptr; + delete &ai; + + /* start playing */ + _box.queue_explict (index()); + + /* Meanwhile, build a new source and region from the data now in rt_midibuffer */ + TriggerBox::worker->request_build_source (this); + + _armed = false; + ArmChanged(); /* EMIT SIGNAL */ } void @@ -2744,6 +2803,36 @@ MIDITrigger::estimate_midi_patches () } +int +MIDITrigger::set_region_in_worker_thread_from_capture (std::shared_ptr r) +{ + assert (r); + + std::shared_ptr mr = std::dynamic_pointer_cast (r); + + if (!mr) { + return -1; + } + + set_region_internal (r); + set_name (mr->name()); + + _model = mr->model(); + _model->ContentsChanged.connect_same_thread (content_connection, boost::bind (&MIDITrigger::model_contents_changed, this)); + + /* we've changed some of our internal values; we need to update our queued UIState or they will be lost when UIState is applied */ + copy_to_ui_state (); + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 loaded midi region, span is %2\n", name(), data_length)); + + /* This is being used as a kind of shorthand for "everything" which is + pretty stupid + */ + send_property_change (ARDOUR::Properties::name); + + return 0; +} + int MIDITrigger::set_region_in_worker_thread (std::shared_ptr r) { @@ -2895,13 +2984,10 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en MidiBuffer* mb (in_process_context? &bufs.get_midi (0) : nullptr); typedef Evoral::Event MidiEvent; - const timepos_t region_start_time = _region->start(); + const timepos_t region_start_time = _region ? _region->start() : timepos_t::zero (Temporal::BeatTime); const Temporal::Beats region_start = region_start_time.beats(); Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use()); - std::shared_ptr mr = std::dynamic_pointer_cast (_region); - assert (mr); - last_event_samples = end_sample; /* see if we're going to start or stop or retrigger in this run() call */ @@ -2929,7 +3015,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en while (iter < rtmb->size() && !_playout) { RTMidiBufferBeats::Item const & item ((*rtmb)[iter]); - + std::cerr << "Looking at event #" << iter << " @ " << item.timestamp << " transition was " << transition_beats << " rs " << region_start << std::endl; /* Event times are in beats, relative to start of source * file. We need to conv+ert to region-relative time, and then * a session timeline time, which is defined by the time at @@ -2941,20 +3027,24 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en /* check that the event is within the bounds for this run() call */ if (maybe_last_event_timeline_beats < start_beats) { + std::cerr << "out1\n"; break; } if (iter >= last_event_index) { iter = rtmb->size(); + std::cerr << "out2\n"; break; } if (maybe_last_event_timeline_beats > final_beat) { iter = rtmb->size(); + std::cerr << "out3\n"; break; } if (maybe_last_event_timeline_beats >= end_beats) { + std::cerr << "out4\n"; break; } @@ -2962,6 +3052,8 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en const samplepos_t timeline_samples = tmap->sample_at (maybe_last_event_timeline_beats); + std::cerr << "Plays at " << timeline_samples << std::endl; + uint32_t evsize; uint8_t const * buf = rtmb->bytes (item, evsize); @@ -3205,6 +3297,25 @@ Trigger::make_property_quarks () DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for queued = %1\n", Properties::queued.property_id)); } +SlotArmInfo::SlotArmInfo (Trigger& s) + : slot (s) + , start (0) + , end (0) + , midi_buf (nullptr) + , beats (nullptr) +{ +} + +SlotArmInfo::~SlotArmInfo() +{ + delete midi_buf; + delete beats; + + for (auto & ab : audio_buf) { + delete ab; + } +} + Temporal::BBT_Offset TriggerBox::_assumed_trigger_duration (4, 0, 0); TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::Custom); int TriggerBox::_first_midi_note = 60; @@ -3214,6 +3325,7 @@ CueRecords TriggerBox::cue_records (256); std::atomic TriggerBox::_cue_recording (false); PBD::Signal0 TriggerBox::CueRecordingChanged; bool TriggerBox::roll_requested = false; +TriggerBox* TriggerBox::currently_recording (nullptr); bool TriggerBox::_learning = false; TriggerBox::CustomMidiMap TriggerBox::_custom_midi_map; std::pair TriggerBox::learning_for; @@ -3271,7 +3383,9 @@ TriggerBox::TriggerBox (Session& s, DataType dt) , _cancel_locate_armed (false) , _fast_forwarding (false) , _record_enabled (false) + , capture_start (0) , requests (1024) + , _arm_info (nullptr) { set_display_to_user (false); @@ -3295,6 +3409,142 @@ TriggerBox::TriggerBox (Session& s, DataType dt) _session.config.ParameterChanged.connect_same_thread (*this, boost::bind (&TriggerBox::parameter_changed, this, _1)); } +void +TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, timecnt_t const & expected_duration, uint32_t chans) +{ + using namespace Temporal; + + SlotArmInfo* ai = new SlotArmInfo (slot); + + if (_data_type == DataType::MIDI) { + ai->midi_buf = new RTMidiBuffer; + ai->midi_buf->resize (1024); // XXX Config->max_slot_midi_event_size + ai->beats = new RTMidiBufferBeats; + } else { + 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 + } + } + + 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 (now)); + + slot.compute_quantized_transition (now, now_beats, std::numeric_limits::max(), + 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; +} + +void +TriggerBox::disarm () +{ + std::cerr << "TB::disarm\n"; +} + +void +TriggerBox::finish_recording (BufferSet& bufs) +{ + SlotArmInfo* ai = _arm_info.load(); + assert (ai); + ai->slot.captured (*ai, bufs, capture_start); + _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) +{ + const size_t n_buffers = bufs.count().n_audio(); + SlotArmInfo* ai = _arm_info.load(); + + if (!ai) { + return; + } + + if (!ai->slot.armed() && (currently_recording == this)) { + finish_recording (bufs); + return; + } + + if (speed <= 0.) { + std::cerr << "not moving\n"; + return; + } + + if (start_sample < ai->start.samples()) { + std::cerr << "waiting to capture\n"; + return; + } + + capture_start = ai->start.samples(); + + std::cerr << "capturing from " << capture_start << std::endl; + + /* Audio */ + + if (n_buffers) { + + /* AUDIO */ + + 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); + } + } + + /* MIDI */ + + MidiBuffer& buf = bufs.get_midi (0); + Track* trk = static_cast (_owner); + MidiTrack* mt = dynamic_cast(trk); + MidiChannelFilter* filter = mt ? &mt->capture_filter() : 0; + + if (buf.size()) { + std::cerr << "GOT " << buf.size() << "MIDI events\n"; + } + + for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) { + Evoral::Event ev (*i, false); + if (ev.time() > nframes) { + break; + } + + bool skip_event = false; + + if (mt) { + /* skip injected immediate/out-of-band events */ + MidiBuffer const& ieb (mt->immediate_event_buffer()); + for (MidiBuffer::const_iterator j = ieb.begin(); j != ieb.end(); ++j) { + if (*j == ev) { + skip_event = true; + } + } + } + + 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()); + } + } +} + void TriggerBox::set_record_enabled (bool yn) { @@ -4338,6 +4588,8 @@ TriggerBox::run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t en } #endif + maybe_capture (bufs, start_sample, end_sample, speed, nframes); + bool allstop = _requests.stop_all.exchange (false); bool was_recorded; int32_t cue_bang = _session.first_cue_within (start_sample, end_sample, was_recorded); @@ -5096,6 +5348,9 @@ TriggerBoxThread::thread_work () case DeleteTrigger: delete_trigger (req->trigger); break; + case BuildSourceAndRegion: + build_source (req->trigger); + break; default: break; } @@ -5163,9 +5418,84 @@ TriggerBoxThread::request_delete_trigger (Trigger* t) queue_request (req); } +void +TriggerBoxThread::request_build_source (Trigger* t) +{ + TriggerBoxThread::Request* req = new TriggerBoxThread::Request (BuildSourceAndRegion); + req->trigger = t; + queue_request (req); +} + void TriggerBoxThread::delete_trigger (Trigger* t) { delete t; } +void +TriggerBoxThread::build_source (Trigger* t) +{ + std::cerr << "Build source!\n"; + MIDITrigger* mt = dynamic_cast (t); + + if (mt) { + build_midi_source (mt); + } +} + +void +TriggerBoxThread::build_midi_source (MIDITrigger* t) +{ + Track* trk = static_cast (t->box().owner()); + std::shared_ptr 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(); + + { + MidiSource::WriterLock lock (ms->mutex()); + ms->mark_streaming_midi_write_started (lock, Sustained); + for (size_t n = 0; n < rtmb.size(); ++n) { + RTMidiBufferBeats::Item const & item (rtmb[n]); + uint32_t sz; + uint8_t const * const b = rtmb.bytes (item, sz); + Evoral::Event ev (Evoral::MIDI_EVENT, item.timestamp, sz, b); + + ms->append_event_beats (lock, ev); + } + ms->mark_streaming_write_completed (lock); + std::cerr << "Filled with data, now loading model\n"; + ms->load_model (lock); + } + + /* now build region */ + + PropertyList plist; + SourceList sources; + std::shared_ptr fs (std::dynamic_pointer_cast (ms)); + assert (fs); + + sources.push_back (ms); + + std::string region_name = region_name_from_path (fs->path(), false, false); + + plist.add (ARDOUR::Properties::start, timecnt_t (Temporal::BeatTime)); + plist.add (ARDOUR::Properties::length, ms->length ()); + plist.add (ARDOUR::Properties::name, region_name); + plist.add (ARDOUR::Properties::layer, 0); + plist.add (ARDOUR::Properties::whole_file, true); + plist.add (ARDOUR::Properties::external, false); + plist.add (ARDOUR::Properties::opaque, true); + + std::shared_ptr whole = RegionFactory::create (sources, plist); + std::cerr << "Created WF region " << whole->name() << std::endl; + /* ... and insert a discrete copy into the playlist*/ + PropertyList plist2; + plist2.add (ARDOUR::Properties::whole_file, false); + std::shared_ptr copy (RegionFactory::create (whole, plist2)); + std::cerr << "Created copy region " << whole->name() << std::endl; + + t->set_region_in_worker_thread_from_capture (copy); +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index e88fa703de..6da16f8c45 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -49,7 +49,6 @@ libardour_sources = [ 'chan_mapping.cc', 'circular_buffer.cc', 'clip_library.cc', - 'cliprec.cc', 'config_text.cc', 'control_group.cc', 'control_protocol_manager.cc',