Compare commits

...

14 Commits

Author SHA1 Message Date
Ben Loftis 9b2d6c136d clip_picker: tweak .smf file info text 2022-02-28 08:11:24 -06:00
Ben Loftis 0ec6a60307 patch_change_widget: only show tabs for the channels that a slot is using 2022-02-28 08:11:24 -06:00
Ben Loftis be5a56fae3 triggerbox: we no longer need to store Triggerbox::estimated_tempo in the snapshot file
* estimated tempo is cached in the SegmentDescriptor
* storing it in the Source's SD allows us to skip minibpm if we find a tempo there
2022-02-28 08:11:24 -06:00
Ben Loftis 00db64a4ed triggerbox: call copy_to_ui_state() after audio|midi state is recalled 2022-02-28 08:11:24 -06:00
Ben Loftis cd63f615e1 triggerbox: implement 'replace midi-file PGMs with the triggerbox PGMs"
* intercept {pgm|bank}-change messages, and replace them with triggerbox values
* check is_set() extensively; we have arrays of pgms but most are unused
2022-02-28 08:11:23 -06:00
Ben Loftis 0cee4f45ce triggerbox: maintain UsedChannels and Patch Change data
preserve this data across drag&drop, session save, etc
2022-02-28 08:11:23 -06:00
Ben Loftis dce3f2eb65 triggerbox: estimate_midi_patches tries to retain patch info across domains
* initialize patches to GM standard (drums on 10) in case file has none
* in the case where file had none, check the Auditioner to see if user set any
* in the case where a file has patches, use those instead

* also stash the UsedChannels so we can show only the used chans in the UI
2022-02-28 08:11:23 -06:00
Ben Loftis fcd45cfca7 auditioner: do not reset my patches when changing files
* if a file has program-changes, then it will set() my patches
* if a file does *not* have PCs, the user might choose one (which sets() it)
* if the next file does *not* have PCs, we should preserve the user's selection
* if the next file has PCs, it will set() it (losing the user selection)
2022-02-28 08:11:23 -06:00
Ben Loftis bda8048873 segment_descriptor: stash UsedChannels here
* similar to segment_tempo, it is conceivable that a midi file would
 use different midi channels in different time ranges of the source
2022-02-28 08:11:23 -06:00
Ben Loftis 75e3646ed8 utils: re-implement used_channels_as_string to use UsedChannels (bitset) 2022-02-28 08:11:23 -06:00
Ben Loftis c83b8396c2 smf_source: implement SMF::UsedChannels, and move midi screening into load_model()
Some MIDI sources (esp immediately after import) are not yet on disk,
 but are only in memory.  So Open() does not properly initalize
 data like UsedChannels and program info
2022-02-28 08:11:22 -06:00
Ben Loftis 9266e47558 PatchChange: fix set_channel() 2022-02-28 08:11:22 -06:00
Ben Loftis 472be6e50e PatchChange: yet more checks to keep patch state in sync (is_set())
* bank 2nd byte is the one that gets 'set'
2022-02-26 13:09:42 -06:00
Ben Loftis 8436e55e1b add DEBUG flag: miditriggers 2022-02-26 13:09:41 -06:00
16 changed files with 339 additions and 125 deletions

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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.
*/

View File

@ -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);

View File

@ -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 */
}

View File

@ -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;

View File

@ -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");

View File

@ -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;

View File

@ -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)
{

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
};