ALSA Sequencer connections.
This commit is contained in:
@@ -59,6 +59,7 @@ namespace pipedal
|
|||||||
|
|
||||||
virtual void ConnectPort(int clientId, int portId) override;
|
virtual void ConnectPort(int clientId, int portId) override;
|
||||||
virtual void ConnectPort(const std::string &name) override;
|
virtual void ConnectPort(const std::string &name) override;
|
||||||
|
virtual void SetConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) override;
|
||||||
|
|
||||||
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
|
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
|
||||||
// A timeout of 0 returns immediately.
|
// A timeout of 0 returns immediately.
|
||||||
@@ -213,6 +214,15 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
port.id = SS("seq:" << port.clientName << "/" << port.name);
|
port.id = SS("seq:" << port.clientName << "/" << port.name);
|
||||||
|
|
||||||
|
port.displaySortOrder = port.client * 256 + port.port;
|
||||||
|
if (port.isVirtual)
|
||||||
|
{
|
||||||
|
port.displaySortOrder += 1 * 256 * 256; // MIDI virtual ports after real ports.
|
||||||
|
}
|
||||||
|
else if (port.clientName == "Midi Through")
|
||||||
|
{
|
||||||
|
port.displaySortOrder += 2 * 256 * 256; // MIDI Through at the very end because it's weird.
|
||||||
|
}
|
||||||
ports.push_back(std::move(port));
|
ports.push_back(std::move(port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,11 +268,11 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
void AlsaSequencerImpl::RemoveAllConnections()
|
void AlsaSequencerImpl::RemoveAllConnections()
|
||||||
{
|
{
|
||||||
for (const auto &connection : connections)
|
while (connections.size() != 0)
|
||||||
{
|
{
|
||||||
snd_seq_disconnect_from(seqHandle, inPort, connection.clientId, connection.portId);
|
auto connection = connections.back();
|
||||||
|
ModifyConnection(connection.clientId, connection.portId, ConnectAction::Unsubscribe);
|
||||||
}
|
}
|
||||||
connections.clear();
|
|
||||||
}
|
}
|
||||||
AlsaSequencerImpl::~AlsaSequencerImpl()
|
AlsaSequencerImpl::~AlsaSequencerImpl()
|
||||||
{
|
{
|
||||||
@@ -303,6 +313,25 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
throw std::runtime_error("ALSA port not found");
|
throw std::runtime_error("ALSA port not found");
|
||||||
}
|
}
|
||||||
|
void AlsaSequencerImpl::SetConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration)
|
||||||
|
{
|
||||||
|
this->RemoveAllConnections(); // Currently no configuration options to set
|
||||||
|
|
||||||
|
auto ports = EnumeratePorts();
|
||||||
|
for (const auto &connection : alsaSequencerConfiguration.connections())
|
||||||
|
{
|
||||||
|
// Connect to each port specified in the configuration
|
||||||
|
std::string id = connection.id();
|
||||||
|
for (const auto &port : ports)
|
||||||
|
{
|
||||||
|
if (port.id == id)
|
||||||
|
{
|
||||||
|
ConnectPort(port.client, port.port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AlsaSequencerImpl::WaitForMessage(int timeoutMs)
|
bool AlsaSequencerImpl::WaitForMessage(int timeoutMs)
|
||||||
{
|
{
|
||||||
@@ -547,6 +576,23 @@ namespace pipedal
|
|||||||
case SND_SEQ_EVENT_CLOCK:
|
case SND_SEQ_EVENT_CLOCK:
|
||||||
message.Set(0xF8); // MIDI Real Time Clock Tick
|
message.Set(0xF8); // MIDI Real Time Clock Tick
|
||||||
break;
|
break;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define MSG_DEBUG_LOG(x) \
|
||||||
|
case x:\
|
||||||
|
Lv2Log::debug("ALSA Sequencer Message" #x);\
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
#define MSG_DEBUG_LOG(x)
|
||||||
|
#endif
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_CLIENT_START)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_CLIENT_EXIT)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_CLIENT_CHANGE)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_PORT_START)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_PORT_EXIT)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_PORT_CHANGE)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_PORT_SUBSCRIBED)
|
||||||
|
MSG_DEBUG_LOG(SND_SEQ_EVENT_PORT_UNSUBSCRIBED)
|
||||||
|
|
||||||
case SND_SEQ_EVENT_KEYSIGN:
|
case SND_SEQ_EVENT_KEYSIGN:
|
||||||
case SND_SEQ_EVENT_TIMESIGN:
|
case SND_SEQ_EVENT_TIMESIGN:
|
||||||
// and a PASSEL of others!
|
// and a PASSEL of others!
|
||||||
@@ -677,9 +723,8 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
throw std::runtime_error("Failed to open ALSA sequencer for connection");
|
throw std::runtime_error("Failed to open ALSA sequencer for connection");
|
||||||
}
|
}
|
||||||
Finally seq_finally([seq]() {
|
Finally seq_finally([seq]()
|
||||||
snd_seq_close(seq);
|
{ snd_seq_close(seq); });
|
||||||
});
|
|
||||||
|
|
||||||
snd_seq_addr_t sender, dest;
|
snd_seq_addr_t sender, dest;
|
||||||
dest.client = myClientId;
|
dest.client = myClientId;
|
||||||
@@ -704,44 +749,55 @@ namespace pipedal
|
|||||||
"Failed to disconnect ALSA sequencer port %d:%d. Subscripton not found.",
|
"Failed to disconnect ALSA sequencer port %d:%d. Subscripton not found.",
|
||||||
(int)clientId,
|
(int)clientId,
|
||||||
(int)portId);
|
(int)portId);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int rc = snd_seq_unsubscribe_port(seq, subs);
|
int rc = snd_seq_unsubscribe_port(seq, subs);
|
||||||
if (rc < 0) {
|
if (rc < 0)
|
||||||
|
{
|
||||||
Lv2Log::warning(
|
Lv2Log::warning(
|
||||||
"Failed to disconnect ALSA sequencer port %d:%d. (%s)",
|
"Failed to disconnect ALSA sequencer port %d:%d. (%s)",
|
||||||
(int)clientId,
|
(int)clientId,
|
||||||
(int)portId,
|
(int)portId,
|
||||||
snd_strerror(rc));
|
snd_strerror(rc));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
for (auto it = this->connections.begin(); it != this->connections.end();++it)
|
for (auto it = this->connections.begin(); it != this->connections.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->clientId == clientId && it->portId == portId)
|
if (it->clientId == clientId && it->portId == portId)
|
||||||
{
|
{
|
||||||
it = this->connections.erase(it);
|
it = this->connections.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (snd_seq_get_port_subscription(seq, subs) == 0)
|
if (snd_seq_get_port_subscription(seq, subs) == 0)
|
||||||
{
|
{
|
||||||
Lv2Log::warning("ALSA sequencer port %d:%d is already subscribed.",(int)clientId,(int)portId);
|
Lv2Log::warning("ALSA sequencer port %d:%d is already subscribed.", (int)clientId, (int)portId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int rc = snd_seq_subscribe_port(seq, subs);
|
int rc = snd_seq_subscribe_port(seq, subs);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
{
|
{
|
||||||
Lv2Log::error("Failed to connect ALSA sequencer port %d:%d. (%s)",
|
Lv2Log::error("Failed to connect ALSA sequencer port %d:%d. (%s)",
|
||||||
(int)clientId,(int)portId,
|
(int)clientId, (int)portId,
|
||||||
snd_strerror(rc));
|
snd_strerror(rc));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->connections.push_back({clientId, portId});
|
this->connections.push_back({clientId, portId});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(AlsaSequencerPortSelection)
|
||||||
|
JSON_MAP_REFERENCE(AlsaSequencerPortSelection, id)
|
||||||
|
JSON_MAP_REFERENCE(AlsaSequencerPortSelection, name)
|
||||||
|
JSON_MAP_REFERENCE(AlsaSequencerPortSelection, sortOrder)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(AlsaSequencerConfiguration)
|
||||||
|
JSON_MAP_REFERENCE(AlsaSequencerConfiguration, connections)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
} // namespace pipedal
|
} // namespace pipedal
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
@@ -42,6 +43,58 @@ namespace pipedal
|
|||||||
SetPositionTime
|
SetPositionTime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AlsaSequencerPortSelection {
|
||||||
|
private:
|
||||||
|
std::string id_;
|
||||||
|
std::string name_;
|
||||||
|
int32_t sortOrder_ = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AlsaSequencerPortSelection() = default;
|
||||||
|
AlsaSequencerPortSelection(const std::string &id, const std::string &name, int32_t displaySortOrder)
|
||||||
|
: id_(id), name_(name), sortOrder_(displaySortOrder) {}
|
||||||
|
AlsaSequencerPortSelection(const AlsaSequencerPortSelection &other) = default;
|
||||||
|
AlsaSequencerPortSelection(AlsaSequencerPortSelection &&other) = default;
|
||||||
|
AlsaSequencerPortSelection &operator=(const AlsaSequencerPortSelection &other) = default;
|
||||||
|
AlsaSequencerPortSelection &operator=(AlsaSequencerPortSelection &&other) = default;
|
||||||
|
|
||||||
|
|
||||||
|
const std::string& id() const { return id_; }
|
||||||
|
void id(const std::string &value) { id_ = value; }
|
||||||
|
const std::string &name() const { return name_; }
|
||||||
|
void name(const std::string &value) { name_ = value; }
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(AlsaSequencerPortSelection);
|
||||||
|
};
|
||||||
|
class AlsaSequencerConfiguration {
|
||||||
|
private:
|
||||||
|
std::vector<AlsaSequencerPortSelection> connections_;
|
||||||
|
public:
|
||||||
|
const std::vector<AlsaSequencerPortSelection>& connections() const { return connections_; }
|
||||||
|
std::vector<AlsaSequencerPortSelection>& connections() { return connections_; }
|
||||||
|
void connections(const std::vector<AlsaSequencerPortSelection>& value) { connections_ = value; }
|
||||||
|
|
||||||
|
|
||||||
|
bool operator==(const AlsaSequencerConfiguration &other) const
|
||||||
|
{
|
||||||
|
if (connections_.size() != other.connections_.size())
|
||||||
|
return false;
|
||||||
|
for (size_t i = 0; i < connections_.size(); ++i)
|
||||||
|
{
|
||||||
|
if (connections_[i].id() != other.connections_[i].id() ||
|
||||||
|
connections_[i].name() != other.connections_[i].name())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(AlsaSequencerConfiguration);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct AlsaSequencerPort
|
struct AlsaSequencerPort
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
@@ -67,6 +120,7 @@ namespace pipedal
|
|||||||
bool isPort = false;
|
bool isPort = false;
|
||||||
bool isVirtual = false;
|
bool isVirtual = false;
|
||||||
int cardNumber = -1;
|
int cardNumber = -1;
|
||||||
|
int32_t displaySortOrder = 0;
|
||||||
std::string rawMidiDevice; // e.g. "hw:0,0,0" for kernel devices
|
std::string rawMidiDevice; // e.g. "hw:0,0,0" for kernel devices
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,6 +319,8 @@ namespace pipedal
|
|||||||
virtual void ConnectPort(int clientId, int portId) = 0;
|
virtual void ConnectPort(int clientId, int portId) = 0;
|
||||||
virtual void ConnectPort(const std::string &id) = 0;
|
virtual void ConnectPort(const std::string &id) = 0;
|
||||||
|
|
||||||
|
virtual void SetConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
|
||||||
|
|
||||||
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
|
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
|
||||||
// A timeout of 0 returns immediately.
|
// A timeout of 0 returns immediately.
|
||||||
virtual bool ReadMessage(AlsaMidiMessage &message, int timeoutMs = -1) = 0;
|
virtual bool ReadMessage(AlsaMidiMessage &message, int timeoutMs = -1) = 0;
|
||||||
|
|||||||
+12
-3
@@ -532,7 +532,7 @@ private:
|
|||||||
|
|
||||||
std::string GetAtomObjectType(uint8_t *pData)
|
std::string GetAtomObjectType(uint8_t *pData)
|
||||||
{
|
{
|
||||||
LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData;
|
LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData;
|
||||||
if (pAtom->atom.type != uris.atom_Object)
|
if (pAtom->atom.type != uris.atom_Object)
|
||||||
{
|
{
|
||||||
throw std::invalid_argument("Not an Lv2 Object");
|
throw std::invalid_argument("Not an Lv2 Object");
|
||||||
@@ -602,7 +602,6 @@ private:
|
|||||||
this->outputRingBuffer.reset();
|
this->outputRingBuffer.reset();
|
||||||
|
|
||||||
audioDriver = nullptr;
|
audioDriver = nullptr;
|
||||||
alsaSequencer = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZeroBuffer(float *buffer, size_t nframes)
|
void ZeroBuffer(float *buffer, size_t nframes)
|
||||||
@@ -1259,6 +1258,7 @@ public:
|
|||||||
Close();
|
Close();
|
||||||
CleanRestartThreads(true);
|
CleanRestartThreads(true);
|
||||||
audioDriver = nullptr;
|
audioDriver = nullptr;
|
||||||
|
this->alsaSequencer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual JackConfiguration GetServerConfiguration()
|
virtual JackConfiguration GetServerConfiguration()
|
||||||
@@ -1642,7 +1642,6 @@ public:
|
|||||||
{
|
{
|
||||||
this->isDummyAudioDriver = true;
|
this->isDummyAudioDriver = true;
|
||||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
|
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
|
||||||
this->audioDriver->SetAlsaSequencer(this->alsaSequencer);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1820,6 +1819,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) override;
|
||||||
|
|
||||||
|
|
||||||
void OnNotifyPathPatchPropertyReceived(
|
void OnNotifyPathPatchPropertyReceived(
|
||||||
int64_t instanceId,
|
int64_t instanceId,
|
||||||
const std::string &pathPatchPropertyUri,
|
const std::string &pathPatchPropertyUri,
|
||||||
@@ -2267,6 +2269,13 @@ void AudioHostImpl::OnWritePatchPropertyBuffer(
|
|||||||
this->realtimeWriter.SendPathPropertyBuffer(buffer);
|
this->realtimeWriter.SendPathPropertyBuffer(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioHostImpl::SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration)
|
||||||
|
{
|
||||||
|
this->alsaSequencer->SetConfiguration(alsaSequencerConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JSON_MAP_BEGIN(JackHostStatus)
|
JSON_MAP_BEGIN(JackHostStatus)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, active)
|
JSON_MAP_REFERENCE(JackHostStatus, active)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ namespace pipedal
|
|||||||
struct RealtimeNextMidiProgramRequest;
|
struct RealtimeNextMidiProgramRequest;
|
||||||
class PluginHost;
|
class PluginHost;
|
||||||
class Pedalboard;
|
class Pedalboard;
|
||||||
|
class AlsaSequencerConfiguration;
|
||||||
|
|
||||||
using PortMonitorCallback = std::function<void(int64_t handle, float value)>;
|
using PortMonitorCallback = std::function<void(int64_t handle, float value)>;
|
||||||
|
|
||||||
@@ -225,6 +226,7 @@ namespace pipedal
|
|||||||
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0;
|
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
||||||
|
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
|
||||||
virtual uint32_t GetSampleRate() = 0;
|
virtual uint32_t GetSampleRate() = 0;
|
||||||
|
|
||||||
virtual JackConfiguration GetServerConfiguration() = 0;
|
virtual JackConfiguration GetServerConfiguration() = 0;
|
||||||
|
|||||||
@@ -107,11 +107,13 @@ namespace pipedal
|
|||||||
return outputAudioPorts_;
|
return outputAudioPorts_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<AlsaMidiDeviceInfo>& GetInputMidiDevices() const
|
// replaced with AlsaSequencerConfiguration
|
||||||
|
const std::vector<AlsaMidiDeviceInfo>& LegacyGetInputMidiDevices() const
|
||||||
{
|
{
|
||||||
return inputMidiDevices_;
|
return inputMidiDevices_;
|
||||||
}
|
}
|
||||||
std::vector<AlsaMidiDeviceInfo>& GetInputMidiDevices()
|
// replaced with AlsaSequencerConfiguration
|
||||||
|
std::vector<AlsaMidiDeviceInfo>& LegacyGetInputMidiDevices()
|
||||||
{
|
{
|
||||||
return inputMidiDevices_;
|
return inputMidiDevices_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,10 +292,16 @@ void PiPedalModel::Load()
|
|||||||
|
|
||||||
this->audioHost->SetNotificationCallbacks(this);
|
this->audioHost->SetNotificationCallbacks(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
||||||
|
|
||||||
this->audioHost->SetSystemMidiBindings(this->systemMidiBindings);
|
this->audioHost->SetSystemMidiBindings(this->systemMidiBindings);
|
||||||
|
|
||||||
|
audioHost->SetAlsaSequencerConfiguration(storage.GetAlsaSequencerConfiguration());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (configuration.GetMLock())
|
if (configuration.GetMLock())
|
||||||
{
|
{
|
||||||
#ifndef NO_MLOCK
|
#ifndef NO_MLOCK
|
||||||
@@ -1400,6 +1406,49 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
// reset midi connections even if the configuration hasn't changed.
|
||||||
|
this->audioHost->SetAlsaSequencerConfiguration(alsaSequencerConfiguration);
|
||||||
|
|
||||||
|
auto current = storage.GetAlsaSequencerConfiguration();
|
||||||
|
if (alsaSequencerConfiguration != current)
|
||||||
|
{
|
||||||
|
this->storage.SetAlsaSequencerConfiguration(alsaSequencerConfiguration);
|
||||||
|
// notify subscribers.
|
||||||
|
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||||
|
for (auto &subscriber : t)
|
||||||
|
{
|
||||||
|
subscriber->OnAlsaSequencerConfigurationChanged(alsaSequencerConfiguration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
AlsaSequencerConfiguration PiPedalModel::GetAlsaSequencerConfiguration()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
return this->storage.GetAlsaSequencerConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AlsaSequencerPortSelection> PiPedalModel::GetAlsaSequencerPorts()
|
||||||
|
{
|
||||||
|
auto ports = AlsaSequencer::EnumeratePorts();
|
||||||
|
std::vector<AlsaSequencerPortSelection> result;
|
||||||
|
for (auto &port : ports)
|
||||||
|
{
|
||||||
|
result.push_back(AlsaSequencerPortSelection{
|
||||||
|
port.id,
|
||||||
|
port.name,
|
||||||
|
port.displaySortOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
|
void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ namespace pipedal
|
|||||||
|
|
||||||
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
||||||
virtual void OnHasWifiChanged(bool hasWifi) = 0;
|
virtual void OnHasWifiChanged(bool hasWifi) = 0;
|
||||||
|
virtual void OnAlsaSequencerConfigurationChanged(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -168,6 +169,7 @@ namespace pipedal
|
|||||||
std::vector<AtomOutputListener> atomOutputListeners;
|
std::vector<AtomOutputListener> atomOutputListeners;
|
||||||
|
|
||||||
JackServerSettings jackServerSettings;
|
JackServerSettings jackServerSettings;
|
||||||
|
|
||||||
PluginHost pluginHost;
|
PluginHost pluginHost;
|
||||||
AtomConverter atomConverter; // must be AFTER pluginHost!
|
AtomConverter atomConverter; // must be AFTER pluginHost!
|
||||||
|
|
||||||
@@ -387,6 +389,11 @@ namespace pipedal
|
|||||||
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
|
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
|
||||||
JackChannelSelection GetJackChannelSelection();
|
JackChannelSelection GetJackChannelSelection();
|
||||||
|
|
||||||
|
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
|
||||||
|
AlsaSequencerConfiguration GetAlsaSequencerConfiguration();
|
||||||
|
|
||||||
|
std::vector<AlsaSequencerPortSelection> GetAlsaSequencerPorts();
|
||||||
|
|
||||||
void SetShowStatusMonitor(bool show);
|
void SetShowStatusMonitor(bool show);
|
||||||
bool GetShowStatusMonitor();
|
bool GetShowStatusMonitor();
|
||||||
|
|
||||||
|
|||||||
@@ -1730,6 +1730,22 @@ public:
|
|||||||
{
|
{
|
||||||
auto regulatoryDomains = this->model.GetWifiRegulatoryDomains();
|
auto regulatoryDomains = this->model.GetWifiRegulatoryDomains();
|
||||||
this->Reply(replyTo, "getWifiRegulatoryDomains", regulatoryDomains);
|
this->Reply(replyTo, "getWifiRegulatoryDomains", regulatoryDomains);
|
||||||
|
} else if (message == "setAlsaSequencerConfiguration")
|
||||||
|
{
|
||||||
|
AlsaSequencerConfiguration config;
|
||||||
|
pReader->read(&config);
|
||||||
|
this->model.SetAlsaSequencerConfiguration(config);
|
||||||
|
this->Reply(replyTo, "setAlsaSequencerConfiguration");
|
||||||
|
}
|
||||||
|
else if (message == "getAlsaSequencerConfiguration")
|
||||||
|
{
|
||||||
|
AlsaSequencerConfiguration config = this->model.GetAlsaSequencerConfiguration();
|
||||||
|
this->Reply(replyTo, "getAlsaSequencerConfiguration", config);
|
||||||
|
}
|
||||||
|
else if (message == "getAlsaSequencerPorts")
|
||||||
|
{
|
||||||
|
std::vector<AlsaSequencerPortSelection> result = model.GetAlsaSequencerPorts();
|
||||||
|
this->Reply(replyTo,"getAlsaSequencerPorts", result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1831,6 +1847,11 @@ private:
|
|||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void OnAlsaSequencerConfigurationChanged(const AlsaSequencerConfiguration &alsaSequencerConfiguration) override
|
||||||
|
{
|
||||||
|
Send("onAlsaSequencerConfigurationChanged", alsaSequencerConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void OnNetworkChanging(bool hotspotConnected) override
|
virtual void OnNetworkChanging(bool hotspotConnected) override
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
+79
-18
@@ -261,6 +261,8 @@ void Storage::Initialize()
|
|||||||
catch (const std::exception &)
|
catch (const std::exception &)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
LoadAlsaSequencerConfiguration();
|
||||||
|
|
||||||
LoadWifiConfigSettings();
|
LoadWifiConfigSettings();
|
||||||
LoadWifiDirectConfigSettings();
|
LoadWifiDirectConfigSettings();
|
||||||
LoadUserSettings();
|
LoadUserSettings();
|
||||||
@@ -313,6 +315,10 @@ std::filesystem::path Storage::GetChannelSelectionFileName()
|
|||||||
{
|
{
|
||||||
return this->dataRoot / "JackChannelSelection.json";
|
return this->dataRoot / "JackChannelSelection.json";
|
||||||
}
|
}
|
||||||
|
std::filesystem::path Storage::GetAlsaSequencerConfigurationFileName()
|
||||||
|
{
|
||||||
|
return this->dataRoot / "MidiDevices.json";
|
||||||
|
}
|
||||||
std::filesystem::path Storage::GetIndexFileName() const
|
std::filesystem::path Storage::GetIndexFileName() const
|
||||||
{
|
{
|
||||||
return this->GetPresetsDirectory() / BANKS_FILENAME;
|
return this->GetPresetsDirectory() / BANKS_FILENAME;
|
||||||
@@ -736,46 +742,100 @@ JackChannelSelection Storage::GetJackChannelSelection(const JackConfiguration &j
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void MigrateRawMidiToAlsaSequencer(JackChannelSelection &channelSelection)
|
static AlsaSequencerConfiguration MigrateRawMidiToAlsaSequencer(std::vector<AlsaMidiDeviceInfo> &selectedDevices)
|
||||||
{
|
{
|
||||||
|
AlsaSequencerConfiguration result;
|
||||||
try {
|
try {
|
||||||
auto sequencerPorts = AlsaSequencer::EnumeratePorts();
|
auto sequencerPorts = AlsaSequencer::EnumeratePorts();
|
||||||
|
|
||||||
// Migrate raw MIDI devices to ALSA sequencer ports.
|
// Prepare Migrate raw MIDI devices to ALSA sequencer ports.
|
||||||
std::vector<AlsaMidiDeviceInfo>& selectedDevices = channelSelection.GetInputMidiDevices();
|
|
||||||
|
|
||||||
for (auto i = selectedDevices.begin(); i != selectedDevices.end(); ++i)
|
for (auto i = selectedDevices.begin(); i != selectedDevices.end(); ++i)
|
||||||
{
|
{
|
||||||
if (i->name_.starts_with("hw:")) {
|
if (i->name_.starts_with("hw:")) {
|
||||||
bool migrated = false;
|
|
||||||
for (const auto &port: sequencerPorts)
|
for (const auto &port: sequencerPorts)
|
||||||
{
|
{
|
||||||
if (i->name_ == port.rawMidiDevice)
|
if (i->name_ == port.rawMidiDevice)
|
||||||
{
|
{
|
||||||
// Found a matching sequencer port.
|
result.connections().push_back(
|
||||||
i->name_ = port.rawMidiDevice;
|
AlsaSequencerPortSelection(port.id, port.name,port.displaySortOrder)
|
||||||
i->description_ = port.name;
|
);
|
||||||
migrated = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!migrated) {
|
|
||||||
i = selectedDevices.erase(i);
|
|
||||||
if (i == selectedDevices.end())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const std::exception&e) {
|
} catch (const std::exception&e) {
|
||||||
Lv2Log::error(SS("Failed to migrate MIDI settings. " << e.what()));
|
Lv2Log::error(SS("Failed to migrate MIDI settings. " << e.what()));
|
||||||
// ick.
|
// ick.
|
||||||
channelSelection.GetInputMidiDevices().clear();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
void Storage::LoadAlsaSequencerConfiguration()
|
||||||
|
{
|
||||||
|
auto fileName = this->GetAlsaSequencerConfigurationFileName();
|
||||||
|
|
||||||
|
if (std::filesystem::exists(fileName))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::ifstream s(fileName);
|
||||||
|
json_reader reader(s);
|
||||||
|
AlsaSequencerConfiguration result;
|
||||||
|
reader.read(&result);
|
||||||
|
this->alsaSequencerConfiguration = result;
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::error("I/O error reading %s: %s", fileName.c_str(), e.what());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// migrate legacy settings from JackConfiguration?
|
||||||
|
if (this->isJackChannelSelectionValid)
|
||||||
|
{
|
||||||
|
this->alsaSequencerConfiguration =
|
||||||
|
MigrateRawMidiToAlsaSequencer(
|
||||||
|
this->jackChannelSelection.LegacyGetInputMidiDevices());
|
||||||
|
this->jackChannelSelection.LegacyGetInputMidiDevices().clear(); // let them be clear, the next it gets updated.
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no legacy settings, so just create a default configuration.
|
||||||
|
this->alsaSequencerConfiguration = AlsaSequencerConfiguration();
|
||||||
|
}
|
||||||
|
SaveAlsaSequencerConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void Storage::SaveAlsaSequencerConfiguration()
|
||||||
|
{
|
||||||
|
auto fileName = this->GetAlsaSequencerConfigurationFileName();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pipedal::ofstream_synced s(fileName);
|
||||||
|
json_writer writer(s);
|
||||||
|
writer.write(this->alsaSequencerConfiguration);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::error("I/O error writing %s: %s", fileName.c_str(), e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration)
|
||||||
|
{
|
||||||
|
this->alsaSequencerConfiguration = alsaSequencerConfiguration;
|
||||||
|
SaveAlsaSequencerConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
AlsaSequencerConfiguration Storage::GetAlsaSequencerConfiguration() const
|
||||||
|
{
|
||||||
|
return this->alsaSequencerConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Storage::LoadChannelSelection()
|
void Storage::LoadChannelSelection()
|
||||||
{
|
{
|
||||||
@@ -788,7 +848,6 @@ void Storage::LoadChannelSelection()
|
|||||||
json_reader reader(s);
|
json_reader reader(s);
|
||||||
reader.read(&this->jackChannelSelection);
|
reader.read(&this->jackChannelSelection);
|
||||||
this->isJackChannelSelectionValid = true;
|
this->isJackChannelSelectionValid = true;
|
||||||
MigrateRawMidiToAlsaSequencer(this->jackChannelSelection);
|
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
@@ -801,6 +860,8 @@ void Storage::SaveChannelSelection()
|
|||||||
auto fileName = this->GetChannelSelectionFileName();
|
auto fileName = this->GetChannelSelectionFileName();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// replaced with AlsaSequencerConfiguration. Delete legacy data.
|
||||||
|
this->jackChannelSelection.LegacyGetInputMidiDevices().clear();
|
||||||
pipedal::ofstream_synced s(fileName);
|
pipedal::ofstream_synced s(fileName);
|
||||||
json_writer writer(s, false);
|
json_writer writer(s, false);
|
||||||
writer.write(this->jackChannelSelection);
|
writer.write(this->jackChannelSelection);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "FileEntry.hpp"
|
#include "FileEntry.hpp"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "FilePropertyDirectoryTree.hpp"
|
#include "FilePropertyDirectoryTree.hpp"
|
||||||
|
#include "AlsaSequencer.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
@@ -84,6 +85,7 @@ private:
|
|||||||
std::filesystem::path GetIndexFileName() const;
|
std::filesystem::path GetIndexFileName() const;
|
||||||
std::filesystem::path GetBankFileName(const std::string & name) const;
|
std::filesystem::path GetBankFileName(const std::string & name) const;
|
||||||
std::filesystem::path GetChannelSelectionFileName();
|
std::filesystem::path GetChannelSelectionFileName();
|
||||||
|
std::filesystem::path GetAlsaSequencerConfigurationFileName();
|
||||||
std::filesystem::path GetCurrentPresetPath() const;
|
std::filesystem::path GetCurrentPresetPath() const;
|
||||||
|
|
||||||
void LoadBankIndex();
|
void LoadBankIndex();
|
||||||
@@ -94,11 +96,19 @@ private:
|
|||||||
|
|
||||||
void LoadChannelSelection();
|
void LoadChannelSelection();
|
||||||
void SaveChannelSelection();
|
void SaveChannelSelection();
|
||||||
|
|
||||||
|
void LoadAlsaSequencerConfiguration();
|
||||||
|
void SaveAlsaSequencerConfiguration();
|
||||||
|
|
||||||
|
|
||||||
void SaveBankFile(const std::string& name,const BankFile&bankFile);
|
void SaveBankFile(const std::string& name,const BankFile&bankFile);
|
||||||
void LoadBankFile(const std::string &name,BankFile *pBank);
|
void LoadBankFile(const std::string &name,BankFile *pBank);
|
||||||
std::string GetPresetCopyName(const std::string &name);
|
std::string GetPresetCopyName(const std::string &name);
|
||||||
bool isJackChannelSelectionValid = false;
|
bool isJackChannelSelectionValid = false;
|
||||||
JackChannelSelection jackChannelSelection;
|
JackChannelSelection jackChannelSelection;
|
||||||
|
|
||||||
|
AlsaSequencerConfiguration alsaSequencerConfiguration;;
|
||||||
|
|
||||||
WifiConfigSettings wifiConfigSettings;
|
WifiConfigSettings wifiConfigSettings;
|
||||||
WifiDirectConfigSettings wifiDirectConfigSettings;
|
WifiDirectConfigSettings wifiDirectConfigSettings;
|
||||||
|
|
||||||
@@ -126,6 +136,7 @@ public:
|
|||||||
void LoadWifiDirectConfigSettings();
|
void LoadWifiDirectConfigSettings();
|
||||||
void LoadUserSettings();
|
void LoadUserSettings();
|
||||||
void SaveUserSettings();
|
void SaveUserSettings();
|
||||||
|
|
||||||
void LoadBank(int64_t instanceId);
|
void LoadBank(int64_t instanceId);
|
||||||
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
|
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
|
||||||
const Pedalboard& GetCurrentPreset();
|
const Pedalboard& GetCurrentPreset();
|
||||||
@@ -181,6 +192,10 @@ public:
|
|||||||
void SaveCurrentPreset(const CurrentPreset ¤tPreset);
|
void SaveCurrentPreset(const CurrentPreset ¤tPreset);
|
||||||
bool RestoreCurrentPreset(CurrentPreset*pResult);
|
bool RestoreCurrentPreset(CurrentPreset*pResult);
|
||||||
|
|
||||||
|
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
|
||||||
|
AlsaSequencerConfiguration GetAlsaSequencerConfiguration() const;
|
||||||
|
|
||||||
|
|
||||||
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
|
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2025 Robin E. R. Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
export class AlsaSequencerPortSelection {
|
||||||
|
deserialize(json: any) {
|
||||||
|
this.id = json.id;
|
||||||
|
this.name = json.name;
|
||||||
|
this.sortOrder = json.sortOrder;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
static deserialize_array(input: any): AlsaSequencerPortSelection[] {
|
||||||
|
let result: AlsaSequencerPortSelection[] = [];
|
||||||
|
for (let i = 0; i < input.length; ++i) {
|
||||||
|
result[i] = new AlsaSequencerPortSelection().deserialize(input[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
id: string = "";
|
||||||
|
name: string = "";
|
||||||
|
sortOrder: number = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AlsaSequencerConfiguration {
|
||||||
|
deserialize(input: any) {
|
||||||
|
this.connections = AlsaSequencerPortSelection.deserialize_array(input.connections);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
deserialize_array(input: any): AlsaSequencerConfiguration[] {
|
||||||
|
let result: AlsaSequencerConfiguration[] = [];
|
||||||
|
for (let i = 0; i < input.length; ++i) {
|
||||||
|
result[i] = new AlsaSequencerConfiguration().deserialize(input[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
connections: AlsaSequencerPortSelection[] = [];
|
||||||
|
};
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
|
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
|
||||||
import { CssBaseline } from '@mui/material';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
|
||||||
import VirtualKeyboardHandler from './VirtualKeyboardHandler';
|
import VirtualKeyboardHandler from './VirtualKeyboardHandler';
|
||||||
import AppThemed from "./AppThemed";
|
import AppThemed from "./AppThemed";
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import Slider, { SliderProps } from "@mui/material/Slider";
|
|||||||
import Checkbox from "@mui/material/Checkbox";
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
import TimebaseSelectorDialog from "./TimebaseselectorDialog";
|
import TimebaseSelectorDialog from "./TimebaseselectorDialog";
|
||||||
import Timebase, { TimebaseUnits } from "./Timebase";
|
import Timebase, { TimebaseUnits } from "./Timebase";
|
||||||
import { useWindowHeight } from "@react-hook/window-size";
|
import useWindowSize from "./UseWindowSize";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import PlayArrow from "@mui/icons-material/PlayArrow";
|
import PlayArrow from "@mui/icons-material/PlayArrow";
|
||||||
import StopIcon from '@mui/icons-material/Stop';
|
import StopIcon from '@mui/icons-material/Stop';
|
||||||
@@ -405,8 +405,8 @@ export default function LoopDialog(props: LoopDialogProps) {
|
|||||||
const [loopEnd, setLoopEnd] = React.useState(props.value.loopEnd);
|
const [loopEnd, setLoopEnd] = React.useState(props.value.loopEnd);
|
||||||
|
|
||||||
|
|
||||||
const height = useWindowHeight();
|
const [windowSize] = useWindowSize();
|
||||||
const fullScreen = height < 500;
|
const fullScreen = windowSize.height < 500;
|
||||||
|
|
||||||
function cancelPlaying() {
|
function cancelPlaying() {
|
||||||
props.onCancelPlaying();
|
props.onCancelPlaying();
|
||||||
@@ -444,7 +444,7 @@ export default function LoopDialog(props: LoopDialogProps) {
|
|||||||
open={props.isOpen}
|
open={props.isOpen}
|
||||||
onEnterKey={() => {
|
onEnterKey={() => {
|
||||||
}}
|
}}
|
||||||
fullScreen={height < 500}
|
fullScreen={fullScreen}
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiDialog-container": {
|
"& .MuiDialog-container": {
|
||||||
"& .MuiPaper-root": {
|
"& .MuiPaper-root": {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { ColorTheme, getColorScheme, setColorScheme } from './DarkMode';
|
|||||||
import FilePropertyDirectoryTree from './FilePropertyDirectoryTree';
|
import FilePropertyDirectoryTree from './FilePropertyDirectoryTree';
|
||||||
import AudioFileMetadata from './AudioFileMetadata';
|
import AudioFileMetadata from './AudioFileMetadata';
|
||||||
import { pathFileName } from './FileUtils';
|
import { pathFileName } from './FileUtils';
|
||||||
|
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||||
|
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
@@ -452,6 +453,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
favorites: ObservableProperty<FavoritesList> = new ObservableProperty<FavoritesList>({});
|
favorites: ObservableProperty<FavoritesList> = new ObservableProperty<FavoritesList>({});
|
||||||
|
|
||||||
|
alsaSequencerConfiguration : ObservableProperty<AlsaSequencerConfiguration> = new ObservableProperty<AlsaSequencerConfiguration>(new AlsaSequencerConfiguration());
|
||||||
|
|
||||||
presets: ObservableProperty<PresetIndex> = new ObservableProperty<PresetIndex>
|
presets: ObservableProperty<PresetIndex> = new ObservableProperty<PresetIndex>
|
||||||
(
|
(
|
||||||
@@ -668,6 +670,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.handlePluginPresetsChanged(pluginUri);
|
this.handlePluginPresetsChanged(pluginUri);
|
||||||
} else if (message === "onJackConfigurationChanged") {
|
} else if (message === "onJackConfigurationChanged") {
|
||||||
this.jackConfiguration.set(new JackConfiguration().deserialize(body));
|
this.jackConfiguration.set(new JackConfiguration().deserialize(body));
|
||||||
|
} else if (message === "onAlsaSequencerConfigurationChanged") {
|
||||||
|
this.alsaSequencerConfiguration.set(new AlsaSequencerConfiguration().deserialize(body));
|
||||||
|
|
||||||
} else if (message === "onLoadPluginPreset") {
|
} else if (message === "onLoadPluginPreset") {
|
||||||
let instanceId = body.instanceId as number;
|
let instanceId = body.instanceId as number;
|
||||||
let controlValues = ControlValue.deserializeArray(body.controlValues);
|
let controlValues = ControlValue.deserializeArray(body.controlValues);
|
||||||
@@ -1134,6 +1139,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.jackSettings.set(new JackChannelSelection().deserialize(
|
this.jackSettings.set(new JackChannelSelection().deserialize(
|
||||||
await this.getWebSocket().request<any>("getJackSettings")
|
await this.getWebSocket().request<any>("getJackSettings")
|
||||||
));
|
));
|
||||||
|
this.alsaSequencerConfiguration.set(new AlsaSequencerConfiguration().deserialize(
|
||||||
|
await this.getWebSocket().request<any>("getAlsaSequencerConfiguration")
|
||||||
|
));
|
||||||
this.banks.set(new BankIndex().deserialize(await this.getWebSocket().request<any>("getBankIndex")));
|
this.banks.set(new BankIndex().deserialize(await this.getWebSocket().request<any>("getBankIndex")));
|
||||||
|
|
||||||
this.favorites.set(await this.getWebSocket().request<FavoritesList>("getFavorites"));
|
this.favorites.set(await this.getWebSocket().request<FavoritesList>("getFavorites"));
|
||||||
@@ -3193,6 +3201,25 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
// notify the server.
|
// notify the server.
|
||||||
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title });
|
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title });
|
||||||
}
|
}
|
||||||
|
setAlsaSequencerConfiguration(alsaSequencerConfiguration: AlsaSequencerConfiguration): void {
|
||||||
|
this.webSocket?.send("setAlsaSequencerConfiguration", alsaSequencerConfiguration);
|
||||||
|
}
|
||||||
|
async getAlsaSequencerConfiguration(): Promise<AlsaSequencerConfiguration> {
|
||||||
|
if (this.webSocket)
|
||||||
|
{
|
||||||
|
let result = await this.webSocket.request<any>("getAlsaSequencerConfiguration");
|
||||||
|
return new AlsaSequencerConfiguration().deserialize(result);
|
||||||
|
}
|
||||||
|
throw new Error("No connection.");
|
||||||
|
}
|
||||||
|
async getAlsaSequencerPorts(): Promise<AlsaSequencerPortSelection[]> {
|
||||||
|
if (this.webSocket) {
|
||||||
|
let result = await this.webSocket.request<AlsaSequencerPortSelection[]>("getAlsaSequencerPorts");
|
||||||
|
return AlsaSequencerPortSelection.deserialize_array(result);
|
||||||
|
}
|
||||||
|
throw new Error("No connection.");
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let instance: PiPedalModel | undefined = undefined;
|
let instance: PiPedalModel | undefined = undefined;
|
||||||
|
|||||||
@@ -17,105 +17,191 @@
|
|||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
// 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 React from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
|
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||||
import DialogEx from './DialogEx';
|
import DialogEx from './DialogEx';
|
||||||
|
|
||||||
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
|
||||||
export interface SelectMidiChannelsDialogProps {
|
export interface SelectMidiChannelsDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
selectedChannels: AlsaMidiDeviceInfo[];
|
onClose: () => void;
|
||||||
availableChannels: AlsaMidiDeviceInfo[];
|
|
||||||
onClose: (selectedChannels: AlsaMidiDeviceInfo[] | null) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChecked(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo): boolean {
|
interface DialogItem {
|
||||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
id: string;
|
||||||
if (selectedChannels[i].equals(channel)) return true;
|
name: string;
|
||||||
}
|
sortOrder: number
|
||||||
return false;
|
offline: boolean;
|
||||||
}
|
};
|
||||||
function addPort(availableChannels: AlsaMidiDeviceInfo[], selectedChannels: AlsaMidiDeviceInfo[], newChannel: AlsaMidiDeviceInfo)
|
|
||||||
:AlsaMidiDeviceInfo[]
|
|
||||||
{
|
|
||||||
let result: AlsaMidiDeviceInfo[] = [];
|
|
||||||
for (let i = 0; i < availableChannels.length; ++i) {
|
|
||||||
let channel = availableChannels[i];
|
|
||||||
if (isChecked(selectedChannels, channel) || channel.equals(newChannel)) {
|
|
||||||
result.push(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePort(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo)
|
|
||||||
:AlsaMidiDeviceInfo[]
|
|
||||||
{
|
|
||||||
let result: AlsaMidiDeviceInfo[] = [];
|
|
||||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
|
||||||
if (!selectedChannels[i].equals(channel)) {
|
|
||||||
result.push(selectedChannels[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
|
function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
|
||||||
//const classes = useStyles();
|
//const classes = useStyles();
|
||||||
const { onClose, selectedChannels, availableChannels, open } = props;
|
const { open, onClose } = props;
|
||||||
const [currentSelection, setCurrentSelection] = useState(selectedChannels);
|
const [availablePorts, setAvailablePorts] = useState<AlsaSequencerPortSelection[] | null>(null);
|
||||||
|
const [configuration, setConfiguration] = useState<AlsaSequencerConfiguration | null>(null);
|
||||||
|
const [allPorts, setAllPorts] = useState<DialogItem[] | null>(null);
|
||||||
|
const [model] = useState<PiPedalModel>(PiPedalModelFactory.getInstance());
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (open) {
|
||||||
let toggleSelect = (value: AlsaMidiDeviceInfo) => {
|
model.getAlsaSequencerPorts().then((ports) => {
|
||||||
if (!isChecked(currentSelection, value)) {
|
setAvailablePorts(ports);
|
||||||
setCurrentSelection(addPort(availableChannels, currentSelection, value));
|
}).catch((error) => {
|
||||||
|
model.showAlert(error);
|
||||||
|
setAvailablePorts(null);
|
||||||
|
});
|
||||||
|
model.getAlsaSequencerConfiguration().then((config) => {
|
||||||
|
setConfiguration(config);
|
||||||
|
}).catch((error) => {
|
||||||
|
model.showAlert(error);
|
||||||
|
setConfiguration(null);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setCurrentSelection(removePort(currentSelection, value));
|
return () => { };
|
||||||
}
|
}
|
||||||
|
}, [open]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (availablePorts !== null && configuration !== null) {
|
||||||
|
let result: DialogItem[] = [];
|
||||||
|
|
||||||
|
for (let port of availablePorts) {
|
||||||
|
result.push({
|
||||||
|
id: port.id,
|
||||||
|
name: port.name,
|
||||||
|
sortOrder: port.sortOrder,
|
||||||
|
offline: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// include ports that have been previously selected but are not in the current list of available ports
|
||||||
|
for (let port of configuration.connections) {
|
||||||
|
if (!availablePorts.some((p) => p.id === port.id)) {
|
||||||
|
result.push(
|
||||||
|
{
|
||||||
|
id: port.id,
|
||||||
|
name: port.name,
|
||||||
|
sortOrder: port.sortOrder,
|
||||||
|
offline: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.sort((a, b) => {
|
||||||
|
return a.sortOrder - b.sortOrder;
|
||||||
|
});
|
||||||
|
setAllPorts(result);
|
||||||
|
} else {
|
||||||
|
setAllPorts(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [availablePorts, configuration]);
|
||||||
|
|
||||||
|
const isChecked = (value: DialogItem) => {
|
||||||
|
if (availablePorts === null || configuration === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return configuration.connections.some((port) => port.id === value.id);
|
||||||
|
};
|
||||||
|
const setChecked = (value_: DialogItem, checked: boolean) => {
|
||||||
|
if (availablePorts === null || configuration === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value = new AlsaSequencerPortSelection();
|
||||||
|
value.id = value_.id;
|
||||||
|
value.name = value_.name;
|
||||||
|
value.sortOrder = value_.sortOrder;
|
||||||
|
let newConnections = configuration.connections.slice();
|
||||||
|
if (checked) {
|
||||||
|
newConnections.push(value);
|
||||||
|
} else {
|
||||||
|
newConnections = newConnections.filter((port) => port.id !== value.id);
|
||||||
|
}
|
||||||
|
let newConfiguration = new AlsaSequencerConfiguration();
|
||||||
|
newConfiguration.connections = newConnections;
|
||||||
|
setConfiguration(newConfiguration);
|
||||||
|
};
|
||||||
|
let toggleSelect = (value: DialogItem) => {
|
||||||
|
if (availablePorts === null || configuration === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isChecked(value)) {
|
||||||
|
setChecked(value, true);
|
||||||
|
} else {
|
||||||
|
setChecked(value, false);
|
||||||
|
}
|
||||||
|
setChanged(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
onClose(null);
|
onClose();
|
||||||
};
|
};
|
||||||
const handleOk = (): void => {
|
const handleOk = (): void => {
|
||||||
onClose(currentSelection);
|
if (changed && configuration !== null) {
|
||||||
|
model.setAlsaSequencerConfiguration(configuration);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogEx tag="midiChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
<DialogEx tag="midiChannels" onClose={handleClose} aria-labelledby="select-midi-inputs" open={open}
|
||||||
onEnterKey={handleOk}
|
fullWidth maxWidth="xs"
|
||||||
|
onEnterKey={handleClose}
|
||||||
>
|
>
|
||||||
<DialogTitle id="simple-dialog-title">Select MIDI Device</DialogTitle>
|
<DialogTitle id="select-midi-inputs">Select MIDI Inputs</DialogTitle>
|
||||||
<List>
|
<DialogContent dividers>
|
||||||
{availableChannels.map((channel) => (
|
<List>
|
||||||
<ListItemButton>
|
{allPorts !== null && allPorts.length === 0 && (
|
||||||
<FormControlLabel
|
<Typography variant="body2" style={{ marginLeft: 32, marginRight: 24, marginTop: 8, marginBottom: 16 }}>
|
||||||
control={
|
No MIDI devices found.
|
||||||
<Checkbox checked={isChecked(currentSelection, channel)} onClick={() => toggleSelect(channel)} key={channel.name} />
|
</Typography>)}
|
||||||
}
|
{allPorts != null && allPorts.map((port) => (
|
||||||
label={channel.description}
|
<ListItemButton key={port.id}>
|
||||||
/>
|
<FormControlLabel
|
||||||
</ListItemButton>
|
control={
|
||||||
)
|
<Checkbox
|
||||||
|
checked={isChecked(port)}
|
||||||
|
onClick={() => toggleSelect(port)} />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
(
|
||||||
|
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
|
||||||
|
<Typography
|
||||||
|
color={ port.offline ? "textSecondary" : "textPrimary" }
|
||||||
|
noWrap variant="body2"
|
||||||
|
style={{ flex: "1 1 auto" }}
|
||||||
|
>
|
||||||
|
{port.name}
|
||||||
|
</Typography>
|
||||||
|
{port.offline && (
|
||||||
|
<Typography color="textSecondary" variant="body2" >
|
||||||
|
(offline)
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
|
||||||
)}
|
)}
|
||||||
{availableChannels.length === 0 && (
|
</List>
|
||||||
<Typography variant="body2" style={{ marginLeft: 32, marginRight: 24,marginTop:8, marginBottom: 16}}>
|
</DialogContent>
|
||||||
No MIDI devices found.</Typography>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ import WifiConfigDialog from './WifiConfigDialog';
|
|||||||
import WifiDirectConfigDialog from './WifiDirectConfigDialog';
|
import WifiDirectConfigDialog from './WifiDirectConfigDialog';
|
||||||
import DialogEx from './DialogEx'
|
import DialogEx from './DialogEx'
|
||||||
import GovernorSettings from './GovernorSettings';
|
import GovernorSettings from './GovernorSettings';
|
||||||
import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo';
|
|
||||||
import SystemMidiBindingsDialog from './SystemMidiBindingsDialog';
|
import SystemMidiBindingsDialog from './SystemMidiBindingsDialog';
|
||||||
import SelectThemeDialog from './SelectThemeDialog';
|
import SelectThemeDialog from './SelectThemeDialog';
|
||||||
|
import {AlsaSequencerConfiguration} from './AlsaSequencer';
|
||||||
|
|
||||||
import Slide, { SlideProps } from '@mui/material/Slide';
|
import Slide, { SlideProps } from '@mui/material/Slide';
|
||||||
import {createStyles} from './WithStyles';
|
import {createStyles} from './WithStyles';
|
||||||
@@ -73,6 +73,8 @@ interface SettingsDialogState {
|
|||||||
jackConfiguration: JackConfiguration;
|
jackConfiguration: JackConfiguration;
|
||||||
jackSettings: JackChannelSelection;
|
jackSettings: JackChannelSelection;
|
||||||
jackServerSettings: JackServerSettings;
|
jackServerSettings: JackServerSettings;
|
||||||
|
alsaSequencerConfiguration: AlsaSequencerConfiguration;
|
||||||
|
|
||||||
jackStatus?: JackHostStatus;
|
jackStatus?: JackHostStatus;
|
||||||
governorSettings: GovernorSettings;
|
governorSettings: GovernorSettings;
|
||||||
continueDisabled: boolean;
|
continueDisabled: boolean;
|
||||||
@@ -184,6 +186,7 @@ const SettingsDialog = withStyles(
|
|||||||
jackConfiguration: this.model.jackConfiguration.get(),
|
jackConfiguration: this.model.jackConfiguration.get(),
|
||||||
jackStatus: undefined,
|
jackStatus: undefined,
|
||||||
jackSettings: this.model.jackSettings.get(),
|
jackSettings: this.model.jackSettings.get(),
|
||||||
|
alsaSequencerConfiguration: this.model.alsaSequencerConfiguration.get(),
|
||||||
wifiConfigSettings: this.model.wifiConfigSettings.get(),
|
wifiConfigSettings: this.model.wifiConfigSettings.get(),
|
||||||
wifiDirectConfigSettings: this.model.wifiDirectConfigSettings.get(),
|
wifiDirectConfigSettings: this.model.wifiDirectConfigSettings.get(),
|
||||||
governorSettings: this.model.governorSettings.get(),
|
governorSettings: this.model.governorSettings.get(),
|
||||||
@@ -207,6 +210,7 @@ const SettingsDialog = withStyles(
|
|||||||
hasWifiDevice: this.model.hasWifiDevice.get()
|
hasWifiDevice: this.model.hasWifiDevice.get()
|
||||||
};
|
};
|
||||||
this.handleJackConfigurationChanged = this.handleJackConfigurationChanged.bind(this);
|
this.handleJackConfigurationChanged = this.handleJackConfigurationChanged.bind(this);
|
||||||
|
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
|
||||||
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
||||||
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
|
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
|
||||||
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
|
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
|
||||||
@@ -306,6 +310,11 @@ const SettingsDialog = withStyles(
|
|||||||
jackConfiguration: this.model.jackConfiguration.get()
|
jackConfiguration: this.model.jackConfiguration.get()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
handleAlsaSequencerConfigurationChanged(): void {
|
||||||
|
this.setState({
|
||||||
|
alsaSequencerConfiguration: this.model.alsaSequencerConfiguration.get()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mounted: boolean = false;
|
mounted: boolean = false;
|
||||||
active: boolean = false;
|
active: boolean = false;
|
||||||
@@ -333,6 +342,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.model.showStatusMonitor.addOnChangedHandler(this.handleShowStatusMonitorChanged);
|
this.model.showStatusMonitor.addOnChangedHandler(this.handleShowStatusMonitorChanged);
|
||||||
this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged);
|
this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged);
|
||||||
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
|
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
|
||||||
|
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||||
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
|
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||||
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||||
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||||
@@ -352,6 +362,7 @@ const SettingsDialog = withStyles(
|
|||||||
});
|
});
|
||||||
this.handleConnectionStateChanged();
|
this.handleConnectionStateChanged();
|
||||||
this.handleJackConfigurationChanged();
|
this.handleJackConfigurationChanged();
|
||||||
|
this.handleAlsaSequencerConfigurationChanged();
|
||||||
this.handleJackSettingsChanged();
|
this.handleJackSettingsChanged();
|
||||||
this.handleShowStatusMonitorChanged();
|
this.handleShowStatusMonitorChanged();
|
||||||
this.handleJackServerSettingsChanged();
|
this.handleJackServerSettingsChanged();
|
||||||
@@ -368,6 +379,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.model.showStatusMonitor.removeOnChangedHandler(this.handleShowStatusMonitorChanged);
|
this.model.showStatusMonitor.removeOnChangedHandler(this.handleShowStatusMonitorChanged);
|
||||||
|
|
||||||
this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged);
|
this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged);
|
||||||
|
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||||
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
|
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
|
||||||
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
|
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||||
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||||
@@ -437,13 +449,7 @@ const SettingsDialog = withStyles(
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
handleSelectMidiDialogResult(channels: AlsaMidiDeviceInfo[] | null): void {
|
handleSelectMidiDialogResult(): void {
|
||||||
if (channels) {
|
|
||||||
let newSelection: JackChannelSelection = this.state.jackSettings.clone();
|
|
||||||
newSelection.inputMidiDevices = channels;
|
|
||||||
this.model.setJackSettings(newSelection);
|
|
||||||
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showMidiSelectDialog: false,
|
showMidiSelectDialog: false,
|
||||||
|
|
||||||
@@ -477,7 +483,7 @@ const SettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
midiSummary(): string {
|
midiSummary(): string {
|
||||||
let ports = this.state.jackSettings.inputMidiDevices;
|
let ports = this.state.alsaSequencerConfiguration.connections;
|
||||||
if (ports.length === 0) return "Disabled";
|
if (ports.length === 0) return "Disabled";
|
||||||
|
|
||||||
let result = "";
|
let result = "";
|
||||||
@@ -485,7 +491,7 @@ const SettingsDialog = withStyles(
|
|||||||
if (result.length !== 0) {
|
if (result.length !== 0) {
|
||||||
result += ", ";
|
result += ", ";
|
||||||
}
|
}
|
||||||
result += port.description;
|
result += port.name;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -689,7 +695,9 @@ const SettingsDialog = withStyles(
|
|||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>Select MIDI input</Typography>
|
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>Select MIDI input</Typography>
|
||||||
|
|
||||||
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{this.midiSummary()}</Typography>
|
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{
|
||||||
|
this.midiSummary()
|
||||||
|
}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiMessageSettings()} >
|
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiMessageSettings()} >
|
||||||
@@ -929,9 +937,7 @@ const SettingsDialog = withStyles(
|
|||||||
(this.state.showMidiSelectDialog) &&
|
(this.state.showMidiSelectDialog) &&
|
||||||
(
|
(
|
||||||
<SelectMidiChannelsDialog open={this.state.showMidiSelectDialog}
|
<SelectMidiChannelsDialog open={this.state.showMidiSelectDialog}
|
||||||
onClose={(selectedChannels: AlsaMidiDeviceInfo[] | null) => this.handleSelectMidiDialogResult(selectedChannels)}
|
onClose={() => this.handleSelectMidiDialogResult()}
|
||||||
selectedChannels={this.state.jackSettings.inputMidiDevices}
|
|
||||||
availableChannels={this.state.jackConfiguration.inputMidiDevices}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import ButtonBase from '@mui/material/ButtonBase';
|
|||||||
import FilePropertyDialog from './FilePropertyDialog';
|
import FilePropertyDialog from './FilePropertyDialog';
|
||||||
import JsonAtom from './JsonAtom';
|
import JsonAtom from './JsonAtom';
|
||||||
import { UiFileProperty } from './Lv2Plugin';
|
import { UiFileProperty } from './Lv2Plugin';
|
||||||
import { Divider } from '@mui/material';
|
import Divider from '@mui/material/Divider';
|
||||||
import useWindowSize from './UseWindowSize';
|
import useWindowSize from './UseWindowSize';
|
||||||
import { getAlbumArtUri } from './AudioFileMetadata';
|
import { getAlbumArtUri } from './AudioFileMetadata';
|
||||||
import RepeatIcon from '@mui/icons-material/Repeat';
|
import RepeatIcon from '@mui/icons-material/Repeat';
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -161,6 +161,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let effectiveTitle: React.ReactNode | null = null;
|
let effectiveTitle: React.ReactNode | null = null;
|
||||||
let placement: "top-start" | "right" = "top-start";
|
let placement: "top-start" | "right" = "top-start";
|
||||||
if (valueTooltip !== undefined && !isLongPress) {
|
if (valueTooltip !== undefined && !isLongPress) {
|
||||||
@@ -181,44 +182,40 @@ function ToolTipEx(props: ToolTipExProps) {
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: 'inline-block' }}
|
<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
|
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);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -21,38 +21,43 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
const getSize = () => {
|
const getSize = () => {
|
||||||
return {
|
return {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface WindowSize {
|
export interface WindowSize {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
export default function useWindowSize() {
|
export default function useWindowSize() {
|
||||||
|
|
||||||
const [size, setSize] = useState<WindowSize>(getSize());
|
|
||||||
|
|
||||||
const handleResize = useCallback(() => {
|
|
||||||
let ticking = false;
|
|
||||||
if (!ticking) {
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
setSize(getSize());
|
|
||||||
ticking = false;
|
|
||||||
});
|
|
||||||
ticking = true;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [size, setSize] = useState<WindowSize>(getSize());
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
useEffect(() => {
|
||||||
}, []);
|
let mounted = true;
|
||||||
|
let handleResizeT = () => {
|
||||||
return [size];
|
let pendingCallback = false;
|
||||||
|
if (!pendingCallback) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (mounted) {
|
||||||
|
setSize(getSize());
|
||||||
|
pendingCallback = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pendingCallback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handleResizeT);
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
window.removeEventListener('resize', handleResizeT);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [size];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user