bug: invalid mono audio device channel selects

This commit is contained in:
Robin Davies
2024-10-12 03:53:57 -04:00
parent 21f5c9e43a
commit b0107d4490
17 changed files with 163 additions and 68 deletions
+21 -4
View File
@@ -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
+19 -7
View File
@@ -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 (
<DialogEx tag="audioChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
<DialogEx tag="audioChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open} fullWidth maxWidth="xs"
onEnterKey={handleOk}
>
<DialogTitle id="simple-dialog-title">Select Channels</DialogTitle>
<List>
{availableChannels.map((channel) => (
<ListItem button >
<ListItem button key={channel}>
<FormControlLabel
control={
<Checkbox checked={isChecked(currentSelection, channel)} onClick={() => toggleSelect(channel)} key={channel} />
<Checkbox checked={isChecked(currentSelection, channel)} onClick={() => toggleSelect(channel)} style={{marginLeft: 24}} />
}
label={channel}
label={makeChannelName(channel)}
/>
</ListItem>
)
+4 -2
View File
@@ -651,7 +651,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()} disabled={!isConfigValid}
<ButtonBase className={classes.setting} onClick={() => 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 })(
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()} disabled={!isConfigValid}
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
>
<SelectHoverBackground selected={false} showHover={true} />
+8 -5
View File
@@ -32,6 +32,7 @@
#include <thread>
#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;
}
+3 -3
View File
@@ -75,9 +75,9 @@ public:
}
JackChannelSelection channelSelection(
jackConfiguration.GetInputAudioPorts(),
jackConfiguration.GetOutputAudioPorts(),
jackConfiguration.GetInputMidiDevices());
jackConfiguration.inputAudioPorts(),
jackConfiguration.outputAudioPorts(),
jackConfiguration.inputMidiDevices());
#if JACK_HOST
if (useJack)
+1 -1
View File
@@ -1636,7 +1636,7 @@ public:
if (jackServerSettings.IsDummyAudioDevice())
{
this->isDummyAudioDriver = true;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this));
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice()));
}
else
{
+1
View File
@@ -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
+48 -8
View File
@@ -35,6 +35,8 @@
#include <atomic>
#include <chrono>
#include <thread>
#include <stdexcept>
#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<std::string> &inputAudioPorts,
std::vector<std::string> &outputAudioPorts)
std::vector<std::string> &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;
}
+4 -1
View File
@@ -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);
}
+17 -7
View File
@@ -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<std::string> makeValid(const std::vector<std::string> & 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<AlsaMidiDeviceInfo> makeValid(const std::vector<AlsaMidiDeviceInfo> & selected, const std::vector<AlsaMidiDeviceInfo> &available)
@@ -284,9 +294,9 @@ static std::vector<AlsaMidiDeviceInfo> makeValid(const std::vector<AlsaMidiDevic
JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfiguration&config) const
{
JackChannelSelection result;
result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,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);
+5 -5
View File
@@ -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<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
const std::vector<AlsaMidiDeviceInfo> &GetInputMidiDevices() const { return inputMidiDevices_; }
const std::vector<std::string> &inputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &outputAudioPorts() const { return outputAudioPorts_; }
const std::vector<AlsaMidiDeviceInfo> &inputMidiDevices() const { return inputMidiDevices_; }
DECLARE_JSON_MAP(JackConfiguration);
+4 -2
View File
@@ -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();
+2 -2
View File
@@ -183,8 +183,8 @@ public:
JackConfiguration jackConfiguration;
jackConfiguration.AlsaInitialize(serverSettings);
auto & availableInputs = jackConfiguration.GetInputAudioPorts();
auto & availableOutputs = jackConfiguration.GetOutputAudioPorts();
auto & availableInputs = jackConfiguration.inputAudioPorts();
auto & availableOutputs = jackConfiguration.outputAudioPorts();
std::vector<std::string> inputAudioPorts, outputAudioPorts;
+6 -1
View File
@@ -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<uint32_t> sampleRates_;
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
bool isDummyDevice() const {
return id_.starts_with("dummy:");
}
DECLARE_JSON_MAP(AlsaDeviceInfo);
};
+17 -16
View File
@@ -42,6 +42,7 @@
#include "util.hpp"
#include "DBusLog.hpp"
#include "AvahiService.hpp"
#include "DummyAudioDriver.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -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<IPiPedalModelSubscriber::ptr> 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<std::recursive_mutex> 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<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
{
return this->alsaDevices.GetAlsaDevices();
std::vector<AlsaDeviceInfo> 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
+2 -3
View File
@@ -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()
+1 -1
View File
@@ -156,7 +156,7 @@ public:
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);