listen for midi binding control range.
This commit is contained in:
+20
-12
@@ -294,12 +294,14 @@ class SystemMidiBinding
|
|||||||
private:
|
private:
|
||||||
MidiBinding currentBinding;
|
MidiBinding currentBinding;
|
||||||
bool controlState = false;
|
bool controlState = false;
|
||||||
|
uint8_t lastControlValue = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void SetBinding(const MidiBinding &binding)
|
void SetBinding(const MidiBinding &binding)
|
||||||
{
|
{
|
||||||
currentBinding = binding;
|
currentBinding = binding;
|
||||||
controlState = false;
|
controlState = false;
|
||||||
|
lastControlValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsMatch(const MidiEvent &event);
|
bool IsMatch(const MidiEvent &event);
|
||||||
@@ -335,13 +337,18 @@ bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
|
|||||||
return false;
|
return false;
|
||||||
if (event.buffer[1] != currentBinding.control())
|
if (event.buffer[1] != currentBinding.control())
|
||||||
return false;
|
return false;
|
||||||
bool state = event.buffer[2] >= 0x64;
|
|
||||||
if (state != this->controlState)
|
uint8_t value = event.buffer[2];
|
||||||
|
bool result = false;
|
||||||
|
if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_RISING_EDGE)
|
||||||
{
|
{
|
||||||
this->controlState = state;
|
result = value >= 0x64 && lastControlValue < 0x64;
|
||||||
return state;
|
} else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
}
|
}
|
||||||
return false;
|
lastControlValue = value;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -918,11 +925,12 @@ private:
|
|||||||
if (event.size >= 3)
|
if (event.size >= 3)
|
||||||
{
|
{
|
||||||
uint8_t cmd = (uint8_t)(event.buffer[0] & 0xF0);
|
uint8_t cmd = (uint8_t)(event.buffer[0] & 0xF0);
|
||||||
bool isNote = cmd == 0x90;
|
bool isNote = cmd == 0x90 && event.buffer[2] != 0; // note on with velocity > 0.
|
||||||
bool isControl = cmd == 0xB0;
|
bool isControl = cmd == 0xB0;
|
||||||
if (isNote || isControl)
|
if (isNote || isControl)
|
||||||
{
|
{
|
||||||
realtimeWriter.OnMidiListen(isNote, event.buffer[1]);
|
MidiNotifyBody notifyBody (event.buffer[0],event.buffer[1], event.buffer[2]);
|
||||||
|
realtimeWriter.OnMidiListen(notifyBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1333,11 +1341,11 @@ public:
|
|||||||
{
|
{
|
||||||
if (command == RingBufferCommand::OnMidiListen)
|
if (command == RingBufferCommand::OnMidiListen)
|
||||||
{
|
{
|
||||||
uint16_t msg;
|
MidiNotifyBody body;
|
||||||
hostReader.read(&msg);
|
hostReader.read(&body);
|
||||||
if (this->pNotifyCallbacks)
|
if (this->pNotifyCallbacks)
|
||||||
{
|
{
|
||||||
pNotifyCallbacks->OnNotifyMidiListen((msg & 0xFF00) != 0, (uint8_t)msg);
|
pNotifyCallbacks->OnNotifyMidiListen(body.cc0_, body.cc1_, body.cc2_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (command == RingBufferCommand::MidiValueChanged)
|
else if (command == RingBufferCommand::MidiValueChanged)
|
||||||
@@ -2080,8 +2088,8 @@ public:
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
volatile bool listenForMidiEvent = false;
|
std::atomic<bool> listenForMidiEvent = false;
|
||||||
volatile bool listenForAtomOutput = false;
|
std::atomic<bool> listenForAtomOutput = false;
|
||||||
|
|
||||||
virtual void SetListenForMidiEvent(bool listen)
|
virtual void SetListenForMidiEvent(bool listen)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -158,7 +158,7 @@ namespace pipedal
|
|||||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
|
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
|
||||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
||||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
virtual void OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2) = 0;
|
||||||
|
|
||||||
virtual void OnNotifyPathPatchPropertyReceived(
|
virtual void OnNotifyPathPatchPropertyReceived(
|
||||||
int64_t instanceId,
|
int64_t instanceId,
|
||||||
|
|||||||
@@ -824,7 +824,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
|||||||
case MidiControlType::Dial:
|
case MidiControlType::Dial:
|
||||||
{
|
{
|
||||||
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
|
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
|
||||||
range = mapping.midiBinding.adjustRange(range);
|
float range = mapping.midiBinding.calculateRange(value);
|
||||||
float currentValue = mapping.pPortInfo->rangeToValue(range);
|
float currentValue = mapping.pPortInfo->rangeToValue(range);
|
||||||
if (pEffect->GetControlValue(mapping.controlIndex) != currentValue)
|
if (pEffect->GetControlValue(mapping.controlIndex) != currentValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ JSON_MAP_BEGIN(MidiBinding)
|
|||||||
JSON_MAP_REFERENCE(MidiBinding,bindingType)
|
JSON_MAP_REFERENCE(MidiBinding,bindingType)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,note)
|
JSON_MAP_REFERENCE(MidiBinding,note)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,control)
|
JSON_MAP_REFERENCE(MidiBinding,control)
|
||||||
|
JSON_MAP_REFERENCE(MidiBinding,minControlValue)
|
||||||
|
JSON_MAP_REFERENCE(MidiBinding,maxControlValue)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,minValue)
|
JSON_MAP_REFERENCE(MidiBinding,minValue)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,maxValue)
|
JSON_MAP_REFERENCE(MidiBinding,maxValue)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,rotaryScale)
|
JSON_MAP_REFERENCE(MidiBinding,rotaryScale)
|
||||||
|
|||||||
+19
-2
@@ -80,6 +80,8 @@ private:
|
|||||||
int bindingType_ = BINDING_TYPE_NONE;
|
int bindingType_ = BINDING_TYPE_NONE;
|
||||||
int note_ = 12*4+24;
|
int note_ = 12*4+24;
|
||||||
int control_ = 1;
|
int control_ = 1;
|
||||||
|
int minControlValue_ = 0;
|
||||||
|
int maxControlValue_ = 127;
|
||||||
float minValue_ = 0;
|
float minValue_ = 0;
|
||||||
float maxValue_ = 1;
|
float maxValue_ = 1;
|
||||||
float rotaryScale_ = 1;
|
float rotaryScale_ = 1;
|
||||||
@@ -99,6 +101,8 @@ public:
|
|||||||
&& this->bindingType_ == other.bindingType_
|
&& this->bindingType_ == other.bindingType_
|
||||||
&& this->note_ == other.note_
|
&& this->note_ == other.note_
|
||||||
&& this->control_ == other.control_
|
&& this->control_ == other.control_
|
||||||
|
&& this->minControlValue_ == other.minControlValue_
|
||||||
|
&& this->maxControlValue_ == other.maxControlValue_
|
||||||
&& this->minValue_ == other.minValue_
|
&& this->minValue_ == other.minValue_
|
||||||
&& this->maxValue_ == other.maxValue_
|
&& this->maxValue_ == other.maxValue_
|
||||||
&& this->rotaryScale_ == other.rotaryScale_
|
&& this->rotaryScale_ == other.rotaryScale_
|
||||||
@@ -111,6 +115,8 @@ public:
|
|||||||
GETTER_SETTER(bindingType);
|
GETTER_SETTER(bindingType);
|
||||||
GETTER_SETTER(note);
|
GETTER_SETTER(note);
|
||||||
GETTER_SETTER(control);
|
GETTER_SETTER(control);
|
||||||
|
GETTER_SETTER(minControlValue);
|
||||||
|
GETTER_SETTER(maxControlValue);
|
||||||
GETTER_SETTER(minValue);
|
GETTER_SETTER(minValue);
|
||||||
GETTER_SETTER(maxValue);
|
GETTER_SETTER(maxValue);
|
||||||
GETTER_SETTER(rotaryScale);
|
GETTER_SETTER(rotaryScale);
|
||||||
@@ -120,9 +126,20 @@ public:
|
|||||||
void switchControlType(SwitchControlTypeT value) { switchControlType_ = (int)value; }
|
void switchControlType(SwitchControlTypeT value) { switchControlType_ = (int)value; }
|
||||||
|
|
||||||
|
|
||||||
float adjustRange(float range)
|
float calculateRange(uint8_t value)
|
||||||
{
|
{
|
||||||
return maxValue_*range+minValue_*(1.0f-range);
|
float controlRange;
|
||||||
|
if (maxControlValue_ == minControlValue_)
|
||||||
|
{
|
||||||
|
controlRange = minControlValue_;
|
||||||
|
} else {
|
||||||
|
controlRange =
|
||||||
|
(float)((int8_t)value - (int8_t)minControlValue_) /
|
||||||
|
(float)((int8_t)maxControlValue_ - (int8_t)minControlValue_);
|
||||||
|
}
|
||||||
|
if (controlRange < 0) controlRange = 0;
|
||||||
|
else if (controlRange > 1) controlRange = 1;
|
||||||
|
return maxValue_*controlRange+minValue_*(1.0f-controlRange);
|
||||||
}
|
}
|
||||||
DECLARE_JSON_MAP(MidiBinding);
|
DECLARE_JSON_MAP(MidiBinding);
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,15 @@ bool Pedalboard::SetControlValue(int64_t pedalItemId, const std::string &symbol,
|
|||||||
return item->SetControlValue(symbol,value);
|
return item->SetControlValue(symbol,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Pedalboard::SetItemTitle(int64_t pedalItemId, const std::string &title)
|
||||||
|
{
|
||||||
|
PedalboardItem*item = GetItem(pedalItemId);
|
||||||
|
if (!item) return false;
|
||||||
|
if (item->title() == title) return false; // no change.
|
||||||
|
item->title(title);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PedalboardItem Pedalboard::MakeEmptyItem()
|
PedalboardItem Pedalboard::MakeEmptyItem()
|
||||||
{
|
{
|
||||||
@@ -503,6 +512,7 @@ JSON_MAP_BEGIN(PedalboardItem)
|
|||||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||||
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
||||||
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||||
|
JSON_MAP_REFERENCE(PedalboardItem,title)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ public:
|
|||||||
Lv2PluginState lv2State_;
|
Lv2PluginState lv2State_;
|
||||||
std::string lilvPresetUri_;
|
std::string lilvPresetUri_;
|
||||||
std::map<std::string,std::string> pathProperties_;
|
std::map<std::string,std::string> pathProperties_;
|
||||||
|
std::string title_;
|
||||||
|
|
||||||
// non persistent state.
|
// non persistent state.
|
||||||
PropertyMap patchProperties;
|
PropertyMap patchProperties;
|
||||||
@@ -126,6 +127,8 @@ public:
|
|||||||
GETTER_SETTER_REF(midiChannelBinding)
|
GETTER_SETTER_REF(midiChannelBinding)
|
||||||
GETTER_SETTER(stateUpdateCount)
|
GETTER_SETTER(stateUpdateCount)
|
||||||
GETTER_SETTER_REF(lv2State)
|
GETTER_SETTER_REF(lv2State)
|
||||||
|
GETTER_SETTER_REF(title)
|
||||||
|
|
||||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||||
GETTER_SETTER_REF(lilvPresetUri)
|
GETTER_SETTER_REF(lilvPresetUri)
|
||||||
|
|
||||||
@@ -198,6 +201,7 @@ public:
|
|||||||
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
||||||
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||||
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
||||||
|
bool SetItemTitle(int64_t pedalItemId, const std::string &title);
|
||||||
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
||||||
void SetCurrentSnapshotModified(bool modified);
|
void SetCurrentSnapshotModified(bool modified);
|
||||||
|
|
||||||
|
|||||||
+34
-15
@@ -2192,34 +2192,40 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
void PiPedalModel::OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
bool isNote = (cc0 & 0xF0) == 0x90; // Note On
|
||||||
|
if (isNote && cc2 == 0) {
|
||||||
|
return; // Note off. Oopsie.
|
||||||
|
}
|
||||||
|
bool isControl = (cc0 & 0xF0) == 0xB0; // Control Change
|
||||||
|
if (!isNote && !isControl) {
|
||||||
|
return; // Not a note on or control change.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < midiEventListeners.size(); ++i)
|
for (int i = 0; i < midiEventListeners.size(); ++i)
|
||||||
{
|
{
|
||||||
auto &listener = midiEventListeners[i];
|
auto &listener = midiEventListeners[i];
|
||||||
if ((!isNote) == (listener.listenForControls))
|
auto subscriber = this->GetNotificationSubscriber(listener.clientId);
|
||||||
|
if (subscriber)
|
||||||
{
|
{
|
||||||
auto subscriber = this->GetNotificationSubscriber(listener.clientId);
|
subscriber->OnNotifyMidiListener(listener.clientHandle, cc0,cc1,cc2);
|
||||||
if (subscriber)
|
}
|
||||||
{
|
else
|
||||||
subscriber->OnNotifyMidiListener(listener.clientHandle, isNote, noteOrControl);
|
{
|
||||||
}
|
midiEventListeners.erase(midiEventListeners.begin() + i);
|
||||||
else
|
--i;
|
||||||
{
|
|
||||||
midiEventListeners.erase(midiEventListeners.begin() + i);
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControls)
|
void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
MidiListener listener{clientId, clientHandle, listenForControls};
|
MidiListener listener{clientId, clientHandle};
|
||||||
midiEventListeners.push_back(listener);
|
midiEventListeners.push_back(listener);
|
||||||
audioHost->SetListenForMidiEvent(true);
|
audioHost->SetListenForMidiEvent(true);
|
||||||
}
|
}
|
||||||
@@ -2946,4 +2952,17 @@ void PiPedalModel::MoveAudioFile(
|
|||||||
}
|
}
|
||||||
AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory);
|
AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory);
|
||||||
dir->MoveAudioFile(directory, fromPosition, toPosition);
|
dir->MoveAudioFile(directory, fromPosition, toPosition);
|
||||||
}
|
}
|
||||||
|
void PiPedalModel::SetPedalboardItemTitle(int64_t instanceId, const std::string &title)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
if (!this->pedalboard.SetItemTitle(instanceId,title))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// no need to reload the pedalboard, but we do need to notify subscribers.
|
||||||
|
this->SetPresetChanged(-1, true);
|
||||||
|
this->FirePedalboardChanged(-1,false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace pipedal
|
|||||||
virtual void OnJackConfigurationChanged(const JackConfiguration &jackServerConfiguration) = 0;
|
virtual void OnJackConfigurationChanged(const JackConfiguration &jackServerConfiguration) = 0;
|
||||||
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
|
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
|
||||||
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
|
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
|
||||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
|
virtual void OnNotifyMidiListener(int64_t clientHandle, uint8_t cc0, uint8_t cc1, uint8_t cc2) = 0;
|
||||||
virtual void OnNotifyPatchProperty(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0;
|
virtual void OnNotifyPatchProperty(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0;
|
||||||
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
|
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
|
||||||
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
|
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
|
||||||
@@ -146,7 +146,6 @@ namespace pipedal
|
|||||||
public:
|
public:
|
||||||
int64_t clientId;
|
int64_t clientId;
|
||||||
int64_t clientHandle;
|
int64_t clientHandle;
|
||||||
bool listenForControls;
|
|
||||||
};
|
};
|
||||||
class AtomOutputListener
|
class AtomOutputListener
|
||||||
{
|
{
|
||||||
@@ -234,7 +233,7 @@ namespace pipedal
|
|||||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
|
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
|
||||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
||||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
||||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
|
virtual void OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2) override;
|
||||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
||||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
||||||
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
|
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
|
||||||
@@ -437,7 +436,7 @@ namespace pipedal
|
|||||||
JackServerSettings GetJackServerSettings();
|
JackServerSettings GetJackServerSettings();
|
||||||
void SetJackServerSettings(const JackServerSettings &jackServerSettings);
|
void SetJackServerSettings(const JackServerSettings &jackServerSettings);
|
||||||
|
|
||||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControls);
|
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||||
|
|
||||||
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri);
|
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri);
|
||||||
@@ -469,6 +468,8 @@ namespace pipedal
|
|||||||
const std::string & path,
|
const std::string & path,
|
||||||
int32_t from,
|
int32_t from,
|
||||||
int32_t to);
|
int32_t to);
|
||||||
|
|
||||||
|
void SetPedalboardItemTitle(int64_t instanceId, const std::string &title);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pipedal.
|
} // namespace pipedal.
|
||||||
+33
-11
@@ -174,18 +174,36 @@ JSON_MAP_REFERENCE(SetPatchPropertyBody, propertyUri)
|
|||||||
JSON_MAP_REFERENCE(SetPatchPropertyBody, value)
|
JSON_MAP_REFERENCE(SetPatchPropertyBody, value)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
class SetPedalboardItemTitleBody
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint64_t instanceId_;
|
||||||
|
std::string title_;
|
||||||
|
DECLARE_JSON_MAP(SetPedalboardItemTitleBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(SetPedalboardItemTitleBody)
|
||||||
|
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, instanceId)
|
||||||
|
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, title)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyMidiListenerBody
|
class NotifyMidiListenerBody
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
int64_t clientHandle_;
|
int64_t clientHandle_;
|
||||||
bool isNote_;
|
uint16_t cc0_;
|
||||||
int32_t noteOrControl_;
|
uint16_t cc1_;
|
||||||
|
uint16_t cc2_;
|
||||||
DECLARE_JSON_MAP(NotifyMidiListenerBody);
|
DECLARE_JSON_MAP(NotifyMidiListenerBody);
|
||||||
};
|
};
|
||||||
JSON_MAP_BEGIN(NotifyMidiListenerBody)
|
JSON_MAP_BEGIN(NotifyMidiListenerBody)
|
||||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, clientHandle)
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, clientHandle)
|
||||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, isNote)
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc0)
|
||||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, noteOrControl)
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc1)
|
||||||
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc2)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
class NotifyAtomOutputBody
|
class NotifyAtomOutputBody
|
||||||
@@ -208,13 +226,11 @@ JSON_MAP_END()
|
|||||||
class ListenForMidiEventBody
|
class ListenForMidiEventBody
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool listenForControls_;
|
|
||||||
int64_t handle_;
|
int64_t handle_;
|
||||||
DECLARE_JSON_MAP(ListenForMidiEventBody);
|
DECLARE_JSON_MAP(ListenForMidiEventBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
JSON_MAP_BEGIN(ListenForMidiEventBody)
|
JSON_MAP_BEGIN(ListenForMidiEventBody)
|
||||||
JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControls)
|
|
||||||
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
|
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
@@ -1099,7 +1115,7 @@ public:
|
|||||||
{
|
{
|
||||||
ListenForMidiEventBody body;
|
ListenForMidiEventBody body;
|
||||||
pReader->read(&body);
|
pReader->read(&body);
|
||||||
this->model.ListenForMidiEvent(this->clientId, body.handle_, body.listenForControls_);
|
this->model.ListenForMidiEvent(this->clientId, body.handle_);
|
||||||
}
|
}
|
||||||
else if (message == "cancelListenForMidiEvent")
|
else if (message == "cancelListenForMidiEvent")
|
||||||
{
|
{
|
||||||
@@ -1503,7 +1519,12 @@ public:
|
|||||||
{ this->JsonReply(replyTo, "setPatchProperty", "true"); }, [this, replyTo](const std::string &error)
|
{ this->JsonReply(replyTo, "setPatchProperty", "true"); }, [this, replyTo](const std::string &error)
|
||||||
{ this->SendError(replyTo, error.c_str()); });
|
{ this->SendError(replyTo, error.c_str()); });
|
||||||
}
|
}
|
||||||
|
else if (message == "setPedalboardItemTitle")
|
||||||
|
{
|
||||||
|
SetPedalboardItemTitleBody body;
|
||||||
|
pReader->read(&body);
|
||||||
|
model.SetPedalboardItemTitle(body.instanceId_, body.title_);
|
||||||
|
}
|
||||||
else if (message == "getPatchProperty")
|
else if (message == "getPatchProperty")
|
||||||
{
|
{
|
||||||
GetPatchPropertyBody body;
|
GetPatchPropertyBody body;
|
||||||
@@ -2121,12 +2142,13 @@ private:
|
|||||||
Send("onNotifyPathPatchPropertyChanged", body);
|
Send("onNotifyPathPatchPropertyChanged", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl)
|
virtual void OnNotifyMidiListener(int64_t clientHandle, uint8_t cc0, uint8_t cc1, uint8_t cc2) override
|
||||||
{
|
{
|
||||||
NotifyMidiListenerBody body;
|
NotifyMidiListenerBody body;
|
||||||
body.clientHandle_ = clientHandle;
|
body.clientHandle_ = clientHandle;
|
||||||
body.isNote_ = isNote;
|
body.cc0_ = cc0;
|
||||||
body.noteOrControl_ = noteOrControl;
|
body.cc1_ = cc1;
|
||||||
|
body.cc2_ = cc2;
|
||||||
|
|
||||||
Send("onNotifyMidiListener", body);
|
Send("onNotifyMidiListener", body);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
class IndexedSnapshot;
|
class IndexedSnapshot;
|
||||||
|
|
||||||
|
class MidiNotifyBody
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MidiNotifyBody() = default;
|
||||||
|
MidiNotifyBody(uint8_t cc0, uint8_t cc1, uint8_t cc2)
|
||||||
|
: cc0_(cc0), cc1_(cc1), cc2_(cc2)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cc0_;
|
||||||
|
uint8_t cc1_;
|
||||||
|
uint8_t cc2_;
|
||||||
|
};
|
||||||
|
|
||||||
enum class RingBufferCommand : int64_t
|
enum class RingBufferCommand : int64_t
|
||||||
{
|
{
|
||||||
Invalid = 0,
|
Invalid = 0,
|
||||||
@@ -38,7 +52,7 @@ namespace pipedal
|
|||||||
EffectReplaced,
|
EffectReplaced,
|
||||||
SetValue,
|
SetValue,
|
||||||
SetBypass,
|
SetBypass,
|
||||||
//AudioStopped,
|
// AudioStopped,
|
||||||
AudioTerminatedAbnormally, // specifically for an ALSA loss of connection.
|
AudioTerminatedAbnormally, // specifically for an ALSA loss of connection.
|
||||||
SetVuSubscriptions,
|
SetVuSubscriptions,
|
||||||
FreeVuSubscriptions,
|
FreeVuSubscriptions,
|
||||||
@@ -327,7 +341,7 @@ namespace pipedal
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t *variableData)
|
void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t *variableData)
|
||||||
{
|
{
|
||||||
@@ -374,12 +388,9 @@ namespace pipedal
|
|||||||
write(RingBufferCommand::MidiValueChanged, body);
|
write(RingBufferCommand::MidiValueChanged, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnMidiListen(bool isNote, uint8_t noteOrControl)
|
void OnMidiListen(const MidiNotifyBody &body)
|
||||||
{
|
{
|
||||||
uint16_t msg = noteOrControl;
|
write(RingBufferCommand::OnMidiListen, body);
|
||||||
if (isNote)
|
|
||||||
msg |= 0x100;
|
|
||||||
write(RingBufferCommand::OnMidiListen, msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,7 +496,8 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
write(RingBufferCommand::AckMidiProgramChange, requestId);
|
write(RingBufferCommand::AckMidiProgramChange, requestId);
|
||||||
}
|
}
|
||||||
void AckMidiSnapshotRequest(uint64_t snapshotRequestId) {
|
void AckMidiSnapshotRequest(uint64_t snapshotRequestId)
|
||||||
|
{
|
||||||
write(RingBufferCommand::AckMidiSnapshotRequest, snapshotRequestId);
|
write(RingBufferCommand::AckMidiSnapshotRequest, snapshotRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
window.addEventListener .... done wrong everywhere!
|
|
||||||
|
|
||||||
touch scroll in file propertty dialog.
|
|
||||||
|
|
||||||
channel bindings help dialog. <ChannelBindingHelpDialog
|
|
||||||
|
|
||||||
|
|
||||||
draggable UI: scrolloffset!?
|
|
||||||
|
|
||||||
- pipewire aux in?
|
- pipewire aux in?
|
||||||
|
|
||||||
|
|
||||||
Check that OnNotifyPatchProperty still worsk for
|
|
||||||
- FilePropertyControl
|
|
||||||
- PowerStage2.
|
|
||||||
- ToobSpectrumAnaylyzer.
|
|
||||||
|
|
||||||
pcm.pipedal_aux_in {
|
pcm.pipedal_aux_in {
|
||||||
type file
|
type file
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export default class MidiBinding {
|
|||||||
this.bindingType = input.bindingType;
|
this.bindingType = input.bindingType;
|
||||||
this.note = input.note;
|
this.note = input.note;
|
||||||
this.control = input.control;
|
this.control = input.control;
|
||||||
|
this.minControlValue = input.minControlValue?? 0;
|
||||||
|
this.maxControlValue = input.maxControlValue?? 127;
|
||||||
|
this.rotaryScale = input.rotaryScale?? 1;
|
||||||
this.minValue = input.minValue;
|
this.minValue = input.minValue;
|
||||||
this.maxValue = input.maxValue;
|
this.maxValue = input.maxValue;
|
||||||
this.linearControlType = input.linearControlType;
|
this.linearControlType = input.linearControlType;
|
||||||
@@ -53,6 +56,9 @@ export default class MidiBinding {
|
|||||||
&& (this.note === other.note)
|
&& (this.note === other.note)
|
||||||
&& (this.control === other.control)
|
&& (this.control === other.control)
|
||||||
&& (this.minValue === other.minValue)
|
&& (this.minValue === other.minValue)
|
||||||
|
&& (this.minControlValue === other.minControlValue)
|
||||||
|
&& (this.maxControlValue === other.maxControlValue)
|
||||||
|
&& (this.rotaryScale === other.rotaryScale)
|
||||||
&& (this.maxValue === other.maxValue)
|
&& (this.maxValue === other.maxValue)
|
||||||
&& (this.linearControlType === other.linearControlType)
|
&& (this.linearControlType === other.linearControlType)
|
||||||
&& (this.switchControlType === other.switchControlType)
|
&& (this.switchControlType === other.switchControlType)
|
||||||
@@ -73,6 +79,8 @@ export default class MidiBinding {
|
|||||||
bindingType: number = MidiBinding.BINDING_TYPE_NONE;
|
bindingType: number = MidiBinding.BINDING_TYPE_NONE;
|
||||||
note: number = 12*4+24; // C4.
|
note: number = 12*4+24; // C4.
|
||||||
control: number = 1;
|
control: number = 1;
|
||||||
|
minControlValue: number = 0;
|
||||||
|
maxControlValue: number = 127;
|
||||||
minValue: number = 0;
|
minValue: number = 0;
|
||||||
maxValue: number = 1;
|
maxValue: number = 1;
|
||||||
rotaryScale: number = 1;
|
rotaryScale: number = 1;
|
||||||
|
|||||||
@@ -18,11 +18,12 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
import { ListenHandle, MidiMessage, PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
import { Theme } from '@mui/material/styles';
|
import { Theme } from '@mui/material/styles';
|
||||||
import WithStyles from './WithStyles';
|
import WithStyles from './WithStyles';
|
||||||
import { withStyles } from "tss-react/mui";
|
import { withStyles } from "tss-react/mui";
|
||||||
import {createStyles} from './WithStyles';
|
import { createStyles } from './WithStyles';
|
||||||
|
import Snackbar from '@mui/material/Snackbar';
|
||||||
|
|
||||||
import { UiPlugin } from './Lv2Plugin';
|
import { UiPlugin } from './Lv2Plugin';
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
enum MidiControlType {
|
export enum MidiControlType {
|
||||||
None,
|
None,
|
||||||
Select,
|
Select,
|
||||||
Dial,
|
Dial,
|
||||||
@@ -56,18 +57,57 @@ enum MidiControlType {
|
|||||||
MomentarySwitch
|
MomentarySwitch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: string): MidiControlType {
|
||||||
|
if (!uiPlugin) return MidiControlType.None;
|
||||||
|
let port = uiPlugin.getControl(symbol);
|
||||||
|
if (!port) return MidiControlType.None;
|
||||||
|
|
||||||
|
if (symbol === "__bypass") {
|
||||||
|
return MidiControlType.Toggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!port) return MidiControlType.None;
|
||||||
|
|
||||||
|
if (port.mod_momentaryOffByDefault || port.mod_momentaryOnByDefault) {
|
||||||
|
return MidiControlType.MomentarySwitch;
|
||||||
|
}
|
||||||
|
if (port.trigger_property) {
|
||||||
|
return MidiControlType.Trigger;
|
||||||
|
}
|
||||||
|
if (port.trigger_property) {
|
||||||
|
return MidiControlType.Trigger;
|
||||||
|
}
|
||||||
|
if (port.isAbToggle() || port.isOnOffSwitch()) {
|
||||||
|
return MidiControlType.Toggle;
|
||||||
|
}
|
||||||
|
if (port.isSelect()) {
|
||||||
|
return MidiControlType.Select;
|
||||||
|
}
|
||||||
|
return MidiControlType.Dial;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canBindToNote(controlType: MidiControlType): boolean {
|
||||||
|
return controlType === MidiControlType.Toggle
|
||||||
|
|| controlType === MidiControlType.Trigger
|
||||||
|
|| controlType === MidiControlType.MomentarySwitch
|
||||||
|
}
|
||||||
|
|
||||||
interface MidiBindingViewProps extends WithStyles<typeof styles> {
|
interface MidiBindingViewProps extends WithStyles<typeof styles> {
|
||||||
instanceId: number;
|
instanceId: number;
|
||||||
listen: boolean;
|
|
||||||
midiBinding: MidiBinding;
|
midiBinding: MidiBinding;
|
||||||
uiPlugin: UiPlugin;
|
midiControlType: MidiControlType;
|
||||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface MidiBindingViewState {
|
interface MidiBindingViewState {
|
||||||
midiControlType: MidiControlType;
|
listenSymbol: string;
|
||||||
|
listenForRangeSymbol: string;
|
||||||
|
listenForRangeMax: number;
|
||||||
|
listenForRangeMin: number;
|
||||||
|
listenSnackbarOpen: boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +123,11 @@ const MidiBindingView =
|
|||||||
super(props);
|
super(props);
|
||||||
this.model = PiPedalModelFactory.getInstance();
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
this.state = {
|
this.state = {
|
||||||
midiControlType: this.getControlType()
|
listenSymbol: "",
|
||||||
|
listenForRangeSymbol: "",
|
||||||
|
listenForRangeMax: 127,
|
||||||
|
listenForRangeMin: 0,
|
||||||
|
listenSnackbarOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +183,16 @@ const MidiBindingView =
|
|||||||
newBinding.maxValue = value;
|
newBinding.maxValue = value;
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
this.props.onChange(this.props.instanceId, newBinding);
|
||||||
}
|
}
|
||||||
|
handleCtlMinChange(value: number): void {
|
||||||
|
let newBinding = this.props.midiBinding.clone();
|
||||||
|
newBinding.minControlValue = Math.round(value);
|
||||||
|
this.props.onChange(this.props.instanceId, newBinding);
|
||||||
|
}
|
||||||
|
handleCtlMaxChange(value: number): void {
|
||||||
|
let newBinding = this.props.midiBinding.clone();
|
||||||
|
newBinding.maxControlValue = Math.round(value);
|
||||||
|
this.props.onChange(this.props.instanceId, newBinding);
|
||||||
|
}
|
||||||
handleScaleChange(value: number): void {
|
handleScaleChange(value: number): void {
|
||||||
let newBinding = this.props.midiBinding.clone();
|
let newBinding = this.props.midiBinding.clone();
|
||||||
newBinding.rotaryScale = value;
|
newBinding.rotaryScale = value;
|
||||||
@@ -158,10 +212,23 @@ const MidiBindingView =
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
super.componentDidMount?.();
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
|
this.cancelListenForControl();
|
||||||
|
super.componentWillUnmount?.();
|
||||||
|
}
|
||||||
validateSwitchControlType(midiBinding: MidiBinding) {
|
validateSwitchControlType(midiBinding: MidiBinding) {
|
||||||
// :-(
|
// :-(
|
||||||
|
|
||||||
let controlType = this.state.midiControlType;
|
let controlType = this.props.midiControlType;
|
||||||
if (controlType === MidiControlType.Toggle
|
if (controlType === MidiControlType.Toggle
|
||||||
&& midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) {
|
&& midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) {
|
||||||
if (midiBinding.switchControlType !== MidiBinding.TRIGGER_ON_RISING_EDGE // Toggle on Note On.
|
if (midiBinding.switchControlType !== MidiBinding.TRIGGER_ON_RISING_EDGE // Toggle on Note On.
|
||||||
@@ -181,41 +248,154 @@ const MidiBindingView =
|
|||||||
}
|
}
|
||||||
|
|
||||||
getControlType(): MidiControlType {
|
getControlType(): MidiControlType {
|
||||||
|
return this.props.midiControlType;
|
||||||
if (this.props.midiBinding.symbol === "__bypass") {
|
|
||||||
return MidiControlType.Toggle;
|
|
||||||
}
|
|
||||||
if (!this.props.uiPlugin) return MidiControlType.None;
|
|
||||||
|
|
||||||
let port = this.props.uiPlugin.getControl(this.props.midiBinding.symbol);
|
|
||||||
|
|
||||||
if (!port) return MidiControlType.None;
|
|
||||||
if (port.mod_momentaryOffByDefault || port.mod_momentaryOnByDefault) {
|
|
||||||
return MidiControlType.MomentarySwitch;
|
|
||||||
}
|
|
||||||
if (port.trigger_property) {
|
|
||||||
return MidiControlType.Trigger;
|
|
||||||
}
|
|
||||||
if (port.trigger_property) {
|
|
||||||
return MidiControlType.Trigger;
|
|
||||||
}
|
|
||||||
if (port.isAbToggle() || port.isOnOffSwitch()) {
|
|
||||||
return MidiControlType.Toggle;
|
|
||||||
}
|
|
||||||
if (port.isSelect()) {
|
|
||||||
return MidiControlType.Select;
|
|
||||||
}
|
|
||||||
return MidiControlType.Dial;
|
|
||||||
}
|
}
|
||||||
|
///////////////////////////////////////
|
||||||
|
listenTimeoutHandle?: number;
|
||||||
|
|
||||||
|
listenHandle?: ListenHandle;
|
||||||
|
|
||||||
|
cancelListenForControl() {
|
||||||
|
this.stopListenTimeout();
|
||||||
|
if (this.listenHandle) {
|
||||||
|
this.model.cancelListenForMidiEvent(this.listenHandle)
|
||||||
|
this.listenHandle = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ listenSymbol: "", listenForRangeSymbol: "" });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleListenForRangeSucceeded(instanceId: number, symbol: string, midiMessage: MidiMessage) {
|
||||||
|
if (!midiMessage.isControl()) return;
|
||||||
|
|
||||||
|
let binding = this.props.midiBinding;
|
||||||
|
if (binding.control != midiMessage.cc1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let value = midiMessage.cc2;
|
||||||
|
let min = this.state.listenForRangeMin;
|
||||||
|
let max = this.state.listenForRangeMax;
|
||||||
|
|
||||||
|
let update = false;
|
||||||
|
if (value < min) {
|
||||||
|
min = value;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (value > max) {
|
||||||
|
max = value;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (!update) {
|
||||||
|
return; // No change, so ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
let newBinding = binding.clone();
|
||||||
|
newBinding.minControlValue = min;
|
||||||
|
newBinding.maxControlValue = max;
|
||||||
|
|
||||||
|
this.props.onChange(this.props.instanceId, newBinding);
|
||||||
|
this.setState({ listenForRangeMin: min, listenForRangeMax: max });
|
||||||
|
this.startListenTimeout(20000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleListenSucceeded(instanceId: number, symbol: string, midiMessage: MidiMessage) {
|
||||||
|
if (instanceId !== this.props.instanceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (symbol !== this.props.midiBinding.symbol) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let binding = this.props.midiBinding;
|
||||||
|
let newBinding = binding.clone();
|
||||||
|
if (midiMessage.isNote()) {
|
||||||
|
if (!canBindToNote(this.props.midiControlType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE
|
||||||
|
newBinding.note = midiMessage.cc1;
|
||||||
|
} else if (midiMessage.isControl()) {
|
||||||
|
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL
|
||||||
|
newBinding.control = midiMessage.cc1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onChange(this.props.instanceId, newBinding);
|
||||||
|
this.cancelListenForControl();
|
||||||
|
}
|
||||||
|
handleListenForControlRange(instanceId: number, symbol: string): void {
|
||||||
|
if (this.state.listenForRangeSymbol !== "") {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cancelListenForControl();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
listenSymbol: "",
|
||||||
|
listenForRangeSymbol: symbol,
|
||||||
|
listenForRangeMin: 127,
|
||||||
|
listenForRangeMax: 0,
|
||||||
|
listenSnackbarOpen: true
|
||||||
|
});
|
||||||
|
this.startListenTimeout(20000);
|
||||||
|
|
||||||
|
this.listenHandle = this.model.listenForMidiEvent(
|
||||||
|
(midiMessage) => {
|
||||||
|
this.handleListenForRangeSucceeded(instanceId, symbol, midiMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopListenTimeout(): void {
|
||||||
|
if (this.listenTimeoutHandle) {
|
||||||
|
clearTimeout(this.listenTimeoutHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startListenTimeout(timeout: number): void {
|
||||||
|
this.stopListenTimeout();
|
||||||
|
this.listenTimeoutHandle = setTimeout(() => {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
this.listenTimeoutHandle = undefined;
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleListen(): void {
|
||||||
|
if (this.state.listenSymbol !== "") {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cancelListenForControl();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
listenSymbol: this.props.midiBinding.symbol,
|
||||||
|
listenForRangeSymbol: "",
|
||||||
|
listenSnackbarOpen: true
|
||||||
|
});
|
||||||
|
let instanceId = this.props.instanceId;
|
||||||
|
let symbol = this.props.midiBinding.symbol;
|
||||||
|
|
||||||
|
this.startListenTimeout(8000);
|
||||||
|
|
||||||
|
this.listenHandle = this.model.listenForMidiEvent(
|
||||||
|
(midiMessage) => {
|
||||||
|
this.handleListenSucceeded(instanceId, symbol, midiMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const classes = withStyles.getClasses(this.props);
|
const classes = withStyles.getClasses(this.props);
|
||||||
let midiBinding = this.props.midiBinding;
|
let midiBinding = this.props.midiBinding;
|
||||||
let uiPlugin = this.props.uiPlugin;
|
if (this.props.midiControlType === MidiControlType.None) {
|
||||||
if (!uiPlugin) {
|
|
||||||
return (<div />);
|
return (<div />);
|
||||||
}
|
}
|
||||||
let controlType = this.state.midiControlType;
|
let controlType = this.props.midiControlType;
|
||||||
|
|
||||||
let showLinearRange =
|
let showLinearRange =
|
||||||
controlType === MidiControlType.Dial &&
|
controlType === MidiControlType.Dial &&
|
||||||
@@ -238,9 +418,7 @@ const MidiBindingView =
|
|||||||
value={midiBinding.bindingType}
|
value={midiBinding.bindingType}
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>None</MenuItem>
|
<MenuItem value={0}>None</MenuItem>
|
||||||
{(controlType === MidiControlType.Toggle
|
{(canBindToNote(this.props.midiControlType)) && (
|
||||||
|| controlType === MidiControlType.Trigger
|
|
||||||
|| controlType === MidiControlType.MomentarySwitch) && (
|
|
||||||
<MenuItem value={1}>Note</MenuItem>
|
<MenuItem value={1}>Note</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem value={2}>Control</MenuItem>
|
<MenuItem value={2}>Control</MenuItem>
|
||||||
@@ -262,15 +440,15 @@ const MidiBindingView =
|
|||||||
</Select>
|
</Select>
|
||||||
<IconButtonEx
|
<IconButtonEx
|
||||||
tooltip="Listen for MIDI input"
|
tooltip="Listen for MIDI input"
|
||||||
|
onBlur={() => {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
}}
|
||||||
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (this.props.listen) {
|
this.handleListen()
|
||||||
this.props.onListen(-2, "", false)
|
|
||||||
} else {
|
|
||||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
size="large">
|
size="large">
|
||||||
{this.props.listen ? (
|
{this.state.listenSymbol !== "" ? (
|
||||||
<MicOutlinedIcon />
|
<MicOutlinedIcon />
|
||||||
) : (
|
) : (
|
||||||
<MicNoneOutlinedIcon />
|
<MicNoneOutlinedIcon />
|
||||||
@@ -296,15 +474,15 @@ const MidiBindingView =
|
|||||||
</Select>
|
</Select>
|
||||||
<IconButtonEx
|
<IconButtonEx
|
||||||
tooltip="Listen for MIDI input"
|
tooltip="Listen for MIDI input"
|
||||||
|
onBlur={() => {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
}}
|
||||||
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (this.props.listen) {
|
this.handleListen()
|
||||||
this.props.onListen(-2, "", false)
|
|
||||||
} else {
|
|
||||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, true)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
size="large">
|
size="large">
|
||||||
{this.props.listen ? (
|
{this.state.listenSymbol !== "" ? (
|
||||||
<MicOutlinedIcon />
|
<MicOutlinedIcon />
|
||||||
) : (
|
) : (
|
||||||
<MicNoneOutlinedIcon />
|
<MicNoneOutlinedIcon />
|
||||||
@@ -314,6 +492,27 @@ const MidiBindingView =
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{midiBinding.bindingType === MidiBinding.BINDING_TYPE_NONE &&
|
||||||
|
(
|
||||||
|
<IconButtonEx
|
||||||
|
tooltip="Listen for MIDI input"
|
||||||
|
onBlur={() => {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => {
|
||||||
|
this.handleListen()
|
||||||
|
}}
|
||||||
|
size="large">
|
||||||
|
{this.state.listenSymbol !== "" ? (
|
||||||
|
<MicOutlinedIcon />
|
||||||
|
) : (
|
||||||
|
<MicNoneOutlinedIcon />
|
||||||
|
)}
|
||||||
|
</IconButtonEx>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
((controlType === MidiControlType.Toggle && midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
((controlType === MidiControlType.Toggle && midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
||||||
(
|
(
|
||||||
@@ -378,25 +577,58 @@ const MidiBindingView =
|
|||||||
<MenuItem value={MidiBinding.CIRCULAR_CONTROL_TYPE}>Rotary</MenuItem>
|
<MenuItem value={MidiBinding.CIRCULAR_CONTROL_TYPE}>Rotary</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
)
|
{showLinearRange && (
|
||||||
}
|
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||||
{
|
|
||||||
showLinearRange && (
|
|
||||||
<div className={classes.controlDiv}>
|
<div className={classes.controlDiv}>
|
||||||
<Typography display="inline">Min: </Typography>
|
<Typography display="inline" noWrap>Min Ctl: </Typography>
|
||||||
<NumericInput defaultValue={midiBinding.minValue} ariaLabel='min'
|
<NumericInput integer value={midiBinding.minControlValue} ariaLabel='min ctl val'
|
||||||
min={0} max={1} onChange={(value) => { this.handleMinChange(value); }}
|
min={0} max={127} step={1} onChange={(value) => { this.handleCtlMinChange(value); }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
|
||||||
|
<div className={classes.controlDiv}>
|
||||||
|
<Typography display="inline" noWrap>Max Ctl: </Typography>
|
||||||
|
<NumericInput value={midiBinding.maxControlValue} ariaLabel='max ctl val'
|
||||||
|
integer={true}
|
||||||
|
min={0} max={127} step={1} onChange={(value) => { this.handleCtlMaxChange(value); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconButtonEx
|
||||||
|
tooltip="Listen for control range"
|
||||||
|
onBlur={() => {
|
||||||
|
this.cancelListenForControl();
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
this.handleListenForControlRange(this.props.instanceId, this.props.midiBinding.symbol);
|
||||||
|
}}
|
||||||
|
size="large">
|
||||||
|
{this.state.listenForRangeSymbol !== "" ? (
|
||||||
|
<MicOutlinedIcon />
|
||||||
|
) : (
|
||||||
|
<MicNoneOutlinedIcon />
|
||||||
|
)}
|
||||||
|
</IconButtonEx>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
showLinearRange && (
|
showLinearRange && (
|
||||||
<div className={classes.controlDiv}>
|
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||||
<Typography display="inline">Max: </Typography>
|
<div className={classes.controlDiv}>
|
||||||
<NumericInput defaultValue={midiBinding.maxValue} ariaLabel='max'
|
<Typography noWrap display="inline">Min Val: </Typography>
|
||||||
min={0} max={1} onChange={(value) => { this.handleMaxChange(value); }} />
|
<NumericInput value={midiBinding.minValue} ariaLabel='min'
|
||||||
|
min={0} max={1} onChange={(value) => { this.handleMinChange(value); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.controlDiv}>
|
||||||
|
<Typography noWrap display="inline">Max Val: </Typography>
|
||||||
|
<NumericInput value={midiBinding.maxValue} ariaLabel='max'
|
||||||
|
min={0} max={1} onChange={(value) => { this.handleMaxChange(value); }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -404,11 +636,24 @@ const MidiBindingView =
|
|||||||
canRotaryScale && (
|
canRotaryScale && (
|
||||||
<div className={classes.controlDiv}>
|
<div className={classes.controlDiv}>
|
||||||
<Typography display="inline">Scale: </Typography>
|
<Typography display="inline">Scale: </Typography>
|
||||||
<NumericInput defaultValue={midiBinding.maxValue} ariaLabel='scale'
|
<NumericInput value={midiBinding.maxValue} ariaLabel='scale'
|
||||||
min={-100} max={100} onChange={(value) => { this.handleScaleChange(value); }} />
|
min={-100} max={100} onChange={(value) => { this.handleScaleChange(value); }} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={this.state.listenSnackbarOpen}
|
||||||
|
autoHideDuration={1500}
|
||||||
|
onClose={() => this.setState({ listenSnackbarOpen: false })}
|
||||||
|
message="Listening for MIDI input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import React, { SyntheticEvent } from 'react';
|
import React, { SyntheticEvent } from 'react';
|
||||||
import DialogEx from './DialogEx';
|
import DialogEx from './DialogEx';
|
||||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { Theme } from '@mui/material/styles';
|
import { Theme } from '@mui/material/styles';
|
||||||
import WithStyles from './WithStyles';
|
import WithStyles from './WithStyles';
|
||||||
@@ -32,8 +32,7 @@ import Toolbar from '@mui/material/Toolbar';
|
|||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import IconButtonEx from './IconButtonEx';
|
import IconButtonEx from './IconButtonEx';
|
||||||
import MidiBinding from './MidiBinding';
|
import MidiBinding from './MidiBinding';
|
||||||
import MidiBindingView from './MidiBindingView';
|
import MidiBindingView, { getMidiControlType } from './MidiBindingView';
|
||||||
import Snackbar from '@mui/material/Snackbar';
|
|
||||||
import { PortGroup, UiPlugin, makeSplitUiPlugin } from './Lv2Plugin';
|
import { PortGroup, UiPlugin, makeSplitUiPlugin } from './Lv2Plugin';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
@@ -81,9 +80,6 @@ export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MidiBindingDialogState {
|
export interface MidiBindingDialogState {
|
||||||
listenInstanceId: number;
|
|
||||||
listenSymbol: string;
|
|
||||||
listenSnackbarOpen: boolean;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +94,10 @@ export const MidiBindingDialog =
|
|||||||
this.state = {
|
this.state = {
|
||||||
listenInstanceId: -2,
|
listenInstanceId: -2,
|
||||||
listenSymbol: "",
|
listenSymbol: "",
|
||||||
listenSnackbarOpen: false
|
listenForRangeSymbol: "",
|
||||||
|
listenSnackbarOpen: false,
|
||||||
|
listenForRangeMin: 127,
|
||||||
|
listenForRangeMax: 0
|
||||||
};
|
};
|
||||||
this.model = PiPedalModelFactory.getInstance();
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
this.handleClose = this.handleClose.bind(this);
|
this.handleClose = this.handleClose.bind(this);
|
||||||
@@ -108,64 +107,9 @@ export const MidiBindingDialog =
|
|||||||
hasHooks: boolean = false;
|
hasHooks: boolean = false;
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.cancelListenForControl();
|
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
listenTimeoutHandle?: number;
|
|
||||||
|
|
||||||
listenHandle?: ListenHandle;
|
|
||||||
|
|
||||||
cancelListenForControl() {
|
|
||||||
if (this.listenTimeoutHandle) {
|
|
||||||
clearTimeout(this.listenTimeoutHandle);
|
|
||||||
this.listenTimeoutHandle = undefined;
|
|
||||||
}
|
|
||||||
if (this.listenHandle) {
|
|
||||||
this.model.cancelListenForMidiEvent(this.listenHandle)
|
|
||||||
this.listenHandle = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ listenInstanceId: -2, listenSymbol: "" });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number) {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
|
|
||||||
let pedalboard = this.model.pedalboard.get();
|
|
||||||
let item = pedalboard.getItem(instanceId);
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
let binding = item.getMidiBinding(symbol);
|
|
||||||
let newBinding = binding.clone();
|
|
||||||
if (isNote) {
|
|
||||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE;
|
|
||||||
newBinding.note = noteOrControl;
|
|
||||||
} else {
|
|
||||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL;
|
|
||||||
newBinding.control = noteOrControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model.setMidiBinding(instanceId, newBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleListenForControl(instanceId: number, symbol: string, listenForControl: boolean): void {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
this.setState({ listenInstanceId: instanceId, listenSymbol: symbol, listenSnackbarOpen: true });
|
|
||||||
this.listenTimeoutHandle = setTimeout(() => {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
}, 8000);
|
|
||||||
|
|
||||||
this.listenHandle = this.model.listenForMidiEvent(listenForControl,
|
|
||||||
(isNote: boolean, noteOrControl: number) => {
|
|
||||||
this.handleListenSucceeded(instanceId, symbol, isNote, noteOrControl);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindowSizeChanged(width: number, height: number): void {
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
}
|
}
|
||||||
@@ -176,10 +120,11 @@ export const MidiBindingDialog =
|
|||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
super.componentWillUnmount();
|
|
||||||
|
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
|
||||||
|
super.componentWillUnmount();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -255,15 +200,7 @@ export const MidiBindingDialog =
|
|||||||
</td>
|
</td>
|
||||||
<td className={classes.bindingTd}>
|
<td className={classes.bindingTd}>
|
||||||
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
|
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
|
||||||
uiPlugin={plugin}
|
midiControlType={ getMidiControlType(plugin, "__bypass") }
|
||||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
|
||||||
if (instanceId === -2) {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
} else {
|
|
||||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"}
|
|
||||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -305,13 +242,8 @@ export const MidiBindingDialog =
|
|||||||
</td>
|
</td>
|
||||||
<td className={classes.bindingTd}>
|
<td className={classes.bindingTd}>
|
||||||
<MidiBindingView instanceId={item.instanceId}
|
<MidiBindingView instanceId={item.instanceId}
|
||||||
uiPlugin={plugin}
|
midiControlType={getMidiControlType(plugin, symbol)}
|
||||||
midiBinding={item.getMidiBinding(symbol)}
|
midiBinding={item.getMidiBinding(symbol)}
|
||||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol}
|
|
||||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
|
||||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
|
||||||
}}
|
|
||||||
|
|
||||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -379,16 +311,6 @@ export const MidiBindingDialog =
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Snackbar
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'left',
|
|
||||||
}}
|
|
||||||
open={this.state.listenSnackbarOpen}
|
|
||||||
autoHideDuration={1500}
|
|
||||||
onClose={() => this.setState({ listenSnackbarOpen: false })}
|
|
||||||
message="Listening for MIDI input"
|
|
||||||
/>
|
|
||||||
</DialogEx >
|
</DialogEx >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,18 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
|
|
||||||
interface NumericInputProps extends WithStyles<typeof styles> {
|
interface NumericInputProps extends WithStyles<typeof styles> {
|
||||||
ariaLabel: string;
|
ariaLabel: string;
|
||||||
defaultValue: number;
|
value: number;
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
|
step?: number;
|
||||||
|
integer?: boolean;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NumericInputState {
|
interface NumericInputState {
|
||||||
|
stateValue: string;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
|
focused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumericInput =
|
export const NumericInput =
|
||||||
@@ -54,22 +58,28 @@ export const NumericInput =
|
|||||||
constructor(props: NumericInputProps) {
|
constructor(props: NumericInputProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
error: false
|
stateValue: this.toDisplayValue(props.value),
|
||||||
|
error: false,
|
||||||
|
focused: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
changed: boolean = false;
|
changed: boolean = false;
|
||||||
|
|
||||||
handleChange(event: any): void {
|
handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void {
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
let strValue = event.target.value;
|
let strValue = event.target.value;
|
||||||
|
this.setState({ stateValue: strValue });
|
||||||
try {
|
try {
|
||||||
let value = Number(strValue);
|
let value = Number(strValue);
|
||||||
if (isNaN(value))
|
if (isNaN(value))
|
||||||
{
|
{
|
||||||
this.setState({ error: true });
|
this.setState({ error: true });
|
||||||
|
} else if (this.props.integer === true && !Number.isInteger(value)) {
|
||||||
|
this.setState({ error: true });
|
||||||
} else if (value >= this.props.min && value <= this.props.max) {
|
} else if (value >= this.props.min && value <= this.props.max) {
|
||||||
this.setState({ error: false });
|
this.setState({ error: false });
|
||||||
|
this.props.onChange(value);
|
||||||
} else {
|
} else {
|
||||||
this.setState({ error: true });
|
this.setState({ error: true });
|
||||||
}
|
}
|
||||||
@@ -80,6 +90,10 @@ export const NumericInput =
|
|||||||
}
|
}
|
||||||
|
|
||||||
toDisplayValue(value: number): string {
|
toDisplayValue(value: number): string {
|
||||||
|
if (this.props.integer === true)
|
||||||
|
{
|
||||||
|
return Math.round(value).toFixed(0);
|
||||||
|
}
|
||||||
if (value <= -1000 || value >= 1000)
|
if (value <= -1000 || value >= 1000)
|
||||||
{
|
{
|
||||||
return value.toFixed(0);
|
return value.toFixed(0);
|
||||||
@@ -96,7 +110,7 @@ export const NumericInput =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(input: HTMLInputElement) {
|
apply(input: HTMLInputElement| HTMLTextAreaElement) {
|
||||||
if (this.changed) {
|
if (this.changed) {
|
||||||
let strValue = input.value;
|
let strValue = input.value;
|
||||||
let value = Number(strValue);
|
let value = Number(strValue);
|
||||||
@@ -104,28 +118,34 @@ export const NumericInput =
|
|||||||
{
|
{
|
||||||
if (value < this.props.min) value = this.props.min;
|
if (value < this.props.min) value = this.props.min;
|
||||||
if (value > this.props.max) value = this.props.max;
|
if (value > this.props.max) value = this.props.max;
|
||||||
|
if (this.props.integer === true) value = Math.round(value);
|
||||||
input.value = this.toDisplayValue(value);
|
input.value = this.toDisplayValue(value);
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
} else {
|
} else {
|
||||||
input.value = this.toDisplayValue(this.props.defaultValue);
|
input.value = this.toDisplayValue(this.props.value);
|
||||||
|
this.props.onChange(this.props.value);
|
||||||
}
|
}
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
this.setState({ error: false });
|
this.setState({ error: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLostFocus(e: any) {
|
handleFocus(e: React.FocusEvent<HTMLInputElement| HTMLTextAreaElement>) {
|
||||||
this.apply(e.currentTarget as HTMLInputElement);
|
this.changed = false;
|
||||||
|
this.setState({ focused: true });
|
||||||
|
e.currentTarget.select();
|
||||||
|
this.setState({ stateValue: this.toDisplayValue(this.props.value) });
|
||||||
}
|
}
|
||||||
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
handleBlur(e: any) {
|
||||||
|
this.apply(e.currentTarget as HTMLInputElement);
|
||||||
|
this.setState({ focused: false });
|
||||||
|
}
|
||||||
|
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
||||||
if (e.key === "Enter")
|
if (e.key === "Enter")
|
||||||
{
|
{
|
||||||
this.apply(e.currentTarget as HTMLInputElement);
|
this.setState({ stateValue: this.toDisplayValue(this.props.value) });
|
||||||
} else if (e.key === "Escape")
|
this.changed = false;
|
||||||
{
|
}
|
||||||
e.currentTarget.value = this.toDisplayValue(this.props.defaultValue);
|
|
||||||
this.changed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,11 +155,15 @@ export const NumericInput =
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
'aria-label': this.props.ariaLabel,
|
'aria-label': this.props.ariaLabel,
|
||||||
style: { textAlign: 'right' },
|
style: { textAlign: 'right' },
|
||||||
"onBlur": (e: any) => this.handleLostFocus(e),
|
"min": this.props.min,
|
||||||
"onKeyDown": (e: any) => this.handleKeyDown(e)
|
"max": this.props.max,
|
||||||
|
"step:": this.props.step
|
||||||
}}
|
}}
|
||||||
onChange={(event) => this.handleChange(event)}
|
onFocus={(e) => {this.handleFocus(e);}}
|
||||||
defaultValue={this.toDisplayValue(this.props.defaultValue)}
|
onBlur= {(e) => { this.handleBlur(e); }}
|
||||||
|
onKeyDown={(e) => {this.handleKeyDown(e);}}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
value={this.state.focused ? this.state.stateValue: this.toDisplayValue(this.props.value)}
|
||||||
error={this.state.error}
|
error={this.state.error}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -158,14 +158,31 @@ interface ControlValueChangeItem {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class MidiMessage {
|
||||||
|
constructor(cc0: number, cc1: number, cc2: number) {
|
||||||
|
this.cc0 = cc0;
|
||||||
|
this.cc1 = cc1;
|
||||||
|
this.cc2 = cc2;
|
||||||
|
}
|
||||||
|
cc0: number;
|
||||||
|
cc1: number;
|
||||||
|
cc2: number;
|
||||||
|
|
||||||
|
isNote() {
|
||||||
|
return (this.cc0 & 0xF0) == 0x90 && this.cc2 !== 0;
|
||||||
|
}
|
||||||
|
isControl() {
|
||||||
|
return (this.cc0 & 0xF0) == 0xB0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class MidiEventListener {
|
class MidiEventListener {
|
||||||
constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) {
|
constructor(handle: number, callback: (message: MidiMessage) => void) {
|
||||||
this.handle = handle;
|
this.handle = handle;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
handle: number;
|
handle: number;
|
||||||
callback: (isNote: boolean, noteOrControl: number) => void;
|
callback: (midiMessage: MidiMessage) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PatchPropertyListener = (instanceId: number, propertyUri: string, atomObject: any) => void;
|
export type PatchPropertyListener = (instanceId: number, propertyUri: string, atomObject: any) => void;
|
||||||
@@ -679,10 +696,13 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (message === "onNotifyMidiListener") {
|
} else if (message === "onNotifyMidiListener") {
|
||||||
let clientHandle = body.clientHandle as number;
|
let notifyBody = body as { clientHandle: number, cc0: number, cc1: number, cc2: number };
|
||||||
let isNote = body.isNote as boolean;
|
let clientHandle = notifyBody.clientHandle as number;
|
||||||
let noteOrControl = body.noteOrControl as number;
|
this.handleNotifyMidiListener(
|
||||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
clientHandle,
|
||||||
|
notifyBody.cc0,
|
||||||
|
notifyBody.cc1,
|
||||||
|
notifyBody.cc2);
|
||||||
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
||||||
let instanceId = body.instanceId as number;
|
let instanceId = body.instanceId as number;
|
||||||
let propertyUri = body.propertyUri as string;
|
let propertyUri = body.propertyUri as string;
|
||||||
@@ -2355,12 +2375,12 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = [];
|
private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = [];
|
||||||
|
|
||||||
nextListenHandle = 1;
|
nextListenHandle = 1;
|
||||||
listenForMidiEvent(listenForControl: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle {
|
listenForMidiEvent(onComplete: (midiMessage: MidiMessage) => void): ListenHandle {
|
||||||
let handle = this.nextListenHandle++;
|
let handle = this.nextListenHandle++;
|
||||||
|
|
||||||
this.midiListeners.push(new MidiEventListener(handle, onComplete));
|
this.midiListeners.push(new MidiEventListener(handle, onComplete));
|
||||||
|
|
||||||
this.webSocket?.send("listenForMidiEvent", { listenForControls: listenForControl, handle: handle });
|
this.webSocket?.send("listenForMidiEvent", { handle: handle });
|
||||||
return {
|
return {
|
||||||
_handle: handle
|
_handle: handle
|
||||||
};
|
};
|
||||||
@@ -2418,12 +2438,16 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handleNotifyMidiListener(clientHandle: number, isNote: boolean, noteOrControl: number) {
|
handleNotifyMidiListener(clientHandle: number, cc0: number, cc1: number, cc2: number): void {
|
||||||
|
let midiMessage = new MidiMessage(cc0, cc1, cc2);
|
||||||
|
|
||||||
|
if (!midiMessage.isNote() && !midiMessage.isControl()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (let i = 0; i < this.midiListeners.length; ++i) {
|
for (let i = 0; i < this.midiListeners.length; ++i) {
|
||||||
let listener = this.midiListeners[i];
|
let listener = this.midiListeners[i];
|
||||||
if (listener.handle === clientHandle) {
|
if (listener.handle === clientHandle) {
|
||||||
listener.callback(isNote, noteOrControl);
|
listener.callback(midiMessage);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2464,7 +2488,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let link = window.document.createElement("A") as HTMLAnchorElement;
|
let link = window.document.createElement("A") as HTMLAnchorElement;
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.target = "_blank";
|
link.target = "_blank";
|
||||||
link.download = "download.piPreset";
|
link.download = "download.piPreset";
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
@@ -2705,8 +2729,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
copyFilePropertyFile(
|
copyFilePropertyFile(
|
||||||
oldRelativePath: string,
|
oldRelativePath: string,
|
||||||
newRelativePath: string,
|
newRelativePath: string,
|
||||||
uiFileProperty: UiFileProperty,
|
uiFileProperty: UiFileProperty,
|
||||||
overwrite: boolean
|
overwrite: boolean
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
@@ -3167,7 +3191,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
item.title = title;
|
item.title = title;
|
||||||
this.pedalboard.set(newPedalboard);
|
this.pedalboard.set(newPedalboard);
|
||||||
// notify the server.
|
// notify the server.
|
||||||
// xxx;
|
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,200 +23,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Component } from 'react';
|
|
||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
|
||||||
import { Theme } from '@mui/material/styles';
|
|
||||||
import WithStyles from './WithStyles';
|
|
||||||
import { withStyles } from "tss-react/mui";
|
|
||||||
import {createStyles} from './WithStyles';
|
|
||||||
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
|
||||||
import Select from '@mui/material/Select';
|
|
||||||
import MidiBinding from './MidiBinding';
|
import MidiBinding from './MidiBinding';
|
||||||
import Utility from './Utility';
|
import MidiBindingView, {MidiControlType} from './MidiBindingView';
|
||||||
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
|
interface SystemMidiBindingViewProps {
|
||||||
import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
|
|
||||||
import IconButtonEx from './IconButtonEx';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const styles = (theme: Theme) => createStyles({
|
|
||||||
controlDiv: { flex: "0 0 auto", marginRight: 12, verticalAlign: "center", height: 48, paddingTop: 8, paddingBottom: 8 },
|
|
||||||
controlDiv2: {
|
|
||||||
flex: "0 0 auto", marginRight: 12, verticalAlign: "center",
|
|
||||||
height: 48, paddingTop: 0, paddingBottom: 0, whiteSpace: "nowrap"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface SystemMidiBindingViewProps extends WithStyles<typeof styles> {
|
|
||||||
instanceId: number;
|
instanceId: number;
|
||||||
listen: boolean;
|
|
||||||
midiBinding: MidiBinding;
|
midiBinding: MidiBinding;
|
||||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
function SystemMidiBindingView(props: SystemMidiBindingViewProps) {
|
||||||
|
return (
|
||||||
interface SystemMidiBindingViewState {
|
<MidiBindingView
|
||||||
|
instanceId={props.instanceId}
|
||||||
|
midiBinding={props.midiBinding}
|
||||||
|
onChange={props.onChange}
|
||||||
|
midiControlType={MidiControlType.Trigger}
|
||||||
|
/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SystemMidiBindingView =
|
|
||||||
withStyles(
|
|
||||||
class extends Component<SystemMidiBindingViewProps, SystemMidiBindingViewState> {
|
|
||||||
|
|
||||||
model: PiPedalModel;
|
|
||||||
|
|
||||||
constructor(props: SystemMidiBindingViewProps) {
|
|
||||||
super(props);
|
|
||||||
this.model = PiPedalModelFactory.getInstance();
|
|
||||||
this.state = {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTypeChange(e: any, extra: any) {
|
|
||||||
let newValue = parseInt(e.target.value);
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.bindingType = newValue;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleNoteChange(e: any, extra: any) {
|
|
||||||
let newValue = parseInt(e.target.value);
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.note = newValue;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleControlChange(e: any, extra: any) {
|
|
||||||
let newValue = parseInt(e.target.value);
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.control = newValue;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleLatchControlTypeChange(e: any, extra: any) {
|
|
||||||
let newValue = parseInt(e.target.value);
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.switchControlType = newValue;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleLinearControlTypeChange(e: any, extra: any) {
|
|
||||||
let newValue = parseInt(e.target.value);
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.linearControlType = newValue;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleMinChange(value: number): void {
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.minValue = value;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleMaxChange(value: number): void {
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.maxValue = value;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
handleScaleChange(value: number): void {
|
|
||||||
let newBinding = this.props.midiBinding.clone();
|
|
||||||
newBinding.rotaryScale = value;
|
|
||||||
this.props.onChange(this.props.instanceId, newBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
generateMidiSelects(): React.ReactNode[] {
|
|
||||||
let result: React.ReactNode[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 127; ++i) {
|
|
||||||
result.push(
|
|
||||||
<MenuItem value={i}>{Utility.midiNoteName(i)}</MenuItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
generateControlSelects(): React.ReactNode[] {
|
|
||||||
|
|
||||||
return Utility.validMidiControllers.map((control) => (
|
|
||||||
<MenuItem key={control.value} value={control.value}>{control.displayName}</MenuItem>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const classes = withStyles.getClasses(this.props);
|
|
||||||
let midiBinding = this.props.midiBinding;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "left", alignItems: "center", minHeight: 48 }}>
|
|
||||||
<div className={classes.controlDiv} >
|
|
||||||
<Select variant="standard"
|
|
||||||
style={{ width: 80, }}
|
|
||||||
onChange={(e, extra) => this.handleTypeChange(e, extra)}
|
|
||||||
value={midiBinding.bindingType}
|
|
||||||
>
|
|
||||||
<MenuItem value={0}>None</MenuItem>
|
|
||||||
<MenuItem value={1}>Note</MenuItem>
|
|
||||||
<MenuItem value={2}>Control</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
|
||||||
(
|
|
||||||
<div className={classes.controlDiv} >
|
|
||||||
<Select variant="standard"
|
|
||||||
style={{ width: 80, verticalAlign: 'center' }}
|
|
||||||
onChange={(e, extra) => this.handleNoteChange(e, extra)}
|
|
||||||
value={midiBinding.note}
|
|
||||||
|
|
||||||
>
|
|
||||||
{
|
|
||||||
this.generateMidiSelects()
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_CONTROL) &&
|
|
||||||
(
|
|
||||||
<div className={classes.controlDiv} >
|
|
||||||
|
|
||||||
<Select variant="standard"
|
|
||||||
style={{ width: 80 }}
|
|
||||||
onChange={(e, extra) => this.handleControlChange(e, extra)}
|
|
||||||
value={midiBinding.control}
|
|
||||||
renderValue={(value) => { return "CC-" + value }}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
this.generateControlSelects()
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<IconButtonEx
|
|
||||||
tooltip="Listen for MIDI input"
|
|
||||||
onClick={() => {
|
|
||||||
if (this.props.listen) {
|
|
||||||
this.props.onListen(-2, "", false)
|
|
||||||
} else {
|
|
||||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
size="large">
|
|
||||||
{this.props.listen ? (
|
|
||||||
<MicOutlinedIcon />
|
|
||||||
) : (
|
|
||||||
<MicNoneOutlinedIcon />
|
|
||||||
)}
|
|
||||||
</IconButtonEx>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
styles
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SystemMidiBindingView;
|
export default SystemMidiBindingView;
|
||||||
@@ -27,7 +27,7 @@ import React, { SyntheticEvent } from 'react';
|
|||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import DialogEx from './DialogEx';
|
import DialogEx from './DialogEx';
|
||||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { Theme } from '@mui/material/styles';
|
import { Theme } from '@mui/material/styles';
|
||||||
import WithStyles from './WithStyles';
|
import WithStyles from './WithStyles';
|
||||||
@@ -182,66 +182,9 @@ export const SystemMidiBindingDialog =
|
|||||||
hasHooks: boolean = false;
|
hasHooks: boolean = false;
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.cancelListenForControl();
|
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
listenTimeoutHandle?: number;
|
|
||||||
|
|
||||||
listenHandle?: ListenHandle;
|
|
||||||
|
|
||||||
cancelListenForControl() {
|
|
||||||
if (this.listenTimeoutHandle) {
|
|
||||||
clearTimeout(this.listenTimeoutHandle);
|
|
||||||
this.listenTimeoutHandle = undefined;
|
|
||||||
}
|
|
||||||
if (this.listenHandle) {
|
|
||||||
this.model.cancelListenForMidiEvent(this.listenHandle)
|
|
||||||
this.listenHandle = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ listenInstanceId: -2, listenSymbol: "" });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number) {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
|
|
||||||
for (var binding of this.state.systemMidiBindings) {
|
|
||||||
if (binding.instanceId === instanceId) {
|
|
||||||
let newBinding = binding.midiBinding.clone();
|
|
||||||
|
|
||||||
if (isNote) {
|
|
||||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE;
|
|
||||||
newBinding.note = noteOrControl;
|
|
||||||
} else {
|
|
||||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL;
|
|
||||||
newBinding.control = noteOrControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model.setSystemMidiBinding(instanceId, newBinding);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleListenForControl(instanceId: number, symbol: string, listenForControl: boolean): void {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
this.setState({ listenInstanceId: instanceId, listenSymbol: symbol, listenSnackbarOpen: true });
|
|
||||||
this.listenTimeoutHandle = setTimeout(() => {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
}, 8000);
|
|
||||||
|
|
||||||
this.listenHandle = this.model.listenForMidiEvent(listenForControl,
|
|
||||||
(isNote: boolean, noteOrControl: number) => {
|
|
||||||
this.handleListenSucceeded(instanceId, symbol, isNote, noteOrControl);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindowSizeChanged(width: number, height: number): void {
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,14 +236,6 @@ export const SystemMidiBindingDialog =
|
|||||||
</td>
|
</td>
|
||||||
<td className={classes.bindingTd}>
|
<td className={classes.bindingTd}>
|
||||||
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
|
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
|
||||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
|
||||||
if (instanceId === -2) {
|
|
||||||
this.cancelListenForControl();
|
|
||||||
} else {
|
|
||||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === item.midiBinding.symbol}
|
|
||||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
|
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
|
||||||
}
|
}
|
||||||
function stopTimeout() {
|
function stopTimeout() {
|
||||||
if (timeout > 0) {
|
setTimeout(0);
|
||||||
startTimeout(0);
|
setTimeoutInstance(0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
|
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -60,6 +59,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
|
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
|
setLongPressLeaving(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||||
@@ -81,7 +81,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
if (valueTooltip === undefined) // no timeout if there's a value tooltip
|
if (valueTooltip === undefined) // no timeout if there's a value tooltip
|
||||||
{
|
{
|
||||||
if (t > 0) {
|
if (t > 0) {
|
||||||
// console.log("ToolTipEx: starting timeout for ", t);
|
console.log("ToolTipEx: starting timeout for ", t);
|
||||||
handle = window.setTimeout(() => {
|
handle = window.setTimeout(() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
},t);
|
},t);
|
||||||
@@ -89,7 +89,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (handle !== null) {
|
if (handle !== null) {
|
||||||
// console.log("ToolTipEx: clearing timeout for ", t);
|
console.log("ToolTipEx: clearing timeout for ", t);
|
||||||
window.clearTimeout(handle);
|
window.clearTimeout(handle);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -179,41 +179,46 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
stopTimeout(); // Reset hover timeout on click
|
stopTimeout(); // Reset hover timeout on click
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div style={{ display: 'inline-block' }}
|
||||||
onClickCapture={(e) => {
|
onClickCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onClickCapture");
|
console.log("ToolTipEx: onClickCapture");
|
||||||
handleClickCapture(e);
|
handleClickCapture(e);
|
||||||
|
setTimeout(0);
|
||||||
|
setOpen(false);
|
||||||
|
setIsLongPress(false);
|
||||||
|
setLongPressLeaving(true);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
onMouseEnter={(e)=> {
|
onMouseEnter={(e)=> {
|
||||||
// console.log("ToolTipEx: onMouseEnter");
|
console.log("ToolTipEx: onMouseEnter");
|
||||||
|
if (!longPressLeaving) {// Don't handle mouse enter if we're in a long press leaving state
|
||||||
handleMouseEnter(e);
|
handleMouseEnter(e);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e)=> {
|
onMouseLeave={(e)=> {
|
||||||
// console.log("ToolTipEx: onMouseLeave");
|
console.log("ToolTipEx: onMouseLeave");
|
||||||
handleMouseLeave(e);
|
handleMouseLeave(e);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
onPointerCancelCapture={(e) => {
|
onPointerCancelCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onPointerCancelCapture");
|
console.log("ToolTipEx: onPointerCancelCapture");
|
||||||
handlePointerCancel(e);
|
handlePointerCancel(e);
|
||||||
}}
|
}}
|
||||||
onContextMenuCapture={(e) => {
|
onContextMenuCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onContextMenuCapture");
|
console.log("ToolTipEx: onContextMenuCapture");
|
||||||
handleConextMenu(e);
|
handleConextMenu(e);
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
onPointerDownCapture={(e) => {
|
onPointerDownCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onPointerDownCapture");
|
console.log("ToolTipEx: onPointerDownCapture");
|
||||||
handlePointerDownCapture(e);
|
handlePointerDownCapture(e);
|
||||||
}}
|
}}
|
||||||
onPointerMoveCapture={(e) => {
|
onPointerMoveCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onPointerMoveCapture");
|
console.log("ToolTipEx: onPointerMoveCapture");
|
||||||
handlePointerMoveCapture(e);
|
handlePointerMoveCapture(e);
|
||||||
}}
|
}}
|
||||||
onPointerUpCapture={(e) => {
|
onPointerUpCapture={(e) => {
|
||||||
// console.log("ToolTipEx: onPointerUpCapture");
|
console.log("ToolTipEx: onPointerUpCapture");
|
||||||
handlePointerUpCapture(e);
|
handlePointerUpCapture(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user