diff --git a/3rdparty/libremidi b/3rdparty/libremidi index b5ef80a462b..13239cd2aeb 160000 --- a/3rdparty/libremidi +++ b/3rdparty/libremidi @@ -1 +1 @@ -Subproject commit b5ef80a462b2d06583f16e9c88c46a143268763f +Subproject commit 13239cd2aebbbce9ddf789ca0a04b1c835881a93 diff --git a/src/ossia/dataflow/execution/pull_visitors.hpp b/src/ossia/dataflow/execution/pull_visitors.hpp index d4e2682b96b..22463277038 100644 --- a/src/ossia/dataflow/execution/pull_visitors.hpp +++ b/src/ossia/dataflow/execution/pull_visitors.hpp @@ -113,7 +113,7 @@ struct global_pull_visitor auto it = state.m_receivedMidi.find(midi); if(it != state.m_receivedMidi.end()) { - for(const libremidi::message& v : it->second.second) + for(const libremidi::message& v : it->second.messages) { val.messages.push_back(v); } @@ -165,14 +165,14 @@ struct global_pull_node_visitor { if(channel == -1) { - for(const libremidi::message& v : it->second.second) + for(const libremidi::message& v : it->second.messages) { val.messages.push_back(v); } } else { - for(const libremidi::message& v : it->second.second) + for(const libremidi::message& v : it->second.messages) { if(v.get_channel() == channel) val.messages.push_back(v); diff --git a/src/ossia/dataflow/execution_state.cpp b/src/ossia/dataflow/execution_state.cpp index 3e728650c6c..0422502ce53 100644 --- a/src/ossia/dataflow/execution_state.cpp +++ b/src/ossia/dataflow/execution_state.cpp @@ -16,6 +16,7 @@ #include #include +#include namespace ossia { execution_state::execution_state() { @@ -106,11 +107,11 @@ void execution_state::register_midi_parameter(net::midi::midi_protocol& p) auto it = m_receivedMidi.find(&p); if(it == m_receivedMidi.end()) { - m_receivedMidi.insert({&p, {0, {}}}); + m_receivedMidi.insert({&p, received_midi_state{.messages = {}, .count = 1}}); } else { - it->second.first++; + it->second.count++; } #endif } @@ -121,8 +122,8 @@ void execution_state::unregister_midi_parameter(net::midi::midi_protocol& p) auto it = m_receivedMidi.find(&p); if(it != m_receivedMidi.end()) { - it->second.first--; - if(it->second.first <= 0) + it->second.count--; + if(it->second.count <= 0) { m_receivedMidi.erase(it); // TODO p.disable_registration(); @@ -145,8 +146,40 @@ void execution_state::get_new_values() for(auto it = m_receivedMidi.begin(), end = m_receivedMidi.end(); it != end; ++it) { - it->second.second.clear(); - it->first->clone_value(it->second.second); + auto& [proto, state] = *it; + + auto midi = proto->midi_in(); + auto& input_messages = proto->messages; + auto& port = state.messages; + libremidi::message msg; + port.clear(); + port.reserve(input_messages.size_approx()); + if(midi->get_current_api() == libremidi::API::JACK_MIDI) + { + // sample-accurate, see MIDIDevice.cpp in score + while(input_messages.try_dequeue(msg)) + { + port.push_back(msg); + } + } + else + { + // Adapt the Absolute timestamps + const auto cur_t = state.current_buffer_start; + const auto prev_t = state.last_buffer_start; + const double timestamp_mult = cur_t > prev_t ? 1. / (cur_t - prev_t) : 0.; + + while(input_messages.try_dequeue(msg)) + { + msg.timestamp = this->bufferSize * ((msg.timestamp - prev_t) * timestamp_mult); + if(msg.timestamp >= bufferSize) + msg.timestamp = this->bufferSize - 1; + if(msg.timestamp < 0) + msg.timestamp = 0; + + port.push_back(msg); + } + } } } @@ -328,8 +361,22 @@ void execution_state::apply_device_changes() } } } + +void execution_state::init_midi_timings() +{ + for(auto& [proto, state] : m_receivedMidi) + { + if(auto m = proto->midi_in()) + { + state.last_buffer_start = state.current_buffer_start; + state.current_buffer_start = m->absolute_timestamp(); + } + } +} + void execution_state::begin_tick() { + init_midi_timings(); m_policy->clear_local_state(); get_new_values(); apply_device_changes(); diff --git a/src/ossia/dataflow/execution_state.hpp b/src/ossia/dataflow/execution_state.hpp index c815b5acd02..c3c1afc7ab9 100644 --- a/src/ossia/dataflow/execution_state.hpp +++ b/src/ossia/dataflow/execution_state.hpp @@ -108,6 +108,7 @@ struct OSSIA_EXPORT execution_state : public Nano::Observer #if !defined(OSSIA_TESTING) private: #endif + void init_midi_timings(); void get_new_values(); void register_parameter(ossia::net::parameter_base& p); @@ -131,9 +132,16 @@ struct OSSIA_EXPORT execution_state : public Nano::Observer ossia::ptr_map> m_receivedValues; - ossia::ptr_map< - ossia::net::midi::midi_protocol*, std::pair>> - m_receivedMidi; + + struct received_midi_state + { + value_vector messages; + int64_t last_buffer_start{}; + int64_t current_buffer_start{}; + int count{}; + }; + + ossia::ptr_map m_receivedMidi; friend struct local_pull_visitor; friend struct global_pull_visitor; diff --git a/src/ossia/protocols/midi/midi_protocol.cpp b/src/ossia/protocols/midi/midi_protocol.cpp index 007d609b146..23deda82f4c 100644 --- a/src/ossia/protocols/midi/midi_protocol.cpp +++ b/src/ossia/protocols/midi/midi_protocol.cpp @@ -15,48 +15,58 @@ static constexpr auto midi_api(libremidi::API api) return (api == libremidi::API::UNSPECIFIED) ? libremidi::midi1::default_api() : api; } midi_protocol::midi_protocol( - ossia::net::network_context_ptr ctx, std::string device_name, libremidi::API api) + ossia::net::network_context_ptr ctx, std::string device_name, + libremidi::input_configuration& conf, std::any api) : protocol_base{flags{}} , m_context{ctx} -// , m_input{std::make_unique(midi_api(api), device_name)} -// , m_output{std::make_unique(midi_api(api), device_name)} { - api = midi_api(api); + conf.on_message = [this](const libremidi::message& m) { midi_callback(m); }; - libremidi::input_configuration input_conf; - input_conf.on_message = [this](const libremidi::message& m) { midi_callback(m); }; - m_input = std::make_unique( - input_conf, libremidi::midi_in_configuration_for(api)); + m_input = std::make_unique(conf, api); +} - libremidi::output_configuration output_conf; - m_output = std::make_unique( - output_conf, libremidi::midi_out_configuration_for(api)); +midi_protocol::midi_protocol( + ossia::net::network_context_ptr ctx, std::string device_name, + libremidi::output_configuration& conf, std::any api) + : protocol_base{flags{}} + , m_context{ctx} +{ + m_output = std::make_unique(conf, api); } +/* midi_protocol::midi_protocol( ossia::net::network_context_ptr ctx, midi_info m, libremidi::API api) : midi_protocol{std::move(ctx), m.handle.display_name, api} { set_info(m); } +*/ midi_protocol::~midi_protocol() { - try + if(m_input) { - m_input->close_port(); - } - catch(...) - { - logger().error("midi_protocol::~midi_protocol() error"); - } - try - { - m_output->close_port(); + try + { + m_input->close_port(); + } + catch(...) + { + logger().error("midi_protocol::~midi_protocol() error"); + } } - catch(...) + + if(m_output) { - logger().error("midi_protocol::~midi_protocol() error"); + try + { + m_output->close_port(); + } + catch(...) + { + logger().error("midi_protocol::~midi_protocol() error"); + } } } @@ -126,6 +136,13 @@ midi_info midi_protocol::get_info() const return m_info; } +int64_t midi_protocol::get_timestamp() const noexcept +{ + if(m_info.type != midi_info::Type::Input || !m_input) + return 0; + return m_input->absolute_timestamp(); +} + bool midi_protocol::pull(parameter_base& address) { midi_parameter& adrs = dynamic_cast(address); @@ -304,10 +321,10 @@ bool midi_protocol::push_raw(const full_parameter_data& parameter_base) bool midi_protocol::observe(parameter_base& address, bool enable) { - midi_parameter& adrs = dynamic_cast(address); if(m_info.type != midi_info::Type::Input) return false; + midi_parameter& adrs = dynamic_cast(address); auto& adrinfo = adrs.info(); switch(adrinfo.type) { diff --git a/src/ossia/protocols/midi/midi_protocol.hpp b/src/ossia/protocols/midi/midi_protocol.hpp index e61ced86076..fa3e05c4a68 100644 --- a/src/ossia/protocols/midi/midi_protocol.hpp +++ b/src/ossia/protocols/midi/midi_protocol.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,8 @@ namespace libremidi { class midi_in; class midi_out; +struct input_configuration; +struct output_configuration; struct message; } namespace ossia::net::midi @@ -66,7 +69,10 @@ class OSSIA_EXPORT midi_protocol final public: explicit midi_protocol( ossia::net::network_context_ptr, std::string device_name, - libremidi::API api = libremidi::API::UNSPECIFIED); + libremidi::input_configuration&, std::any midi_api); + explicit midi_protocol( + ossia::net::network_context_ptr, std::string device_name, + libremidi::output_configuration&, std::any midi_api); explicit midi_protocol( ossia::net::network_context_ptr, midi_info, libremidi::API api = libremidi::API::UNSPECIFIED); @@ -75,6 +81,8 @@ class OSSIA_EXPORT midi_protocol final bool set_info(midi_info); midi_info get_info() const; + int64_t get_timestamp() const noexcept; + static std::string get_midi_port_name(ossia::net::device_base* dev, const midi_info& info); @@ -82,23 +90,16 @@ class OSSIA_EXPORT midi_protocol final void push_value(const libremidi::message&); - template - void clone_value(T& port) - { - typename T::value_type mess; - while(messages.try_dequeue(mess)) - { - port.push_back(mess); - } - } - void enable_registration(); bool learning() const; void set_learning(bool); -private: + libremidi::midi_in* midi_in() const noexcept { return m_input.get(); } + ossia::spsc_queue messages; + +private: ossia::net::network_context_ptr m_context; std::unique_ptr m_input; std::unique_ptr m_output;