@@ -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. | |||
@@ -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(); | |||
} | |||