export step sequencer pattern to SMF
This commit is contained in:
parent
617fcd660c
commit
5c886ecb6e
@ -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<Temporal::Beats,samplepos_t> BeatPosition;
|
||||
typedef std::vector<BeatPosition> BeatPositions;
|
||||
|
||||
typedef Evoral::Event<Temporal::Beats> MusicTimeEvent;
|
||||
typedef std::vector<MusicTimeEvent*> 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<Source> 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<SMFSource> src) const;
|
||||
|
||||
};
|
||||
|
||||
} /* namespace */
|
||||
|
@ -91,51 +91,3 @@ BeatBox::state()
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
bool
|
||||
BeatBox::fill_source (boost::shared_ptr<Source> src)
|
||||
{
|
||||
boost::shared_ptr<SMFSource> msrc = boost::dynamic_pointer_cast<SMFSource> (src);
|
||||
|
||||
if (msrc) {
|
||||
return fill_midi_source (msrc);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
BeatBox::fill_midi_source (boost::shared_ptr<SMFSource> 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<Temporal::Beats> 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;
|
||||
}
|
||||
|
@ -18,11 +18,16 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <glibmm/fileutils.h>
|
||||
|
||||
#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<Source>
|
||||
StepSequencer::write_to_source (Session& session, string path) const
|
||||
{
|
||||
boost::shared_ptr<SMFSource> 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<SMFSource>(SourceFactory::createWritable (DataType::MIDI, session, path, false, session.sample_rate()));
|
||||
|
||||
try {
|
||||
if (src->create (path)) {
|
||||
return boost::shared_ptr<Source>();
|
||||
}
|
||||
} catch (...) {
|
||||
return boost::shared_ptr<Source>();
|
||||
}
|
||||
|
||||
if (!fill_midi_source (src)) {
|
||||
/* src will go out of scope, and its destructor will remove the
|
||||
file, if any
|
||||
*/
|
||||
return boost::shared_ptr<Source>();
|
||||
}
|
||||
|
||||
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<SMFSource> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user