diff --git a/libs/ardour/ardour/step_sequencer.h b/libs/ardour/ardour/step_sequencer.h index 925cab07e2..629ed60f1c 100644 --- a/libs/ardour/ardour/step_sequencer.h +++ b/libs/ardour/ardour/step_sequencer.h @@ -32,6 +32,8 @@ #include "pbd/ringbuffer.h" #include "pbd/stateful.h" +#include "evoral/Event.hpp" + #include "temporal/types.h" #include "temporal/beats.h" @@ -46,10 +48,14 @@ class MidiStateTracker; class StepSequencer; class StepSequence; class TempoMap; +class SMFSource; typedef std::pair BeatPosition; typedef std::vector BeatPositions; +typedef Evoral::Event MusicTimeEvent; +typedef std::vector MusicTimeEvents; + class Step : public PBD::Stateful { public: enum Mode { @@ -112,6 +118,11 @@ class Step : public PBD::Stateful { XMLNode& get_state(); int set_state (XMLNode const &, int); + void dump (MusicTimeEvents&, Temporal::Beats const&) const; + + static const int _notes_per_step = 5; + static const int _parameters_per_step = 5; + private: friend class StepSequence; /* HACK */ @@ -143,15 +154,14 @@ class Step : public PBD::Stateful { Note (double n, double v,Temporal::Beats const & o) : number (n), velocity (v), offset (o) {} }; - static const int _notes_per_step = 5; - static const int _parameters_per_step = 5; - Note _notes[_notes_per_step]; ParameterValue _parameters[_parameters_per_step]; size_t _repeat; void check_note (size_t n, MidiBuffer& buf, bool, samplepos_t, samplepos_t, MidiStateTracker&); void check_parameter (size_t n, MidiBuffer& buf, bool, samplepos_t, samplepos_t); + void dump_note (MusicTimeEvents&, size_t n, Temporal::Beats const &) const; + void dump_parameter (MusicTimeEvents&, size_t n, Temporal::Beats const &) const; StepSequencer& sequencer() const; }; @@ -201,6 +211,8 @@ class StepSequence : public PBD::Stateful XMLNode& get_state(); int set_state (XMLNode const &, int); + void dump (MusicTimeEvents&, Temporal::Beats const &) const; + private: StepSequencer& _sequencer; int _index; @@ -250,6 +262,8 @@ class StepSequencer : public PBD::Stateful void queue_note_off (Temporal::Beats const &, uint8_t note, uint8_t velocity, uint8_t channel); + boost::shared_ptr write_to_source (Session& s, std::string p = std::string()) const; + private: mutable Glib::Threads::Mutex _sequence_lock; TempoMap& _tempo_map; @@ -331,6 +345,9 @@ class StepSequencer : public PBD::Stateful NoteOffList note_offs; void check_note_offs (ARDOUR::MidiBuffer&, samplepos_t start_sample, samplepos_t last_sample); void clear_note_offs (); + + bool fill_midi_source (boost::shared_ptr src) const; + }; } /* namespace */ diff --git a/libs/ardour/beatbox.cc b/libs/ardour/beatbox.cc index 8cde28481c..32f8fc39ff 100644 --- a/libs/ardour/beatbox.cc +++ b/libs/ardour/beatbox.cc @@ -91,51 +91,3 @@ BeatBox::state() return node; } - -bool -BeatBox::fill_source (boost::shared_ptr src) -{ - boost::shared_ptr msrc = boost::dynamic_pointer_cast (src); - - if (msrc) { - return fill_midi_source (msrc); - } - - return false; -} - -bool -BeatBox::fill_midi_source (boost::shared_ptr src) -{ -#if 0 - Temporal::Beats smf_beats; - - if (_current_events.empty()) { - return false; - } - - Source::Lock lck (src->mutex()); - - try { - src->mark_streaming_midi_write_started (lck, Sustained); - src->begin_write (); - - for (Events::const_iterator e = _current_events.begin(); e != _current_events.end(); ++e) { - /* convert to quarter notes */ - smf_beats = Temporal::Beats ((*e)->time / (beat_superclocks * (4.0 / _meter_beat_type))); - Evoral::Event ee (Evoral::MIDI_EVENT, smf_beats, (*e)->size, (*e)->buf, false); - src->append_event_beats (lck, ee); - // last_time = (*e)->time; - } - - src->end_write (src->path()); - src->mark_nonremovable (); - src->mark_streaming_write_completed (lck); - return true; - - } catch (...) { - cerr << "Exception during beatbox write to SMF... " << endl; - } -#endif - return false; -} diff --git a/libs/ardour/step_sequencer.cc b/libs/ardour/step_sequencer.cc index 18bd0f8ed1..0db460daad 100644 --- a/libs/ardour/step_sequencer.cc +++ b/libs/ardour/step_sequencer.cc @@ -18,11 +18,16 @@ #include +#include + #include "pbd/i18n.h" #include "ardour/audioengine.h" #include "ardour/midi_buffer.h" #include "ardour/midi_state_tracker.h" +#include "ardour/session.h" +#include "ardour/smf_source.h" +#include "ardour/source_factory.h" #include "ardour/step_sequencer.h" #include "ardour/tempo.h" @@ -30,6 +35,9 @@ using namespace PBD; using namespace ARDOUR; using namespace std; +using std::cerr; +using std::endl; + Step::Step (StepSequence &s, size_t n, Temporal::Beats const & b, int base_note) : _sequence (s) , _index (n) @@ -218,7 +226,7 @@ Step::adjust_octave (int amt) bool Step::run (MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) { - for (size_t n = 0; n < _notes_per_step; ++n) { + for (size_t n = 0; n < _parameters_per_step; ++n) { check_parameter (n, buf, running, start_sample, end_sample); } @@ -250,6 +258,11 @@ Step::check_parameter (size_t n, MidiBuffer& buf, bool running, samplepos_t star { } +void +Step::dump_parameter (MusicTimeEvents& events, size_t n, Temporal::Beats const & pattern_length) const +{ +} + void Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) { @@ -336,6 +349,83 @@ Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sam } } +void +Step::dump_note (MusicTimeEvents& events, size_t n, Temporal::Beats const & pattern_length) const +{ + Note const & note (_notes[n]); + + if (_duration == DurationRatio ()) { + /* no duration, so no new notes on */ + return; + } + + if (note.number < 0) { + /* note not set .. ignore */ + return; + } + + /* figure out when this note would sound */ + + Temporal::Beats note_on_time (sequencer().step_size() * _index); + + note_on_time += note.offset; + note_on_time %= pattern_length; + + /* don't play silent notes */ + + if (note.velocity == 0) { + return; + } + + uint8_t mbuf[3]; + + /* prepare 3 MIDI bytes for note on */ + + mbuf[0] = 0x90 | _sequence.channel(); + + switch (_mode) { + case AbsolutePitch: + mbuf[1] = note.number; + break; + case RelativePitch: + mbuf[1] = _sequence.root() + note.interval; + break; + } + + if (_octave_shift) { + + const int t = mbuf[1] + (12 * _octave_shift); + + if (t > 127 || t < 0) { + /* Out of range */ + return; + } + + mbuf[1] = t; + } + + mbuf[2] = (uint8_t) floor (note.velocity * 127.0); + events.push_back (new MusicTimeEvent (Evoral::MIDI_EVENT, note_on_time, 3, mbuf, true)); + mbuf[0] = 0x80 | _sequence.channel(); + + /* compute note off time based on our duration */ + + Temporal::Beats off_at = note_on_time; + + if (_duration == DurationRatio (1)) { + /* use 1 tick less than the sequence step size + * just to get non-simultaneous on/off events at + * step boundaries. + */ + off_at += Temporal::Beats (0, sequencer().step_size().to_ticks() - 1); + } else { + off_at += Temporal::Beats (0, (sequencer().step_size().to_ticks() * _duration.numerator()) / _duration.denominator()); + } + + off_at %= pattern_length; + events.push_back (new MusicTimeEvent (Evoral::MIDI_EVENT, off_at, 3, mbuf, true)); +} + void Step::reschedule (Temporal::Beats const & start, Temporal::Beats const & offset) { @@ -358,6 +448,18 @@ Step::set_state (XMLNode const &, int) return 0; } +void +Step::dump (MusicTimeEvents& events, Temporal::Beats const & pattern_length) const +{ + for (size_t n = 0; n < _parameters_per_step; ++n) { + dump_parameter (events, n, pattern_length); + } + + for (size_t n = 0; n < _notes_per_step; ++n) { + dump_note (events, n, pattern_length); + } +} + /**/ StepSequence::StepSequence (StepSequencer& s, size_t cnt, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size, int r) @@ -394,10 +496,8 @@ StepSequence::schedule (Temporal::Beats const & start) for (size_t n = s; n < e; ++n) { _steps[n]->set_beat (beats); - cerr << "beat " << n << " @ " << beats << ' '; beats += _sequencer.step_size(); } - cerr << endl; } void @@ -466,6 +566,17 @@ StepSequence::set_state (XMLNode const &, int) return 0; } +void +StepSequence::dump (MusicTimeEvents& events, Temporal::Beats const & pattern_length) const +{ + const size_t s = _sequencer.start_step(); + const size_t e = _sequencer.end_step(); + + for (size_t n = s; n < e; ++n) { + _steps[n]->dump (events, pattern_length); + } +} + /**/ MultiAllocSingleReleasePool StepSequencer::Request::pool (X_("step sequencer requests"), sizeof (StepSequencer::Request), 64); @@ -499,8 +610,6 @@ StepSequencer::~StepSequencer () Temporal::Beats StepSequencer::reschedule (samplepos_t start_sample) { - cerr << "SEQ reschedule\n"; - /* compute the beat position of this first "while-moving * run() call as an offset into the sequencer's current loop * length. @@ -736,3 +845,88 @@ StepSequencer::clear_note_offs () i = note_offs.erase (i); } } + +boost::shared_ptr +StepSequencer::write_to_source (Session& session, string path) const +{ + boost::shared_ptr src; + + /* caller must check for pre-existing file */ + + assert (!path.empty()); + assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)); + + src = boost::dynamic_pointer_cast(SourceFactory::createWritable (DataType::MIDI, session, path, false, session.sample_rate())); + + try { + if (src->create (path)) { + return boost::shared_ptr(); + } + } catch (...) { + return boost::shared_ptr(); + } + + if (!fill_midi_source (src)) { + /* src will go out of scope, and its destructor will remove the + file, if any + */ + return boost::shared_ptr(); + } + + return src; +} + +struct ETC { + bool operator() (MusicTimeEvent const * a, MusicTimeEvent const * b) { + return a->time() < b->time(); + } +}; + +bool +StepSequencer::fill_midi_source (boost::shared_ptr src) const +{ + Temporal::Beats smf_beats; + + Source::Lock lck (src->mutex()); + + /* first pass: run through the sequence one time to get all events, and + * then sort them. We have no idea what order they are in when we pull + * them, because each step may consist of several messages with + * arbitrary offsets. + */ + + MusicTimeEvents events; + events.reserve ((_sequences.size() * nsteps() * (Step::_notes_per_step * 2) * 3) + // the note on and off messages + ((_sequences.size() * nsteps() * Step::_parameters_per_step) * 3)); // CC messages + + const Temporal::Beats total_steps = _step_size * nsteps(); + + for (StepSequences::const_iterator s = _sequences.begin(); s != _sequences.end(); ++s) { + (*s)->dump (events, total_steps); + } + + sort (events.begin(), events.end(), ETC()); + + try { + src->mark_streaming_midi_write_started (lck, Sustained); + src->begin_write (); + + for (MusicTimeEvents::iterator e = events.begin(); e != events.end(); ++e) { + src->append_event_beats (lck, **e); + } + + src->end_write (src->path()); + src->mark_nonremovable (); + src->mark_streaming_write_completed (lck); + return true; + + } catch (...) { + cerr << "Exception during beatbox write to SMF... " << endl; + } + + for (MusicTimeEvents::iterator e = events.begin(); e != events.end(); ++e) { + delete *e; + } + + return false; +}