listen for midi binding control range.
This commit is contained in:
+20
-12
@@ -294,12 +294,14 @@ class SystemMidiBinding
|
||||
private:
|
||||
MidiBinding currentBinding;
|
||||
bool controlState = false;
|
||||
uint8_t lastControlValue = 0;
|
||||
|
||||
public:
|
||||
void SetBinding(const MidiBinding &binding)
|
||||
{
|
||||
currentBinding = binding;
|
||||
controlState = false;
|
||||
lastControlValue = 0;
|
||||
}
|
||||
|
||||
bool IsMatch(const MidiEvent &event);
|
||||
@@ -335,13 +337,18 @@ bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
|
||||
return false;
|
||||
if (event.buffer[1] != currentBinding.control())
|
||||
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;
|
||||
return state;
|
||||
result = value >= 0x64 && lastControlValue < 0x64;
|
||||
} else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
return false;
|
||||
lastControlValue = value;
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -918,11 +925,12 @@ private:
|
||||
if (event.size >= 3)
|
||||
{
|
||||
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;
|
||||
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)
|
||||
{
|
||||
uint16_t msg;
|
||||
hostReader.read(&msg);
|
||||
MidiNotifyBody body;
|
||||
hostReader.read(&body);
|
||||
if (this->pNotifyCallbacks)
|
||||
{
|
||||
pNotifyCallbacks->OnNotifyMidiListen((msg & 0xFF00) != 0, (uint8_t)msg);
|
||||
pNotifyCallbacks->OnNotifyMidiListen(body.cc0_, body.cc1_, body.cc2_);
|
||||
}
|
||||
}
|
||||
else if (command == RingBufferCommand::MidiValueChanged)
|
||||
@@ -2080,8 +2088,8 @@ public:
|
||||
|
||||
return result;
|
||||
}
|
||||
volatile bool listenForMidiEvent = false;
|
||||
volatile bool listenForAtomOutput = false;
|
||||
std::atomic<bool> listenForMidiEvent = false;
|
||||
std::atomic<bool> listenForAtomOutput = false;
|
||||
|
||||
virtual void SetListenForMidiEvent(bool listen)
|
||||
{
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ namespace pipedal
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 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(
|
||||
int64_t instanceId,
|
||||
|
||||
@@ -824,7 +824,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
case MidiControlType::Dial:
|
||||
{
|
||||
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
|
||||
range = mapping.midiBinding.adjustRange(range);
|
||||
float range = mapping.midiBinding.calculateRange(value);
|
||||
float currentValue = mapping.pPortInfo->rangeToValue(range);
|
||||
if (pEffect->GetControlValue(mapping.controlIndex) != currentValue)
|
||||
{
|
||||
|
||||
@@ -58,6 +58,8 @@ JSON_MAP_BEGIN(MidiBinding)
|
||||
JSON_MAP_REFERENCE(MidiBinding,bindingType)
|
||||
JSON_MAP_REFERENCE(MidiBinding,note)
|
||||
JSON_MAP_REFERENCE(MidiBinding,control)
|
||||
JSON_MAP_REFERENCE(MidiBinding,minControlValue)
|
||||
JSON_MAP_REFERENCE(MidiBinding,maxControlValue)
|
||||
JSON_MAP_REFERENCE(MidiBinding,minValue)
|
||||
JSON_MAP_REFERENCE(MidiBinding,maxValue)
|
||||
JSON_MAP_REFERENCE(MidiBinding,rotaryScale)
|
||||
|
||||
+19
-2
@@ -80,6 +80,8 @@ private:
|
||||
int bindingType_ = BINDING_TYPE_NONE;
|
||||
int note_ = 12*4+24;
|
||||
int control_ = 1;
|
||||
int minControlValue_ = 0;
|
||||
int maxControlValue_ = 127;
|
||||
float minValue_ = 0;
|
||||
float maxValue_ = 1;
|
||||
float rotaryScale_ = 1;
|
||||
@@ -99,6 +101,8 @@ public:
|
||||
&& this->bindingType_ == other.bindingType_
|
||||
&& this->note_ == other.note_
|
||||
&& this->control_ == other.control_
|
||||
&& this->minControlValue_ == other.minControlValue_
|
||||
&& this->maxControlValue_ == other.maxControlValue_
|
||||
&& this->minValue_ == other.minValue_
|
||||
&& this->maxValue_ == other.maxValue_
|
||||
&& this->rotaryScale_ == other.rotaryScale_
|
||||
@@ -111,6 +115,8 @@ public:
|
||||
GETTER_SETTER(bindingType);
|
||||
GETTER_SETTER(note);
|
||||
GETTER_SETTER(control);
|
||||
GETTER_SETTER(minControlValue);
|
||||
GETTER_SETTER(maxControlValue);
|
||||
GETTER_SETTER(minValue);
|
||||
GETTER_SETTER(maxValue);
|
||||
GETTER_SETTER(rotaryScale);
|
||||
@@ -120,9 +126,20 @@ public:
|
||||
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);
|
||||
|
||||
|
||||
@@ -136,6 +136,15 @@ bool Pedalboard::SetControlValue(int64_t pedalItemId, const std::string &symbol,
|
||||
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()
|
||||
{
|
||||
@@ -503,6 +512,7 @@ JSON_MAP_BEGIN(PedalboardItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,title)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ public:
|
||||
Lv2PluginState lv2State_;
|
||||
std::string lilvPresetUri_;
|
||||
std::map<std::string,std::string> pathProperties_;
|
||||
std::string title_;
|
||||
|
||||
// non persistent state.
|
||||
PropertyMap patchProperties;
|
||||
@@ -126,6 +127,8 @@ public:
|
||||
GETTER_SETTER_REF(midiChannelBinding)
|
||||
GETTER_SETTER(stateUpdateCount)
|
||||
GETTER_SETTER_REF(lv2State)
|
||||
GETTER_SETTER_REF(title)
|
||||
|
||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||
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 OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||
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);
|
||||
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);
|
||||
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)
|
||||
{
|
||||
auto &listener = midiEventListeners[i];
|
||||
if ((!isNote) == (listener.listenForControls))
|
||||
auto subscriber = this->GetNotificationSubscriber(listener.clientId);
|
||||
if (subscriber)
|
||||
{
|
||||
auto subscriber = this->GetNotificationSubscriber(listener.clientId);
|
||||
if (subscriber)
|
||||
{
|
||||
subscriber->OnNotifyMidiListener(listener.clientHandle, isNote, noteOrControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
midiEventListeners.erase(midiEventListeners.begin() + i);
|
||||
--i;
|
||||
}
|
||||
subscriber->OnNotifyMidiListener(listener.clientHandle, cc0,cc1,cc2);
|
||||
}
|
||||
else
|
||||
{
|
||||
midiEventListeners.erase(midiEventListeners.begin() + i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
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);
|
||||
MidiListener listener{clientId, clientHandle, listenForControls};
|
||||
MidiListener listener{clientId, clientHandle};
|
||||
midiEventListeners.push_back(listener);
|
||||
audioHost->SetListenForMidiEvent(true);
|
||||
}
|
||||
@@ -2946,4 +2952,17 @@ void PiPedalModel::MoveAudioFile(
|
||||
}
|
||||
AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory);
|
||||
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 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 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 OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
|
||||
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
|
||||
@@ -146,7 +146,6 @@ namespace pipedal
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
bool listenForControls;
|
||||
};
|
||||
class AtomOutputListener
|
||||
{
|
||||
@@ -234,7 +233,7 @@ namespace pipedal
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) 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 OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
||||
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
|
||||
@@ -437,7 +436,7 @@ namespace pipedal
|
||||
JackServerSettings GetJackServerSettings();
|
||||
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 MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri);
|
||||
@@ -469,6 +468,8 @@ namespace pipedal
|
||||
const std::string & path,
|
||||
int32_t from,
|
||||
int32_t to);
|
||||
|
||||
void SetPedalboardItemTitle(int64_t instanceId, const std::string &title);
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
+33
-11
@@ -174,18 +174,36 @@ JSON_MAP_REFERENCE(SetPatchPropertyBody, propertyUri)
|
||||
JSON_MAP_REFERENCE(SetPatchPropertyBody, value)
|
||||
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
|
||||
{
|
||||
public:
|
||||
int64_t clientHandle_;
|
||||
bool isNote_;
|
||||
int32_t noteOrControl_;
|
||||
uint16_t cc0_;
|
||||
uint16_t cc1_;
|
||||
uint16_t cc2_;
|
||||
DECLARE_JSON_MAP(NotifyMidiListenerBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(NotifyMidiListenerBody)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, clientHandle)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, isNote)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, noteOrControl)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc0)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc1)
|
||||
JSON_MAP_REFERENCE(NotifyMidiListenerBody, cc2)
|
||||
JSON_MAP_END()
|
||||
|
||||
class NotifyAtomOutputBody
|
||||
@@ -208,13 +226,11 @@ JSON_MAP_END()
|
||||
class ListenForMidiEventBody
|
||||
{
|
||||
public:
|
||||
bool listenForControls_;
|
||||
int64_t handle_;
|
||||
DECLARE_JSON_MAP(ListenForMidiEventBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(ListenForMidiEventBody)
|
||||
JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControls)
|
||||
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -1099,7 +1115,7 @@ public:
|
||||
{
|
||||
ListenForMidiEventBody body;
|
||||
pReader->read(&body);
|
||||
this->model.ListenForMidiEvent(this->clientId, body.handle_, body.listenForControls_);
|
||||
this->model.ListenForMidiEvent(this->clientId, body.handle_);
|
||||
}
|
||||
else if (message == "cancelListenForMidiEvent")
|
||||
{
|
||||
@@ -1503,7 +1519,12 @@ public:
|
||||
{ this->JsonReply(replyTo, "setPatchProperty", "true"); }, [this, replyTo](const std::string &error)
|
||||
{ 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")
|
||||
{
|
||||
GetPatchPropertyBody body;
|
||||
@@ -2121,12 +2142,13 @@ private:
|
||||
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;
|
||||
body.clientHandle_ = clientHandle;
|
||||
body.isNote_ = isNote;
|
||||
body.noteOrControl_ = noteOrControl;
|
||||
body.cc0_ = cc0;
|
||||
body.cc1_ = cc1;
|
||||
body.cc2_ = cc2;
|
||||
|
||||
Send("onNotifyMidiListener", body);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,20 @@ namespace pipedal
|
||||
{
|
||||
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
|
||||
{
|
||||
Invalid = 0,
|
||||
@@ -38,7 +52,7 @@ namespace pipedal
|
||||
EffectReplaced,
|
||||
SetValue,
|
||||
SetBypass,
|
||||
//AudioStopped,
|
||||
// AudioStopped,
|
||||
AudioTerminatedAbnormally, // specifically for an ALSA loss of connection.
|
||||
SetVuSubscriptions,
|
||||
FreeVuSubscriptions,
|
||||
@@ -327,7 +341,7 @@ namespace pipedal
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t *variableData)
|
||||
{
|
||||
@@ -374,12 +388,9 @@ namespace pipedal
|
||||
write(RingBufferCommand::MidiValueChanged, body);
|
||||
}
|
||||
|
||||
void OnMidiListen(bool isNote, uint8_t noteOrControl)
|
||||
void OnMidiListen(const MidiNotifyBody &body)
|
||||
{
|
||||
uint16_t msg = noteOrControl;
|
||||
if (isNote)
|
||||
msg |= 0x100;
|
||||
write(RingBufferCommand::OnMidiListen, msg);
|
||||
write(RingBufferCommand::OnMidiListen, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,7 +496,8 @@ namespace pipedal
|
||||
{
|
||||
write(RingBufferCommand::AckMidiProgramChange, requestId);
|
||||
}
|
||||
void AckMidiSnapshotRequest(uint64_t snapshotRequestId) {
|
||||
void AckMidiSnapshotRequest(uint64_t 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?
|
||||
|
||||
|
||||
Check that OnNotifyPatchProperty still worsk for
|
||||
- FilePropertyControl
|
||||
- PowerStage2.
|
||||
- ToobSpectrumAnaylyzer.
|
||||
|
||||
pcm.pipedal_aux_in {
|
||||
type file
|
||||
|
||||
@@ -25,6 +25,9 @@ export default class MidiBinding {
|
||||
this.bindingType = input.bindingType;
|
||||
this.note = input.note;
|
||||
this.control = input.control;
|
||||
this.minControlValue = input.minControlValue?? 0;
|
||||
this.maxControlValue = input.maxControlValue?? 127;
|
||||
this.rotaryScale = input.rotaryScale?? 1;
|
||||
this.minValue = input.minValue;
|
||||
this.maxValue = input.maxValue;
|
||||
this.linearControlType = input.linearControlType;
|
||||
@@ -53,6 +56,9 @@ export default class MidiBinding {
|
||||
&& (this.note === other.note)
|
||||
&& (this.control === other.control)
|
||||
&& (this.minValue === other.minValue)
|
||||
&& (this.minControlValue === other.minControlValue)
|
||||
&& (this.maxControlValue === other.maxControlValue)
|
||||
&& (this.rotaryScale === other.rotaryScale)
|
||||
&& (this.maxValue === other.maxValue)
|
||||
&& (this.linearControlType === other.linearControlType)
|
||||
&& (this.switchControlType === other.switchControlType)
|
||||
@@ -73,6 +79,8 @@ export default class MidiBinding {
|
||||
bindingType: number = MidiBinding.BINDING_TYPE_NONE;
|
||||
note: number = 12*4+24; // C4.
|
||||
control: number = 1;
|
||||
minControlValue: number = 0;
|
||||
maxControlValue: number = 127;
|
||||
minValue: number = 0;
|
||||
maxValue: number = 1;
|
||||
rotaryScale: number = 1;
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { Component } from 'react';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { ListenHandle, MidiMessage, 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 { createStyles } from './WithStyles';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
|
||||
import { UiPlugin } from './Lv2Plugin';
|
||||
|
||||
@@ -47,7 +48,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
}
|
||||
});
|
||||
|
||||
enum MidiControlType {
|
||||
export enum MidiControlType {
|
||||
None,
|
||||
Select,
|
||||
Dial,
|
||||
@@ -56,18 +57,57 @@ enum MidiControlType {
|
||||
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> {
|
||||
instanceId: number;
|
||||
listen: boolean;
|
||||
midiBinding: MidiBinding;
|
||||
uiPlugin: UiPlugin;
|
||||
midiControlType: MidiControlType;
|
||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
interface MidiBindingViewState {
|
||||
midiControlType: MidiControlType;
|
||||
listenSymbol: string;
|
||||
listenForRangeSymbol: string;
|
||||
listenForRangeMax: number;
|
||||
listenForRangeMin: number;
|
||||
listenSnackbarOpen: boolean;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +123,11 @@ const MidiBindingView =
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
midiControlType: this.getControlType()
|
||||
listenSymbol: "",
|
||||
listenForRangeSymbol: "",
|
||||
listenForRangeMax: 127,
|
||||
listenForRangeMin: 0,
|
||||
listenSnackbarOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,6 +183,16 @@ const MidiBindingView =
|
||||
newBinding.maxValue = value;
|
||||
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 {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.rotaryScale = value;
|
||||
@@ -158,10 +212,23 @@ const MidiBindingView =
|
||||
return result;
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount?.();
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
this.cancelListenForControl();
|
||||
super.componentWillUnmount?.();
|
||||
}
|
||||
validateSwitchControlType(midiBinding: MidiBinding) {
|
||||
// :-(
|
||||
|
||||
let controlType = this.state.midiControlType;
|
||||
let controlType = this.props.midiControlType;
|
||||
if (controlType === MidiControlType.Toggle
|
||||
&& midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) {
|
||||
if (midiBinding.switchControlType !== MidiBinding.TRIGGER_ON_RISING_EDGE // Toggle on Note On.
|
||||
@@ -181,41 +248,154 @@ const MidiBindingView =
|
||||
}
|
||||
|
||||
getControlType(): 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;
|
||||
return this.props.midiControlType;
|
||||
}
|
||||
///////////////////////////////////////
|
||||
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() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let midiBinding = this.props.midiBinding;
|
||||
let uiPlugin = this.props.uiPlugin;
|
||||
if (!uiPlugin) {
|
||||
if (this.props.midiControlType === MidiControlType.None) {
|
||||
return (<div />);
|
||||
}
|
||||
let controlType = this.state.midiControlType;
|
||||
let controlType = this.props.midiControlType;
|
||||
|
||||
let showLinearRange =
|
||||
controlType === MidiControlType.Dial &&
|
||||
@@ -238,9 +418,7 @@ const MidiBindingView =
|
||||
value={midiBinding.bindingType}
|
||||
>
|
||||
<MenuItem value={0}>None</MenuItem>
|
||||
{(controlType === MidiControlType.Toggle
|
||||
|| controlType === MidiControlType.Trigger
|
||||
|| controlType === MidiControlType.MomentarySwitch) && (
|
||||
{(canBindToNote(this.props.midiControlType)) && (
|
||||
<MenuItem value={1}>Note</MenuItem>
|
||||
)}
|
||||
<MenuItem value={2}>Control</MenuItem>
|
||||
@@ -262,15 +440,15 @@ const MidiBindingView =
|
||||
</Select>
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
if (this.props.listen) {
|
||||
this.props.onListen(-2, "", false)
|
||||
} else {
|
||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
|
||||
}
|
||||
this.handleListen()
|
||||
}}
|
||||
size="large">
|
||||
{this.props.listen ? (
|
||||
{this.state.listenSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
@@ -296,15 +474,15 @@ const MidiBindingView =
|
||||
</Select>
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
if (this.props.listen) {
|
||||
this.props.onListen(-2, "", false)
|
||||
} else {
|
||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, true)
|
||||
}
|
||||
this.handleListen()
|
||||
}}
|
||||
size="large">
|
||||
{this.props.listen ? (
|
||||
{this.state.listenSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
@@ -314,6 +492,27 @@ const MidiBindingView =
|
||||
</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) &&
|
||||
(
|
||||
@@ -378,25 +577,58 @@ const MidiBindingView =
|
||||
<MenuItem value={MidiBinding.CIRCULAR_CONTROL_TYPE}>Rotary</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
{
|
||||
showLinearRange && (
|
||||
)}
|
||||
{showLinearRange && (
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline">Min: </Typography>
|
||||
<NumericInput defaultValue={midiBinding.minValue} ariaLabel='min'
|
||||
min={0} max={1} onChange={(value) => { this.handleMinChange(value); }}
|
||||
<Typography display="inline" noWrap>Min Ctl: </Typography>
|
||||
<NumericInput integer value={midiBinding.minControlValue} ariaLabel='min ctl val'
|
||||
min={0} max={127} step={1} onChange={(value) => { this.handleCtlMinChange(value); }}
|
||||
/>
|
||||
</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 && (
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline">Max: </Typography>
|
||||
<NumericInput defaultValue={midiBinding.maxValue} ariaLabel='max'
|
||||
min={0} max={1} onChange={(value) => { this.handleMaxChange(value); }} />
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography noWrap display="inline">Min Val: </Typography>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -404,11 +636,24 @@ const MidiBindingView =
|
||||
canRotaryScale && (
|
||||
<div className={classes.controlDiv}>
|
||||
<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); }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={this.state.listenSnackbarOpen}
|
||||
autoHideDuration={1500}
|
||||
onClose={() => this.setState({ listenSnackbarOpen: false })}
|
||||
message="Listening for MIDI input"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
@@ -32,8 +32,7 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import MidiBinding from './MidiBinding';
|
||||
import MidiBindingView from './MidiBindingView';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import MidiBindingView, { getMidiControlType } from './MidiBindingView';
|
||||
import { PortGroup, UiPlugin, makeSplitUiPlugin } from './Lv2Plugin';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
@@ -81,9 +80,6 @@ export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
|
||||
}
|
||||
|
||||
export interface MidiBindingDialogState {
|
||||
listenInstanceId: number;
|
||||
listenSymbol: string;
|
||||
listenSnackbarOpen: boolean;
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +94,10 @@ export const MidiBindingDialog =
|
||||
this.state = {
|
||||
listenInstanceId: -2,
|
||||
listenSymbol: "",
|
||||
listenSnackbarOpen: false
|
||||
listenForRangeSymbol: "",
|
||||
listenSnackbarOpen: false,
|
||||
listenForRangeMin: 127,
|
||||
listenForRangeMax: 0
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
@@ -108,64 +107,9 @@ export const MidiBindingDialog =
|
||||
hasHooks: boolean = false;
|
||||
|
||||
handleClose() {
|
||||
this.cancelListenForControl();
|
||||
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 {
|
||||
}
|
||||
@@ -176,10 +120,11 @@ export const MidiBindingDialog =
|
||||
this.mounted = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
|
||||
this.mounted = false;
|
||||
|
||||
super.componentWillUnmount();
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -255,15 +200,7 @@ export const MidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
|
||||
uiPlugin={plugin}
|
||||
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"}
|
||||
midiControlType={ getMidiControlType(plugin, "__bypass") }
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
@@ -305,13 +242,8 @@ export const MidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<MidiBindingView instanceId={item.instanceId}
|
||||
uiPlugin={plugin}
|
||||
midiControlType={getMidiControlType(plugin, 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)}
|
||||
/>
|
||||
</td>
|
||||
@@ -379,16 +311,6 @@ export const MidiBindingDialog =
|
||||
</table>
|
||||
</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 >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,14 +35,18 @@ const styles = ({ palette }: Theme) => createStyles({
|
||||
|
||||
interface NumericInputProps extends WithStyles<typeof styles> {
|
||||
ariaLabel: string;
|
||||
defaultValue: number;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
integer?: boolean;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
interface NumericInputState {
|
||||
stateValue: string;
|
||||
error: boolean;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
export const NumericInput =
|
||||
@@ -54,22 +58,28 @@ export const NumericInput =
|
||||
constructor(props: NumericInputProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: false
|
||||
stateValue: this.toDisplayValue(props.value),
|
||||
error: false,
|
||||
focused: false
|
||||
};
|
||||
}
|
||||
|
||||
changed: boolean = false;
|
||||
|
||||
handleChange(event: any): void {
|
||||
handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void {
|
||||
this.changed = true;
|
||||
let strValue = event.target.value;
|
||||
this.setState({ stateValue: strValue });
|
||||
try {
|
||||
let value = Number(strValue);
|
||||
if (isNaN(value))
|
||||
{
|
||||
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) {
|
||||
this.setState({ error: false });
|
||||
this.props.onChange(value);
|
||||
} else {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
@@ -80,6 +90,10 @@ export const NumericInput =
|
||||
}
|
||||
|
||||
toDisplayValue(value: number): string {
|
||||
if (this.props.integer === true)
|
||||
{
|
||||
return Math.round(value).toFixed(0);
|
||||
}
|
||||
if (value <= -1000 || value >= 1000)
|
||||
{
|
||||
return value.toFixed(0);
|
||||
@@ -96,7 +110,7 @@ export const NumericInput =
|
||||
}
|
||||
}
|
||||
|
||||
apply(input: HTMLInputElement) {
|
||||
apply(input: HTMLInputElement| HTMLTextAreaElement) {
|
||||
if (this.changed) {
|
||||
let strValue = input.value;
|
||||
let value = Number(strValue);
|
||||
@@ -104,28 +118,34 @@ export const NumericInput =
|
||||
{
|
||||
if (value < this.props.min) value = this.props.min;
|
||||
if (value > this.props.max) value = this.props.max;
|
||||
if (this.props.integer === true) value = Math.round(value);
|
||||
input.value = this.toDisplayValue(value);
|
||||
this.props.onChange(value);
|
||||
} 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.setState({ error: false });
|
||||
}
|
||||
}
|
||||
|
||||
handleLostFocus(e: any) {
|
||||
this.apply(e.currentTarget as HTMLInputElement);
|
||||
handleFocus(e: React.FocusEvent<HTMLInputElement| HTMLTextAreaElement>) {
|
||||
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")
|
||||
{
|
||||
this.apply(e.currentTarget as HTMLInputElement);
|
||||
} else if (e.key === "Escape")
|
||||
{
|
||||
e.currentTarget.value = this.toDisplayValue(this.props.defaultValue);
|
||||
this.changed = false;
|
||||
}
|
||||
this.setState({ stateValue: this.toDisplayValue(this.props.value) });
|
||||
this.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,11 +155,15 @@ export const NumericInput =
|
||||
inputProps={{
|
||||
'aria-label': this.props.ariaLabel,
|
||||
style: { textAlign: 'right' },
|
||||
"onBlur": (e: any) => this.handleLostFocus(e),
|
||||
"onKeyDown": (e: any) => this.handleKeyDown(e)
|
||||
"min": this.props.min,
|
||||
"max": this.props.max,
|
||||
"step:": this.props.step
|
||||
}}
|
||||
onChange={(event) => this.handleChange(event)}
|
||||
defaultValue={this.toDisplayValue(this.props.defaultValue)}
|
||||
onFocus={(e) => {this.handleFocus(e);}}
|
||||
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}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) {
|
||||
constructor(handle: number, callback: (message: MidiMessage) => void) {
|
||||
this.handle = handle;
|
||||
this.callback = callback;
|
||||
}
|
||||
handle: number;
|
||||
callback: (isNote: boolean, noteOrControl: number) => void;
|
||||
callback: (midiMessage: MidiMessage) => void;
|
||||
};
|
||||
|
||||
export type PatchPropertyListener = (instanceId: number, propertyUri: string, atomObject: any) => void;
|
||||
@@ -679,10 +696,13 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
} else if (message === "onNotifyMidiListener") {
|
||||
let clientHandle = body.clientHandle as number;
|
||||
let isNote = body.isNote as boolean;
|
||||
let noteOrControl = body.noteOrControl as number;
|
||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
||||
let notifyBody = body as { clientHandle: number, cc0: number, cc1: number, cc2: number };
|
||||
let clientHandle = notifyBody.clientHandle as number;
|
||||
this.handleNotifyMidiListener(
|
||||
clientHandle,
|
||||
notifyBody.cc0,
|
||||
notifyBody.cc1,
|
||||
notifyBody.cc2);
|
||||
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
||||
let instanceId = body.instanceId as number;
|
||||
let propertyUri = body.propertyUri as string;
|
||||
@@ -2355,12 +2375,12 @@ export class PiPedalModel //implements PiPedalModel
|
||||
private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = [];
|
||||
|
||||
nextListenHandle = 1;
|
||||
listenForMidiEvent(listenForControl: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle {
|
||||
listenForMidiEvent(onComplete: (midiMessage: MidiMessage) => void): ListenHandle {
|
||||
let handle = this.nextListenHandle++;
|
||||
|
||||
this.midiListeners.push(new MidiEventListener(handle, onComplete));
|
||||
|
||||
this.webSocket?.send("listenForMidiEvent", { listenForControls: listenForControl, handle: handle });
|
||||
this.webSocket?.send("listenForMidiEvent", { handle: handle });
|
||||
return {
|
||||
_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) {
|
||||
let listener = this.midiListeners[i];
|
||||
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;
|
||||
link.href = url;
|
||||
link.target = "_blank";
|
||||
link.download = "download.piPreset";
|
||||
link.download = "download.piPreset";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -2705,8 +2729,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
});
|
||||
}
|
||||
copyFilePropertyFile(
|
||||
oldRelativePath: string,
|
||||
newRelativePath: string,
|
||||
oldRelativePath: string,
|
||||
newRelativePath: string,
|
||||
uiFileProperty: UiFileProperty,
|
||||
overwrite: boolean
|
||||
): Promise<string> {
|
||||
@@ -3167,7 +3191,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
item.title = title;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
// 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 Utility from './Utility';
|
||||
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
|
||||
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> {
|
||||
import MidiBindingView, {MidiControlType} from './MidiBindingView';
|
||||
interface SystemMidiBindingViewProps {
|
||||
instanceId: number;
|
||||
listen: boolean;
|
||||
midiBinding: MidiBinding;
|
||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
interface SystemMidiBindingViewState {
|
||||
function SystemMidiBindingView(props: SystemMidiBindingViewProps) {
|
||||
return (
|
||||
<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;
|
||||
@@ -27,7 +27,7 @@ import React, { SyntheticEvent } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
@@ -182,66 +182,9 @@ export const SystemMidiBindingDialog =
|
||||
hasHooks: boolean = false;
|
||||
|
||||
handleClose() {
|
||||
this.cancelListenForControl();
|
||||
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 {
|
||||
}
|
||||
|
||||
@@ -293,14 +236,6 @@ export const SystemMidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<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)}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -48,9 +48,8 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
|
||||
}
|
||||
function stopTimeout() {
|
||||
if (timeout > 0) {
|
||||
startTimeout(0);
|
||||
}
|
||||
setTimeout(0);
|
||||
setTimeoutInstance(0);
|
||||
}
|
||||
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
@@ -60,6 +59,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
stopTimeout();
|
||||
setLongPressLeaving(false);
|
||||
}
|
||||
|
||||
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 (t > 0) {
|
||||
// console.log("ToolTipEx: starting timeout for ", t);
|
||||
console.log("ToolTipEx: starting timeout for ", t);
|
||||
handle = window.setTimeout(() => {
|
||||
setOpen(true);
|
||||
},t);
|
||||
@@ -89,7 +89,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
}
|
||||
return () => {
|
||||
if (handle !== null) {
|
||||
// console.log("ToolTipEx: clearing timeout for ", t);
|
||||
console.log("ToolTipEx: clearing timeout for ", t);
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
@@ -179,41 +179,46 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
stopTimeout(); // Reset hover timeout on click
|
||||
}
|
||||
return (
|
||||
<div
|
||||
<div style={{ display: 'inline-block' }}
|
||||
onClickCapture={(e) => {
|
||||
// console.log("ToolTipEx: onClickCapture");
|
||||
console.log("ToolTipEx: onClickCapture");
|
||||
handleClickCapture(e);
|
||||
setTimeout(0);
|
||||
setOpen(false);
|
||||
setIsLongPress(false);
|
||||
setLongPressLeaving(true);
|
||||
}}
|
||||
|
||||
onMouseEnter={(e)=> {
|
||||
// console.log("ToolTipEx: onMouseEnter");
|
||||
|
||||
handleMouseEnter(e);
|
||||
console.log("ToolTipEx: onMouseEnter");
|
||||
if (!longPressLeaving) {// Don't handle mouse enter if we're in a long press leaving state
|
||||
handleMouseEnter(e);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e)=> {
|
||||
// console.log("ToolTipEx: onMouseLeave");
|
||||
console.log("ToolTipEx: onMouseLeave");
|
||||
handleMouseLeave(e);
|
||||
}}
|
||||
|
||||
onPointerCancelCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerCancelCapture");
|
||||
console.log("ToolTipEx: onPointerCancelCapture");
|
||||
handlePointerCancel(e);
|
||||
}}
|
||||
onContextMenuCapture={(e) => {
|
||||
// console.log("ToolTipEx: onContextMenuCapture");
|
||||
console.log("ToolTipEx: onContextMenuCapture");
|
||||
handleConextMenu(e);
|
||||
return false;
|
||||
}}
|
||||
onPointerDownCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerDownCapture");
|
||||
console.log("ToolTipEx: onPointerDownCapture");
|
||||
handlePointerDownCapture(e);
|
||||
}}
|
||||
onPointerMoveCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerMoveCapture");
|
||||
console.log("ToolTipEx: onPointerMoveCapture");
|
||||
handlePointerMoveCapture(e);
|
||||
}}
|
||||
onPointerUpCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerUpCapture");
|
||||
console.log("ToolTipEx: onPointerUpCapture");
|
||||
handlePointerUpCapture(e);
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user