658 lines
21 KiB
C++
658 lines
21 KiB
C++
// Copyright (c) Robin E. R. Davies
|
|
// Copyright (c) Gabriel Hernandez
|
|
//
|
|
// 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.
|
|
|
|
#include "pch.h"
|
|
#include "PiPedalAlsa.hpp"
|
|
#include "alsa/asoundlib.h"
|
|
#include "Lv2Log.hpp"
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
#include "Finally.hpp"
|
|
|
|
using namespace pipedal;
|
|
|
|
static uint32_t RATES[] = {
|
|
22050, 24000, 44100, 48000, 44100 * 2, 48000 * 2, 44100 * 4, 48000 * 4};
|
|
|
|
std::mutex alsaMutex;
|
|
|
|
bool PiPedalAlsaDevices::getCachedDevice(const std::string &name, AlsaDeviceInfo *pResult)
|
|
{
|
|
auto it = cachedDevices.find(name);
|
|
if (it != cachedDevices.end())
|
|
{
|
|
*pResult = it->second;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void PiPedalAlsaDevices::cacheDevice(const std::string &name, const AlsaDeviceInfo &deviceInfo)
|
|
{
|
|
cachedDevices[name] = deviceInfo;
|
|
}
|
|
|
|
static bool isSupportedAudioDevice(const AlsaDeviceInfo &d)
|
|
{
|
|
std::string name = d.name_ + " " + d.longName_;
|
|
std::transform(name.begin(), name.end(), name.begin(), [](char c)
|
|
{ return std::tolower(c); });
|
|
if (name.find("hdmi") != std::string::npos) return false;
|
|
// if (name.find("bcm2835") != std::string::npos) return false;
|
|
return true;
|
|
};
|
|
|
|
|
|
struct ProcAlsaDevice {
|
|
int cardId;
|
|
int subdeviceId;
|
|
bool audioCapture;
|
|
bool audioPlayback;
|
|
bool rawMidi;
|
|
};
|
|
|
|
static std::vector<ProcAlsaDevice> getProcAlsaDevices()
|
|
{
|
|
std::vector<ProcAlsaDevice> result;
|
|
|
|
std::ifstream f {"/proc/asound/devices"};
|
|
if (f.is_open())
|
|
{
|
|
std::string line;
|
|
while (std::getline(f, line))
|
|
{
|
|
// Parse each line of /proc/alsa/devices
|
|
// Format: cardnum: [devicenum- subdevicenum]: type : name
|
|
std::istringstream iss(line);
|
|
std::string token;
|
|
|
|
// Skip leading whitespace and get card number
|
|
if (!std::getline(iss, token, ':'))
|
|
{
|
|
continue;
|
|
}
|
|
try {
|
|
int cardId = std::stoi(token.substr(token.find_first_not_of(" \t")));
|
|
|
|
// Get device-subdevice part
|
|
if (!std::getline(iss, token, ':'))
|
|
{
|
|
continue;
|
|
}
|
|
size_t dashPos = token.find('-');
|
|
if (dashPos == std::string::npos)
|
|
{
|
|
continue;
|
|
}
|
|
int deviceId = std::stoi(token.substr(token.find_first_not_of(" \t["), dashPos));
|
|
int subdeviceId = std::stoi(token.substr(dashPos + 1, token.find(']') - dashPos - 1));
|
|
|
|
// Get type
|
|
if (!std::getline(iss, token, ':'))
|
|
{
|
|
continue;
|
|
}
|
|
std::string type = token.substr(token.find_first_not_of(" \t"));
|
|
|
|
ProcAlsaDevice *pDevice = nullptr;
|
|
for (size_t i = 0; i < result.size(); ++i)
|
|
{
|
|
if (result[i].cardId == deviceId && result[i].subdeviceId == subdeviceId)
|
|
{
|
|
pDevice = &result[i];
|
|
break;
|
|
}
|
|
}
|
|
if (pDevice == nullptr)
|
|
{
|
|
ProcAlsaDevice newDevice;
|
|
newDevice.cardId = deviceId;
|
|
newDevice.subdeviceId = subdeviceId;
|
|
newDevice.audioCapture = false;
|
|
newDevice.audioPlayback = false;
|
|
newDevice.rawMidi = false;
|
|
result.push_back(newDevice);
|
|
pDevice = &(result[result.size()-1]);
|
|
}
|
|
if (type.find("digital audio capture") != std::string::npos)
|
|
{
|
|
pDevice->audioCapture = true;
|
|
}
|
|
if ((type.find("digital audio playback") != std::string::npos))
|
|
{
|
|
pDevice->audioPlayback = true;
|
|
}
|
|
if (type.find("rawmidi") != std::string::npos)
|
|
{
|
|
pDevice->rawMidi = true;
|
|
}
|
|
} catch (const std::exception &e)
|
|
{
|
|
Lv2Log::error(SS("invalid ALSA proc entry: " << line));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|
{
|
|
|
|
std::lock_guard guard{alsaMutex};
|
|
|
|
std::vector<AlsaDeviceInfo> result;
|
|
|
|
int cardNum = -1; // Start with first card
|
|
int err;
|
|
|
|
std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices();
|
|
|
|
for (const auto &procAlsaDevice: procAlsaDevices)
|
|
{
|
|
std::stringstream ss;
|
|
if (!procAlsaDevice.audioCapture && !procAlsaDevice.audioPlayback) {
|
|
continue;
|
|
}
|
|
ss << "hw:" << procAlsaDevice.cardId;
|
|
|
|
std::string cardId = ss.str();
|
|
|
|
snd_ctl_t *hDevice = nullptr;
|
|
|
|
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Finally ffhDevice{[hDevice]()
|
|
{ snd_ctl_close(hDevice); }};
|
|
|
|
snd_ctl_card_info_t *alsaInfo = nullptr;
|
|
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
|
{
|
|
Lv2Log::error("Failed to allocate ALSA card info");
|
|
continue;
|
|
}
|
|
|
|
Finally ffCardInfo{[alsaInfo]()
|
|
{ snd_ctl_card_info_free(alsaInfo); }};
|
|
|
|
err = snd_ctl_card_info(hDevice, alsaInfo);
|
|
if (err == 0)
|
|
{
|
|
AlsaDeviceInfo info;
|
|
info.cardId_ = cardNum;
|
|
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
|
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
|
(void)driver;
|
|
|
|
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
|
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
|
|
|
// we can't read our own device if it's open so use data that gets
|
|
// cached before we open audio devices.
|
|
|
|
AlsaDeviceInfo cachedInfo;
|
|
if (getCachedDevice(info.name_, &cachedInfo))
|
|
{
|
|
// may have been plugged into a different USB connector.
|
|
cachedInfo.cardId_ = info.cardId_;
|
|
cachedInfo.id_ = info.id_;
|
|
result.push_back(cachedInfo);
|
|
continue;
|
|
}
|
|
snd_pcm_t *captureDevice = nullptr;
|
|
snd_pcm_t *playbackDevice = nullptr;
|
|
auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
|
bool captureOk = rc == 0;
|
|
|
|
Finally ffCaptureDevice{
|
|
[captureDevice]
|
|
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
|
|
|
rc = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
|
bool playbackOk = rc == 0;
|
|
|
|
Finally ffPlaybackDevice{
|
|
[playbackDevice]
|
|
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
|
if (procAlsaDevice.audioCapture && !captureOk)
|
|
{
|
|
info.captureBusy_ = true;
|
|
}
|
|
if (procAlsaDevice.audioPlayback && !playbackOk)
|
|
{
|
|
info.playbackBusy_ = true;
|
|
}
|
|
|
|
info.supportsCapture_ = captureOk;
|
|
info.supportsPlayback_ = playbackOk;
|
|
|
|
if (captureOk || playbackOk)
|
|
{
|
|
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
|
snd_pcm_hw_params_t *params = nullptr;
|
|
err = snd_pcm_hw_params_malloc(¶ms);
|
|
|
|
if (err == 0)
|
|
{
|
|
Finally ffParams{[params]
|
|
{ snd_pcm_hw_params_free(params); }};
|
|
|
|
err = snd_pcm_hw_params_any(hDevice, params);
|
|
if (err == 0)
|
|
{
|
|
unsigned int minRate = 0, maxRate = 0;
|
|
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
|
int dir;
|
|
|
|
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
|
if (err == 0)
|
|
{
|
|
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
|
if (err == 0)
|
|
{
|
|
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
|
{
|
|
uint32_t rate = RATES[i];
|
|
if (rate >= minRate && rate <= maxRate)
|
|
{
|
|
info.sampleRates_.push_back(rate);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
|
}
|
|
|
|
if (err == 0)
|
|
{
|
|
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
|
if (err == 0)
|
|
{
|
|
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
|
}
|
|
}
|
|
if (err == 0)
|
|
{
|
|
if (minBufferSize < 16)
|
|
{
|
|
minBufferSize = 16;
|
|
}
|
|
|
|
info.minBufferSize_ = (uint32_t)minBufferSize;
|
|
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
|
}
|
|
}
|
|
}
|
|
if (!info.captureBusy_ && !info.playbackBusy_)
|
|
{
|
|
cacheDevice(info.name_, info);
|
|
result.push_back(info);
|
|
}
|
|
} else {
|
|
if (info.captureBusy_ || info.playbackBusy_)
|
|
{
|
|
result.push_back(info);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
Lv2Log::debug("GetAlsaDevices --");
|
|
|
|
std::vector<AlsaDeviceInfo> filtered;
|
|
for (auto &device : result)
|
|
{
|
|
if (isSupportedAudioDevice(device))
|
|
{
|
|
filtered.push_back(device);
|
|
Lv2Log::debug(
|
|
SS(" "
|
|
<< device.name_ << " " << device.longName_ << " " << device.cardId_
|
|
<< (device.supportsCapture_ ? " in" : "")
|
|
<< (device.supportsPlayback_ ? " out" : "")
|
|
<< (device.captureBusy_ ? " in(busy)" : "")
|
|
<< (device.captureBusy_ ? " out(busy)" : "")
|
|
));
|
|
}
|
|
}
|
|
return filtered;
|
|
}
|
|
|
|
static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector<AlsaMidiDeviceInfo> *result)
|
|
{
|
|
snd_rawmidi_info_t *info = nullptr;
|
|
const char *name = nullptr;
|
|
const char *sub_name = nullptr;
|
|
int subs = 0, subs_in = 0, subs_out = 0;
|
|
int sub = 0;
|
|
int err = 0;
|
|
;
|
|
|
|
snd_rawmidi_info_alloca(&info);
|
|
snd_rawmidi_info_set_device(info, device);
|
|
|
|
snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
|
|
err = snd_ctl_rawmidi_info(ctl, info);
|
|
if (err >= 0)
|
|
subs_in = snd_rawmidi_info_get_subdevices_count(info);
|
|
else
|
|
subs_in = 0;
|
|
|
|
snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
|
|
err = snd_ctl_rawmidi_info(ctl, info);
|
|
if (err >= 0)
|
|
subs_out = snd_rawmidi_info_get_subdevices_count(info);
|
|
else
|
|
subs_out = 0;
|
|
|
|
subs = subs_in > subs_out ? subs_in : subs_out;
|
|
if (!subs)
|
|
return;
|
|
|
|
switch (direction)
|
|
{
|
|
case AlsaMidiDeviceInfo::In:
|
|
if (subs_out == 0) // out for the device, in for us.
|
|
{
|
|
return; // no input devices to add
|
|
}
|
|
break;
|
|
case AlsaMidiDeviceInfo::Out: // in for the device, out for us.
|
|
if (subs_in == 0)
|
|
{
|
|
return; // no output devices to add
|
|
}
|
|
break;
|
|
case AlsaMidiDeviceInfo::InOut:
|
|
if (subs_in == 0 || subs_out == 0)
|
|
{
|
|
return; // no input or output devices to add
|
|
}
|
|
break;
|
|
case AlsaMidiDeviceInfo::None:
|
|
return; // no devices to add
|
|
}
|
|
|
|
for (sub = 0; sub < subs; ++sub)
|
|
{
|
|
snd_rawmidi_info_set_stream(info, direction == AlsaMidiDeviceInfo::Direction::Out ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
|
|
snd_rawmidi_info_set_subdevice(info, sub);
|
|
err = snd_ctl_rawmidi_info(ctl, info);
|
|
if (err < 0)
|
|
{
|
|
throw std::runtime_error(
|
|
SS("snd_ctl_rawmidi_info failed for card " << card << ", device " << device << ", subdevice " << sub << ": " << snd_strerror(err)));
|
|
}
|
|
name = snd_rawmidi_info_get_name(info);
|
|
sub_name = snd_rawmidi_info_get_subdevice_name(info);
|
|
// get card name.
|
|
|
|
std::string cardName;
|
|
snd_ctl_card_info_t *card_info = nullptr;
|
|
snd_ctl_card_info_malloc(&card_info);
|
|
if (snd_ctl_card_info(ctl, card_info) == 0)
|
|
{
|
|
cardName = snd_ctl_card_info_get_name(card_info);
|
|
}
|
|
snd_ctl_card_info_free(card_info);
|
|
if (cardName.length() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (sub == 0 && sub_name[0] == '\0')
|
|
{
|
|
AlsaMidiDeviceInfo info;
|
|
info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device);
|
|
info.description_ = SS("Virtual MIDI " << card << "-" << device);
|
|
info.card_ = card;
|
|
info.device_ = device;
|
|
info.subdevice_ = 0;
|
|
info.isVirtual_ = true;
|
|
info.subDevices_ = subs;
|
|
if (subs_in > 0 && subs_out > 0)
|
|
{
|
|
info.direction_ = AlsaMidiDeviceInfo::InOut;
|
|
}
|
|
else if (subs_in > 0)
|
|
{
|
|
info.direction_ = AlsaMidiDeviceInfo::In;
|
|
}
|
|
else if (subs_out > 0)
|
|
{
|
|
info.direction_ = AlsaMidiDeviceInfo::Out;
|
|
}
|
|
else
|
|
{
|
|
info.direction_ = AlsaMidiDeviceInfo::None;
|
|
}
|
|
result->push_back(info);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
AlsaMidiDeviceInfo info;
|
|
info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device);
|
|
if (sub != 0)
|
|
{
|
|
info.name_ = SS(info.name_ << "," << sub);
|
|
}
|
|
info.description_ = sub_name;
|
|
info.card_ = card;
|
|
info.device_ = device;
|
|
info.subdevice_ = sub;
|
|
info.isVirtual_ = false;
|
|
info.subDevices_ = 1;
|
|
result->push_back(info);
|
|
}
|
|
}
|
|
}
|
|
static void AddMidiCardToList(int card, std::vector<AlsaMidiDeviceInfo> *result, AlsaMidiDeviceInfo::Direction direction)
|
|
{
|
|
snd_ctl_t *ctl = nullptr;
|
|
char name[32];
|
|
int device;
|
|
int err;
|
|
|
|
sprintf(name, "hw:%d", card);
|
|
if ((err = snd_ctl_open(&ctl, name, 0)) < 0)
|
|
{
|
|
throw std::runtime_error(SS("cannot open control for card " << card << ": " << snd_strerror(err)));
|
|
}
|
|
device = -1;
|
|
for (;;)
|
|
{
|
|
if ((err = snd_ctl_rawmidi_next_device(ctl, &device)) < 0)
|
|
{
|
|
snd_ctl_close(ctl);
|
|
throw std::runtime_error(SS("cannot get next rawmidi device for card " << card << ": " << snd_strerror(err)));
|
|
}
|
|
if (device < 0)
|
|
break;
|
|
AddMidiCardDevicesToList(ctl, card, device, direction, result);
|
|
}
|
|
snd_ctl_close(ctl);
|
|
}
|
|
|
|
static std::vector<AlsaMidiDeviceInfo> GetAlsaDevices(const char *devname, const char *direction_)
|
|
{
|
|
std::vector<AlsaMidiDeviceInfo> result;
|
|
|
|
int card, err;
|
|
|
|
AlsaMidiDeviceInfo::Direction direction;
|
|
if (strcmp(direction_, "Input") == 0)
|
|
{
|
|
direction = AlsaMidiDeviceInfo::In;
|
|
}
|
|
else if (strcmp(direction_, "Output") == 0)
|
|
{
|
|
direction = AlsaMidiDeviceInfo::Out;
|
|
}
|
|
else if (strcmp(direction_, "InOut") == 0)
|
|
{
|
|
direction = AlsaMidiDeviceInfo::InOut;
|
|
}
|
|
else
|
|
{
|
|
direction = AlsaMidiDeviceInfo::None;
|
|
return result;
|
|
}
|
|
|
|
card = -1;
|
|
if ((err = snd_card_next(&card)) < 0)
|
|
{
|
|
throw std::runtime_error(SS("snd_card_next failed.: " << snd_strerror(err)));
|
|
}
|
|
if (card < 0)
|
|
{
|
|
// no devices!
|
|
return result;
|
|
}
|
|
do
|
|
{
|
|
AddMidiCardToList(card, &result, direction);
|
|
if ((err = snd_card_next(&card)) < 0)
|
|
{
|
|
throw std::runtime_error(SS("snd_card_next failed: " << snd_strerror(err)));
|
|
}
|
|
} while (card >= 0);
|
|
|
|
return result;
|
|
}
|
|
static std::vector<AlsaMidiDeviceInfo> OldGetAlsaDevices(const char *devname, const char *direction)
|
|
{
|
|
std::vector<AlsaMidiDeviceInfo> result;
|
|
{
|
|
|
|
char **hints;
|
|
int err;
|
|
char **n;
|
|
char *name;
|
|
char *desc;
|
|
char *ioid;
|
|
|
|
/* Enumerate sound devices */
|
|
err = snd_device_name_hint(-1, devname, (void ***)&hints);
|
|
if (err != 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
n = hints;
|
|
while (*n != NULL)
|
|
{
|
|
|
|
name = snd_device_name_get_hint(*n, "NAME");
|
|
desc = snd_device_name_get_hint(*n, "DESC");
|
|
ioid = snd_device_name_get_hint(*n, "IOID");
|
|
|
|
if (desc != nullptr) // skip virtual device
|
|
{
|
|
if (ioid == nullptr || strcmp(ioid, direction) == 0)
|
|
{
|
|
result.push_back(AlsaMidiDeviceInfo(name, desc));
|
|
}
|
|
}
|
|
// translate rawmidi device name to device number.
|
|
|
|
if (name && strcmp("null", name) != 0)
|
|
free(name);
|
|
if (desc && strcmp("null", desc) != 0)
|
|
free(desc);
|
|
if (ioid && strcmp("null", ioid) != 0)
|
|
free(ioid);
|
|
n++;
|
|
}
|
|
|
|
// Free hint buffer too
|
|
snd_device_name_free_hint((void **)hints);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<AlsaMidiDeviceInfo> pipedal::LegacyGetAlsaMidiInputDevices()
|
|
{
|
|
return GetAlsaDevices("rawmidi", "Input");
|
|
}
|
|
std::vector<AlsaMidiDeviceInfo> pipedal::LegacyGetAlsaMidiOutputDevices()
|
|
{
|
|
return GetAlsaDevices("rawmidi", "Output");
|
|
}
|
|
|
|
AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char *name, const char *description)
|
|
: name_(name)
|
|
{
|
|
// extract just the display name from description.
|
|
// undocumented but e.g.: M2, M2\nM2 Raw Midi
|
|
const char *p = description;
|
|
const char *pEnd = p;
|
|
// undocumented but e.g.: M2, M2\nM2 Raw Midi
|
|
while (pEnd != nullptr && *pEnd != 0 && *pEnd != ',' && *pEnd != '\n')
|
|
{
|
|
++pEnd;
|
|
}
|
|
if (p != pEnd)
|
|
{
|
|
description_ = std::string(p, pEnd);
|
|
}
|
|
else
|
|
{
|
|
description = name;
|
|
}
|
|
}
|
|
|
|
AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char *name, const char *description, int card, int device, int subdevice)
|
|
: AlsaMidiDeviceInfo(name, description)
|
|
{
|
|
this->card_ = card;
|
|
this->device_ = device;
|
|
this->subdevice_ = subdevice;
|
|
}
|
|
|
|
JSON_MAP_BEGIN(AlsaDeviceInfo)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, name)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, captureBusy)
|
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, playbackBusy)
|
|
JSON_MAP_END()
|
|
|
|
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
|
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, name)
|
|
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, description)
|
|
JSON_MAP_END()
|