mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
rawinput: refactor midi (#795)
pure movement of code only, no functional changes
This commit is contained in:
@@ -618,6 +618,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
|
|
||||||
# rawinput
|
# rawinput
|
||||||
rawinput/rawinput.cpp
|
rawinput/rawinput.cpp
|
||||||
|
rawinput/midi.cpp
|
||||||
rawinput/sextet.cpp
|
rawinput/sextet.cpp
|
||||||
rawinput/piuio.cpp
|
rawinput/piuio.cpp
|
||||||
rawinput/touch.cpp
|
rawinput/touch.cpp
|
||||||
|
|||||||
@@ -0,0 +1,727 @@
|
|||||||
|
#include "rawinput.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
namespace rawinput {
|
||||||
|
|
||||||
|
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
||||||
|
return rawinput::MIDI_NOTE_ALGORITHM;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
|
||||||
|
rawinput::MIDI_NOTE_ALGORITHM = new_algo;
|
||||||
|
std::string s = "Unknown";
|
||||||
|
switch (new_algo) {
|
||||||
|
case rawinput::MidiNoteAlgorithm::LEGACY:
|
||||||
|
s = "legacy";
|
||||||
|
break;
|
||||||
|
case rawinput::MidiNoteAlgorithm::V2:
|
||||||
|
s = "v2";
|
||||||
|
break;
|
||||||
|
case rawinput::MidiNoteAlgorithm::V2_DRUM:
|
||||||
|
s = "v2_drum";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log_info("rawinput", "assert failed: invalid midi algorithm");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log_info("rawinput", "using MIDI algorithm: {}", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputManager::midi_scan_start() {
|
||||||
|
|
||||||
|
// single-flight: only one scan runs at a time. if one is already running, set
|
||||||
|
// the pending flag so it rescans once more when it finishes - MIDI hotplug
|
||||||
|
// events fire while the slow enumeration is still going and must not be lost.
|
||||||
|
// the scheduler mutex makes this check-and-set atomic with the worker's
|
||||||
|
// exit-or-rescan decision below, so a request set while a scan is running is
|
||||||
|
// never dropped
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->midi_scan_m);
|
||||||
|
if (this->midi_scan_active) {
|
||||||
|
this->midi_scan_pending = true;
|
||||||
|
log_misc("rawinput", "MIDI scan already running, queued rescan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->midi_scan_active = true;
|
||||||
|
this->midi_scan_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up the previous (already finished) scan thread handle
|
||||||
|
this->midi_scan_join();
|
||||||
|
|
||||||
|
// run the (potentially slow) MIDI enumeration on its own thread so callers are
|
||||||
|
// not blocked while the Windows MIDI subsystem starts up. rescan if a request
|
||||||
|
// arrived while we were scanning
|
||||||
|
log_misc("rawinput", "starting async MIDI scan thread");
|
||||||
|
this->midi_thread = new std::thread([this]() {
|
||||||
|
for (;;) {
|
||||||
|
this->devices_scan_midi();
|
||||||
|
|
||||||
|
// decide whether to exit under the scheduler lock, atomically with any
|
||||||
|
// concurrent midi_scan_start(): if a rescan was requested, consume it
|
||||||
|
// and loop; otherwise clear active and exit. because both sides take
|
||||||
|
// the same lock, a request set while active is true is never lost, so
|
||||||
|
// we never strand a hotplug event waiting for a future one
|
||||||
|
std::lock_guard<std::mutex> lock(this->midi_scan_m);
|
||||||
|
if (!this->midi_scan_pending) {
|
||||||
|
this->midi_scan_active = false;
|
||||||
|
log_misc("rawinput", "async MIDI scan thread finished");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->midi_scan_pending = false;
|
||||||
|
log_misc("rawinput", "async MIDI scan rescanning (event arrived during scan)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputManager::midi_scan_join() {
|
||||||
|
if (this->midi_thread) {
|
||||||
|
if (this->midi_thread->joinable()) {
|
||||||
|
|
||||||
|
// this blocks until the scan worker returns. if it ever hangs here the
|
||||||
|
// worker is stuck - most likely in midi_close_deferred_flush() waiting
|
||||||
|
// on a WinMM close. a missing "joined" line pinpoints the hang
|
||||||
|
log_misc("rawinput", "joining MIDI scan thread...");
|
||||||
|
this->midi_thread->join();
|
||||||
|
log_misc("rawinput", "MIDI scan thread joined");
|
||||||
|
}
|
||||||
|
delete this->midi_thread;
|
||||||
|
this->midi_thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputManager::midi_close_deferred_flush() {
|
||||||
|
|
||||||
|
// take the queued handles under the lock, then close them without it. WinMM
|
||||||
|
// midiInReset/midiInClose block until in-flight input_midi_proc callbacks
|
||||||
|
// return, and those callbacks take devices_mutex, so closing under the lock
|
||||||
|
// would deadlock
|
||||||
|
std::vector<HMIDIIN> handles;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
||||||
|
handles.swap(this->midi_close_deferred);
|
||||||
|
}
|
||||||
|
if (handles.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a hang is ever reported here it is the classic WinMM deadlock: an
|
||||||
|
// in-flight input_midi_proc callback is blocked on devices_mutex while
|
||||||
|
// midiInReset/midiInClose waits for that callback to return. the per-handle
|
||||||
|
// log below pinpoints exactly which close did not come back
|
||||||
|
log_misc("rawinput", "closing {} deferred MIDI handle(s)", handles.size());
|
||||||
|
for (size_t i = 0; i < handles.size(); i++) {
|
||||||
|
log_misc("rawinput", "closing deferred MIDI handle {}/{}", i + 1, handles.size());
|
||||||
|
midiInReset(handles[i]);
|
||||||
|
midiInClose(handles[i]);
|
||||||
|
}
|
||||||
|
log_misc("rawinput", "deferred MIDI handles closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputManager::devices_scan_midi() {
|
||||||
|
log_misc("rawinput", "scan MIDI devices...");
|
||||||
|
|
||||||
|
// note: the WinMM MIDI calls below (midiInGetNumDevs / midiInGetDevCaps /
|
||||||
|
// midiInOpen / midiInStart) can block for seconds while the Windows MIDI
|
||||||
|
// subsystem starts up, so they must NOT run under devices_mutex. only the
|
||||||
|
// list mutation at the end of each iteration is guarded.
|
||||||
|
|
||||||
|
// identifiers of every MIDI device seen in this scan; used below to
|
||||||
|
// tombstone devices that have since been unplugged
|
||||||
|
std::vector<std::string> present_identifiers;
|
||||||
|
|
||||||
|
// add midi devices
|
||||||
|
auto midi_device_count = midiInGetNumDevs();
|
||||||
|
for (size_t midi_device_id = 0; midi_device_id < midi_device_count; midi_device_id++) {
|
||||||
|
|
||||||
|
// get dev caps
|
||||||
|
MIDIINCAPS midi_device_caps{};
|
||||||
|
if (midiInGetDevCaps(midi_device_id, &midi_device_caps, sizeof(MIDIINCAPS)) != MMSYSERR_NOERROR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc("rawinput", "found MIDI device: id {}, name {}, mid {}, pid {}",
|
||||||
|
midi_device_id, midi_device_caps.szPname, midi_device_caps.wMid, midi_device_caps.wPid);
|
||||||
|
|
||||||
|
// build identifier for MIDI
|
||||||
|
// ;MIDI; format is now set in stone (in other parts of the code base and in the config xml file)
|
||||||
|
// so it should never be changed
|
||||||
|
std::ostringstream midi_identifier_stream;
|
||||||
|
midi_identifier_stream << ";" << "MIDI";
|
||||||
|
midi_identifier_stream << ";" << midi_device_id;
|
||||||
|
midi_identifier_stream << ";" << midi_device_caps.szPname;
|
||||||
|
midi_identifier_stream << ";" << midi_device_caps.wMid;
|
||||||
|
midi_identifier_stream << ";" << midi_device_caps.wPid;
|
||||||
|
const auto midi_identifier = midi_identifier_stream.str();
|
||||||
|
|
||||||
|
// record that this device is currently present
|
||||||
|
present_identifiers.push_back(midi_identifier);
|
||||||
|
|
||||||
|
// if already open, leave it alone: hotplug fires many change events, and
|
||||||
|
// reopening on every rescan would drop the WinMM handle (and its input).
|
||||||
|
// only (re)open when the device is missing or a destroyed tombstone
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
||||||
|
bool already_open = false;
|
||||||
|
for (auto &device : this->devices) {
|
||||||
|
if (device.type == MIDI && device.name == midi_identifier) {
|
||||||
|
already_open = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (already_open) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// open device
|
||||||
|
HMIDIIN midi_device_handle;
|
||||||
|
if (midiInOpen(&midi_device_handle,
|
||||||
|
(UINT) midi_device_id,
|
||||||
|
(DWORD_PTR) &input_midi_proc,
|
||||||
|
(DWORD_PTR) this,
|
||||||
|
CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start input
|
||||||
|
if (midiInStart(midi_device_handle) != MMSYSERR_NOERROR) {
|
||||||
|
|
||||||
|
// close the handle we just opened so it does not leak on repeated rescans
|
||||||
|
midiInClose(midi_device_handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// device info
|
||||||
|
DeviceInfo midi_device_info {};
|
||||||
|
|
||||||
|
// device midi info
|
||||||
|
auto midi_device_midi_info = new DeviceMIDIInfo();
|
||||||
|
midi_device_midi_info->states = std::vector<bool>(16 * 128);
|
||||||
|
midi_device_midi_info->states_events = std::vector<uint8_t>(16 * 128);
|
||||||
|
midi_device_midi_info->bind_states = std::vector<bool>(16 * 128);
|
||||||
|
midi_device_midi_info->v2_last_on_time = std::vector<double>(16 * 128);
|
||||||
|
midi_device_midi_info->v2_last_off_time = std::vector<double>(16 * 128);
|
||||||
|
midi_device_midi_info->v2_velocity_threshold = std::vector<uint8_t>(16 * 128);
|
||||||
|
midi_device_midi_info->v2_velocity_threshold_set_on_device = std::vector<bool>(16 * 128);
|
||||||
|
midi_device_midi_info->velocity = std::vector<uint8_t>(16 * 128);
|
||||||
|
midi_device_midi_info->freeze = false;
|
||||||
|
midi_device_midi_info->controls_precision = std::vector<uint16_t>(16 * 32);
|
||||||
|
midi_device_midi_info->controls_precision_bind = std::vector<uint16_t>(16 * 32);
|
||||||
|
midi_device_midi_info->controls_precision_msb = std::vector<bool>(16 * 32);
|
||||||
|
midi_device_midi_info->controls_precision_lsb = std::vector<bool>(16 * 32);
|
||||||
|
midi_device_midi_info->controls_precision_set = std::vector<bool>(16 * 32);
|
||||||
|
midi_device_midi_info->controls_single = std::vector<uint8_t>(16 * 44);
|
||||||
|
midi_device_midi_info->controls_single_bind = std::vector<uint8_t>(16 * 44);
|
||||||
|
midi_device_midi_info->controls_single_set = std::vector<bool>(16 * 44);
|
||||||
|
midi_device_midi_info->controls_onoff = std::vector<bool>(16 * 6);
|
||||||
|
midi_device_midi_info->controls_onoff_bind = std::vector<bool>(16 * 6);
|
||||||
|
midi_device_midi_info->controls_onoff_set = std::vector<bool>(16 * 6);
|
||||||
|
midi_device_midi_info->v2_controls_onoff_last_on_time = std::vector<double>(16 * 6);
|
||||||
|
midi_device_midi_info->v2_controls_onoff_last_off_time = std::vector<double>(16 * 6);
|
||||||
|
midi_device_midi_info->pitch_bend = std::vector<int16_t>(16 * 6);
|
||||||
|
midi_device_midi_info->pitch_bend_set = std::vector<bool>(16 * 6);
|
||||||
|
|
||||||
|
// build device
|
||||||
|
Device midi_device {};
|
||||||
|
midi_device.type = MIDI;
|
||||||
|
midi_device.handle = midi_device_handle;
|
||||||
|
midi_device.name = midi_identifier;
|
||||||
|
midi_device.desc = to_string(midi_device_caps.szPname);
|
||||||
|
midi_device.info = midi_device_info;
|
||||||
|
midi_device.mutex = new std::mutex();
|
||||||
|
midi_device.mutex_out = new std::mutex();
|
||||||
|
midi_device.midiInfo = midi_device_midi_info;
|
||||||
|
|
||||||
|
// mutate the shared device list under lock (the slow WinMM calls above
|
||||||
|
// ran without it so other threads were not blocked)
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
||||||
|
midi_device.id = devices.size() + 1;
|
||||||
|
|
||||||
|
// reuse a previously destroyed tombstone with the same identifier, if any.
|
||||||
|
// (a live device with this identifier was already skipped above)
|
||||||
|
bool replaced = false;
|
||||||
|
for (auto &device : this->devices) {
|
||||||
|
if (device.name == midi_identifier) {
|
||||||
|
|
||||||
|
// carry over ID
|
||||||
|
midi_device.id = device.id;
|
||||||
|
|
||||||
|
// destruct and replace, reusing the slot's existing mutexes
|
||||||
|
this->devices_destruct(&device);
|
||||||
|
reuse_device_mutexes(midi_device, device);
|
||||||
|
device = midi_device;
|
||||||
|
|
||||||
|
// notify change
|
||||||
|
for (auto &cb : this->callback_change) {
|
||||||
|
cb.f(cb.data, &device);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (replaced) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add device to list
|
||||||
|
auto &device = this->devices.emplace_back(midi_device);
|
||||||
|
|
||||||
|
// notify add
|
||||||
|
for (auto &cb : this->callback_add) {
|
||||||
|
cb.f(cb.data, &device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tombstone MIDI devices that were open but are no longer present (unplugged).
|
||||||
|
// otherwise a replugged device matches the stale live entry in the skip check
|
||||||
|
// above and never gets reopened, silently losing its input
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
||||||
|
for (auto &device : this->devices) {
|
||||||
|
if (device.type != MIDI) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool present = false;
|
||||||
|
for (const auto &identifier : present_identifiers) {
|
||||||
|
if (identifier == device.name) {
|
||||||
|
present = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!present) {
|
||||||
|
log_info("rawinput", "MIDI device unplugged, releasing: {}", device.desc);
|
||||||
|
this->devices_destruct(&device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the MIDI handles detached above, now that devices_mutex is released
|
||||||
|
this->midi_close_deferred_flush();
|
||||||
|
|
||||||
|
log_misc("rawinput", "scan MIDI devices done ({} enumerated)", (unsigned) midi_device_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
||||||
|
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||||
|
// get instance
|
||||||
|
auto ri_mgr = reinterpret_cast<RawInputManager *>(dwInstance);
|
||||||
|
|
||||||
|
// handle message
|
||||||
|
switch (wMsg) {
|
||||||
|
case MIM_OPEN:
|
||||||
|
case MIM_CLOSE:
|
||||||
|
break;
|
||||||
|
case MIM_MOREDATA:
|
||||||
|
case MIM_DATA: {
|
||||||
|
|
||||||
|
// lock the device list so a concurrent scan can't mutate it while we iterate
|
||||||
|
std::lock_guard<std::recursive_mutex> devices_lock(ri_mgr->devices_mutex);
|
||||||
|
|
||||||
|
// param mapping
|
||||||
|
auto dwMidiMessage = dwParam1;
|
||||||
|
//auto dwTimestamp = dwParam2;
|
||||||
|
|
||||||
|
// message unpacking
|
||||||
|
auto midi_status = LOBYTE(LOWORD(dwMidiMessage));
|
||||||
|
auto midi_status_command = (midi_status & 0xF0u) >> 4u;
|
||||||
|
auto midi_status_channel = (midi_status & 0x0Fu);
|
||||||
|
auto midi_byte1 = HIBYTE(LOWORD(dwMidiMessage));
|
||||||
|
auto midi_byte2 = LOBYTE(HIWORD(dwMidiMessage));
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
for (auto &callback : ri_mgr->callback_midi) {
|
||||||
|
|
||||||
|
// find device
|
||||||
|
for (auto &device : ri_mgr->devices_get()) {
|
||||||
|
if (device.type == MIDI && device.handle == hMidiIn) {
|
||||||
|
|
||||||
|
// call function
|
||||||
|
callback.f(callback.data, &device,
|
||||||
|
midi_status_command, midi_status_channel,
|
||||||
|
midi_byte1, midi_byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip unused messages types early for performance
|
||||||
|
bool skip = false;
|
||||||
|
switch (midi_status_command) {
|
||||||
|
case 0xA: // POLYPHONIC PRESSURE
|
||||||
|
case 0xC: // PROGRAM CHANGE
|
||||||
|
case 0xD: // CHANNEL PRESSURE
|
||||||
|
case 0xF: // SYSTEM EXCLUSIVE
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (skip) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find device
|
||||||
|
for (auto &device : ri_mgr->devices_get()) {
|
||||||
|
|
||||||
|
// filter non MIDI devices
|
||||||
|
if (device.type != MIDI) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter wrong handles
|
||||||
|
if (device.handle != hMidiIn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get input time
|
||||||
|
const auto input_time = get_performance_seconds();
|
||||||
|
|
||||||
|
// lock device
|
||||||
|
std::lock_guard<std::mutex> lock(*device.mutex);
|
||||||
|
|
||||||
|
// update hz
|
||||||
|
auto diff_time = input_time - device.input_time;
|
||||||
|
if (diff_time > 0.0001) {
|
||||||
|
device.input_hz = 1.f / diff_time;
|
||||||
|
device.input_hz_max = MAX(device.input_hz_max, device.input_hz);
|
||||||
|
device.input_time = input_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// command logic
|
||||||
|
switch (midi_status_command) {
|
||||||
|
case 0x8: { // NOTE OFF
|
||||||
|
|
||||||
|
// param mapping
|
||||||
|
const auto midi_note = midi_byte1 & 127u;
|
||||||
|
|
||||||
|
// log_misc("midi", "[{}] OFF", midi_note);
|
||||||
|
|
||||||
|
// get index
|
||||||
|
const auto midi_index = midi_status_channel * 128 + midi_note;
|
||||||
|
if (midi_index < 16 * 128) {
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
||||||
|
// update velocity
|
||||||
|
device.midiInfo->velocity[midi_index] = 0;
|
||||||
|
// disable note
|
||||||
|
if (device.midiInfo->states_events[midi_index]) {
|
||||||
|
device.midiInfo->states[midi_index] = false;
|
||||||
|
}
|
||||||
|
device.updated = true;
|
||||||
|
} else {
|
||||||
|
// v2 logic
|
||||||
|
// exactly the same as NOTE ON with 0 velocity
|
||||||
|
// velocity is kept; api will ignore it if button is not pressed
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2) {
|
||||||
|
device.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds();
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
// for v2_drum, NOTE OFF is ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x9: { // NOTE ON
|
||||||
|
|
||||||
|
// param mapping
|
||||||
|
const auto midi_note = midi_byte1 & 127u;
|
||||||
|
|
||||||
|
// per MIDI spec, if NOTE ON is sent with 0 velocity, it's the same thing as NOTE OFF.
|
||||||
|
const auto midi_velocity = midi_byte2 & 127u;
|
||||||
|
|
||||||
|
// log_misc("midi", "[{}] ON v={}", midi_note, midi_velocity);
|
||||||
|
|
||||||
|
// get index
|
||||||
|
const auto midi_index = midi_status_channel * 128 + midi_note;
|
||||||
|
if (midi_index < 16 * 128) {
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
||||||
|
// update velocity
|
||||||
|
device.midiInfo->velocity[midi_index] = (uint8_t) midi_velocity;
|
||||||
|
|
||||||
|
if (midi_velocity) {
|
||||||
|
// update events (for legacy logic)
|
||||||
|
// how does this work? see the comment in api.cpp around the check for
|
||||||
|
// get_midi_algorithm() for an explanation
|
||||||
|
|
||||||
|
// so currently it's meant to be turned on
|
||||||
|
device.midiInfo->states[midi_index] = true;
|
||||||
|
|
||||||
|
// if its already on just increase it by one to turn it off
|
||||||
|
if (device.midiInfo->states_events[midi_index] % 2)
|
||||||
|
device.midiInfo->states_events[midi_index]++;
|
||||||
|
else
|
||||||
|
device.midiInfo->states_events[midi_index] += 2;
|
||||||
|
|
||||||
|
} else if (!device.midiInfo->freeze) {
|
||||||
|
// velocity 0 means turn it off
|
||||||
|
device.midiInfo->states[midi_index] = false;
|
||||||
|
}
|
||||||
|
device.updated = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// v2 logic
|
||||||
|
const auto now = get_performance_milliseconds();
|
||||||
|
auto threshold = device.midiInfo->v2_velocity_threshold[midi_index];
|
||||||
|
// when device is frozen (binding is happening) ignore the velocity threshold
|
||||||
|
// this allows users to bind keys even if the midi note is set to high threshold at
|
||||||
|
// rawinput layer, either from a previous binding that was cleared, or existing binding
|
||||||
|
// for another button
|
||||||
|
if (device.midiInfo->freeze) {
|
||||||
|
threshold = 0;
|
||||||
|
}
|
||||||
|
if (threshold < midi_velocity) {
|
||||||
|
device.midiInfo->velocity[midi_index] = (uint8_t)midi_velocity;
|
||||||
|
device.midiInfo->v2_last_on_time[midi_index] = now;
|
||||||
|
|
||||||
|
// disable holds and release all notes immediately
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2_DRUM) {
|
||||||
|
device.midiInfo->v2_last_off_time[midi_index] = now;
|
||||||
|
}
|
||||||
|
device.updated = true;
|
||||||
|
} else {
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2) {
|
||||||
|
// insufficient velocity ON == exactly the same as NOTE OFF
|
||||||
|
device.midiInfo->v2_last_off_time[midi_index] = now;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
// for v2_drum, NOTE ON with insufficient velocity is ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xA: // POLYPHONIC PRESSURE
|
||||||
|
break; // skipped above (!)
|
||||||
|
case 0xB: { // CONTROL CHANGE
|
||||||
|
|
||||||
|
// param mapping
|
||||||
|
auto midi_control = midi_byte1 & 127;
|
||||||
|
auto midi_value = midi_byte2 & 127u;
|
||||||
|
|
||||||
|
// get index
|
||||||
|
auto channel_offset = midi_status_channel * 128;
|
||||||
|
auto midi_index = channel_offset + midi_control;
|
||||||
|
if (midi_index < 16 * 128) {
|
||||||
|
|
||||||
|
// continuous controller MSB
|
||||||
|
if (midi_control >= 0x00 && midi_control <= 0x1F) {
|
||||||
|
|
||||||
|
// update index
|
||||||
|
midi_index = midi_status_channel * 32 + midi_control;
|
||||||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||||||
|
|
||||||
|
// check if MSB wasn't sent yet
|
||||||
|
if (!device.midiInfo->controls_precision_msb[midi_index]) {
|
||||||
|
device.midiInfo->controls_precision_msb[midi_index] = true;
|
||||||
|
|
||||||
|
// move LSB value to actual position
|
||||||
|
device.midiInfo->controls_precision[midi_index] >>= 7u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update MSB
|
||||||
|
auto tmp = device.midiInfo->controls_precision[midi_index];
|
||||||
|
tmp = (tmp & 127u) | midi_value << 7u;
|
||||||
|
if (!device.midiInfo->controls_precision_lsb[midi_index])
|
||||||
|
tmp = (tmp & (127u << 7u)) | midi_value;
|
||||||
|
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
||||||
|
device.midiInfo->controls_precision[midi_index] = tmp;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continuous controller LSB
|
||||||
|
else if (midi_control >= 0x20 && midi_control <= 0x3F) {
|
||||||
|
|
||||||
|
// update index
|
||||||
|
midi_index = midi_status_channel * 32 + midi_control - 0x20;
|
||||||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||||||
|
device.midiInfo->controls_precision_lsb[midi_index] = true;
|
||||||
|
|
||||||
|
// check for MSB flag
|
||||||
|
if (device.midiInfo->controls_precision_msb[midi_index]) {
|
||||||
|
|
||||||
|
// update LSB only
|
||||||
|
auto tmp = device.midiInfo->controls_precision[midi_index];
|
||||||
|
tmp &= 127u << 7u;
|
||||||
|
tmp |= midi_value;
|
||||||
|
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
||||||
|
device.midiInfo->controls_precision[midi_index] = tmp;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// cast to MSB
|
||||||
|
if (device.midiInfo->controls_precision[midi_index] != midi_value << 7u) {
|
||||||
|
device.midiInfo->controls_precision[midi_index] = midi_value << 7u | midi_value;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on/off controls
|
||||||
|
else if (midi_control >= 0x40 && midi_control <= 0x45) {
|
||||||
|
|
||||||
|
// update index
|
||||||
|
midi_index = midi_status_channel * 6 + midi_control - 0x40;
|
||||||
|
device.midiInfo->controls_onoff_set[midi_index] = true;
|
||||||
|
|
||||||
|
// get on/off state
|
||||||
|
const auto onoff_state = midi_value >= 64;
|
||||||
|
|
||||||
|
// update device
|
||||||
|
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
||||||
|
if (device.midiInfo->controls_onoff[midi_index] != onoff_state) {
|
||||||
|
device.midiInfo->controls_onoff[midi_index] = onoff_state;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// v2 and v2_drum:
|
||||||
|
// unlike notes (drum pads), controls can send continuous ON signal
|
||||||
|
// therefore, check for rising and falling edges
|
||||||
|
const auto now = get_performance_milliseconds();
|
||||||
|
const auto previous_value = device.midiInfo->controls_onoff[midi_index];
|
||||||
|
if (!previous_value && onoff_state) {
|
||||||
|
device.midiInfo->v2_controls_onoff_last_on_time[midi_index] = now;
|
||||||
|
device.updated = true;
|
||||||
|
} else if (previous_value && !onoff_state) {
|
||||||
|
device.midiInfo->v2_controls_onoff_last_off_time[midi_index] = now;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.midiInfo->controls_onoff[midi_index] = onoff_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// single byte controllers
|
||||||
|
else if (midi_control >= 0x46 && midi_control <= 0x5F) {
|
||||||
|
|
||||||
|
// update index
|
||||||
|
midi_index = midi_status_channel * 44 + midi_control - 0x46;
|
||||||
|
device.midiInfo->controls_single_set[midi_index] = true;
|
||||||
|
|
||||||
|
// update device
|
||||||
|
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
||||||
|
device.midiInfo->controls_single[midi_index] = midi_value;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment/decrement and parameter numbers
|
||||||
|
else if (midi_control >= 0x60 && midi_control <= 0x65) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// undefined single-byte controllers
|
||||||
|
else if (midi_control >= 0x66 && midi_control <= 0x77) {
|
||||||
|
|
||||||
|
// update index
|
||||||
|
auto sbc_count = 0x5F - 0x46 + 1;
|
||||||
|
midi_index = midi_status_channel * 44 + midi_control - 0x66 + sbc_count;
|
||||||
|
device.midiInfo->controls_single_set[midi_index] = true;
|
||||||
|
|
||||||
|
// update device
|
||||||
|
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
||||||
|
device.midiInfo->controls_single[midi_index] = midi_value;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel mode messages
|
||||||
|
else if (midi_control >= 0x78 && midi_control <= 0x7F) {
|
||||||
|
switch (midi_control) {
|
||||||
|
case 0x78: // all sound off
|
||||||
|
break;
|
||||||
|
case 0x79: { // reset all controllers
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
device.midiInfo->controls_precision[midi_status_channel * 32 + i] = 0;
|
||||||
|
for (int i = 0; i < 44; i++)
|
||||||
|
device.midiInfo->controls_single[midi_status_channel * 44 + i] = 0;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
const auto index = midi_status_channel * 6 + i;
|
||||||
|
device.midiInfo->controls_onoff[index] = false;
|
||||||
|
device.midiInfo->v2_controls_onoff_last_on_time[index] = 0;
|
||||||
|
device.midiInfo->v2_controls_onoff_last_off_time[index] = 0;
|
||||||
|
}
|
||||||
|
device.updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x7A: // local control on/off
|
||||||
|
break;
|
||||||
|
case 0x7B: // all notes off
|
||||||
|
case 0x7C: // omni mode off + all notes off
|
||||||
|
case 0x7D: // omni mode on + all notes off
|
||||||
|
case 0x7E: // mono mode on + poly off + all notes off
|
||||||
|
case 0x7F: // poly mode on + mono off + all notes off
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
// common
|
||||||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||||||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
device.midiInfo->states[channel_offset + i] = false;
|
||||||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||||||
|
|
||||||
|
// v2
|
||||||
|
device.midiInfo->v2_last_off_time[channel_offset + i] = 0.0;
|
||||||
|
device.midiInfo->v2_last_on_time[channel_offset + i] = 0.0;
|
||||||
|
}
|
||||||
|
device.updated = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xC: // PROGRAM CHANGE
|
||||||
|
break; // skipped above (!)
|
||||||
|
case 0xD: // CHANNEL PRESSURE
|
||||||
|
break; // skipped above (!)
|
||||||
|
case 0xE: { // PITCH BENDING
|
||||||
|
|
||||||
|
// raw values range from [0, 0x3FFF] (16383)
|
||||||
|
// build value, centered around zero [-8192, 8191]
|
||||||
|
int16_t value = ((midi_byte1) | (midi_byte2 << 7u)) - 0x2000;
|
||||||
|
|
||||||
|
// update device
|
||||||
|
if (device.midiInfo->pitch_bend[midi_status_channel] != value) {
|
||||||
|
device.midiInfo->pitch_bend[midi_status_channel] = value;
|
||||||
|
device.midiInfo->pitch_bend_set[midi_status_channel] = true;
|
||||||
|
device.updated = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xF: // SYSTEM EXCLUSIVE
|
||||||
|
break; // skipped above (!)
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't iterate through the other devices
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MIM_LONGDATA:
|
||||||
|
case MIM_ERROR:
|
||||||
|
case MIM_LONGERROR:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,53 +39,23 @@ namespace rawinput {
|
|||||||
// often than 60Hz
|
// often than 60Hz
|
||||||
uint32_t MIDI_NOTE_SUSTAIN = 20;
|
uint32_t MIDI_NOTE_SUSTAIN = 20;
|
||||||
|
|
||||||
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
|
||||||
|
|
||||||
// the price we pay for making spice overlay consume from raw input
|
// the price we pay for making spice overlay consume from raw input
|
||||||
// making focus detection a nightmare
|
// making focus detection a nightmare
|
||||||
bool OS_WINDOW_ACTIVE = false;
|
bool OS_WINDOW_ACTIVE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
// when replacing a device slot in place, keep the old slot's per-device mutexes
|
||||||
|
// instead of the freshly allocated pair on `replacement`. their addresses stay
|
||||||
// when replacing a device slot in place, keep the old slot's per-device mutexes
|
// stable, so a thread still holding a snapshot pointer to the slot (e.g. the
|
||||||
// instead of the freshly allocated pair on `replacement`. their addresses stay
|
// output thread blocked on mutex_out, which is taken without devices_mutex)
|
||||||
// stable, so a thread still holding a snapshot pointer to the slot (e.g. the
|
// never locks freed memory. the freshly allocated pair is freed here rather
|
||||||
// output thread blocked on mutex_out, which is taken without devices_mutex)
|
// than leaking the old one. the slot must already be destructed so both
|
||||||
// never locks freed memory. the freshly allocated pair is freed here rather
|
// mutexes are unlocked
|
||||||
// than leaking the old one. the slot must already be destructed so both
|
void rawinput::RawInputManager::reuse_device_mutexes(Device &replacement, const Device &existing) {
|
||||||
// mutexes are unlocked
|
|
||||||
void reuse_device_mutexes(rawinput::Device &replacement, const rawinput::Device &existing) {
|
|
||||||
delete replacement.mutex;
|
delete replacement.mutex;
|
||||||
delete replacement.mutex_out;
|
delete replacement.mutex_out;
|
||||||
replacement.mutex = existing.mutex;
|
replacement.mutex = existing.mutex;
|
||||||
replacement.mutex_out = existing.mutex_out;
|
replacement.mutex_out = existing.mutex_out;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
|
||||||
return rawinput::MIDI_NOTE_ALGORITHM;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
|
|
||||||
rawinput::MIDI_NOTE_ALGORITHM = new_algo;
|
|
||||||
std::string s = "Unknown";
|
|
||||||
switch (new_algo) {
|
|
||||||
case rawinput::MidiNoteAlgorithm::LEGACY:
|
|
||||||
s = "legacy";
|
|
||||||
break;
|
|
||||||
case rawinput::MidiNoteAlgorithm::V2:
|
|
||||||
s = "v2";
|
|
||||||
break;
|
|
||||||
case rawinput::MidiNoteAlgorithm::V2_DRUM:
|
|
||||||
s = "v2_drum";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
log_info("rawinput", "assert failed: invalid midi algorithm");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
log_info("rawinput", "using MIDI algorithm: {}", s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rawinput::RawInputManager::RawInputManager() {
|
rawinput::RawInputManager::RawInputManager() {
|
||||||
@@ -234,97 +204,6 @@ void rawinput::RawInputManager::devices_reload() {
|
|||||||
this->devices_register();
|
this->devices_register();
|
||||||
}
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::midi_scan_start() {
|
|
||||||
|
|
||||||
// single-flight: only one scan runs at a time. if one is already running, set
|
|
||||||
// the pending flag so it rescans once more when it finishes - MIDI hotplug
|
|
||||||
// events fire while the slow enumeration is still going and must not be lost.
|
|
||||||
// the scheduler mutex makes this check-and-set atomic with the worker's
|
|
||||||
// exit-or-rescan decision below, so a request set while a scan is running is
|
|
||||||
// never dropped
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(this->midi_scan_m);
|
|
||||||
if (this->midi_scan_active) {
|
|
||||||
this->midi_scan_pending = true;
|
|
||||||
log_misc("rawinput", "MIDI scan already running, queued rescan");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->midi_scan_active = true;
|
|
||||||
this->midi_scan_pending = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up the previous (already finished) scan thread handle
|
|
||||||
this->midi_scan_join();
|
|
||||||
|
|
||||||
// run the (potentially slow) MIDI enumeration on its own thread so callers are
|
|
||||||
// not blocked while the Windows MIDI subsystem starts up. rescan if a request
|
|
||||||
// arrived while we were scanning
|
|
||||||
log_misc("rawinput", "starting async MIDI scan thread");
|
|
||||||
this->midi_thread = new std::thread([this]() {
|
|
||||||
for (;;) {
|
|
||||||
this->devices_scan_midi();
|
|
||||||
|
|
||||||
// decide whether to exit under the scheduler lock, atomically with any
|
|
||||||
// concurrent midi_scan_start(): if a rescan was requested, consume it
|
|
||||||
// and loop; otherwise clear active and exit. because both sides take
|
|
||||||
// the same lock, a request set while active is true is never lost, so
|
|
||||||
// we never strand a hotplug event waiting for a future one
|
|
||||||
std::lock_guard<std::mutex> lock(this->midi_scan_m);
|
|
||||||
if (!this->midi_scan_pending) {
|
|
||||||
this->midi_scan_active = false;
|
|
||||||
log_misc("rawinput", "async MIDI scan thread finished");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->midi_scan_pending = false;
|
|
||||||
log_misc("rawinput", "async MIDI scan rescanning (event arrived during scan)");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::RawInputManager::midi_scan_join() {
|
|
||||||
if (this->midi_thread) {
|
|
||||||
if (this->midi_thread->joinable()) {
|
|
||||||
|
|
||||||
// this blocks until the scan worker returns. if it ever hangs here the
|
|
||||||
// worker is stuck - most likely in midi_close_deferred_flush() waiting
|
|
||||||
// on a WinMM close. a missing "joined" line pinpoints the hang
|
|
||||||
log_misc("rawinput", "joining MIDI scan thread...");
|
|
||||||
this->midi_thread->join();
|
|
||||||
log_misc("rawinput", "MIDI scan thread joined");
|
|
||||||
}
|
|
||||||
delete this->midi_thread;
|
|
||||||
this->midi_thread = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::RawInputManager::midi_close_deferred_flush() {
|
|
||||||
|
|
||||||
// take the queued handles under the lock, then close them without it. WinMM
|
|
||||||
// midiInReset/midiInClose block until in-flight input_midi_proc callbacks
|
|
||||||
// return, and those callbacks take devices_mutex, so closing under the lock
|
|
||||||
// would deadlock
|
|
||||||
std::vector<HMIDIIN> handles;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
|
||||||
handles.swap(this->midi_close_deferred);
|
|
||||||
}
|
|
||||||
if (handles.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if a hang is ever reported here it is the classic WinMM deadlock: an
|
|
||||||
// in-flight input_midi_proc callback is blocked on devices_mutex while
|
|
||||||
// midiInReset/midiInClose waits for that callback to return. the per-handle
|
|
||||||
// log below pinpoints exactly which close did not come back
|
|
||||||
log_misc("rawinput", "closing {} deferred MIDI handle(s)", handles.size());
|
|
||||||
for (size_t i = 0; i < handles.size(); i++) {
|
|
||||||
log_misc("rawinput", "closing deferred MIDI handle {}/{}", i + 1, handles.size());
|
|
||||||
midiInReset(handles[i]);
|
|
||||||
midiInClose(handles[i]);
|
|
||||||
}
|
|
||||||
log_misc("rawinput", "deferred MIDI handles closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) {
|
void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
||||||
log_misc("rawinput", "scan rawinput devices...");
|
log_misc("rawinput", "scan rawinput devices...");
|
||||||
@@ -952,192 +831,6 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::devices_scan_midi() {
|
|
||||||
log_misc("rawinput", "scan MIDI devices...");
|
|
||||||
|
|
||||||
// note: the WinMM MIDI calls below (midiInGetNumDevs / midiInGetDevCaps /
|
|
||||||
// midiInOpen / midiInStart) can block for seconds while the Windows MIDI
|
|
||||||
// subsystem starts up, so they must NOT run under devices_mutex. only the
|
|
||||||
// list mutation at the end of each iteration is guarded.
|
|
||||||
|
|
||||||
// identifiers of every MIDI device seen in this scan; used below to
|
|
||||||
// tombstone devices that have since been unplugged
|
|
||||||
std::vector<std::string> present_identifiers;
|
|
||||||
|
|
||||||
// add midi devices
|
|
||||||
auto midi_device_count = midiInGetNumDevs();
|
|
||||||
for (size_t midi_device_id = 0; midi_device_id < midi_device_count; midi_device_id++) {
|
|
||||||
|
|
||||||
// get dev caps
|
|
||||||
MIDIINCAPS midi_device_caps{};
|
|
||||||
if (midiInGetDevCaps(midi_device_id, &midi_device_caps, sizeof(MIDIINCAPS)) != MMSYSERR_NOERROR) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_misc("rawinput", "found MIDI device: id {}, name {}, mid {}, pid {}",
|
|
||||||
midi_device_id, midi_device_caps.szPname, midi_device_caps.wMid, midi_device_caps.wPid);
|
|
||||||
|
|
||||||
// build identifier for MIDI
|
|
||||||
// ;MIDI; format is now set in stone (in other parts of the code base and in the config xml file)
|
|
||||||
// so it should never be changed
|
|
||||||
std::ostringstream midi_identifier_stream;
|
|
||||||
midi_identifier_stream << ";" << "MIDI";
|
|
||||||
midi_identifier_stream << ";" << midi_device_id;
|
|
||||||
midi_identifier_stream << ";" << midi_device_caps.szPname;
|
|
||||||
midi_identifier_stream << ";" << midi_device_caps.wMid;
|
|
||||||
midi_identifier_stream << ";" << midi_device_caps.wPid;
|
|
||||||
const auto midi_identifier = midi_identifier_stream.str();
|
|
||||||
|
|
||||||
// record that this device is currently present
|
|
||||||
present_identifiers.push_back(midi_identifier);
|
|
||||||
|
|
||||||
// if already open, leave it alone: hotplug fires many change events, and
|
|
||||||
// reopening on every rescan would drop the WinMM handle (and its input).
|
|
||||||
// only (re)open when the device is missing or a destroyed tombstone
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
|
||||||
bool already_open = false;
|
|
||||||
for (auto &device : this->devices) {
|
|
||||||
if (device.type == MIDI && device.name == midi_identifier) {
|
|
||||||
already_open = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (already_open) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// open device
|
|
||||||
HMIDIIN midi_device_handle;
|
|
||||||
if (midiInOpen(&midi_device_handle,
|
|
||||||
(UINT) midi_device_id,
|
|
||||||
(DWORD_PTR) &input_midi_proc,
|
|
||||||
(DWORD_PTR) this,
|
|
||||||
CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start input
|
|
||||||
if (midiInStart(midi_device_handle) != MMSYSERR_NOERROR) {
|
|
||||||
|
|
||||||
// close the handle we just opened so it does not leak on repeated rescans
|
|
||||||
midiInClose(midi_device_handle);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// device info
|
|
||||||
DeviceInfo midi_device_info {};
|
|
||||||
|
|
||||||
// device midi info
|
|
||||||
auto midi_device_midi_info = new DeviceMIDIInfo();
|
|
||||||
midi_device_midi_info->states = std::vector<bool>(16 * 128);
|
|
||||||
midi_device_midi_info->states_events = std::vector<uint8_t>(16 * 128);
|
|
||||||
midi_device_midi_info->bind_states = std::vector<bool>(16 * 128);
|
|
||||||
midi_device_midi_info->v2_last_on_time = std::vector<double>(16 * 128);
|
|
||||||
midi_device_midi_info->v2_last_off_time = std::vector<double>(16 * 128);
|
|
||||||
midi_device_midi_info->v2_velocity_threshold = std::vector<uint8_t>(16 * 128);
|
|
||||||
midi_device_midi_info->v2_velocity_threshold_set_on_device = std::vector<bool>(16 * 128);
|
|
||||||
midi_device_midi_info->velocity = std::vector<uint8_t>(16 * 128);
|
|
||||||
midi_device_midi_info->freeze = false;
|
|
||||||
midi_device_midi_info->controls_precision = std::vector<uint16_t>(16 * 32);
|
|
||||||
midi_device_midi_info->controls_precision_bind = std::vector<uint16_t>(16 * 32);
|
|
||||||
midi_device_midi_info->controls_precision_msb = std::vector<bool>(16 * 32);
|
|
||||||
midi_device_midi_info->controls_precision_lsb = std::vector<bool>(16 * 32);
|
|
||||||
midi_device_midi_info->controls_precision_set = std::vector<bool>(16 * 32);
|
|
||||||
midi_device_midi_info->controls_single = std::vector<uint8_t>(16 * 44);
|
|
||||||
midi_device_midi_info->controls_single_bind = std::vector<uint8_t>(16 * 44);
|
|
||||||
midi_device_midi_info->controls_single_set = std::vector<bool>(16 * 44);
|
|
||||||
midi_device_midi_info->controls_onoff = std::vector<bool>(16 * 6);
|
|
||||||
midi_device_midi_info->controls_onoff_bind = std::vector<bool>(16 * 6);
|
|
||||||
midi_device_midi_info->controls_onoff_set = std::vector<bool>(16 * 6);
|
|
||||||
midi_device_midi_info->v2_controls_onoff_last_on_time = std::vector<double>(16 * 6);
|
|
||||||
midi_device_midi_info->v2_controls_onoff_last_off_time = std::vector<double>(16 * 6);
|
|
||||||
midi_device_midi_info->pitch_bend = std::vector<int16_t>(16 * 6);
|
|
||||||
midi_device_midi_info->pitch_bend_set = std::vector<bool>(16 * 6);
|
|
||||||
|
|
||||||
// build device
|
|
||||||
Device midi_device {};
|
|
||||||
midi_device.type = MIDI;
|
|
||||||
midi_device.handle = midi_device_handle;
|
|
||||||
midi_device.name = midi_identifier;
|
|
||||||
midi_device.desc = to_string(midi_device_caps.szPname);
|
|
||||||
midi_device.info = midi_device_info;
|
|
||||||
midi_device.mutex = new std::mutex();
|
|
||||||
midi_device.mutex_out = new std::mutex();
|
|
||||||
midi_device.midiInfo = midi_device_midi_info;
|
|
||||||
|
|
||||||
// mutate the shared device list under lock (the slow WinMM calls above
|
|
||||||
// ran without it so other threads were not blocked)
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
|
||||||
midi_device.id = devices.size() + 1;
|
|
||||||
|
|
||||||
// reuse a previously destroyed tombstone with the same identifier, if any.
|
|
||||||
// (a live device with this identifier was already skipped above)
|
|
||||||
bool replaced = false;
|
|
||||||
for (auto &device : this->devices) {
|
|
||||||
if (device.name == midi_identifier) {
|
|
||||||
|
|
||||||
// carry over ID
|
|
||||||
midi_device.id = device.id;
|
|
||||||
|
|
||||||
// destruct and replace, reusing the slot's existing mutexes
|
|
||||||
this->devices_destruct(&device);
|
|
||||||
reuse_device_mutexes(midi_device, device);
|
|
||||||
device = midi_device;
|
|
||||||
|
|
||||||
// notify change
|
|
||||||
for (auto &cb : this->callback_change) {
|
|
||||||
cb.f(cb.data, &device);
|
|
||||||
}
|
|
||||||
|
|
||||||
replaced = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (replaced) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add device to list
|
|
||||||
auto &device = this->devices.emplace_back(midi_device);
|
|
||||||
|
|
||||||
// notify add
|
|
||||||
for (auto &cb : this->callback_add) {
|
|
||||||
cb.f(cb.data, &device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tombstone MIDI devices that were open but are no longer present (unplugged).
|
|
||||||
// otherwise a replugged device matches the stale live entry in the skip check
|
|
||||||
// above and never gets reopened, silently losing its input
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(this->devices_mutex);
|
|
||||||
for (auto &device : this->devices) {
|
|
||||||
if (device.type != MIDI) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool present = false;
|
|
||||||
for (const auto &identifier : present_identifiers) {
|
|
||||||
if (identifier == device.name) {
|
|
||||||
present = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!present) {
|
|
||||||
log_info("rawinput", "MIDI device unplugged, releasing: {}", device.desc);
|
|
||||||
this->devices_destruct(&device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close the MIDI handles detached above, now that devices_mutex is released
|
|
||||||
this->midi_close_deferred_flush();
|
|
||||||
|
|
||||||
log_misc("rawinput", "scan MIDI devices done ({} enumerated)", (unsigned) midi_device_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::RawInputManager::devices_scan_piuio() {
|
void rawinput::RawInputManager::devices_scan_piuio() {
|
||||||
log_misc("rawinput", "scan PIUIO devices...");
|
log_misc("rawinput", "scan PIUIO devices...");
|
||||||
|
|
||||||
@@ -2267,420 +1960,6 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
return DefWindowProc(hWnd, msg, wparam, lParam);
|
return DefWindowProc(hWnd, msg, wparam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
|
||||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
|
||||||
// get instance
|
|
||||||
auto ri_mgr = reinterpret_cast<RawInputManager *>(dwInstance);
|
|
||||||
|
|
||||||
// handle message
|
|
||||||
switch (wMsg) {
|
|
||||||
case MIM_OPEN:
|
|
||||||
case MIM_CLOSE:
|
|
||||||
break;
|
|
||||||
case MIM_MOREDATA:
|
|
||||||
case MIM_DATA: {
|
|
||||||
|
|
||||||
// lock the device list so a concurrent scan can't mutate it while we iterate
|
|
||||||
std::lock_guard<std::recursive_mutex> devices_lock(ri_mgr->devices_mutex);
|
|
||||||
|
|
||||||
// param mapping
|
|
||||||
auto dwMidiMessage = dwParam1;
|
|
||||||
//auto dwTimestamp = dwParam2;
|
|
||||||
|
|
||||||
// message unpacking
|
|
||||||
auto midi_status = LOBYTE(LOWORD(dwMidiMessage));
|
|
||||||
auto midi_status_command = (midi_status & 0xF0u) >> 4u;
|
|
||||||
auto midi_status_channel = (midi_status & 0x0Fu);
|
|
||||||
auto midi_byte1 = HIBYTE(LOWORD(dwMidiMessage));
|
|
||||||
auto midi_byte2 = LOBYTE(HIWORD(dwMidiMessage));
|
|
||||||
|
|
||||||
// callbacks
|
|
||||||
for (auto &callback : ri_mgr->callback_midi) {
|
|
||||||
|
|
||||||
// find device
|
|
||||||
for (auto &device : ri_mgr->devices_get()) {
|
|
||||||
if (device.type == MIDI && device.handle == hMidiIn) {
|
|
||||||
|
|
||||||
// call function
|
|
||||||
callback.f(callback.data, &device,
|
|
||||||
midi_status_command, midi_status_channel,
|
|
||||||
midi_byte1, midi_byte2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip unused messages types early for performance
|
|
||||||
bool skip = false;
|
|
||||||
switch (midi_status_command) {
|
|
||||||
case 0xA: // POLYPHONIC PRESSURE
|
|
||||||
case 0xC: // PROGRAM CHANGE
|
|
||||||
case 0xD: // CHANNEL PRESSURE
|
|
||||||
case 0xF: // SYSTEM EXCLUSIVE
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (skip) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find device
|
|
||||||
for (auto &device : ri_mgr->devices_get()) {
|
|
||||||
|
|
||||||
// filter non MIDI devices
|
|
||||||
if (device.type != MIDI) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter wrong handles
|
|
||||||
if (device.handle != hMidiIn) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get input time
|
|
||||||
const auto input_time = get_performance_seconds();
|
|
||||||
|
|
||||||
// lock device
|
|
||||||
std::lock_guard<std::mutex> lock(*device.mutex);
|
|
||||||
|
|
||||||
// update hz
|
|
||||||
auto diff_time = input_time - device.input_time;
|
|
||||||
if (diff_time > 0.0001) {
|
|
||||||
device.input_hz = 1.f / diff_time;
|
|
||||||
device.input_hz_max = MAX(device.input_hz_max, device.input_hz);
|
|
||||||
device.input_time = input_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// command logic
|
|
||||||
switch (midi_status_command) {
|
|
||||||
case 0x8: { // NOTE OFF
|
|
||||||
|
|
||||||
// param mapping
|
|
||||||
const auto midi_note = midi_byte1 & 127u;
|
|
||||||
|
|
||||||
// log_misc("midi", "[{}] OFF", midi_note);
|
|
||||||
|
|
||||||
// get index
|
|
||||||
const auto midi_index = midi_status_channel * 128 + midi_note;
|
|
||||||
if (midi_index < 16 * 128) {
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
|
||||||
// update velocity
|
|
||||||
device.midiInfo->velocity[midi_index] = 0;
|
|
||||||
// disable note
|
|
||||||
if (device.midiInfo->states_events[midi_index]) {
|
|
||||||
device.midiInfo->states[midi_index] = false;
|
|
||||||
}
|
|
||||||
device.updated = true;
|
|
||||||
} else {
|
|
||||||
// v2 logic
|
|
||||||
// exactly the same as NOTE ON with 0 velocity
|
|
||||||
// velocity is kept; api will ignore it if button is not pressed
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2) {
|
|
||||||
device.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds();
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
// for v2_drum, NOTE OFF is ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0x9: { // NOTE ON
|
|
||||||
|
|
||||||
// param mapping
|
|
||||||
const auto midi_note = midi_byte1 & 127u;
|
|
||||||
|
|
||||||
// per MIDI spec, if NOTE ON is sent with 0 velocity, it's the same thing as NOTE OFF.
|
|
||||||
const auto midi_velocity = midi_byte2 & 127u;
|
|
||||||
|
|
||||||
// log_misc("midi", "[{}] ON v={}", midi_note, midi_velocity);
|
|
||||||
|
|
||||||
// get index
|
|
||||||
const auto midi_index = midi_status_channel * 128 + midi_note;
|
|
||||||
if (midi_index < 16 * 128) {
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
|
||||||
// update velocity
|
|
||||||
device.midiInfo->velocity[midi_index] = (uint8_t) midi_velocity;
|
|
||||||
|
|
||||||
if (midi_velocity) {
|
|
||||||
// update events (for legacy logic)
|
|
||||||
// how does this work? see the comment in api.cpp around the check for
|
|
||||||
// get_midi_algorithm() for an explanation
|
|
||||||
|
|
||||||
// so currently it's meant to be turned on
|
|
||||||
device.midiInfo->states[midi_index] = true;
|
|
||||||
|
|
||||||
// if its already on just increase it by one to turn it off
|
|
||||||
if (device.midiInfo->states_events[midi_index] % 2)
|
|
||||||
device.midiInfo->states_events[midi_index]++;
|
|
||||||
else
|
|
||||||
device.midiInfo->states_events[midi_index] += 2;
|
|
||||||
|
|
||||||
} else if (!device.midiInfo->freeze) {
|
|
||||||
// velocity 0 means turn it off
|
|
||||||
device.midiInfo->states[midi_index] = false;
|
|
||||||
}
|
|
||||||
device.updated = true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// v2 logic
|
|
||||||
const auto now = get_performance_milliseconds();
|
|
||||||
auto threshold = device.midiInfo->v2_velocity_threshold[midi_index];
|
|
||||||
// when device is frozen (binding is happening) ignore the velocity threshold
|
|
||||||
// this allows users to bind keys even if the midi note is set to high threshold at
|
|
||||||
// rawinput layer, either from a previous binding that was cleared, or existing binding
|
|
||||||
// for another button
|
|
||||||
if (device.midiInfo->freeze) {
|
|
||||||
threshold = 0;
|
|
||||||
}
|
|
||||||
if (threshold < midi_velocity) {
|
|
||||||
device.midiInfo->velocity[midi_index] = (uint8_t)midi_velocity;
|
|
||||||
device.midiInfo->v2_last_on_time[midi_index] = now;
|
|
||||||
|
|
||||||
// disable holds and release all notes immediately
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2_DRUM) {
|
|
||||||
device.midiInfo->v2_last_off_time[midi_index] = now;
|
|
||||||
}
|
|
||||||
device.updated = true;
|
|
||||||
} else {
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::V2) {
|
|
||||||
// insufficient velocity ON == exactly the same as NOTE OFF
|
|
||||||
device.midiInfo->v2_last_off_time[midi_index] = now;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
// for v2_drum, NOTE ON with insufficient velocity is ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0xA: // POLYPHONIC PRESSURE
|
|
||||||
break; // skipped above (!)
|
|
||||||
case 0xB: { // CONTROL CHANGE
|
|
||||||
|
|
||||||
// param mapping
|
|
||||||
auto midi_control = midi_byte1 & 127;
|
|
||||||
auto midi_value = midi_byte2 & 127u;
|
|
||||||
|
|
||||||
// get index
|
|
||||||
auto channel_offset = midi_status_channel * 128;
|
|
||||||
auto midi_index = channel_offset + midi_control;
|
|
||||||
if (midi_index < 16 * 128) {
|
|
||||||
|
|
||||||
// continuous controller MSB
|
|
||||||
if (midi_control >= 0x00 && midi_control <= 0x1F) {
|
|
||||||
|
|
||||||
// update index
|
|
||||||
midi_index = midi_status_channel * 32 + midi_control;
|
|
||||||
device.midiInfo->controls_precision_set[midi_index] = true;
|
|
||||||
|
|
||||||
// check if MSB wasn't sent yet
|
|
||||||
if (!device.midiInfo->controls_precision_msb[midi_index]) {
|
|
||||||
device.midiInfo->controls_precision_msb[midi_index] = true;
|
|
||||||
|
|
||||||
// move LSB value to actual position
|
|
||||||
device.midiInfo->controls_precision[midi_index] >>= 7u;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update MSB
|
|
||||||
auto tmp = device.midiInfo->controls_precision[midi_index];
|
|
||||||
tmp = (tmp & 127u) | midi_value << 7u;
|
|
||||||
if (!device.midiInfo->controls_precision_lsb[midi_index])
|
|
||||||
tmp = (tmp & (127u << 7u)) | midi_value;
|
|
||||||
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
|
||||||
device.midiInfo->controls_precision[midi_index] = tmp;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// continuous controller LSB
|
|
||||||
else if (midi_control >= 0x20 && midi_control <= 0x3F) {
|
|
||||||
|
|
||||||
// update index
|
|
||||||
midi_index = midi_status_channel * 32 + midi_control - 0x20;
|
|
||||||
device.midiInfo->controls_precision_set[midi_index] = true;
|
|
||||||
device.midiInfo->controls_precision_lsb[midi_index] = true;
|
|
||||||
|
|
||||||
// check for MSB flag
|
|
||||||
if (device.midiInfo->controls_precision_msb[midi_index]) {
|
|
||||||
|
|
||||||
// update LSB only
|
|
||||||
auto tmp = device.midiInfo->controls_precision[midi_index];
|
|
||||||
tmp &= 127u << 7u;
|
|
||||||
tmp |= midi_value;
|
|
||||||
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
|
||||||
device.midiInfo->controls_precision[midi_index] = tmp;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// cast to MSB
|
|
||||||
if (device.midiInfo->controls_precision[midi_index] != midi_value << 7u) {
|
|
||||||
device.midiInfo->controls_precision[midi_index] = midi_value << 7u | midi_value;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// on/off controls
|
|
||||||
else if (midi_control >= 0x40 && midi_control <= 0x45) {
|
|
||||||
|
|
||||||
// update index
|
|
||||||
midi_index = midi_status_channel * 6 + midi_control - 0x40;
|
|
||||||
device.midiInfo->controls_onoff_set[midi_index] = true;
|
|
||||||
|
|
||||||
// get on/off state
|
|
||||||
const auto onoff_state = midi_value >= 64;
|
|
||||||
|
|
||||||
// update device
|
|
||||||
if (MIDI_NOTE_ALGORITHM == MidiNoteAlgorithm::LEGACY) {
|
|
||||||
if (device.midiInfo->controls_onoff[midi_index] != onoff_state) {
|
|
||||||
device.midiInfo->controls_onoff[midi_index] = onoff_state;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// v2 and v2_drum:
|
|
||||||
// unlike notes (drum pads), controls can send continuous ON signal
|
|
||||||
// therefore, check for rising and falling edges
|
|
||||||
const auto now = get_performance_milliseconds();
|
|
||||||
const auto previous_value = device.midiInfo->controls_onoff[midi_index];
|
|
||||||
if (!previous_value && onoff_state) {
|
|
||||||
device.midiInfo->v2_controls_onoff_last_on_time[midi_index] = now;
|
|
||||||
device.updated = true;
|
|
||||||
} else if (previous_value && !onoff_state) {
|
|
||||||
device.midiInfo->v2_controls_onoff_last_off_time[midi_index] = now;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
device.midiInfo->controls_onoff[midi_index] = onoff_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// single byte controllers
|
|
||||||
else if (midi_control >= 0x46 && midi_control <= 0x5F) {
|
|
||||||
|
|
||||||
// update index
|
|
||||||
midi_index = midi_status_channel * 44 + midi_control - 0x46;
|
|
||||||
device.midiInfo->controls_single_set[midi_index] = true;
|
|
||||||
|
|
||||||
// update device
|
|
||||||
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
|
||||||
device.midiInfo->controls_single[midi_index] = midi_value;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment/decrement and parameter numbers
|
|
||||||
else if (midi_control >= 0x60 && midi_control <= 0x65) {
|
|
||||||
// skip
|
|
||||||
}
|
|
||||||
|
|
||||||
// undefined single-byte controllers
|
|
||||||
else if (midi_control >= 0x66 && midi_control <= 0x77) {
|
|
||||||
|
|
||||||
// update index
|
|
||||||
auto sbc_count = 0x5F - 0x46 + 1;
|
|
||||||
midi_index = midi_status_channel * 44 + midi_control - 0x66 + sbc_count;
|
|
||||||
device.midiInfo->controls_single_set[midi_index] = true;
|
|
||||||
|
|
||||||
// update device
|
|
||||||
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
|
||||||
device.midiInfo->controls_single[midi_index] = midi_value;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel mode messages
|
|
||||||
else if (midi_control >= 0x78 && midi_control <= 0x7F) {
|
|
||||||
switch (midi_control) {
|
|
||||||
case 0x78: // all sound off
|
|
||||||
break;
|
|
||||||
case 0x79: { // reset all controllers
|
|
||||||
for (int i = 0; i < 32; i++)
|
|
||||||
device.midiInfo->controls_precision[midi_status_channel * 32 + i] = 0;
|
|
||||||
for (int i = 0; i < 44; i++)
|
|
||||||
device.midiInfo->controls_single[midi_status_channel * 44 + i] = 0;
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
const auto index = midi_status_channel * 6 + i;
|
|
||||||
device.midiInfo->controls_onoff[index] = false;
|
|
||||||
device.midiInfo->v2_controls_onoff_last_on_time[index] = 0;
|
|
||||||
device.midiInfo->v2_controls_onoff_last_off_time[index] = 0;
|
|
||||||
}
|
|
||||||
device.updated = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0x7A: // local control on/off
|
|
||||||
break;
|
|
||||||
case 0x7B: // all notes off
|
|
||||||
case 0x7C: // omni mode off + all notes off
|
|
||||||
case 0x7D: // omni mode on + all notes off
|
|
||||||
case 0x7E: // mono mode on + poly off + all notes off
|
|
||||||
case 0x7F: // poly mode on + mono off + all notes off
|
|
||||||
for (int i = 0; i < 128; i++) {
|
|
||||||
// common
|
|
||||||
device.midiInfo->velocity[channel_offset + i] = 0;
|
|
||||||
device.midiInfo->bind_states[channel_offset + i] = false;
|
|
||||||
|
|
||||||
// legacy
|
|
||||||
device.midiInfo->states[channel_offset + i] = false;
|
|
||||||
device.midiInfo->states_events[channel_offset + i] = 0;
|
|
||||||
|
|
||||||
// v2
|
|
||||||
device.midiInfo->v2_last_off_time[channel_offset + i] = 0.0;
|
|
||||||
device.midiInfo->v2_last_on_time[channel_offset + i] = 0.0;
|
|
||||||
}
|
|
||||||
device.updated = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0xC: // PROGRAM CHANGE
|
|
||||||
break; // skipped above (!)
|
|
||||||
case 0xD: // CHANNEL PRESSURE
|
|
||||||
break; // skipped above (!)
|
|
||||||
case 0xE: { // PITCH BENDING
|
|
||||||
|
|
||||||
// raw values range from [0, 0x3FFF] (16383)
|
|
||||||
// build value, centered around zero [-8192, 8191]
|
|
||||||
int16_t value = ((midi_byte1) | (midi_byte2 << 7u)) - 0x2000;
|
|
||||||
|
|
||||||
// update device
|
|
||||||
if (device.midiInfo->pitch_bend[midi_status_channel] != value) {
|
|
||||||
device.midiInfo->pitch_bend[midi_status_channel] = value;
|
|
||||||
device.midiInfo->pitch_bend_set[midi_status_channel] = true;
|
|
||||||
device.updated = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0xF: // SYSTEM EXCLUSIVE
|
|
||||||
break; // skipped above (!)
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't iterate through the other devices
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MIM_LONGDATA:
|
|
||||||
case MIM_ERROR:
|
|
||||||
case MIM_LONGERROR:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
||||||
|
|
||||||
// check if output is enabled
|
// check if output is enabled
|
||||||
|
|||||||
@@ -134,6 +134,11 @@ namespace rawinput {
|
|||||||
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
||||||
static DeviceInfo get_device_info(const std::string &device_name);
|
static DeviceInfo get_device_info(const std::string &device_name);
|
||||||
|
|
||||||
|
// shared by the rawinput / xinput / midi device scans when replacing a slot
|
||||||
|
// in place; keeps the existing slot's mutex pair so snapshot pointers held by
|
||||||
|
// other threads stay valid (see the definition in rawinput.cpp)
|
||||||
|
static void reuse_device_mutexes(Device &replacement, const Device &existing);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
HWND input_hwnd = nullptr;
|
HWND input_hwnd = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user