| @@ -667,6 +667,27 @@ struct carla_v3_input_param_changes : v3_param_changes_cpp { | |||
| updatedParams[index].updated = true; | |||
| } | |||
| // called as response to MIDI CC | |||
| void setParamValueRT(const uint32_t index, const int32_t offset, const float value) noexcept | |||
| { | |||
| static constexpr const int8_t kQueuePointSize = sizeof(queue[0]->points)/sizeof(queue[0]->points[0]); | |||
| if (queue[index]->numUsed < kQueuePointSize) | |||
| { | |||
| // still has space, add in queue | |||
| carla_v3_input_param_value_queue::Point& point(queue[index]->points[queue[index]->numUsed++]); | |||
| point.offset = offset; | |||
| point.value = value; | |||
| } | |||
| else | |||
| { | |||
| // points are full, replace last one | |||
| carla_v3_input_param_value_queue::Point& point(queue[index]->points[queue[index]->numUsed - 1]); | |||
| point.offset = offset; | |||
| point.value = value; | |||
| } | |||
| } | |||
| private: | |||
| static int32_t V3_API get_param_count(void* const self) | |||
| { | |||
| @@ -875,7 +896,6 @@ private: | |||
| static v3_result V3_API get_event(void* const self, const int32_t index, v3_event* const event) | |||
| { | |||
| carla_debug("TODO %s", __PRETTY_FUNCTION__); | |||
| const carla_v3_input_event_list* const me = *static_cast<const carla_v3_input_event_list**>(self); | |||
| CARLA_SAFE_ASSERT_RETURN(index < static_cast<int32_t>(me->numEvents), V3_INVALID_ARG); | |||
| std::memcpy(event, &me->events[index], sizeof(v3_event)); | |||
| @@ -884,7 +904,6 @@ private: | |||
| static v3_result V3_API add_event(void*, v3_event*) | |||
| { | |||
| carla_debug("TODO %s", __PRETTY_FUNCTION__); | |||
| // there is nothing here for input events, plugins are not meant to call this! | |||
| return V3_NOT_IMPLEMENTED; | |||
| } | |||
| @@ -1377,18 +1396,17 @@ public: | |||
| options |= PLUGIN_OPTION_USE_CHUNKS; | |||
| /* TODO | |||
| if (hasMidiInput()) | |||
| { | |||
| options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; | |||
| options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; | |||
| options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; | |||
| options |= PLUGIN_OPTION_SEND_PITCHBEND; | |||
| /* TODO | |||
| options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; | |||
| options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; | |||
| */ | |||
| options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; | |||
| } | |||
| */ | |||
| return options; | |||
| } | |||
| @@ -1564,7 +1582,7 @@ public: | |||
| fixedValue); | |||
| // report value to component (next process call) | |||
| fEvents.paramInputs->setParamValue(paramIndex, static_cast<float>(normalized)); | |||
| fEvents.paramInputs->setParamValueRT(paramIndex, frameOffset, static_cast<float>(normalized)); | |||
| CarlaPlugin::setParameterValueRT(paramIndex, fixedValue, frameOffset, sendCallbackLater); | |||
| } | |||
| @@ -2442,12 +2460,6 @@ public: | |||
| if (pData->needsReset) | |||
| { | |||
| /* | |||
| if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) | |||
| { | |||
| } | |||
| else | |||
| */ | |||
| if (pData->ctrlChannel >= 0 && pData->ctrlChannel < MAX_MIDI_CHANNELS && fEvents.eventInputs != nullptr) | |||
| { | |||
| fEvents.eventInputs->numEvents = MAX_MIDI_NOTE; | |||
| @@ -2533,12 +2545,12 @@ public: | |||
| // ------------------------------------------------------------------------------------------------------------ | |||
| // Event Input and Processing | |||
| if (pData->event.portIn != nullptr) | |||
| if (pData->event.portIn != nullptr && fEvents.eventInputs != nullptr) | |||
| { | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // MIDI Input (External) | |||
| if (fEvents.eventInputs != nullptr && pData->extNotes.mutex.tryLock()) | |||
| if (pData->extNotes.mutex.tryLock()) | |||
| { | |||
| ExternalMidiNote note = { 0, 0, 0 }; | |||
| uint16_t numEvents = fEvents.eventInputs->numEvents; | |||
| @@ -2576,9 +2588,6 @@ public: | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Event Input (System) | |||
| #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH | |||
| bool allNotesOffSent = false; | |||
| #endif | |||
| bool isSampleAccurate = (pData->options & PLUGIN_OPTION_FIXED_BUFFERS) == 0; | |||
| uint32_t startTime = 0; | |||
| @@ -2709,7 +2718,28 @@ public: | |||
| if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE) | |||
| { | |||
| // TODO | |||
| v3_param_id paramId = 0; | |||
| if (v3_cpp_obj(fV3.midiMapping)->get_midi_controller_assignment(fV3.midiMapping, | |||
| 0, | |||
| event.channel, | |||
| ctrlEvent.param, | |||
| ¶mId) == V3_OK) | |||
| { | |||
| uint32_t index = UINT32_MAX; | |||
| for (uint32_t i=0; i < pData->param.count; ++i) | |||
| { | |||
| if (static_cast<v3_param_id>(pData->param.data[i].rindex) == paramId) | |||
| { | |||
| index = i; | |||
| break; | |||
| } | |||
| } | |||
| if (index == UINT32_MAX) | |||
| break; | |||
| fEvents.paramInputs->setParamValueRT(index, event.time, ctrlEvent.normalizedValue); | |||
| } | |||
| } | |||
| #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH | |||
| @@ -2720,10 +2750,6 @@ public: | |||
| } // case kEngineControlEventTypeParameter | |||
| case kEngineControlEventTypeMidiBank: | |||
| if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0) | |||
| { | |||
| // TODO | |||
| } | |||
| break; | |||
| case kEngineControlEventTypeMidiProgram: | |||
| @@ -2735,31 +2761,11 @@ public: | |||
| break; | |||
| } | |||
| } | |||
| else if (pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) | |||
| { | |||
| // TODO | |||
| } | |||
| break; | |||
| case kEngineControlEventTypeAllSoundOff: | |||
| if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) | |||
| { | |||
| // TODO | |||
| } | |||
| break; | |||
| case kEngineControlEventTypeAllNotesOff: | |||
| if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) | |||
| { | |||
| #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH | |||
| if (event.channel == pData->ctrlChannel && ! allNotesOffSent) | |||
| { | |||
| allNotesOffSent = true; | |||
| postponeRtAllNotesOff(); | |||
| } | |||
| #endif | |||
| // TODO | |||
| } | |||
| // TODO map to CC | |||
| break; | |||
| } // switch (ctrlEvent.type) | |||
| break; | |||
| @@ -2778,29 +2784,167 @@ public: | |||
| if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES)) | |||
| continue; | |||
| if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) | |||
| if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) | |||
| continue; | |||
| if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) | |||
| if (status == MIDI_STATUS_CONTROL_CHANGE && ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0 || fEvents.paramInputs == nullptr || fV3.midiMapping == nullptr)) | |||
| continue; | |||
| if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) | |||
| if (status == MIDI_STATUS_CHANNEL_PRESSURE && ((pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0 || fEvents.paramInputs == nullptr || fV3.midiMapping == nullptr)) | |||
| continue; | |||
| if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) | |||
| if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && ((pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0 || fEvents.paramInputs == nullptr || fV3.midiMapping == nullptr)) | |||
| continue; | |||
| // Fix bad note-off | |||
| if (status == MIDI_STATUS_NOTE_ON && midiEvent.data[2] == 0) | |||
| status = MIDI_STATUS_NOTE_OFF; | |||
| // TODO | |||
| if (status == MIDI_STATUS_NOTE_ON) | |||
| { | |||
| pData->postponeNoteOnRtEvent(true, event.channel, midiEvent.data[1], midiEvent.data[2]); | |||
| } | |||
| else if (status == MIDI_STATUS_NOTE_OFF) | |||
| switch (status) | |||
| { | |||
| pData->postponeNoteOffRtEvent(true, event.channel, midiEvent.data[1]); | |||
| } | |||
| case MIDI_STATUS_NOTE_OFF: | |||
| if (fEvents.eventInputs->numEvents < kPluginMaxMidiEvents) | |||
| { | |||
| const uint8_t note = midiEvent.data[1]; | |||
| v3_event& v3event(fEvents.eventInputs->events[fEvents.eventInputs->numEvents++]); | |||
| carla_zeroStruct(v3event); | |||
| v3event.type = V3_EVENT_NOTE_OFF; | |||
| v3event.note_off.channel = event.channel & MIDI_CHANNEL_BIT; | |||
| v3event.note_off.pitch = note; | |||
| pData->postponeNoteOffRtEvent(true, event.channel, note); | |||
| } | |||
| break; | |||
| case MIDI_STATUS_NOTE_ON: | |||
| if (fEvents.eventInputs->numEvents < kPluginMaxMidiEvents) | |||
| { | |||
| const uint8_t note = midiEvent.data[1]; | |||
| const uint8_t velo = midiEvent.data[2]; | |||
| v3_event& v3event(fEvents.eventInputs->events[fEvents.eventInputs->numEvents++]); | |||
| carla_zeroStruct(v3event); | |||
| v3event.type = V3_EVENT_NOTE_ON; | |||
| v3event.note_on.channel = event.channel & MIDI_CHANNEL_BIT; | |||
| v3event.note_on.pitch = note; | |||
| v3event.note_on.velocity = static_cast<float>(velo) / 127.f; | |||
| pData->postponeNoteOnRtEvent(true, event.channel, note, velo); | |||
| } | |||
| break; | |||
| case MIDI_STATUS_POLYPHONIC_AFTERTOUCH: | |||
| if (fEvents.eventInputs->numEvents < kPluginMaxMidiEvents) | |||
| { | |||
| const uint8_t note = midiEvent.data[1]; | |||
| const uint8_t pressure = midiEvent.data[2]; | |||
| v3_event& v3event(fEvents.eventInputs->events[fEvents.eventInputs->numEvents++]); | |||
| carla_zeroStruct(v3event); | |||
| v3event.type = V3_EVENT_POLY_PRESSURE; | |||
| v3event.poly_pressure.channel = event.channel; | |||
| v3event.poly_pressure.pitch = note; | |||
| v3event.poly_pressure.pressure = static_cast<float>(pressure) / 127.f; | |||
| } | |||
| break; | |||
| case MIDI_STATUS_CONTROL_CHANGE: | |||
| { | |||
| const uint8_t control = midiEvent.data[1]; | |||
| const uint8_t value = midiEvent.data[2]; | |||
| v3_param_id paramId = 0; | |||
| if (v3_cpp_obj(fV3.midiMapping)->get_midi_controller_assignment(fV3.midiMapping, | |||
| midiEvent.port, | |||
| event.channel, | |||
| control, | |||
| ¶mId) == V3_OK) | |||
| { | |||
| uint32_t index = UINT32_MAX; | |||
| for (uint32_t i=0; i < pData->param.count; ++i) | |||
| { | |||
| if (static_cast<v3_param_id>(pData->param.data[i].rindex) == paramId) | |||
| { | |||
| index = i; | |||
| break; | |||
| } | |||
| } | |||
| if (index == UINT32_MAX) | |||
| break; | |||
| fEvents.paramInputs->setParamValueRT(index, | |||
| event.time, | |||
| static_cast<float>(value) / 127.f); | |||
| } | |||
| } | |||
| break; | |||
| case MIDI_STATUS_CHANNEL_PRESSURE: | |||
| { | |||
| const uint8_t pressure = midiEvent.data[1]; | |||
| v3_param_id paramId = 0; | |||
| if (v3_cpp_obj(fV3.midiMapping)->get_midi_controller_assignment(fV3.midiMapping, | |||
| midiEvent.port, | |||
| event.channel, | |||
| 128, | |||
| ¶mId) == V3_OK) | |||
| { | |||
| uint32_t index = UINT32_MAX; | |||
| for (uint32_t i=0; i < pData->param.count; ++i) | |||
| { | |||
| if (static_cast<v3_param_id>(pData->param.data[i].rindex) == paramId) | |||
| { | |||
| index = i; | |||
| break; | |||
| } | |||
| } | |||
| if (index == UINT32_MAX) | |||
| break; | |||
| fEvents.paramInputs->setParamValueRT(index, | |||
| event.time, | |||
| static_cast<float>(pressure) / 127.f); | |||
| } | |||
| } | |||
| break; | |||
| case MIDI_STATUS_PITCH_WHEEL_CONTROL: | |||
| { | |||
| const uint16_t pitchbend = (midiEvent.data[2] << 7) | midiEvent.data[1]; | |||
| v3_param_id paramId = 0; | |||
| if (v3_cpp_obj(fV3.midiMapping)->get_midi_controller_assignment(fV3.midiMapping, | |||
| midiEvent.port, | |||
| event.channel, | |||
| 129, | |||
| ¶mId) == V3_OK) | |||
| { | |||
| uint32_t index = UINT32_MAX; | |||
| for (uint32_t i=0; i < pData->param.count; ++i) | |||
| { | |||
| if (static_cast<v3_param_id>(pData->param.data[i].rindex) == paramId) | |||
| { | |||
| index = i; | |||
| break; | |||
| } | |||
| } | |||
| if (index == UINT32_MAX) | |||
| break; | |||
| fEvents.paramInputs->setParamValueRT(index, | |||
| event.time, | |||
| static_cast<float>(pitchbend) / 16384.f); | |||
| } | |||
| } | |||
| break; | |||
| } // switch (status) | |||
| } break; | |||
| } // switch (event.type) | |||
| } | |||
| @@ -3175,6 +3319,15 @@ public: | |||
| // ---------------------------------------------------------------------------------------------------------------- | |||
| bool hasMidiInput() const noexcept | |||
| { | |||
| return pData->extraHints & PLUGIN_EXTRA_HINT_HAS_MIDI_IN || | |||
| std::strstr(fV3ClassInfo.v2.sub_categories, "Instrument") != nullptr || | |||
| v3_cpp_obj(fV3.component)->get_bus_count(fV3.component, V3_EVENT, V3_INPUT) > 0; | |||
| } | |||
| // ---------------------------------------------------------------------------------------------------------------- | |||
| const void* getNativeDescriptor() const noexcept override | |||
| { | |||
| return fV3.component; | |||
| @@ -3381,7 +3534,6 @@ public: | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS)) | |||
| pData->options |= PLUGIN_OPTION_USE_CHUNKS; | |||
| /* | |||
| if (hasMidiInput()) | |||
| { | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES)) | |||
| @@ -3392,18 +3544,16 @@ public: | |||
| pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND)) | |||
| pData->options |= PLUGIN_OPTION_SEND_PITCHBEND; | |||
| /* TODO | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF)) | |||
| pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PROGRAM_CHANGES)) | |||
| pData->options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; | |||
| */ | |||
| if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES)) | |||
| pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; | |||
| } | |||
| */ | |||
| /* | |||
| if (numPrograms > 1 && (pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) == 0) | |||
| if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES)) | |||
| if (numPrograms > 1 && isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES)) | |||
| pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES; | |||
| */ | |||
| @@ -3617,6 +3767,7 @@ private: | |||
| v3_audio_processor** processor; | |||
| v3_connection_point** connComponent; | |||
| v3_connection_point** connController; | |||
| v3_midi_mapping** midiMapping; | |||
| #ifdef V3_VIEW_PLATFORM_TYPE_NATIVE | |||
| v3_plugin_view** view; | |||
| #endif | |||
| @@ -3633,6 +3784,7 @@ private: | |||
| processor(nullptr), | |||
| connComponent(nullptr), | |||
| connController(nullptr), | |||
| midiMapping(nullptr), | |||
| #ifdef V3_VIEW_PLATFORM_TYPE_NATIVE | |||
| view(nullptr), | |||
| #endif | |||
| @@ -3772,6 +3924,14 @@ private: | |||
| v3_cpp_obj(connController)->connect(connController, connComponent); | |||
| } | |||
| // get midi mapping interface | |||
| if (v3_cpp_obj_query_interface(component, v3_midi_mapping_iid, &midiMapping) != V3_OK) | |||
| { | |||
| midiMapping = nullptr; | |||
| if (v3_cpp_obj_query_interface(controller, v3_midi_mapping_iid, &midiMapping) != V3_OK) | |||
| midiMapping = nullptr; | |||
| } | |||
| #ifdef V3_VIEW_PLATFORM_TYPE_NATIVE | |||
| // create view | |||
| view = v3_cpp_obj(controller)->create_view(controller, "editor"); | |||
| @@ -3787,6 +3947,12 @@ private: | |||
| CARLA_SAFE_ASSERT(view == nullptr); | |||
| #endif | |||
| if (midiMapping != nullptr) | |||
| { | |||
| v3_cpp_obj_unref(midiMapping); | |||
| midiMapping = nullptr; | |||
| } | |||
| if (connComponent != nullptr && connController != nullptr) | |||
| { | |||
| v3_cpp_obj(connComponent)->disconnect(connComponent, connController); | |||