Compare commits
14 Commits
master
...
midi-pgm-t
Author | SHA1 | Date |
---|---|---|
Ben Loftis | 9b2d6c136d | |
Ben Loftis | 0ec6a60307 | |
Ben Loftis | be5a56fae3 | |
Ben Loftis | 00db64a4ed | |
Ben Loftis | cd63f615e1 | |
Ben Loftis | 0cee4f45ce | |
Ben Loftis | dce3f2eb65 | |
Ben Loftis | fcd45cfca7 | |
Ben Loftis | bda8048873 | |
Ben Loftis | 75e3646ed8 | |
Ben Loftis | c83b8396c2 | |
Ben Loftis | 9266e47558 | |
Ben Loftis | 472be6e50e | |
Ben Loftis | 8436e55e1b |
|
@ -850,10 +850,25 @@ PatchChangeTriggerWindow::reset (boost::shared_ptr<Route> r, boost::shared_ptr<M
|
|||
|
||||
r->DropReferences.connect (_route_connection, invalidator(*this), boost::bind (&PatchChangeTriggerWindow::clear, this), gui_context());
|
||||
|
||||
/* only show tabs for the chans that this region uses */
|
||||
Evoral::SMF::UsedChannels used = t->segment_used_channels();
|
||||
uint32_t first_used_chan = 15;
|
||||
for (uint32_t chn = 0; chn < 16; ++chn) {
|
||||
if (used.test(chn)) {
|
||||
if (chn < first_used_chan) {
|
||||
first_used_chan = chn;
|
||||
}
|
||||
_w[chn]->show();
|
||||
} else {
|
||||
_w[chn]->hide();
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t chn = 0; chn < 16; ++chn) {
|
||||
_w[chn]->reset (r, t);
|
||||
}
|
||||
_notebook.set_current_page (0);
|
||||
|
||||
_notebook.set_current_page (first_used_chan);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -486,12 +486,13 @@ TriggerClipPicker::row_selected ()
|
|||
if (ms->smf_format()==0) {
|
||||
format_text.set_text ("MIDI Type 0");
|
||||
} else {
|
||||
format_text.set_text (string_compose( _("%1 (%2 Tracks)"), ms->smf_format()==2 ? X_("MIDI Type 2") : X_("MIDI Type 1"), ms->num_tracks()));
|
||||
format_text.set_text (string_compose( _("%1 (%2 Tracks, only the first track will be used)"), ms->smf_format()==2 ? X_("MIDI Type 2") : X_("MIDI Type 1"), ms->num_tracks()));
|
||||
}
|
||||
channels_value.set_text (string_compose(
|
||||
_("Channel(s) used: %1 - %2 "),
|
||||
_("%1 notes on channel: %2%3 "),
|
||||
ms->n_note_on_events(),
|
||||
ARDOUR_UI_UTILS::midi_channels_as_string (ms->used_channels()),
|
||||
ms->has_pgm_change() ? _("with pgms") : X_("")
|
||||
ms->has_pgm_change() ? _(", with pgms") : X_("")
|
||||
));
|
||||
|
||||
_midi_prop_table.show();
|
||||
|
|
|
@ -804,46 +804,45 @@ ARDOUR_UI_UTILS::samples_as_time_string (samplecnt_t s, float rate, bool show_sa
|
|||
}
|
||||
|
||||
string
|
||||
ARDOUR_UI_UTILS::midi_channels_as_string (std::set<uint8_t> const& channels)
|
||||
ARDOUR_UI_UTILS::midi_channels_as_string (std::bitset<16> channels)
|
||||
{
|
||||
if (channels.empty ()) {
|
||||
if (channels.none ()) {
|
||||
return _("none");
|
||||
}
|
||||
|
||||
string rv;
|
||||
auto i = channels.begin ();
|
||||
|
||||
uint8_t next = *i;
|
||||
uint8_t prev = next;
|
||||
rv += to_string<int> (next + 1);
|
||||
for (int i = 0; i<16; i++) {
|
||||
|
||||
do {
|
||||
if (*i == next) {
|
||||
++i;
|
||||
++next;
|
||||
if (i != channels.end ()) {
|
||||
continue;
|
||||
bool prior = i<1 ? false : channels.test(i-1);
|
||||
bool current = channels.test(i);
|
||||
bool next = i>14 ? false : channels.test(i+1);
|
||||
bool nextnext = i>13 ? false : channels.test(i+2);
|
||||
bool future = false;
|
||||
for (int f = i+1; f<16; f++) {
|
||||
if (channels.test(f)) {
|
||||
future = true;
|
||||
}
|
||||
}
|
||||
if (next - prev > 2) {
|
||||
rv += "-";
|
||||
rv += to_string<int> (next);
|
||||
} else if (next - prev > 1) {
|
||||
rv += ", ";
|
||||
rv += to_string<int> (next);
|
||||
|
||||
if (prior && current && next) {
|
||||
/* I'm in the middle of a consecutive chain, maybe just add a dash */
|
||||
if (!rv.empty() && (rv.rfind("-") != rv.length()-1 )) {
|
||||
rv += "-";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == channels.end ()) {
|
||||
break;
|
||||
if (current) {
|
||||
/* here I am! */
|
||||
rv+=to_string<int> (i+1);
|
||||
}
|
||||
|
||||
rv += ", ";
|
||||
rv += to_string<int> (*i + 1);
|
||||
|
||||
prev = *i;
|
||||
next = prev + 1;
|
||||
++i;
|
||||
} while (i != channels.end ());
|
||||
if (current && future && !(next && nextnext)) {
|
||||
/* there are channels after me but they are not 3 consecutive; add a comma */
|
||||
rv += ",";
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ Gdk::Color unique_random_color (std::list<Gdk::Color> &);
|
|||
std::string rate_as_string (float r);
|
||||
std::string samples_as_time_string (ARDOUR::samplecnt_t s, float r, bool show_samples = false);
|
||||
|
||||
std::string midi_channels_as_string (std::set<uint8_t> const&);
|
||||
std::string midi_channels_as_string (std::bitset<16>);
|
||||
|
||||
bool windows_overlap (Gtk::Window *a, Gtk::Window *b);
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ namespace PBD {
|
|||
LIBARDOUR_API extern DebugBits MidiRingBuffer;
|
||||
LIBARDOUR_API extern DebugBits MidiSourceIO;
|
||||
LIBARDOUR_API extern DebugBits MidiTrackers;
|
||||
LIBARDOUR_API extern DebugBits MidiTriggers;
|
||||
LIBARDOUR_API extern DebugBits Monitor;
|
||||
LIBARDOUR_API extern DebugBits OrderKeys;
|
||||
LIBARDOUR_API extern DebugBits Panning;
|
||||
|
|
|
@ -19,9 +19,13 @@
|
|||
#ifndef __libardour_segment_descriptor_h__
|
||||
#define __libardour_segment_descriptor_h__
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include "temporal/timeline.h"
|
||||
#include "temporal/tempo.h"
|
||||
|
||||
#include "evoral/SMF.h"
|
||||
|
||||
class XMLNode;
|
||||
|
||||
namespace ARDOUR {
|
||||
|
@ -58,6 +62,9 @@ public:
|
|||
Temporal::Tempo tempo() const { return _tempo; }
|
||||
void set_tempo (Temporal::Tempo const&);
|
||||
|
||||
Evoral::SMF::UsedChannels used_channels() const { return _used_channels; }
|
||||
void set_used_channels (Evoral::SMF::UsedChannels);
|
||||
|
||||
Temporal::Meter meter() const { return _meter; }
|
||||
void set_meter (Temporal::Meter const&);
|
||||
|
||||
|
@ -69,6 +76,8 @@ public:
|
|||
private:
|
||||
Temporal::TimeDomain _time_domain;
|
||||
|
||||
Evoral::SMF::UsedChannels _used_channels;
|
||||
|
||||
/* cannot use a union for these because Temporal::Beats has a
|
||||
"non-trivial" constructor.
|
||||
*/
|
||||
|
|
|
@ -76,6 +76,9 @@ public:
|
|||
void prevent_deletion ();
|
||||
void set_path (const std::string& newpath);
|
||||
|
||||
/** Query the smf file for its channel info */
|
||||
SMF::UsedChannels used_midi_channels();
|
||||
|
||||
protected:
|
||||
void close ();
|
||||
void flush_midi (const Lock& lock);
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "temporal/tempo.h"
|
||||
|
||||
#include "evoral/PatchChange.h"
|
||||
#include "evoral/SMF.h"
|
||||
|
||||
#include "ardour/midi_model.h"
|
||||
#include "ardour/midi_state_tracker.h"
|
||||
|
@ -178,6 +179,9 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
bool cue_isolated = false;
|
||||
StretchMode stretch_mode = Trigger::Crisp;
|
||||
|
||||
Evoral::SMF::UsedChannels used_channels = Evoral::SMF::UsedChannels();
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> patch_change[16];
|
||||
|
||||
std::string name = "";
|
||||
color_t color = 0xBEBEBEFF;
|
||||
double tempo = 0; //unset
|
||||
|
@ -204,6 +208,11 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
stretchable = other.stretchable;
|
||||
cue_isolated = other.cue_isolated;
|
||||
stretch_mode = other.stretch_mode;
|
||||
used_channels = other.used_channels;
|
||||
|
||||
for (int i = 0; i<16; i++) {
|
||||
patch_change[i] = other.patch_change[i];
|
||||
}
|
||||
|
||||
name = other.name;
|
||||
color = other.color;
|
||||
|
@ -229,6 +238,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
TRIGGERBOX_PROPERTY_DECL (stretchable, bool);
|
||||
TRIGGERBOX_PROPERTY_DECL (cue_isolated, bool);
|
||||
TRIGGERBOX_PROPERTY_DECL (stretch_mode, StretchMode);
|
||||
TRIGGERBOX_PROPERTY_DECL (used_channels, Evoral::SMF::UsedChannels);
|
||||
TRIGGERBOX_PROPERTY_DECL (color, color_t);
|
||||
TRIGGERBOX_PROPERTY_DECL_CONST_REF (name, std::string);
|
||||
|
||||
|
@ -345,9 +355,23 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
TriggerBox& box() const { return _box; }
|
||||
|
||||
double estimated_tempo() const { return _estimated_tempo; }
|
||||
|
||||
/* the following functions deal with audio- or midi-specific SegmentDescriptor properties, provided as virtuals so we don't have to do lots of dynamic_casting */
|
||||
/* segment_tempo is currently a no-op for MIDI, but may be implemented later */
|
||||
virtual double segment_tempo() const = 0;
|
||||
virtual void set_segment_tempo (double t) = 0;
|
||||
|
||||
/* used_segment_channels is a no-op for audio */
|
||||
virtual Evoral::SMF::UsedChannels segment_used_channels() const { return Evoral::SMF::UsedChannels(); }
|
||||
virtual void set_segment_used_channels (Evoral::SMF::UsedChannels) {}
|
||||
|
||||
/* patch changes are a no-op for audio */
|
||||
virtual void set_patch_change (Evoral::PatchChange<MidiBuffer::TimeType> const &) {}
|
||||
virtual Evoral::PatchChange<MidiBuffer::TimeType> const patch_change (uint8_t) const { return Evoral::PatchChange<MidiBuffer::TimeType>(); }
|
||||
virtual void unset_patch_change (uint8_t channel) {}
|
||||
virtual void unset_all_patch_changes () {}
|
||||
virtual bool patch_change_set (uint8_t channel) const { return false; }
|
||||
|
||||
virtual void setup_stretcher () = 0;
|
||||
|
||||
Temporal::Meter meter() const { return _meter; }
|
||||
|
@ -539,6 +563,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
|||
void reload (BufferSet&, void*);
|
||||
bool probably_oneshot () const;
|
||||
|
||||
void estimate_midi_patches ();
|
||||
|
||||
int set_region_in_worker_thread (boost::shared_ptr<Region>);
|
||||
void jump_start ();
|
||||
void shutdown (BufferSet& bufs, pframes_t dest_offset);
|
||||
|
@ -552,11 +578,15 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
|||
void start_and_roll_to (samplepos_t start, samplepos_t position);
|
||||
|
||||
void set_patch_change (Evoral::PatchChange<MidiBuffer::TimeType> const &);
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> const & patch_change (uint8_t) const;
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> const patch_change (uint8_t) const;
|
||||
void unset_patch_change (uint8_t channel);
|
||||
void unset_all_patch_changes ();
|
||||
bool patch_change_set (uint8_t channel) const;
|
||||
|
||||
/* It's possible that a portion of a midi file would use a subset of the total channels used, so store that info in the segment descriptor */
|
||||
Evoral::SMF::UsedChannels segment_used_channels() const { return _segment_used_channels; }
|
||||
void set_segment_used_channels (Evoral::SMF::UsedChannels);
|
||||
|
||||
/* theoretically, MIDI files can have a dedicated tempo outside the session tempo map (*un-stretched*) but this is currently unimplemented */
|
||||
/* boilerplate tempo functions are provided here so we don't have to do constant dynamic_cast checks to use the tempo+stretch APIs */
|
||||
virtual double segment_tempo() const {return 120.0;}
|
||||
|
@ -589,6 +619,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
|||
Evoral::PatchChange<MidiBuffer::TimeType> _patch_change[16];
|
||||
std::vector<int> _channel_map;
|
||||
|
||||
Evoral::SMF::UsedChannels _segment_used_channels;
|
||||
|
||||
int load_data (boost::shared_ptr<MidiRegion>);
|
||||
void compute_and_set_length ();
|
||||
void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &);
|
||||
|
@ -892,6 +924,7 @@ namespace Properties {
|
|||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> cue_isolated;
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> patch_change; /* type not important */
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> channel_map; /* type not important */
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> used_channels; /* type not important */
|
||||
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> tempo_meter; /* only used to transmit changes, not storage */
|
||||
}
|
||||
|
|
|
@ -372,10 +372,6 @@ Auditioner::audition_region (boost::shared_ptr<Region> region, bool loop)
|
|||
|
||||
Glib::Threads::Mutex::Lock lm (lock);
|
||||
|
||||
for (uint8_t c = 0; c < 16; ++c) {
|
||||
_patch_change[c].unset ();
|
||||
}
|
||||
|
||||
if (boost::dynamic_pointer_cast<AudioRegion>(region) != 0) {
|
||||
|
||||
_midi_audition = false;
|
||||
|
|
|
@ -70,6 +70,7 @@ PBD::DebugBits PBD::DEBUG::MidiPlaylistIO = PBD::new_debug_bit ("midiplaylistio"
|
|||
PBD::DebugBits PBD::DEBUG::MidiRingBuffer = PBD::new_debug_bit ("midiringbuffer");
|
||||
PBD::DebugBits PBD::DEBUG::MidiSourceIO = PBD::new_debug_bit ("midisourceio");
|
||||
PBD::DebugBits PBD::DEBUG::MidiTrackers = PBD::new_debug_bit ("miditrackers");
|
||||
PBD::DebugBits PBD::DEBUG::MidiTriggers = PBD::new_debug_bit ("miditriggers");
|
||||
PBD::DebugBits PBD::DEBUG::Monitor = PBD::new_debug_bit ("monitor");
|
||||
PBD::DebugBits PBD::DEBUG::OrderKeys = PBD::new_debug_bit ("orderkeys");
|
||||
PBD::DebugBits PBD::DEBUG::Panning = PBD::new_debug_bit ("panning");
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "pbd/enumwriter.h"
|
||||
#include "pbd/failed_constructor.h"
|
||||
#include "pbd/i18n.h"
|
||||
|
@ -128,6 +130,12 @@ SegmentDescriptor::set_meter (Meter const & m)
|
|||
_meter = m;
|
||||
}
|
||||
|
||||
void
|
||||
SegmentDescriptor::set_used_channels (Evoral::SMF::UsedChannels used)
|
||||
{
|
||||
_used_channels = used;
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
SegmentDescriptor::get_state (void) const
|
||||
{
|
||||
|
@ -146,6 +154,9 @@ SegmentDescriptor::get_state (void) const
|
|||
root->add_child_nocopy (_tempo.get_state());
|
||||
root->add_child_nocopy (_meter.get_state());
|
||||
|
||||
std::string uchan = string_compose ("%1", _used_channels.to_ulong());
|
||||
root->set_property (X_("used-channels"), uchan);
|
||||
|
||||
return *root;
|
||||
}
|
||||
|
||||
|
@ -160,6 +171,18 @@ SegmentDescriptor::set_state (XMLNode const & node, int version)
|
|||
return -1;
|
||||
}
|
||||
|
||||
std::string uchan;
|
||||
if (node.get_property (X_("used-channels"), uchan)) {
|
||||
} else {
|
||||
unsigned long ul;
|
||||
std::stringstream ss (uchan);
|
||||
ss >> ul;
|
||||
if (!ss) {
|
||||
return -1;
|
||||
}
|
||||
_used_channels = Evoral::SMF::UsedChannels(ul);
|
||||
}
|
||||
|
||||
if (_time_domain == Temporal::AudioTime) {
|
||||
if (node.get_property (X_("position"), _position_samples)) {
|
||||
return -1;
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "ardour/midi_state_tracker.h"
|
||||
#include "ardour/parameter_types.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/segment_descriptor.h"
|
||||
#include "ardour/smf_source.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
@ -670,6 +671,11 @@ SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload
|
|||
Evoral::event_id_t event_id;
|
||||
bool have_event_id;
|
||||
|
||||
_num_channels = 0;
|
||||
_n_note_on_events = 0;
|
||||
_has_pgm_change = false;
|
||||
_used_channels.reset ();
|
||||
|
||||
// TODO simplify event allocation
|
||||
std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > > eventlist;
|
||||
|
||||
|
@ -691,6 +697,23 @@ SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload
|
|||
continue;
|
||||
}
|
||||
|
||||
/* aggregate information about channels and pgm-changes */
|
||||
uint8_t type = buf[0] & 0xf0;
|
||||
uint8_t chan = buf[0] & 0x0f;
|
||||
if (type >= 0x80 && type <= 0xE0) {
|
||||
_used_channels.set(chan);
|
||||
switch (type) {
|
||||
case MIDI_CMD_NOTE_ON:
|
||||
++_n_note_on_events;
|
||||
break;
|
||||
case MIDI_CMD_PGM_CHANGE:
|
||||
_has_pgm_change = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
/* not a meta-event */
|
||||
|
||||
|
@ -730,6 +753,8 @@ SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload
|
|||
}
|
||||
}
|
||||
|
||||
_num_channels = _used_channels.size();
|
||||
|
||||
eventlist.sort(compare_eventlist);
|
||||
|
||||
std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > >::iterator it;
|
||||
|
@ -749,6 +774,12 @@ SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload
|
|||
free(buf);
|
||||
}
|
||||
|
||||
Evoral::SMF::UsedChannels
|
||||
SMFSource::used_midi_channels()
|
||||
{
|
||||
return _used_channels;
|
||||
}
|
||||
|
||||
void
|
||||
SMFSource::destroy_model (const Glib::Threads::Mutex::Lock& lock)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "temporal/tempo.h"
|
||||
|
||||
#include "ardour/auditioner.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/audioregion.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
|
@ -68,6 +69,7 @@ namespace ARDOUR {
|
|||
PBD::PropertyDescriptor<bool> tempo_meter; /* only to transmit updates, not storage */
|
||||
PBD::PropertyDescriptor<bool> patch_change; /* only to transmit updates, not storage */
|
||||
PBD::PropertyDescriptor<bool> channel_map; /* only to transmit updates, not storage */
|
||||
PBD::PropertyDescriptor<bool> used_channels; /* only to transmit updates, not storage */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,6 +242,13 @@ Trigger::get_ui_state (Trigger::UIState &state) const
|
|||
state.name = _name;
|
||||
state.color = _color;
|
||||
|
||||
state.used_channels = segment_used_channels();
|
||||
for (int i = 0; i<16; i++) {
|
||||
if (patch_change(i).is_set()) {
|
||||
state.patch_change[i] = patch_change(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* tempo is currently not a property */
|
||||
state.tempo = segment_tempo();
|
||||
}
|
||||
|
@ -257,6 +266,13 @@ Trigger::set_ui_state (Trigger::UIState &state)
|
|||
if (state.tempo > 0) {
|
||||
set_segment_tempo(state.tempo);
|
||||
}
|
||||
|
||||
set_segment_used_channels(state.used_channels);
|
||||
for (int chan = 0; chan<16; chan++) {
|
||||
if (state.patch_change[chan].is_set()) {
|
||||
set_patch_change(state.patch_change[chan]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -303,6 +319,14 @@ Trigger::update_properties ()
|
|||
_name = ui_state.name;
|
||||
}
|
||||
|
||||
set_segment_used_channels(ui_state.used_channels);
|
||||
|
||||
for (int chan = 0; chan<16; chan++) {
|
||||
if (ui_state.patch_change[chan].is_set()) {
|
||||
set_patch_change(ui_state.patch_change[chan]);
|
||||
}
|
||||
}
|
||||
|
||||
last_property_generation = g;
|
||||
}
|
||||
|
||||
|
@ -333,6 +357,13 @@ Trigger::copy_to_ui_state ()
|
|||
ui_state.stretch_mode = _stretch_mode;
|
||||
ui_state.name = _name;
|
||||
ui_state.color = _color;
|
||||
|
||||
ui_state.used_channels = segment_used_channels();
|
||||
for (int i = 0; i<16; i++) {
|
||||
if (patch_change(i).is_set()) {
|
||||
ui_state.patch_change[i] = patch_change(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -508,7 +539,7 @@ Trigger::get_state (void)
|
|||
}
|
||||
|
||||
node->set_property (X_("index"), _index);
|
||||
node->set_property (X_("estimated-tempo"), _estimated_tempo);
|
||||
|
||||
node->set_property (X_("segment-tempo"), _segment_tempo);
|
||||
|
||||
if (_region) {
|
||||
|
@ -532,20 +563,16 @@ Trigger::set_state (const XMLNode& node, int version)
|
|||
boost::shared_ptr<Region> r = RegionFactory::region_by_id (rid);
|
||||
|
||||
if (r) {
|
||||
set_region (r, false); //TODO: this results in a call to estimate_tempo() which should be avoided if bpm is already known
|
||||
set_region (r, false); //this results in a call to estimate_tempo()
|
||||
}
|
||||
|
||||
node.get_property (X_("estimated-tempo"), _estimated_tempo); //TODO: for now: if we know the bpm, overwrite the value that estimate_tempo() found
|
||||
|
||||
double tempo;
|
||||
node.get_property (X_("segment-tempo"), tempo);
|
||||
node.get_property (X_("segment-tempo"), tempo); //this is the user-selected tempo which overrides estimated_tempo
|
||||
set_segment_tempo(tempo);
|
||||
|
||||
node.get_property (X_("index"), _index);
|
||||
set_values (node);
|
||||
|
||||
copy_to_ui_state ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1253,6 +1280,9 @@ AudioTrigger::set_state (const XMLNode& node, int version)
|
|||
node.get_property (X_("start"), t);
|
||||
_start_offset = t.samples();
|
||||
|
||||
/* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
|
||||
copy_to_ui_state ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1999,12 +2029,8 @@ MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b)
|
|||
, last_event_beats (Temporal::Beats())
|
||||
, _start_offset (0, 0, 0)
|
||||
, _legato_offset (0, 0, 0)
|
||||
, _segment_used_channels (Evoral::SMF::UsedChannels())
|
||||
{
|
||||
#if 0 /* for prototype + testing only */
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> pc (0, 0, 12, 0);
|
||||
set_patch_change (pc);
|
||||
#endif
|
||||
|
||||
_channel_map.assign (16, -1);
|
||||
}
|
||||
|
||||
|
@ -2012,6 +2038,28 @@ MIDITrigger::~MIDITrigger ()
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
MIDITrigger::set_segment_used_channels (Evoral::SMF::UsedChannels used)
|
||||
{
|
||||
if (_segment_used_channels != used) {
|
||||
|
||||
_segment_used_channels = used;
|
||||
|
||||
send_property_change (ARDOUR::Properties::used_channels);
|
||||
_box.session().set_dirty();
|
||||
}
|
||||
|
||||
/* TODO: once we have a Region Trimmer, this could get more complicated:
|
||||
* this segment might overlap another SD (Coverage==Internal|Start|End)
|
||||
* in which case we might be setting both SDs, or not. TBD*/
|
||||
if (_region) {
|
||||
SegmentDescriptor segment = get_segment_descriptor();
|
||||
for (auto & src : _region->sources()) {
|
||||
src->set_segment_descriptor (segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MIDITrigger::set_channel_map (int channel, int target)
|
||||
{
|
||||
|
@ -2091,11 +2139,17 @@ MIDITrigger::patch_change_set (uint8_t channel) const
|
|||
return _patch_change[channel].is_set();
|
||||
}
|
||||
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> const &
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> const
|
||||
MIDITrigger::patch_change (uint8_t channel) const
|
||||
{
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> ret;
|
||||
|
||||
assert (channel < 16);
|
||||
return _patch_change[channel];
|
||||
if (_patch_change[channel].is_set()) {
|
||||
ret = _patch_change[channel];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2174,14 +2228,14 @@ MIDITrigger::_startup (BufferSet& bufs, pframes_t dest_offset, Temporal::BBT_Off
|
|||
/* Possibly inject patch changes, if set */
|
||||
|
||||
for (int chn = 0; chn < 16; ++chn) {
|
||||
if (_patch_change[chn].is_set()) {
|
||||
if (_segment_used_channels.test(chn) && _patch_change[chn].is_set()) {
|
||||
_patch_change[chn].set_time (dest_offset);
|
||||
cerr << index() << " Injecting patch change " << _patch_change[chn].program() << " @ " << dest_offset << endl;
|
||||
DEBUG_TRACE (DEBUG::MidiTriggers, string_compose ("Injecting patch change c:%1 b:%2 p:%3\n", (uint32_t) _patch_change[chn].channel(), (uint32_t) _patch_change[chn].bank(), (uint32_t) _patch_change[chn].program()));
|
||||
for (int msg = 0; msg < _patch_change[chn].messages(); ++msg) {
|
||||
if (mb) {
|
||||
mb->insert_event (_patch_change[chn].message (msg));
|
||||
_box.tracker->track (_patch_change[chn].message (msg).buffer());
|
||||
}
|
||||
_box.tracker->track (_patch_change[chn].message (msg).buffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2227,6 +2281,9 @@ MIDITrigger::get_state (void)
|
|||
|
||||
node.set_property (X_("start"), start_offset());
|
||||
|
||||
std::string uchan = string_compose ("%1", _segment_used_channels.to_ulong());
|
||||
node.set_property (X_("used-channels"), uchan);
|
||||
|
||||
XMLNode* patches_node = 0;
|
||||
|
||||
for (int chn = 0; chn < 16; ++chn) {
|
||||
|
@ -2274,6 +2331,18 @@ MIDITrigger::set_state (const XMLNode& node, int version)
|
|||
return -1;
|
||||
}
|
||||
|
||||
std::string uchan;
|
||||
if (node.get_property (X_("used-channels"), uchan)) {
|
||||
} else {
|
||||
unsigned long ul;
|
||||
std::stringstream ss (uchan);
|
||||
ss >> ul;
|
||||
if (!ss) {
|
||||
return -1;
|
||||
}
|
||||
set_segment_used_channels( Evoral::SMF::UsedChannels(ul) );
|
||||
}
|
||||
|
||||
node.get_property (X_("start"), t);
|
||||
Temporal::Beats b (t.beats());
|
||||
/* XXX need to deal with bar offsets */
|
||||
|
@ -2312,6 +2381,9 @@ MIDITrigger::set_state (const XMLNode& node, int version)
|
|||
}
|
||||
}
|
||||
|
||||
/* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
|
||||
copy_to_ui_state ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2371,6 +2443,48 @@ MIDITrigger::natural_length() const
|
|||
return timepos_t (Temporal::BeatTime);
|
||||
}
|
||||
|
||||
void
|
||||
MIDITrigger::estimate_midi_patches ()
|
||||
{
|
||||
/* first, intialize all our slot's patches to GM defaults, to make playback deterministic */
|
||||
for (uint8_t chan = 0; chan < 16; ++chan) {
|
||||
_patch_change[chan].set_channel(chan);
|
||||
_patch_change[chan].set_bank( chan == 9 ? 120 : 0 );
|
||||
_patch_change[chan].set_program( 0 );
|
||||
}
|
||||
|
||||
boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource> (_region->source(0));
|
||||
if (smfs) {
|
||||
/* second, apply any patches that the Auditioner has in its memory
|
||||
* ...this handles the case where the user chose patches for a file that itself lacked patch-settings
|
||||
* (it's possible that the user didn't audition the actual file they dragged in, but this is still the best starting-point we have)
|
||||
* */
|
||||
boost::shared_ptr<ARDOUR::Auditioner> aud = _box.session().the_auditioner();
|
||||
if (aud) {
|
||||
for (uint8_t chan = 0; chan < 16; ++chan) {
|
||||
if (aud->patch_change (chan).is_set()) {
|
||||
_patch_change[chan] = aud->patch_change (chan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* thirdly, apply the patches from the file itself (if it has any) */
|
||||
boost::shared_ptr<MidiModel> model = smfs->model();
|
||||
for (MidiModel::PatchChanges::const_iterator i = model->patch_changes().begin(); i != model->patch_changes().end(); ++i) {
|
||||
if ((*i)->is_set()) {
|
||||
int chan = (*i)->channel(); /* behavior is undefined for SMF's with multiple patch changes. I'm not sure that we care */
|
||||
_patch_change[chan].set_channel ((*i)->channel());
|
||||
_patch_change[chan].set_bank((*i)->bank());
|
||||
_patch_change[chan].set_program((*i)->program());
|
||||
}
|
||||
}
|
||||
|
||||
/* finally, store the used_channels so the UI can show patches only for those chans actually used */
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 estimate_midi_patches(), using channels %2\n", name(), smfs->used_channels().to_string().c_str()));
|
||||
set_segment_used_channels(smfs->used_channels());
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
MIDITrigger::set_region_in_worker_thread (boost::shared_ptr<Region> r)
|
||||
{
|
||||
|
@ -2386,6 +2500,11 @@ MIDITrigger::set_region_in_worker_thread (boost::shared_ptr<Region> r)
|
|||
set_length (mr->length());
|
||||
model = mr->model ();
|
||||
|
||||
estimate_midi_patches ();
|
||||
|
||||
/* 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));
|
||||
|
||||
send_property_change (ARDOUR::Properties::name);
|
||||
|
@ -2494,15 +2613,29 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
|
|||
ev.scale_velocity (_gain);
|
||||
}
|
||||
|
||||
int chn = ev.channel();
|
||||
|
||||
if (_channel_map[ev.channel()] > 0) {
|
||||
ev.set_channel (_channel_map[ev.channel()]);
|
||||
ev.set_channel (_channel_map[chn]);
|
||||
}
|
||||
|
||||
if (ev.is_pgm_change() || (ev.is_cc() && ((ev.cc_number() == MIDI_CTL_LSB_BANK) || (ev.cc_number() == MIDI_CTL_MSB_BANK)))) {
|
||||
if (_patch_change[ev.channel()].is_set() || _box.ignore_patch_changes ()) {
|
||||
/* skip pgm change info in data because trigger has its own */
|
||||
if (_box.ignore_patch_changes ()) {
|
||||
/* do not send ANY patch or bank messages, just skip them */
|
||||
DEBUG_TRACE (DEBUG::MidiTriggers, string_compose ("Ignoring patch change on chn:%1\n", (uint32_t) _patch_change[chn].channel()));
|
||||
++iter;
|
||||
continue;
|
||||
} else if ( _patch_change[chn].is_set() ) {
|
||||
/* from this context we don't know if a pgm message in the midi buffer is from the file or from triggerbox */
|
||||
/* so when a bank or pgm message is recognized, just replace it with the desired patch */
|
||||
DEBUG_TRACE (DEBUG::MidiTriggers, string_compose ("Replacing patch change c:%1 b:%2 p:%3\n", (uint32_t) _patch_change[chn].channel(), (uint32_t) _patch_change[chn].bank(), (uint32_t) _patch_change[chn].program()));
|
||||
if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK)) {
|
||||
ev.set_cc_value(_patch_change[chn].bank_msb());
|
||||
} else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_LSB_BANK)) {
|
||||
ev.set_cc_value(_patch_change[chn].bank_lsb());
|
||||
} else if (ev.is_pgm_change()) {
|
||||
ev.set_pgm_number(_patch_change[chn].program());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,11 +125,6 @@ SMF::open(const std::string& path, int track)
|
|||
{
|
||||
Glib::Threads::Mutex::Lock lm (_smf_lock);
|
||||
|
||||
_num_channels = 0;
|
||||
_n_note_on_events = 0;
|
||||
_has_pgm_change = false;
|
||||
_used_channels.clear ();
|
||||
|
||||
assert(track >= 1);
|
||||
if (_smf) {
|
||||
smf_delete(_smf);
|
||||
|
@ -157,57 +152,11 @@ SMF::open(const std::string& path, int track)
|
|||
|
||||
fclose(f);
|
||||
|
||||
bool type0 = _smf->format==0;
|
||||
|
||||
lm.release ();
|
||||
if (!_empty) {
|
||||
|
||||
for (int i = 1; i <= _smf->number_of_tracks; ++i) {
|
||||
|
||||
// scan file for used channels.
|
||||
int ret;
|
||||
uint32_t delta_t = 0;
|
||||
uint32_t size = 0;
|
||||
uint8_t* buf = NULL;
|
||||
event_id_t event_id = 0;
|
||||
|
||||
if (type0) {
|
||||
seek_to_start (); //type0 files have no 'track' concept, just seek_to_start
|
||||
} else {
|
||||
seek_to_track (i);
|
||||
}
|
||||
|
||||
std::set<uint8_t> used;
|
||||
while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
|
||||
if (ret == 0) {
|
||||
continue;
|
||||
}
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
uint8_t type = buf[0] & 0xf0;
|
||||
uint8_t chan = buf[0] & 0x0f;
|
||||
|
||||
if (type >= 0x80 && type <= 0xE0) {
|
||||
_used_channels.insert(chan);
|
||||
switch (type) {
|
||||
case MIDI_CMD_NOTE_ON:
|
||||
++_n_note_on_events;
|
||||
break;
|
||||
case MIDI_CMD_PGM_CHANGE:
|
||||
_has_pgm_change = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_num_channels += _used_channels.size();
|
||||
free (buf);
|
||||
}
|
||||
|
||||
seek_to_start();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
, _program_change (other._program_change, true)
|
||||
{
|
||||
set_id (other.id ());
|
||||
|
||||
assert (is_set());
|
||||
}
|
||||
|
||||
PatchChange ()
|
||||
|
@ -73,6 +75,17 @@ public:
|
|||
, _bank_change_lsb (MIDI_EVENT, 0, 3, 0, true)
|
||||
, _program_change (MIDI_EVENT, 0, 2, 0, true)
|
||||
{
|
||||
_bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL;
|
||||
_bank_change_msb.buffer()[1] = MIDI_CTL_MSB_BANK;
|
||||
_bank_change_msb.buffer()[2] = 0;
|
||||
|
||||
_bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL;
|
||||
_bank_change_lsb.buffer()[1] = MIDI_CTL_LSB_BANK;
|
||||
_bank_change_lsb.buffer()[2] = 0;
|
||||
|
||||
_program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE;
|
||||
_program_change.buffer()[1] = 0;
|
||||
|
||||
unset ();
|
||||
}
|
||||
|
||||
|
@ -83,18 +96,20 @@ public:
|
|||
_bank_change_msb.set (other._bank_change_msb.buffer(), 3, other.time());
|
||||
_bank_change_lsb.set (other._bank_change_lsb.buffer(), 3, other.time());
|
||||
_program_change.set (other._program_change.buffer(), 2, other.time());
|
||||
|
||||
assert (is_set());
|
||||
return *this;
|
||||
}
|
||||
void unset() {
|
||||
_bank_change_msb.buffer()[1] = 0x80; /* unset */
|
||||
_bank_change_lsb.buffer()[1] = 0x80; /* unset */
|
||||
_bank_change_msb.buffer()[2] = 0x80; /* unset */
|
||||
_bank_change_lsb.buffer()[2] = 0x80; /* unset */
|
||||
_program_change.buffer()[1] = 0x80; /* unset */
|
||||
assert (!is_set());
|
||||
}
|
||||
|
||||
bool is_set() const {
|
||||
return ((_bank_change_msb.buffer()[1] & 0x80) == 0) &&
|
||||
((_bank_change_lsb.buffer()[1] & 0x80) == 0) &&
|
||||
return ((_bank_change_msb.buffer()[2] & 0x80) == 0) &&
|
||||
((_bank_change_lsb.buffer()[2] & 0x80) == 0) &&
|
||||
((_program_change.buffer()[1] & 0x80) == 0);
|
||||
}
|
||||
|
||||
|
@ -119,21 +134,22 @@ public:
|
|||
}
|
||||
|
||||
void set_channel (uint8_t c) {
|
||||
_bank_change_msb.buffer()[0] = c & 0xf;
|
||||
_bank_change_lsb.buffer()[0] = c & 0xf;
|
||||
_program_change.buffer()[0] = c & 0xf;
|
||||
_bank_change_msb.buffer()[0] = MIDI_CMD_CONTROL | c;
|
||||
_bank_change_lsb.buffer()[0] = MIDI_CMD_CONTROL | c;
|
||||
_program_change.buffer()[0] = MIDI_CMD_PGM_CHANGE | c;
|
||||
}
|
||||
|
||||
uint8_t program () const {
|
||||
assert (is_set());
|
||||
return _program_change.buffer()[1];
|
||||
return _program_change.buffer()[1] & 0x7f;
|
||||
}
|
||||
|
||||
void set_program (uint8_t p) {
|
||||
_program_change.buffer()[1] = p;
|
||||
_program_change.buffer()[1] = p & 0x7f;
|
||||
}
|
||||
|
||||
int bank () const {
|
||||
assert (is_set());
|
||||
return (bank_msb() << 7) | bank_lsb();
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,9 @@ public:
|
|||
int smf_format () const;
|
||||
|
||||
int num_channels () const { return _num_channels; }
|
||||
std::set<uint8_t> const& used_channels () const { return _used_channels; }
|
||||
typedef std::bitset<16> UsedChannels;
|
||||
UsedChannels const& used_channels () const { return _used_channels; }
|
||||
void set_used_channels (UsedChannels used) { _used_channels = used; }
|
||||
uint64_t n_note_on_events () const { return _n_note_on_events; }
|
||||
bool has_pgm_change () const { return _has_pgm_change; }
|
||||
|
||||
|
@ -134,18 +136,20 @@ public:
|
|||
Markers const & markers() const { return _markers; }
|
||||
void load_markers ();
|
||||
|
||||
protected:
|
||||
|
||||
uint64_t _n_note_on_events;
|
||||
bool _has_pgm_change;
|
||||
|
||||
int _num_channels;
|
||||
UsedChannels _used_channels;
|
||||
|
||||
private:
|
||||
smf_t* _smf;
|
||||
smf_track_t* _smf_track;
|
||||
bool _empty; ///< true iff file contains(non-empty) events
|
||||
mutable Glib::Threads::Mutex _smf_lock;
|
||||
|
||||
int _num_channels;
|
||||
std::set<uint8_t> _used_channels;
|
||||
|
||||
uint64_t _n_note_on_events;
|
||||
bool _has_pgm_change;
|
||||
|
||||
mutable Markers _markers;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue