Skip to content

Commit

Permalink
Add the "channels" configuration option for setting channel counts.
Browse files Browse the repository at this point in the history
Closes #25.
  • Loading branch information
dechamps committed Nov 11, 2018
1 parent 85c8de6 commit fcb8841
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 55 deletions.
42 changes: 42 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ device = ""
# PortAudioDevices program.
device = "Speakers (Realtek High Definition Audio)"

# Open the hardware output device with 6 channels. This is only required if you
# are unhappy with the default channel count.
channels = 6

# Set the output to WASAPI Exclusive Mode.
wasapiExclusiveMode = true
```
Expand Down Expand Up @@ -122,6 +126,44 @@ The default behaviour is to use the default device for the selected backend.
`PortAudioDevices` will show which device that is. Typically, this would be the
device set as default in the Windows audio control panel.

#### Option `channels`

*Integer*-typed option that determines how many channels FlexASIO will open the
hardware audio device with. This is the number of channels the ASIO Host
Application will see.

**Note:** even if the ASIO Host Application only decides to use a subset of the
available channels, the hardware audio device will still be opened with the
number of channels configured here. In other words, the host application has no
control over the hardware channel configuration.

If the requested channel count doesn't match what the audio device is configured
for, the resulting behaviour depends on the backend. Some backends will accept
any channel count, upmixing or downmixing as necessary. Other backends might
refuse to initialize.

The value of this option must be strictly positive. To completely disable the
input or output, set the `device` option to the empty string (see above).

**Note:** with the WASAPI backend, setting this option has the side effect of
disabling channel masks. This means channel names will not be shown, and the
backend might behave differently with regard to channel routing.

Example:

```toml
[input]
channels = 2

[output]
channels = 6
```

The default behaviour is to use the maximum channel count for the selected
device as reported by PortAudio. This information is shown in the output of the
`PortAudioDevice` program. Sadly, PortAudio often gets the channel count wrong,
so setting this option explicitly might be necessary for correct operation.

#### Option `wasapiExclusiveMode`

*Boolean*-typed option that determines if the stream should be opened in
Expand Down
11 changes: 10 additions & 1 deletion FlexASIO/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,23 @@ namespace flexasio {
template <typename T> struct RemoveOptional { using Value = T; };
template <typename T> struct RemoveOptional<std::optional<T>> { using Value = T; };

template <typename T> void SetOption(const toml::Table& table, const std::string& key, T& option) {
template <typename T, typename Validator> void SetOption(const toml::Table& table, const std::string& key, T& option, Validator validator) {
ProcessTypedOption<RemoveOptional<T>::Value>(table, key, [&](const RemoveOptional<T>::Value& value) {
validator(value);
option = value;
});
}
template <typename T> void SetOption(const toml::Table& table, const std::string& key, T& option) {
return SetOption(table, key, option, [](const T&) {});
}

void ValidateChannelCount(const int& channelCount) {
if (channelCount <= 0) throw std::runtime_error("channel count must be strictly positive - to disable a stream direction, set the 'device' option to the empty string \"\" instead");
}

void SetStream(const toml::Table& table, Config::Stream& stream) {
SetOption(table, "device", stream.device);
SetOption(table, "channels", stream.channels, ValidateChannelCount);
SetOption(table, "wasapiExclusiveMode", stream.wasapiExclusiveMode);
}

Expand Down
1 change: 1 addition & 0 deletions FlexASIO/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace flexasio {

struct Stream {
std::optional<std::string> device;
std::optional<int> channels;
bool wasapiExclusiveMode = false;
};
Stream input;
Expand Down
30 changes: 29 additions & 1 deletion FlexASIO/flexasio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,35 @@ namespace flexasio {

if (!inputDevice.has_value() && !outputDevice.has_value()) throw ASIOException(ASE_HWMalfunction, "No usable input nor output devices");

Log() << "Channel count: " << GetInputChannelCount() << " input, " << GetOutputChannelCount() << " output";
Log() << "Input channel count: " << GetInputChannelCount() << " mask: " << GetWaveFormatChannelMaskString(GetInputChannelMask());
if (inputDevice.has_value() && GetInputChannelCount() > inputDevice->info.maxInputChannels)
Log() << "WARNING: input channel count is higher than the max channel count for this device. Input device initialization might fail.";

Log() << "Output channel count: " << GetOutputChannelCount() << " mask: " << GetWaveFormatChannelMaskString(GetOutputChannelMask());
if (outputDevice.has_value() && GetOutputChannelCount() > outputDevice->info.maxOutputChannels)
Log() << "WARNING: output channel count is higher than the max channel count for this device. Output device initialization might fail.";
}

int FlexASIO::GetInputChannelCount() const {
if (!inputDevice.has_value()) return 0;
if (config.input.channels.has_value()) return *config.input.channels;
return inputDevice->info.maxInputChannels;
}
int FlexASIO::GetOutputChannelCount() const {
if (!outputDevice.has_value()) return 0;
if (config.output.channels.has_value()) return *config.output.channels;
return outputDevice->info.maxOutputChannels;
}

DWORD FlexASIO::GetInputChannelMask() const {
if (!inputFormat.has_value()) return 0;
if (config.input.channels.has_value()) return 0;
return inputFormat->dwChannelMask;
}
DWORD FlexASIO::GetOutputChannelMask() const {
if (!outputFormat.has_value()) return 0;
if (config.output.channels.has_value()) return 0;
return outputFormat->dwChannelMask;
}

void FlexASIO::GetChannels(long* numInputChannels, long* numOutputChannels)
Expand Down
8 changes: 4 additions & 4 deletions FlexASIO/flexasio.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ namespace flexasio {
std::optional<RunningState> runningState;
};

int GetInputChannelCount() const { return inputDevice.has_value() ? inputDevice->info.maxInputChannels : 0; }
int GetOutputChannelCount() const { return outputDevice.has_value() ? outputDevice->info.maxOutputChannels : 0; }
DWORD GetInputChannelMask() const { return inputFormat.has_value() ? inputFormat->dwChannelMask : 0; }
DWORD GetOutputChannelMask() const { return outputFormat.has_value() ? outputFormat->dwChannelMask : 0; }
int GetInputChannelCount() const;
int GetOutputChannelCount() const;
DWORD GetInputChannelMask() const;
DWORD GetOutputChannelMask() const;

Stream OpenStream(double sampleRate, unsigned long framesPerBuffer, PaStreamCallback callback, void* callbackUserData);

Expand Down
94 changes: 45 additions & 49 deletions FlexASIOUtil/portaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,58 +204,54 @@ namespace flexasio {
return format;
}

namespace {

std::string GetWaveFormatTagString(WORD formatTag) {
return EnumToString(formatTag, {
{ WAVE_FORMAT_EXTENSIBLE, "EXTENSIBLE" },
{ WAVE_FORMAT_MPEG, "MPEG" },
{ WAVE_FORMAT_MPEGLAYER3, "MPEGLAYER3" },
});
}

std::string GetWaveFormatChannelMaskString(DWORD channelMask) {
return BitfieldToString(channelMask, {
{SPEAKER_FRONT_LEFT, "Front Left"},
{SPEAKER_FRONT_RIGHT, "Front Right"},
{SPEAKER_FRONT_CENTER, "Front Center"},
{SPEAKER_LOW_FREQUENCY, "Low Frequency"},
{SPEAKER_BACK_LEFT, "Back Left"},
{SPEAKER_BACK_RIGHT, "Back Right"},
{SPEAKER_FRONT_LEFT_OF_CENTER, "Front Left of Center"},
{SPEAKER_FRONT_RIGHT_OF_CENTER, "Front Right of Center"},
{SPEAKER_BACK_CENTER, "Back Center"},
{SPEAKER_SIDE_LEFT, "Side Left"},
{SPEAKER_SIDE_RIGHT, "Side Right"},
{SPEAKER_TOP_CENTER, "Top Center"},
{SPEAKER_TOP_FRONT_LEFT, "Top Front Left"},
{SPEAKER_TOP_FRONT_CENTER, "Top Front Center"},
{SPEAKER_TOP_FRONT_RIGHT, "Top Front Right"},
{SPEAKER_TOP_BACK_LEFT, "Top Back Left"},
{SPEAKER_TOP_BACK_CENTER, "Top Back Center"},
{SPEAKER_TOP_BACK_RIGHT, "Top Back Right"},
});
}
std::string GetWaveFormatTagString(WORD formatTag) {
return EnumToString(formatTag, {
{ WAVE_FORMAT_EXTENSIBLE, "EXTENSIBLE" },
{ WAVE_FORMAT_MPEG, "MPEG" },
{ WAVE_FORMAT_MPEGLAYER3, "MPEGLAYER3" },
});
}

std::string GetWaveSubFormatString(const GUID& subFormat) {
return EnumToString(subFormat, {
{ KSDATAFORMAT_SUBTYPE_ADPCM, "ADPCM" },
{ KSDATAFORMAT_SUBTYPE_ALAW, "A-law" },
{ KSDATAFORMAT_SUBTYPE_DRM, "DRM" },
{ KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, "IEC61937 Dolby Digital Plus" },
{ KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, "IEC61937 Dolby Digital" },
{ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, "IEEE Float" },
{ KSDATAFORMAT_SUBTYPE_MPEG, "MPEG-1" },
{ KSDATAFORMAT_SUBTYPE_MULAW, "Mu-law" },
{ KSDATAFORMAT_SUBTYPE_PCM, "PCM" },
}, [](const GUID& guid) {
char str[128];
// Shamelessly stolen from https://stackoverflow.com/a/18555932/172594
snprintf(str, sizeof(str), "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
return std::string(str);
std::string GetWaveFormatChannelMaskString(DWORD channelMask) {
return BitfieldToString(channelMask, {
{SPEAKER_FRONT_LEFT, "Front Left"},
{SPEAKER_FRONT_RIGHT, "Front Right"},
{SPEAKER_FRONT_CENTER, "Front Center"},
{SPEAKER_LOW_FREQUENCY, "Low Frequency"},
{SPEAKER_BACK_LEFT, "Back Left"},
{SPEAKER_BACK_RIGHT, "Back Right"},
{SPEAKER_FRONT_LEFT_OF_CENTER, "Front Left of Center"},
{SPEAKER_FRONT_RIGHT_OF_CENTER, "Front Right of Center"},
{SPEAKER_BACK_CENTER, "Back Center"},
{SPEAKER_SIDE_LEFT, "Side Left"},
{SPEAKER_SIDE_RIGHT, "Side Right"},
{SPEAKER_TOP_CENTER, "Top Center"},
{SPEAKER_TOP_FRONT_LEFT, "Top Front Left"},
{SPEAKER_TOP_FRONT_CENTER, "Top Front Center"},
{SPEAKER_TOP_FRONT_RIGHT, "Top Front Right"},
{SPEAKER_TOP_BACK_LEFT, "Top Back Left"},
{SPEAKER_TOP_BACK_CENTER, "Top Back Center"},
{SPEAKER_TOP_BACK_RIGHT, "Top Back Right"},
});
}
}

std::string GetWaveSubFormatString(const GUID& subFormat) {
return EnumToString(subFormat, {
{ KSDATAFORMAT_SUBTYPE_ADPCM, "ADPCM" },
{ KSDATAFORMAT_SUBTYPE_ALAW, "A-law" },
{ KSDATAFORMAT_SUBTYPE_DRM, "DRM" },
{ KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, "IEC61937 Dolby Digital Plus" },
{ KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, "IEC61937 Dolby Digital" },
{ KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, "IEEE Float" },
{ KSDATAFORMAT_SUBTYPE_MPEG, "MPEG-1" },
{ KSDATAFORMAT_SUBTYPE_MULAW, "Mu-law" },
{ KSDATAFORMAT_SUBTYPE_PCM, "PCM" },
}, [](const GUID& guid) {
char str[128];
// Shamelessly stolen from https://stackoverflow.com/a/18555932/172594
snprintf(str, sizeof(str), "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
return std::string(str);
});
}

std::string DescribeWaveFormat(const WAVEFORMATEXTENSIBLE& waveFormatExtensible) {
Expand Down
3 changes: 3 additions & 0 deletions FlexASIOUtil/portaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ namespace flexasio {

WAVEFORMATEXTENSIBLE GetWasapiDeviceDefaultFormat(PaDeviceIndex index);

std::string GetWaveFormatTagString(WORD formatTag);
std::string GetWaveFormatChannelMaskString(DWORD channelMask);
std::string GetWaveSubFormatString(const GUID& subFormat);
std::string DescribeWaveFormat(const WAVEFORMATEXTENSIBLE& waveFormatExtensible);

std::string DescribeStreamParameters(const PaStreamParameters& parameters);
Expand Down

0 comments on commit fcb8841

Please sign in to comment.