| @@ -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. | |||
|  | |||
| 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. | |||
| @@ -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 */ | |||
| @@ -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>(&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); | |||
| } | |||
| }; | |||
| @@ -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)); | |||
| @@ -35,11 +35,10 @@ struct AudioInterfaceIO : AudioIO { | |||
| DoubleRingBuffer<Frame<OUTPUTS>, (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(); | |||
| } | |||