diff --git a/README.md b/README.md index f0359040..fda2083b 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Rack -*Rack* is the engine for the VCV open-source virtual Eurorack DAW. +*Rack* is the engine for the VCV open-source virtual modular synthesizer. ![Rack screenshot](https://vcvrack.com/images/screenshot.png) This README includes instructions for building Rack from source. For information about the software, go to https://vcvrack.com/. -## The [Issue Tracker](https://github.com/VCVRack/Rack/issues) *is* the official developer's forum +## The [Issue Tracker](https://github.com/VCVRack/Rack/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) *is* the official developer's forum Bug reports, feature requests, and even *questions/discussions* are welcome on the GitHub Issue Tracker for all VCVRack repos. However, please search before posting to avoid duplicates, and limit to one issue per post. -You may vote on feature requests by using the Thumbs Up/Down reaction on the first post. +Please vote on feature requests by using the Thumbs Up/Down reaction on the first post. -I rarely accept Pull Requests, so please notify me in advance to plan your contribution before writing code. +I rarely accept code contributions to Rack itself, so please notify me in advance if you wish to send a pull request. ## Setting up your development environment @@ -33,14 +33,16 @@ Install build dependencies with the pacman package manger. ### Linux -With your distro's package manager, make sure you have installed `git`, `gcc`, `make`, `cmake`, `tar`, and `unzip`. +With your distro's package manager, make sure you have installed `git`, `gcc`, `make`, `cmake`, `tar`, `unzip`, and `curl`. ## Building *If the build fails for you, please report the issue with a detailed error message to help the portability of Rack.* Clone this repository with `git clone https://github.com/VCVRack/Rack.git` and `cd Rack`. -If you would like to build a previous version of Rack instead of the master branch, check out the desired tag with `git checkout v0.4.0` for example. + +The `master` branch contains the latest public code and breaks its plugin [API](https://en.wikipedia.org/wiki/Application_programming_interface) and [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) frequently. +If you wish to build a previous version of Rack which is API/ABI-compatible with an official Rack release, check out the desired branch with `git checkout v0.5` for example. Clone submodules. @@ -51,7 +53,7 @@ You may use make's `-j$(nproc)` flag to parallelize builds across all your CPU c make dep -You may use `make dep RTAUDIO_ALL_APIS=1` to attempt to build with all audio driver APIs enabled for your operating system. +You may use `make dep RTAUDIO_ALL_APIS=1` to attempt to build with all audio driver APIs enabled for your operating system, although this is unsupported. You should see a message that all dependencies built successfully. diff --git a/include/audio.hpp b/include/audio.hpp index d7d834f1..5af6d6a7 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -12,12 +12,10 @@ namespace rack { struct AudioIO { - int maxOutputs = 8; - int maxInputs = 8; - // Stream properties int driver = 0; int device = -1; + int offset = 0; int sampleRate = 44100; int blockSize = 256; int numOutputs = 0; @@ -34,8 +32,16 @@ struct AudioIO { void setDriver(int driver); int getDeviceCount(); + bool getDeviceInfo(int device, RtAudio::DeviceInfo *deviceInfo); + int getDeviceMaxChannels(int device); std::string getDeviceName(int device); - std::string getDeviceDetail(int device); + std::string getDeviceDetail(int device, int offset, int maxChannels); + void setDevice(int device, int offset, int maxChannels); + + void setSampleRate(int sampleRate); + void setBlockSize(int blockSize); + + /** Must close the stream before opening */ void openStream(); void closeStream(); /** Returns whether the audio stream is open and running */ diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index 2f0752e2..106fe63d 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -36,14 +36,17 @@ struct AudioDriverChoice : LedDisplayChoice { struct AudioDeviceItem : MenuItem { AudioIO *audioIO; int device; + int offset; + int maxChannels; void onAction(EventAction &e) override { - audioIO->device = device; - audioIO->openStream(); + audioIO->setDevice(device, offset, maxChannels); } }; struct AudioDeviceChoice : LedDisplayChoice { AudioWidget *audioWidget; + int groupChannels = 8; + int maxTotalChannels = 64; void onAction(EventAction &e) override { Menu *menu = gScene->createMenu(); menu->addChild(construct(&MenuLabel::text, "Audio device")); @@ -57,16 +60,21 @@ struct AudioDeviceChoice : LedDisplayChoice { menu->addChild(item); } for (int device = 0; device < deviceCount; device++) { - AudioDeviceItem *item = new AudioDeviceItem(); - item->audioIO = audioWidget->audioIO; - item->device = device; - item->text = audioWidget->audioIO->getDeviceDetail(device); - item->rightText = CHECKMARK(item->device == audioWidget->audioIO->device); - menu->addChild(item); + int maxChannels = min(maxTotalChannels, audioWidget->audioIO->getDeviceMaxChannels(device)); + for (int offset = 0; offset < maxChannels; offset += groupChannels) { + AudioDeviceItem *item = new AudioDeviceItem(); + item->audioIO = audioWidget->audioIO; + item->device = device; + item->offset = offset; + item->maxChannels = groupChannels; + item->text = audioWidget->audioIO->getDeviceDetail(device, offset, groupChannels); + item->rightText = CHECKMARK(item->device == audioWidget->audioIO->device && item->offset == audioWidget->audioIO->offset); + menu->addChild(item); + } } } void step() override { - text = audioWidget->audioIO->getDeviceDetail(audioWidget->audioIO->device); + text = audioWidget->audioIO->getDeviceDetail(audioWidget->audioIO->device, audioWidget->audioIO->offset, groupChannels); if (text.empty()) { text = "(No device)"; color.a = 0.5f; @@ -83,8 +91,7 @@ struct AudioSampleRateItem : MenuItem { AudioIO *audioIO; int sampleRate; void onAction(EventAction &e) override { - audioIO->sampleRate = sampleRate; - audioIO->openStream(); + audioIO->setSampleRate(sampleRate); } }; @@ -112,8 +119,7 @@ struct AudioBlockSizeItem : MenuItem { AudioIO *audioIO; int blockSize; void onAction(EventAction &e) override { - audioIO->blockSize = blockSize; - audioIO->openStream(); + audioIO->setBlockSize(blockSize); } }; diff --git a/src/audio.cpp b/src/audio.cpp index 8d14cca3..0a6fa47a 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -75,54 +75,107 @@ int AudioIO::getDeviceCount() { return 0; } +bool AudioIO::getDeviceInfo(int device, RtAudio::DeviceInfo *deviceInfo) { + if (!deviceInfo) + return false; + + if (rtAudio) { + if (device == this->device) { + *deviceInfo = this->deviceInfo; + return true; + } + else { + try { + *deviceInfo = rtAudio->getDeviceInfo(device); + return true; + } + catch (RtAudioError &e) { + warn("Failed to query RtAudio device: %s", e.what()); + return false; + } + } + } + else { + return false; + } +} + +int AudioIO::getDeviceMaxChannels(int device) { + if (device < 0) + return 0; + + if (rtAudio) { + RtAudio::DeviceInfo deviceInfo; + if (getDeviceInfo(device, &deviceInfo)) + return max(deviceInfo.inputChannels, deviceInfo.outputChannels); + } + if (driver == BRIDGE_DRIVER) { + return 2; + } + return 0; +} + std::string AudioIO::getDeviceName(int device) { if (device < 0) return ""; if (rtAudio) { - try { - RtAudio::DeviceInfo deviceInfo; - if (device == this->device) - deviceInfo = this->deviceInfo; - else - deviceInfo = rtAudio->getDeviceInfo(device); + RtAudio::DeviceInfo deviceInfo; + if (getDeviceInfo(device, &deviceInfo)) return deviceInfo.name; - } - catch (RtAudioError &e) { - warn("Failed to query RtAudio device: %s", e.what()); - } } if (driver == BRIDGE_DRIVER) { - if (device >= 0) - return stringf("%d", device + 1); + return stringf("%d", device + 1); } return ""; } -std::string AudioIO::getDeviceDetail(int device) { +std::string AudioIO::getDeviceDetail(int device, int offset, int maxChannels) { if (device < 0) return ""; if (rtAudio) { - try { - RtAudio::DeviceInfo deviceInfo; - if (device == this->device) - deviceInfo = this->deviceInfo; - else - deviceInfo = rtAudio->getDeviceInfo(device); - return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); - } - catch (RtAudioError &e) { - warn("Failed to query RtAudio device: %s", e.what()); + RtAudio::DeviceInfo deviceInfo; + if (getDeviceInfo(device, &deviceInfo)) { + std::string deviceDetail = stringf("%s (", deviceInfo.name.c_str()); + if (offset < (int) deviceInfo.inputChannels) + deviceDetail += stringf("%d-%d in", offset + 1, min(offset + maxChannels, deviceInfo.inputChannels)); + if (offset < (int) deviceInfo.inputChannels && offset < (int) deviceInfo.outputChannels) + deviceDetail += ", "; + if (offset < (int) deviceInfo.outputChannels) + deviceDetail += stringf("%d-%d out", offset + 1, min(offset + maxChannels, deviceInfo.outputChannels)); + deviceDetail += ")"; + return deviceDetail; } } if (driver == BRIDGE_DRIVER) { - if (device >= 0) - return stringf("Channel %d", device + 1); + return stringf("Channel %d", device + 1); } return ""; } +void AudioIO::setDevice(int device, int offset, int maxChannels) { + closeStream(); + this->device = device; + this->offset = offset; + this->numOutputs = maxChannels; + this->numInputs = maxChannels; + openStream(); +} + +void AudioIO::setSampleRate(int sampleRate) { + closeStream(); + this->sampleRate = sampleRate; + openStream(); +} + +void AudioIO::setBlockSize(int blockSize) { + closeStream(); + this->blockSize = blockSize; + openStream(); +} + + static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { AudioIO *audioIO = (AudioIO*) userData; assert(audioIO); @@ -131,10 +184,6 @@ static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrame } void AudioIO::openStream() { - // Close device but remember the current device number - int device = this->device; - closeStream(); - if (device < 0) return; @@ -148,8 +197,11 @@ void AudioIO::openStream() { return; } - numOutputs = min(deviceInfo.outputChannels, maxOutputs); - numInputs = min(deviceInfo.inputChannels, maxInputs); + if (rtAudio->isStreamOpen()) + return; + + numOutputs = clamp((int) deviceInfo.outputChannels - offset, 0, numOutputs); + numInputs = clamp((int) deviceInfo.inputChannels - offset, 0, numInputs); if (numOutputs == 0 && numInputs == 0) { warn("RtAudio device %d has 0 inputs and 0 outputs"); @@ -159,10 +211,12 @@ void AudioIO::openStream() { RtAudio::StreamParameters outParameters; outParameters.deviceId = device; outParameters.nChannels = numOutputs; + outParameters.firstChannel = offset; RtAudio::StreamParameters inParameters; inParameters.deviceId = device; inParameters.nChannels = numInputs; + inParameters.firstChannel = offset; RtAudio::StreamOptions options; // options.flags |= RTAUDIO_SCHEDULE_REALTIME; @@ -175,11 +229,12 @@ void AudioIO::openStream() { } try { - debug("Opening audio RtAudio device %d", device); + debug("Opening audio RtAudio device %d with %d in %d out", device, numInputs, numOutputs); rtAudio->openStream( numOutputs == 0 ? NULL : &outParameters, numInputs == 0 ? NULL : &inParameters, - RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); + RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, + &rtCallback, this, &options, NULL); } catch (RtAudioError &e) { warn("Failed to open RtAudio stream: %s", e.what()); @@ -197,12 +252,11 @@ void AudioIO::openStream() { // Update sample rate because this may have changed this->sampleRate = rtAudio->getStreamSampleRate(); - this->device = device; onOpenStream(); } if (driver == BRIDGE_DRIVER) { if (device < BRIDGE_CHANNELS) { - this->device = device; + // TODO } } } @@ -230,10 +284,6 @@ void AudioIO::closeStream() { deviceInfo = RtAudio::DeviceInfo(); } - // Reset rtAudio settings - device = -1; - numOutputs = 0; - numInputs = 0; onCloseStream(); } @@ -274,6 +324,8 @@ json_t *AudioIO::toJson() { } void AudioIO::fromJson(json_t *rootJ) { + closeStream(); + json_t *driverJ = json_object_get(rootJ, "driver"); if (driverJ) setDriver(json_number_value(driverJ)); diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 861b3f8c..4a9c210f 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -35,11 +35,10 @@ struct AudioInterfaceIO : AudioIO { DoubleRingBuffer, (1<<15)> outputBuffer; AudioInterfaceIO() { - maxOutputs = OUTPUTS; - maxInputs = INPUTS; } ~AudioInterfaceIO() { + // Close stream here before destructing AudioInterfaceIO, so the mutexes are still valid when waiting to close. closeStream(); }