#include #include #include #include namespace rack { namespace app { static std::string getDetailTemplate(std::string name, int numInputs, int inputOffset, int numOutputs, int outputOffset) { std::string text = name; text += " ("; if (numInputs > 0) { text += string::f("%d-%d in", inputOffset + 1, inputOffset + numInputs); } if (numInputs > 0 && numOutputs > 0) { text += ", "; } if (numOutputs > 0) { text += string::f("%d-%d out", outputOffset + 1, outputOffset + numOutputs); } text += ")"; return text; } struct AudioDriverValueItem : ui::MenuItem { audio::Port* port; int driverId; void onAction(const ActionEvent& e) override { port->setDriverId(driverId); } }; static void appendAudioDriverMenu(ui::Menu* menu, audio::Port* port) { if (!port) return; for (int driverId : audio::getDriverIds()) { AudioDriverValueItem* item = new AudioDriverValueItem; item->port = port; item->driverId = driverId; item->text = audio::getDriver(driverId)->getName(); item->rightText = CHECKMARK(item->driverId == port->getDriverId()); menu->addChild(item); } } void AudioDriverChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver"))); appendAudioDriverMenu(menu, port); } void AudioDriverChoice::step() { text = ""; if (box.size.x >= 200.0) text += string::translate("AudioDisplay.driver"); audio::Driver* driver = port ? port->getDriver() : NULL; std::string driverName = driver ? driver->getName() : ""; if (driverName != "") { text += driverName; color.a = 1.0; } else { text += "(" + string::translate("AudioDisplay.noDriver") + ")"; color.a = 0.5; } } struct AudioDriverItem : ui::MenuItem { audio::Port* port; ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; appendAudioDriverMenu(menu, port); return menu; } }; struct AudioDeviceValueItem : ui::MenuItem { audio::Port* port; int deviceId; int inputOffset; int outputOffset; void onAction(const ActionEvent& e) override { port->setDeviceId(deviceId); port->inputOffset = inputOffset; port->outputOffset = outputOffset; } }; static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { if (!port) return; { AudioDeviceValueItem* item = new AudioDeviceValueItem; item->port = port; item->deviceId = -1; item->text = "(" + string::translate("AudioDisplay.noDevice") + ")"; item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); menu->addChild(item); } for (int deviceId : port->getDeviceIds()) { int numDeviceInputs = port->getDeviceNumInputs(deviceId); int numDeviceOutputs = port->getDeviceNumOutputs(deviceId); std::string name = port->getDeviceName(deviceId); // Display only 32 channel offsets per device, because some virtual devices (e.g. ALSA) can have thousands of useless channels. for (int i = 0; i < 32; i++) { int inputOffset = i * port->maxInputs; int outputOffset = i * port->maxOutputs; if (inputOffset >= numDeviceInputs && outputOffset >= numDeviceOutputs) break; int numInputs = math::clamp(numDeviceInputs - inputOffset, 0, port->maxInputs); int numOutputs = math::clamp(numDeviceOutputs - outputOffset, 0, port->maxOutputs); AudioDeviceValueItem* item = new AudioDeviceValueItem; item->port = port; item->deviceId = deviceId; item->inputOffset = inputOffset; item->outputOffset = outputOffset; item->text = getDetailTemplate(name, numInputs, inputOffset, numOutputs, outputOffset); item->rightText = CHECKMARK(deviceId == port->getDeviceId() && inputOffset == port->inputOffset && outputOffset == port->outputOffset); menu->addChild(item); } } } void AudioDeviceChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice"))); appendAudioDeviceMenu(menu, port); } void AudioDeviceChoice::step() { text = ""; if (box.size.x >= 200.0) text += string::translate("AudioDisplay.device"); std::string detail = ""; if (port && port->getDevice()) detail = getDetailTemplate(port->getDevice()->getName(), port->getNumInputs(), port->inputOffset, port->getNumOutputs(), port->outputOffset); if (detail != "") { text += detail; color.a = 1.0; } else { text += string::translate("AudioDisplay.noDevice"); color.a = 0.5; } } struct AudioDeviceItem : ui::MenuItem { audio::Port* port; ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; appendAudioDeviceMenu(menu, port); return menu; } }; struct AudioSampleRateValueItem : ui::MenuItem { audio::Port* port; float sampleRate; void onAction(const ActionEvent& e) override { port->setSampleRate(sampleRate); } }; static void appendAudioSampleRateMenu(ui::Menu* menu, audio::Port* port) { if (!port) return; std::set sampleRates = port->getSampleRates(); // Add current sample rate in case it's not in the list sampleRates.insert(port->getSampleRate()); if (sampleRates.empty()) { menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")")); } for (float sampleRate : sampleRates) { if (sampleRate <= 0) continue; AudioSampleRateValueItem* item = new AudioSampleRateValueItem; item->port = port; item->sampleRate = sampleRate; item->text = string::f("%g kHz", sampleRate / 1000.0); item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate()); menu->addChild(item); } } void AudioSampleRateChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate"))); appendAudioSampleRateMenu(menu, port); } void AudioSampleRateChoice::step() { text = ""; if (box.size.x >= 100.0) text += string::translate("AudioDisplay.sampleRateColon"); float sampleRate = port ? port->getSampleRate() : 0; if (sampleRate > 0) { text += string::f("%g", sampleRate / 1000.f); color.a = 1.0; } else { text += "---"; color.a = 0.5; } text += " kHz"; } struct AudioSampleRateItem : ui::MenuItem { audio::Port* port; ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; appendAudioSampleRateMenu(menu, port); return menu; } }; struct AudioBlockSizeValueItem : ui::MenuItem { audio::Port* port; int blockSize; void onAction(const ActionEvent& e) override { port->setBlockSize(blockSize); } }; static void appendAudioBlockSizeMenu(ui::Menu* menu, audio::Port* port) { if (!port) return; std::set blockSizes = port->getBlockSizes(); // Add current block size in case it's not in the list blockSizes.insert(port->getBlockSize()); if (blockSizes.empty()) { menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")")); } for (int blockSize : blockSizes) { if (blockSize <= 0) continue; AudioBlockSizeValueItem* item = new AudioBlockSizeValueItem; item->port = port; item->blockSize = blockSize; float latency = (float) blockSize / port->getSampleRate() * 1000.0; item->text = string::f("%d (%.1f ms)", blockSize, latency); item->rightText = CHECKMARK(item->blockSize == port->getBlockSize()); menu->addChild(item); } } void AudioBlockSizeChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize"))); appendAudioBlockSizeMenu(menu, port); } void AudioBlockSizeChoice::step() { text = ""; if (box.size.x >= 100.0) text += string::translate("AudioDisplay.blockSizeColon"); int blockSize = port ? port->getBlockSize() : 0; if (blockSize > 0) { text += string::f("%d", blockSize); color.a = 1.0; } else { text += "---"; color.a = 0.5; } } struct AudioBlockSizeItem : ui::MenuItem { audio::Port* port; ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; appendAudioBlockSizeMenu(menu, port); return menu; } }; void AudioDisplay::setAudioPort(audio::Port* port) { clearChildren(); math::Vec pos; AudioDriverChoice* driverChoice = createWidget(pos); driverChoice->box.size.x = box.size.x; driverChoice->port = port; addChild(driverChoice); pos = driverChoice->box.getBottomLeft(); this->driverChoice = driverChoice; this->driverSeparator = createWidget(pos); this->driverSeparator->box.size.x = box.size.x; addChild(this->driverSeparator); AudioDeviceChoice* deviceChoice = createWidget(pos); deviceChoice->box.size.x = box.size.x; deviceChoice->port = port; addChild(deviceChoice); pos = deviceChoice->box.getBottomLeft(); this->deviceChoice = deviceChoice; this->deviceSeparator = createWidget(pos); this->deviceSeparator->box.size.x = box.size.x; addChild(this->deviceSeparator); AudioSampleRateChoice* sampleRateChoice = createWidget(pos); sampleRateChoice->box.size.x = box.size.x / 2; sampleRateChoice->port = port; addChild(sampleRateChoice); this->sampleRateChoice = sampleRateChoice; this->sampleRateSeparator = createWidget(pos); this->sampleRateSeparator->box.pos.x = box.size.x / 2; this->sampleRateSeparator->box.size.y = this->sampleRateChoice->box.size.y; addChild(this->sampleRateSeparator); AudioBlockSizeChoice* bufferSizeChoice = createWidget(pos); bufferSizeChoice->box.pos.x = box.size.x / 2; bufferSizeChoice->box.size.x = box.size.x / 2; bufferSizeChoice->port = port; addChild(bufferSizeChoice); this->bufferSizeChoice = bufferSizeChoice; } void AudioDeviceMenuChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); appendAudioMenu(menu, port); } void AudioButton::setAudioPort(audio::Port* port) { this->port = port; } void AudioButton::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); appendAudioMenu(menu, port); } void appendAudioMenu(ui::Menu* menu, audio::Port* port) { menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver"))); appendAudioDriverMenu(menu, port); menu->addChild(new ui::MenuSeparator); menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice"))); appendAudioDeviceMenu(menu, port); menu->addChild(new ui::MenuSeparator); menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate"))); appendAudioSampleRateMenu(menu, port); menu->addChild(new ui::MenuSeparator); menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize"))); appendAudioBlockSizeMenu(menu, port); // Uncomment this to use sub-menus instead of one big menu. // AudioDriverItem* driverItem = createMenuItem(string::translate("AudioDisplay.audioDriver"), RIGHT_ARROW); // driverItem->port = port; // menu->addChild(driverItem); // AudioDeviceItem* deviceItem = createMenuItem(string::translate("AudioDisplay.audioDevice"), RIGHT_ARROW); // deviceItem->port = port; // menu->addChild(deviceItem); // AudioSampleRateItem* sampleRateItem = createMenuItem(string::translate("AudioDisplay.sampleRate"), RIGHT_ARROW); // sampleRateItem->port = port; // menu->addChild(sampleRateItem); // AudioBlockSizeItem* blockSizeItem = createMenuItem(string::translate("AudioDisplay.blockSize"), RIGHT_ARROW); // blockSizeItem->port = port; // menu->addChild(blockSizeItem); } } // namespace app } // namespace rack