diff --git a/react/src/Jack.tsx b/react/src/Jack.tsx index ebca391a..1f84d126 100644 --- a/react/src/Jack.tsx +++ b/react/src/Jack.tsx @@ -20,6 +20,12 @@ import {PiPedalArgumentError} from './PiPedalError'; import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo'; +function getChannelNumber(channelId: string): number { + let pos = channelId.indexOf("_"); + let i = Number(channelId.substring(pos+1)); + return i; +} + export class JackChannelSelection { deserialize(input: any): JackChannelSelection { @@ -57,7 +63,8 @@ export class JackChannelSelection { return "Stereo"; } if (selectedChannels.length === 1) return "Mono"; - return "Invalid selection"; + + return "\u00A0"; // nbsp } if (selectedChannels.length === 0) return "Invalid selection"; @@ -69,12 +76,22 @@ export class JackChannelSelection { } else if (selectedChannels[0] === availableChannels[1]) { return "Mono (right channel only)"; } else { + return "\u00A0"; // nbsp throw new PiPedalArgumentError("Invalid channel selection."); // should be subset of jackConfiguration. } } else { - if (selectedChannels.length === 2) return "Stereo (custom selection)"; - if (selectedChannels.length === 1) return "Mono (custom selection)"; - throw new PiPedalArgumentError("Invalid channel selection."); // should be subset of jackConfiguration. + if (selectedChannels.length === 2) + { + let result: string = "Stereo (" + (getChannelNumber(selectedChannels[0])+1) + "," + (getChannelNumber(selectedChannels[1])+1) + ")"; + return result; + } + if (selectedChannels.length === 1) + { + let result: string = "Mono (" + (getChannelNumber(selectedChannels[0])+1) +")"; + return result; + + } + return "\u00A0"; // nbsp } } getAudioInputDisplayValue(jackConfiguration: JackConfiguration): string diff --git a/react/src/SelectChannelsDialog.tsx b/react/src/SelectChannelsDialog.tsx index 59672dd9..c79f7e48 100644 --- a/react/src/SelectChannelsDialog.tsx +++ b/react/src/SelectChannelsDialog.tsx @@ -65,12 +65,22 @@ function removePort(selectedChannels: string[], channel: string) { return result; } +function makeChannelName(id: string) +{ + let pos = id.lastIndexOf("_"); + if (pos === -1) + { + return id; + } + let i = Number(id.substring(pos+1)); + return "Channel " + (i+1); +} function SelectChannelsDialog(props: SelectChannelsDialogProps) { //const classes = useStyles(); const { onClose, selectedChannels, availableChannels, open } = props; const [currentSelection, setCurrentSelection] = useState(selectedChannels); - let showCheckboxes = availableChannels.length > 2; + let showCheckboxes = availableChannels.length !== 2; if (showCheckboxes) { let toggleSelect = (value: string) => { @@ -85,23 +95,25 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) { onClose(null); }; const handleOk = (): void => { - onClose(currentSelection); + if (currentSelection.length >= 1 && currentSelection.length <= 2) + { + onClose(currentSelection); + } }; - return ( - Select Channels {availableChannels.map((channel) => ( - + toggleSelect(channel)} key={channel} /> + toggleSelect(channel)} style={{marginLeft: 24}} /> } - label={channel} + label={makeChannelName(channel)} /> ) diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index 5e0389e1..0009618f 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -651,7 +651,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( - this.handleInputSelection()} disabled={!isConfigValid} + this.handleInputSelection()} + disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1} style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} > @@ -661,7 +662,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( {this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)} - this.handleOutputSelection()} disabled={!isConfigValid} + this.handleOutputSelection()} + disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1} style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} > diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 8696b9ca..11b28898 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -32,6 +32,7 @@ #include #include "RtInversionGuard.hpp" #include "PiPedalException.hpp" +#include "DummyAudioDriver.hpp" #include "CpuUse.hpp" @@ -2022,13 +2023,15 @@ namespace pipedal { if (jackServerSettings.IsDummyAudioDevice()) { + auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice()); + inputAudioPorts.clear(); - inputAudioPorts.push_back(std::string("system::capture_0")); - inputAudioPorts.push_back(std::string("system::capture_1")); outputAudioPorts.clear(); - outputAudioPorts.push_back(std::string("system::playback_0")); - outputAudioPorts.push_back(std::string("system::playback_1")); - + for (uint32_t i = 0; i < nChannels; ++i) + { + inputAudioPorts.push_back(std::string(SS("system::capture_" << i))); + outputAudioPorts.push_back(std::string(SS("system::playback_" << i))); + } return true; } diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 1d39ad54..827b6b62 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -75,9 +75,9 @@ class AlsaTester: private AudioDriverHost { } JackChannelSelection channelSelection( - jackConfiguration.GetInputAudioPorts(), - jackConfiguration.GetOutputAudioPorts(), - jackConfiguration.GetInputMidiDevices()); + jackConfiguration.inputAudioPorts(), + jackConfiguration.outputAudioPorts(), + jackConfiguration.inputMidiDevices()); #if JACK_HOST if (useJack) diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index f9093802..fef5e8ce 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -1636,7 +1636,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW if (jackServerSettings.IsDummyAudioDevice()) { this->isDummyAudioDriver = true; - this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this)); + this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice())); } else { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d35728af..9d7f1a13 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -637,6 +637,7 @@ add_executable(pipedal_latency_test PiPedalAlsa.hpp PiPedalAlsa.cpp asan_options.cpp AlsaDriver.cpp AlsaDriver.hpp + DummyAudioDriver.cpp DummyAudioDriver.hpp JackConfiguration.hpp JackConfiguration.cpp JackServerSettings.hpp JackServerSettings.cpp CpuUse.hpp diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index 6194394a..8353c8b5 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include "ss.hpp" #include "CpuUse.hpp" @@ -91,11 +93,16 @@ namespace pipedal uint8_t *rawPlaybackBuffer = nullptr; AudioDriverHost *driverHost = nullptr; + uint32_t channels = 2; public: - DummyDriverImpl(AudioDriverHost *driverHost) + DummyDriverImpl(AudioDriverHost *driverHost,const std::string&deviceName) : driverHost(driverHost) + , channels(GetDummyAudioChannels(deviceName)) { + captureChannels = channels; + playbackChannels = channels; + } virtual ~DummyDriverImpl() { @@ -201,8 +208,8 @@ namespace pipedal this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers(); this->bufferSize = jackServerSettings.GetBufferSize(); - AllocateBuffers(captureBuffers, 2); - AllocateBuffers(playbackBuffers, 2); + AllocateBuffers(captureBuffers, channels); + AllocateBuffers(playbackBuffers, channels); } @@ -299,7 +306,7 @@ namespace pipedal this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size()); - playbackBuffers.resize(2); + playbackBuffers.resize(channels); int ix = 0; for (auto &x : channelSelection.GetInputAudioPorts()) @@ -419,14 +426,15 @@ namespace pipedal } }; - AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost) + AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost,const std::string&deviceName) { - return new DummyDriverImpl(driverHost); + return new DummyDriverImpl(driverHost,deviceName); } bool GetDummyChannels(const JackServerSettings &jackServerSettings, std::vector &inputAudioPorts, - std::vector &outputAudioPorts) + std::vector &outputAudioPorts, + uint32_t channels) { bool result = false; @@ -434,7 +442,7 @@ namespace pipedal try { - unsigned int playbackChannels = 2, captureChannels = 2; + uint32_t playbackChannels = channels, captureChannels = channels; inputAudioPorts.clear(); for (unsigned int i = 0; i < captureChannels; ++i) @@ -457,4 +465,36 @@ namespace pipedal return result; } + + + AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels) + { + AlsaDeviceInfo result; + constexpr int DUMMY_DEVICE_ID_OFFSET = 100974; + result.cardId_ = DUMMY_DEVICE_ID_OFFSET+channels; + result.id_ = SS("dummy:channels_" << channels); + result.name_ = SS("Dummy Device (" << channels << " channels)"); + result.longName_ = result.name_; + result.sampleRates_.push_back(44100); + result.sampleRates_.push_back(48000); + result.minBufferSize_ = 16; + result.maxBufferSize_ = 1024; + return result; + } + } // namespace + +uint32_t pipedal::GetDummyAudioChannels(const std::string &deviceName) +{ + uint32_t channels; + int pos = deviceName.find_last_of('_'); + if (pos == std::string::npos) + { + throw std::runtime_error("Invalid dummy device name"); + } + std::istringstream ss(deviceName.substr(pos+1)); + ss >> channels; + return channels; + +} + diff --git a/src/DummyAudioDriver.hpp b/src/DummyAudioDriver.hpp index edfcb228..35f2d31d 100644 --- a/src/DummyAudioDriver.hpp +++ b/src/DummyAudioDriver.hpp @@ -29,7 +29,10 @@ namespace pipedal { - AudioDriver* CreateDummyAudioDriver(AudioDriverHost*driverHost); + AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels); + + uint32_t GetDummyAudioChannels(const std::string &deviceName); + AudioDriver* CreateDummyAudioDriver(AudioDriverHost*driverHost,const std::string&deviceId); } diff --git a/src/JackConfiguration.cpp b/src/JackConfiguration.cpp index 11c4129d..6ea39494 100644 --- a/src/JackConfiguration.cpp +++ b/src/JackConfiguration.cpp @@ -199,14 +199,14 @@ void JackConfiguration::JackInitialize() JackChannelSelection JackChannelSelection::MakeDefault(const JackConfiguration&config){ JackChannelSelection result; - for (size_t i = 0; i < std::min((size_t)2,config.GetInputAudioPorts().size()); ++i) + for (size_t i = 0; i < std::min((size_t)2,config.inputAudioPorts().size()); ++i) { - result.inputAudioPorts_.push_back(config.GetInputAudioPorts()[i]); + result.inputAudioPorts_.push_back(config.inputAudioPorts()[i]); } - for (size_t i = 0; i < std::min((size_t)2,config.GetOutputAudioPorts().size()); ++i) + for (size_t i = 0; i < std::min((size_t)2,config.outputAudioPorts().size()); ++i) { - result.outputAudioPorts_.push_back(config.GetOutputAudioPorts()[i]); + result.outputAudioPorts_.push_back(config.outputAudioPorts()[i]); } return result; @@ -232,6 +232,16 @@ static std::vector makeValid(const std::vector & selec result.push_back(t); } } + + // if the result is empty, generate a default selection. + if (result.size() == 0) + { + size_t n = std::min(available.size(),(size_t)2); + for (size_t i = 0; i < n; ++i) + { + result.push_back(available[i]); + } + } return result; } static std::vector makeValid(const std::vector & selected, const std::vector &available) @@ -284,9 +294,9 @@ static std::vector makeValid(const std::vectorinputAudioPorts_,config.GetInputAudioPorts()); - result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.GetOutputAudioPorts()); - result.inputMidiDevices_ = makeValid(this->inputMidiDevices_, config.GetInputMidiDevices()); + result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,config.inputAudioPorts()); + result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.outputAudioPorts()); + result.inputMidiDevices_ = makeValid(this->inputMidiDevices_, config.inputMidiDevices()); if (!result.isValid()) { return this->MakeDefault(config); diff --git a/src/JackConfiguration.hpp b/src/JackConfiguration.hpp index f6325a83..59a2ec8d 100644 --- a/src/JackConfiguration.hpp +++ b/src/JackConfiguration.hpp @@ -50,8 +50,8 @@ namespace pipedal public: JackConfiguration(); - void AlsaInitialize(const JackServerSettings &jackServerSettings); // from also config settings. - + void AlsaInitialize(const JackServerSettings &jackServerSettings); // from alsa config settings. + void JackInitialize(); // from jack server instance. ~JackConfiguration(); bool isValid() const { return isValid_;} @@ -67,9 +67,9 @@ namespace pipedal double maxAllowedMidiDelta() const { return maxAllowedMidiDelta_; } void setErrorStatus(const std::string&message) { this->errorStatus_ = message; } - const std::vector &GetInputAudioPorts() const { return inputAudioPorts_; } - const std::vector &GetOutputAudioPorts() const { return outputAudioPorts_; } - const std::vector &GetInputMidiDevices() const { return inputMidiDevices_; } + const std::vector &inputAudioPorts() const { return inputAudioPorts_; } + const std::vector &outputAudioPorts() const { return outputAudioPorts_; } + const std::vector &inputMidiDevices() const { return inputMidiDevices_; } DECLARE_JSON_MAP(JackConfiguration); diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index 1477a7be..da1f511d 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -57,10 +57,12 @@ namespace pipedal void UseDummyAudioDevice() { this->valid_ = true; if (sampleRate_ == 0) sampleRate_ = 48000; - this->alsaDevice_ = "__DUMMY_AUDIO__"; + this->alsaDevice_ = "dummy:channels_2"; } bool IsDummyAudioDevice() const { - return this->alsaDevice_ == "__DUMMY_AUDIO__"; + return + this->alsaDevice_.starts_with("__DUMMY_AUDIO__") + || this->alsaDevice_.starts_with("dummy:"); } void ReadJackDaemonConfiguration(); diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index a678c238..75857c8c 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -183,8 +183,8 @@ class AlsaTester : private AudioDriverHost JackConfiguration jackConfiguration; jackConfiguration.AlsaInitialize(serverSettings); - auto & availableInputs = jackConfiguration.GetInputAudioPorts(); - auto & availableOutputs = jackConfiguration.GetOutputAudioPorts(); + auto & availableInputs = jackConfiguration.inputAudioPorts(); + auto & availableOutputs = jackConfiguration.outputAudioPorts(); std::vector inputAudioPorts, outputAudioPorts; diff --git a/src/PiPedalAlsa.hpp b/src/PiPedalAlsa.hpp index 58eefec4..3bbf8edf 100644 --- a/src/PiPedalAlsa.hpp +++ b/src/PiPedalAlsa.hpp @@ -22,6 +22,7 @@ #include "json.hpp" namespace pipedal { + class AlsaDeviceInfo { public: int cardId_ = -1; @@ -30,7 +31,11 @@ namespace pipedal { std::string longName_; std::vector sampleRates_; uint32_t minBufferSize_ = 0,maxBufferSize_ = 0; - + + bool isDummyDevice() const { + return id_.starts_with("dummy:"); + } + DECLARE_JSON_MAP(AlsaDeviceInfo); }; diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 65d0adb9..8f7f1a34 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -42,6 +42,7 @@ #include "util.hpp" #include "DBusLog.hpp" #include "AvahiService.hpp" +#include "DummyAudioDriver.hpp" #ifndef NO_MLOCK #include @@ -481,6 +482,8 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std:: void PiPedalModel::FireJackConfigurationChanged(const JackConfiguration &jackConfiguration) { // noify subscribers. + + std::vector t{subscribers.begin(), subscribers.end()}; for (auto &subscriber : t) { @@ -1319,12 +1322,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver) auto jackConfiguration = this->jackConfiguration; jackConfiguration.AlsaInitialize(jackServerSettings); - if (jackConfiguration.isValid()) - { - JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); - selection = selection.RemoveInvalidChannels(jackConfiguration); - } - else + if (!jackConfiguration.isValid()) { jackConfiguration.setErrorStatus("Error"); } @@ -1339,11 +1337,8 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver) throw std::runtime_error("Audio configuration not valid."); } - JackChannelSelection channelSelection = this->storage.GetJackChannelSelection(jackConfiguration); - if (this->jackConfiguration.isValid()) - { - channelSelection.RemoveInvalidChannels(this->jackConfiguration); - } + auto channelSelection = this->storage.GetJackChannelSelection(jackConfiguration); + if (!channelSelection.isValid()) { throw std::runtime_error("Audio configuration not valid."); @@ -1352,6 +1347,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver) this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); + FireChannelSelectionChanged(-1); LoadCurrentPedalboard(); this->UpdateRealtimeVuSubscriptions(); @@ -1408,10 +1404,7 @@ JackChannelSelection PiPedalModel::GetJackChannelSelection() { std::lock_guard lock(mutex); JackChannelSelection t = this->storage.GetJackChannelSelection(this->jackConfiguration); - if (this->jackConfiguration.isValid()) - { - t = t.RemoveInvalidChannels(this->jackConfiguration); - } + return t; } @@ -2235,7 +2228,15 @@ void PiPedalModel::CancelMonitorPatchProperty(int64_t clientId, int64_t clientHa } std::vector PiPedalModel::GetAlsaDevices() { - return this->alsaDevices.GetAlsaDevices(); + std::vector result = this->alsaDevices.GetAlsaDevices(); +#ifdef JUNK + // Useful for debugging non-stereo device configurations + result.push_back(MakeDummyDeviceInfo(1)); + result.push_back(MakeDummyDeviceInfo(2)); + result.push_back(MakeDummyDeviceInfo(8)); +#endif + return result; + } const std::filesystem::path &PiPedalModel::GetWebRoot() const diff --git a/src/Storage.cpp b/src/Storage.cpp index da9ff061..49fabcba 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -683,7 +683,7 @@ void Storage::SetJackChannelSelection(const JackChannelSelection &channelSelecti this->isJackChannelSelectionValid = true; SaveChannelSelection(); } -const JackChannelSelection &Storage::GetJackChannelSelection(const JackConfiguration &jackConfiguration) +JackChannelSelection Storage::GetJackChannelSelection(const JackConfiguration &jackConfiguration) { if (!this->isJackChannelSelectionValid) { @@ -691,10 +691,9 @@ const JackChannelSelection &Storage::GetJackChannelSelection(const JackConfigura { auto defaultSelection = JackChannelSelection::MakeDefault(jackConfiguration); SetJackChannelSelection(defaultSelection); - return this->jackChannelSelection; } } - return jackChannelSelection; + return jackChannelSelection.RemoveInvalidChannels(jackConfiguration); } void Storage::LoadChannelSelection() diff --git a/src/Storage.hpp b/src/Storage.hpp index 913c04d3..b1298524 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -156,7 +156,7 @@ class Storage { void SetJackChannelSelection(const JackChannelSelection&channelSelection); - const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration); + JackChannelSelection GetJackChannelSelection(const JackConfiguration &jackConfiguration); // returns true if services needs to be updated. bool SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings);