Browse Source

Restructure audio namespace to mirror midi class structure. (untested)

tags/v2.0.0
Andrew Belt 5 years ago
parent
commit
b6a7b09e7a
10 changed files with 629 additions and 377 deletions
  1. +152
    -39
      include/audio.hpp
  2. +0
    -1
      include/midi.hpp
  3. +11
    -0
      include/rtaudio.hpp
  4. +17
    -15
      src/app/AudioWidget.cpp
  5. +129
    -269
      src/audio.cpp
  6. +45
    -28
      src/core/AudioInterface.cpp
  7. +5
    -0
      src/main.cpp
  8. +26
    -25
      src/midi.cpp
  9. +242
    -0
      src/rtaudio.cpp
  10. +2
    -0
      src/rtmidi.cpp

+ 152
- 39
include/audio.hpp View File

@@ -1,14 +1,8 @@
#pragma once
#include <common.hpp>
#include <jansson.h>

#pragma GCC diagnostic push
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wsuggest-override"
#endif
#include <RtAudio.h>
#pragma GCC diagnostic pop

#include <vector>
#include <set>

namespace rack {

@@ -18,54 +12,173 @@ namespace rack {
namespace audio {


////////////////////
// Driver
////////////////////

struct Port;
struct Device;

struct Driver {
virtual ~Driver() {}
virtual std::string getName() {
return "";
}
virtual std::vector<int> getDeviceIds() {
return {};
}
virtual std::string getDeviceName(int deviceId) {
return "";
}
virtual Device* subscribe(int deviceId, Port* port) {
return NULL;
}
virtual void unsubscribe(int deviceId, Port* port) {}
};

////////////////////
// Device
////////////////////

struct Device {
std::set<Port*> subscribed;
virtual ~Device() {}
// Called by Driver::subscribe().
void subscribe(Port* port);
void unsubscribe(Port* port);

// Called by Port.
virtual std::vector<int> getSampleRates() {
return {};
}
virtual int getSampleRate() {
return 0;
}
virtual void setSampleRate(int sampleRate) {}

virtual std::vector<int> getBlockSizes() {
return {};
}
virtual int getBlockSize() {
return 0;
}
virtual void setBlockSize(int blockSize) {}

virtual int getNumInputs() {
return 0;
}
virtual int getNumOutputs() {
return 0;
}

// Called by this Device class, forwards to subscribed Ports.
void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames);
void onOpenStream();
void onCloseStream();
};

////////////////////
// Port
////////////////////

struct Port {
// Stream properties
int driverId = 0;
int deviceId = -1;
/** Not owned */
Driver* driver = NULL;
Device* device = NULL;

// Port settings
int offset = 0;
int maxChannels = 8;
int sampleRate = 44100;
int blockSize = 256;
int numOutputs = 0;
int numInputs = 0;
RtAudio* rtAudio = NULL;
/** Cached */
RtAudio::DeviceInfo deviceInfo;

// private
int driverId = -1;
int deviceId = -1;

Port();
virtual ~Port();

std::vector<int> getDriverIds();
std::string getDriverName(int driverId);
int getDriverId() {
return driverId;
}
void setDriverId(int driverId);
std::string getDriverName(int driverId);

int getDeviceCount();
bool getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo);
/** Returns the number of inputs or outputs, whichever is greater */
int getDeviceChannels(int deviceId);
std::string getDeviceName(int deviceId);
std::vector<int> getDeviceIds() {
if (!driver)
return {};
return driver->getDeviceIds();
}
int getDeviceId() {
return deviceId;
}
void setDeviceId(int deviceId);

std::string getDeviceName(int deviceId) {
if (!driver)
return "";
return driver->getDeviceName(deviceId);
}
std::string getDeviceDetail(int deviceId, int offset);
void setDeviceId(int deviceId, int offset);

std::vector<int> getSampleRates();
void setSampleRate(int sampleRate);
std::vector<int> getBlockSizes();
void setBlockSize(int blockSize);

void setChannels(int numOutputs, int numInputs);
std::vector<int> getSampleRates() {
if (!device)
return {};
return device->getSampleRates();
}
int getSampleRate() {
if (!device)
return 0;
return device->getSampleRate();
}
void setSampleRate(int sampleRate) {
if (device)
device->setSampleRate(sampleRate);
}

std::vector<int> getBlockSizes() {
if (!device)
return {};
return device->getBlockSizes();
}
int getBlockSize() {
if (!device)
return 0;
return device->getBlockSize();
}
void setBlockSize(int blockSize) {
if (device)
device->setBlockSize(blockSize);
}

int getNumInputs();
int getNumOutputs();

/** Must close the stream before opening */
void openStream();
void closeStream();

virtual void processStream(const float* input, float* output, int frames) {}
virtual void onCloseStream() {}
virtual void onOpenStream() {}
virtual void onChannelsChange() {}
json_t* toJson();
void fromJson(json_t* rootJ);

/** Callback for processing the audio stream.
`inputStride` and `outputStride` are the number of array elements between frames in the buffers.
*/
virtual void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) {}
/** Called before processBuffer() is called for all Ports of the same device.
Splitting the processBuffer() into these calls is useful for synchronizing Ports of the same device.
Called even if there are no inputs.
*/
virtual void processInput(const float* input, int inputStride, int frames) {}
/** Called after processBuffer() is called for all Ports of the same device.
*/
virtual void processOutput(float* output, int outputStride, int frames) {}
virtual void onOpenStream() {}
virtual void onCloseStream() {}
};


void init();
void destroy();
/** Registers a new audio driver. Takes pointer ownership. */
void addDriver(int driverId, Driver* driver);


} // namespace audio
} // namespace rack

+ 0
- 1
include/midi.hpp View File

@@ -62,7 +62,6 @@ struct Driver {
virtual std::string getName() {
return "";
}

virtual std::vector<int> getInputDeviceIds() {
return {};
}


+ 11
- 0
include/rtaudio.hpp View File

@@ -0,0 +1,11 @@
#pragma once
#include <common.hpp>


namespace rack {


void rtaudioInit();


} // namespace rack

+ 17
- 15
src/app/AudioWidget.cpp View File

@@ -27,13 +27,13 @@ struct AudioDriverChoice : LedDisplayChoice {
item->port = port;
item->driverId = driverId;
item->text = port->getDriverName(driverId);
item->rightText = CHECKMARK(item->driverId == port->driverId);
item->rightText = CHECKMARK(item->driverId == port->getDriverId());
menu->addChild(item);
}
}
void step() override {
text = (box.size.x >= 200.0) ? "Driver: " : "";
std::string driverName = (port) ? port->getDriverName(port->driverId) : "";
std::string driverName = port ? port->getDriverName(port->getDriverId()) : "";
if (driverName != "") {
text += driverName;
color.a = 1.f;
@@ -51,14 +51,13 @@ struct AudioDeviceItem : ui::MenuItem {
int deviceId;
int offset;
void onAction(const event::Action& e) override {
port->setDeviceId(deviceId, offset);
port->setDeviceId(deviceId);
port->offset = offset;
}
};

struct AudioDeviceChoice : LedDisplayChoice {
audio::Port* port;
/** Prevents devices with a ridiculous number of channels from being displayed */
int maxTotalChannels = 128;

void onAction(const event::Action& e) override {
if (!port)
@@ -66,24 +65,27 @@ struct AudioDeviceChoice : LedDisplayChoice {

ui::Menu* menu = createMenu();
menu->addChild(createMenuLabel("Audio device"));
int deviceCount = port->getDeviceCount();
{
AudioDeviceItem* item = new AudioDeviceItem;
item->port = port;
item->deviceId = -1;
item->text = "(No device)";
item->rightText = CHECKMARK(item->deviceId == port->deviceId);
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId());
menu->addChild(item);
}
for (int deviceId = 0; deviceId < deviceCount; deviceId++) {
int channels = std::min(maxTotalChannels, port->getDeviceChannels(deviceId));
for (int deviceId : port->getDeviceIds()) {
int channels = std::max(port->getNumInputs(), port->getNumOutputs());
/** Prevents devices with a ridiculous number of channels from being displayed */
const int maxTotalChannels = 128;
channels = std::min(maxTotalChannels, channels);

for (int offset = 0; offset < channels; offset += port->maxChannels) {
AudioDeviceItem* item = new AudioDeviceItem;
item->port = port;
item->deviceId = deviceId;
item->offset = offset;
item->text = port->getDeviceDetail(deviceId, offset);
item->rightText = CHECKMARK(item->deviceId == port->deviceId && item->offset == port->offset);
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset);
menu->addChild(item);
}
}
@@ -128,14 +130,14 @@ struct AudioSampleRateChoice : LedDisplayChoice {
item->port = port;
item->sampleRate = sampleRate;
item->text = string::f("%g kHz", sampleRate / 1000.0);
item->rightText = CHECKMARK(item->sampleRate == port->sampleRate);
item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate());
menu->addChild(item);
}
}
void step() override {
text = (box.size.x >= 100.0) ? "Rate: " : "";
if (port) {
text += string::f("%g kHz", port->sampleRate / 1000.0);
text += string::f("%g kHz", port->getSampleRate() / 1000.0);
}
else {
text += "0 kHz";
@@ -168,16 +170,16 @@ struct AudioBlockSizeChoice : LedDisplayChoice {
AudioBlockSizeItem* item = new AudioBlockSizeItem;
item->port = port;
item->blockSize = blockSize;
float latency = (float) blockSize / port->sampleRate * 1000.0;
float latency = (float) blockSize / port->getSampleRate() * 1000.0;
item->text = string::f("%d (%.1f ms)", blockSize, latency);
item->rightText = CHECKMARK(item->blockSize == port->blockSize);
item->rightText = CHECKMARK(item->blockSize == port->getBlockSize());
menu->addChild(item);
}
}
void step() override {
text = (box.size.x >= 100.0) ? "Block size: " : "";
if (port) {
text += string::f("%d", port->blockSize);
text += string::f("%d", port->getBlockSize());
}
else {
text += "0";


+ 129
- 269
src/audio.cpp View File

@@ -1,320 +1,166 @@
#include <audio.hpp>
#include <string.hpp>
#include <math.hpp>
#include <system.hpp>


namespace rack {
namespace audio {


Port::Port() {
setDriverId(0);
}
static std::vector<std::pair<int, Driver*>> drivers;

Port::~Port() {
closeStream();
}

std::vector<int> Port::getDriverIds() {
std::vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
std::vector<int> drivers;
for (RtAudio::Api api : apis) {
drivers.push_back((int) api);
}
return drivers;
}
////////////////////
// Device
////////////////////

std::string Port::getDriverName(int driverId) {
switch (driverId) {
case 0: return "Default";
case RtAudio::LINUX_ALSA: return "ALSA";
case RtAudio::LINUX_PULSE: return "PulseAudio";
case RtAudio::LINUX_OSS: return "OSS";
case RtAudio::UNIX_JACK: return "JACK";
case RtAudio::MACOSX_CORE: return "Core Audio";
case RtAudio::WINDOWS_WASAPI: return "WASAPI";
case RtAudio::WINDOWS_ASIO: return "ASIO";
case RtAudio::WINDOWS_DS: return "DirectSound";
case RtAudio::RTAUDIO_DUMMY: return "Dummy Audio";
default: return "Unknown";
}
void Device::subscribe(Port* port) {
subscribed.insert(port);
}

void Port::setDriverId(int driverId) {
// Close device
setDeviceId(-1, 0);
void Device::unsubscribe(Port* port) {
auto it = subscribed.find(port);
if (it != subscribed.end())
subscribed.erase(it);
}

// Close driver
if (rtAudio) {
delete rtAudio;
rtAudio = NULL;
void Device::processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) {
for (Port* port : subscribed) {
port->processInput(input + port->offset, inputStride, frames);
}
this->driverId = 0;

// Open driver
if (driverId >= 0) {
rtAudio = new RtAudio((RtAudio::Api) driverId);
this->driverId = (int) rtAudio->getCurrentApi();
for (Port* port : subscribed) {
port->processBuffer(input + port->offset, inputStride, output + port->offset, outputStride, frames);
}
}

int Port::getDeviceCount() {
if (rtAudio) {
return rtAudio->getDeviceCount();
for (Port* port : subscribed) {
port->processOutput(output + port->offset, outputStride, frames);
}
return 0;
}

bool Port::getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo) {
if (!deviceInfo)
return false;

if (rtAudio) {
if (deviceId == this->deviceId) {
*deviceInfo = this->deviceInfo;
return true;
}
else {
try {
*deviceInfo = rtAudio->getDeviceInfo(deviceId);
return true;
}
catch (RtAudioError& e) {
WARN("Failed to query RtAudio device: %s", e.what());
}
}
void Device::onOpenStream() {
for (Port* port : subscribed) {
port->onOpenStream();
}

return false;
}

int Port::getDeviceChannels(int deviceId) {
if (deviceId < 0)
return 0;

if (rtAudio) {
RtAudio::DeviceInfo deviceInfo;
if (getDeviceInfo(deviceId, &deviceInfo))
return std::max((int) deviceInfo.inputChannels, (int) deviceInfo.outputChannels);
void Device::onCloseStream() {
for (Port* port : subscribed) {
port->onCloseStream();
}
return 0;
}

std::string Port::getDeviceName(int deviceId) {
if (deviceId < 0)
return "";
////////////////////
// Port
////////////////////

if (rtAudio) {
RtAudio::DeviceInfo deviceInfo;
if (getDeviceInfo(deviceId, &deviceInfo))
return deviceInfo.name;
}
return "";
Port::Port() {
setDriverId(-1);
}

std::string Port::getDeviceDetail(int deviceId, int offset) {
if (deviceId < 0)
return "";
Port::~Port() {
setDriverId(-1);
}

if (rtAudio) {
RtAudio::DeviceInfo deviceInfo;
if (getDeviceInfo(deviceId, &deviceInfo)) {
std::string deviceDetail = string::f("%s (", deviceInfo.name.c_str());
if (offset < (int) deviceInfo.inputChannels)
deviceDetail += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.inputChannels));
if (offset < (int) deviceInfo.inputChannels && offset < (int) deviceInfo.outputChannels)
deviceDetail += ", ";
if (offset < (int) deviceInfo.outputChannels)
deviceDetail += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.outputChannels));
deviceDetail += ")";
return deviceDetail;
}
std::vector<int> Port::getDriverIds() {
std::vector<int> driverIds;
for (auto& pair : drivers) {
driverIds.push_back(pair.first);
}
return "";
return driverIds;
}

void Port::setDeviceId(int deviceId, int offset) {
closeStream();
this->deviceId = deviceId;
this->offset = offset;
openStream();
}
void Port::setDriverId(int driverId) {
// Unset device and driver
setDeviceId(-1);
driver = NULL;
this->driverId = -1;

std::vector<int> Port::getSampleRates() {
if (rtAudio) {
try {
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId);
std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end());
return sampleRates;
if (driverId == -1) {
// Set first driver as default
if (!drivers.empty()) {
driver = drivers[0].second;
this->driverId = drivers[0].first;
}
catch (RtAudioError& e) {
WARN("Failed to query RtAudio device: %s", e.what());
}
else {
// Set driver with driverId
for (auto& pair : drivers) {
if (pair.first == driverId) {
driver = pair.second;
this->driverId = driverId;
break;
}
}
}
return {};
}

void Port::setSampleRate(int sampleRate) {
if (sampleRate == this->sampleRate)
return;
closeStream();
this->sampleRate = sampleRate;
openStream();
}

std::vector<int> Port::getBlockSizes() {
if (rtAudio) {
return {64, 128, 256, 512, 1024, 2048, 4096};
std::string Port::getDriverName(int driverId) {
for (auto& pair : drivers) {
if (pair.first == driverId) {
return pair.second->getName();
}
}
return {};
}

void Port::setBlockSize(int blockSize) {
if (blockSize == this->blockSize)
return;
closeStream();
this->blockSize = blockSize;
openStream();
}

void Port::setChannels(int numOutputs, int numInputs) {
this->numOutputs = numOutputs;
this->numInputs = numInputs;
onChannelsChange();
return "";
}

void Port::setDeviceId(int deviceId) {
// Destroy device
if (driver && this->deviceId >= 0) {
driver->unsubscribe(this->deviceId, this);
}
device = NULL;
this->deviceId = -1;

static int rtCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
Port* port = (Port*) userData;
assert(port);
// Exploit the stream time to run code on startup of the audio thread
if (streamTime == 0.0) {
system::setThreadName("Audio");
// system::setThreadRealTime();
// Create device
if (driver && deviceId >= 0) {
device = driver->subscribe(deviceId, this);
this->deviceId = deviceId;
}
port->processStream((const float*) inputBuffer, (float*) outputBuffer, nFrames);
return 0;
}

void Port::openStream() {
if (deviceId < 0)
return;

if (rtAudio) {
// Open new device
try {
deviceInfo = rtAudio->getDeviceInfo(deviceId);
}
catch (RtAudioError& e) {
WARN("Failed to query RtAudio device: %s", e.what());
return;
}

if (rtAudio->isStreamOpen())
return;

setChannels(math::clamp((int) deviceInfo.outputChannels - offset, 0, maxChannels), math::clamp((int) deviceInfo.inputChannels - offset, 0, maxChannels));

if (numOutputs == 0 && numInputs == 0) {
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId);
return;
}

RtAudio::StreamParameters outParameters;
outParameters.deviceId = deviceId;
outParameters.nChannels = numOutputs;
outParameters.firstChannel = offset;

RtAudio::StreamParameters inParameters;
inParameters.deviceId = deviceId;
inParameters.nChannels = numInputs;
inParameters.firstChannel = offset;

RtAudio::StreamOptions options;
options.flags |= RTAUDIO_JACK_DONT_CONNECT;
options.streamName = "VCV Rack";

int closestSampleRate = deviceInfo.preferredSampleRate;
for (int sr : deviceInfo.sampleRates) {
if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) {
closestSampleRate = sr;
}
}

try {
INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, numInputs, numOutputs);
rtAudio->openStream(
numOutputs == 0 ? NULL : &outParameters,
numInputs == 0 ? NULL : &inParameters,
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize,
&rtCallback, this, &options, NULL);
}
catch (RtAudioError& e) {
WARN("Failed to open RtAudio stream: %s", e.what());
return;
}

try {
INFO("Starting RtAudio stream %d", deviceId);
rtAudio->startStream();
}
catch (RtAudioError& e) {
WARN("Failed to start RtAudio stream: %s", e.what());
return;
}

// Update sample rate because this may have changed
this->sampleRate = rtAudio->getStreamSampleRate();
onOpenStream();
std::string Port::getDeviceDetail(int deviceId, int offset) {
if (!driver || !device)
return "";
std::string text = getDeviceName(getDeviceId());
text += " (";
int numInputs = device->getNumInputs();
int numOutputs = device->getNumOutputs();
if (offset < numInputs) {
text += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, numInputs));
}
if (offset < numInputs && offset < numOutputs) {
text += ", ";
}
if (offset < numOutputs) {
text += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, numOutputs));
}
text += ")";
return text;
}

void Port::closeStream() {
setChannels(0, 0);

if (rtAudio) {
if (rtAudio->isStreamRunning()) {
INFO("Stopping RtAudio stream %d", deviceId);
try {
rtAudio->stopStream();
}
catch (RtAudioError& e) {
WARN("Failed to stop RtAudio stream %s", e.what());
}
}
if (rtAudio->isStreamOpen()) {
INFO("Closing RtAudio stream %d", deviceId);
try {
rtAudio->closeStream();
}
catch (RtAudioError& e) {
WARN("Failed to close RtAudio stream %s", e.what());
}
}
deviceInfo = RtAudio::DeviceInfo();
}
int Port::getNumInputs() {
if (!device)
return 0;
return std::min(device->getNumInputs() - offset, maxChannels);
}

onCloseStream();
int Port::getNumOutputs() {
if (!device)
return 0;
return std::min(device->getNumOutputs() - offset, maxChannels);
}

json_t* Port::toJson() {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "driver", json_integer(driverId));
std::string deviceName = getDeviceName(deviceId);
json_object_set_new(rootJ, "driver", json_integer(getDriverId()));
std::string deviceName = getDeviceName(getDeviceId());
if (!deviceName.empty())
json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str()));
json_object_set_new(rootJ, "sampleRate", json_integer(getSampleRate()));
json_object_set_new(rootJ, "blockSize", json_integer(getBlockSize()));
json_object_set_new(rootJ, "offset", json_integer(offset));
json_object_set_new(rootJ, "maxChannels", json_integer(maxChannels));
json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate));
json_object_set_new(rootJ, "blockSize", json_integer(blockSize));
return rootJ;
}

void Port::fromJson(json_t* rootJ) {
closeStream();

json_t* driverJ = json_object_get(rootJ, "driver");
if (driverJ)
setDriverId(json_number_value(driverJ));
@@ -323,31 +169,45 @@ void Port::fromJson(json_t* rootJ) {
if (deviceNameJ) {
std::string deviceName = json_string_value(deviceNameJ);
// Search for device ID with equal name
for (int deviceId = 0; deviceId < getDeviceCount(); deviceId++) {
for (int deviceId : getDeviceIds()) {
if (getDeviceName(deviceId) == deviceName) {
this->deviceId = deviceId;
setDeviceId(deviceId);
break;
}
}
}

json_t* sampleRateJ = json_object_get(rootJ, "sampleRate");
if (sampleRateJ)
setSampleRate(json_integer_value(sampleRateJ));

json_t* blockSizeJ = json_object_get(rootJ, "blockSize");
if (blockSizeJ)
setBlockSize(json_integer_value(blockSizeJ));

json_t* offsetJ = json_object_get(rootJ, "offset");
if (offsetJ)
offset = json_integer_value(offsetJ);
}

json_t* maxChannelsJ = json_object_get(rootJ, "maxChannels");
if (maxChannelsJ)
maxChannels = json_integer_value(maxChannelsJ);

json_t* sampleRateJ = json_object_get(rootJ, "sampleRate");
if (sampleRateJ)
sampleRate = json_integer_value(sampleRateJ);
////////////////////
// audio
////////////////////

json_t* blockSizeJ = json_object_get(rootJ, "blockSize");
if (blockSizeJ)
blockSize = json_integer_value(blockSizeJ);
void init() {
}

void destroy() {
for (auto& pair : drivers) {
delete pair.second;
}
drivers.clear();
}

openStream();
void addDriver(int driverId, Driver* driver) {
assert(driver);
drivers.push_back(std::make_pair(driverId, driver));
}




+ 45
- 28
src/core/AudioInterface.cpp View File

@@ -37,20 +37,32 @@ struct AudioInterface : Module, audio::Port {
dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc;
dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc;

dsp::ClockDivider lightDivider;

// Port variables
int requestedEngineFrames = 0;
int maxEngineFrames = 0;

AudioInterface() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
for (int i = 0; i < NUM_AUDIO_INPUTS; i++)
configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1));
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++)
configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1));

lightDivider.setDivision(512);
maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS);
inputSrc.setQuality(6);
outputSrc.setQuality(6);
}

~AudioInterface() {
// Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close.
setDeviceId(-1, 0);
// Close stream here before destructing AudioInterfacePort, so processBuffer() etc are not called on another thread while destructing.
setDriverId(-1);
}

void onReset() override {
setDriverId(-1);
}

void onSampleRateChange(const SampleRateChangeEvent& e) override {
@@ -59,7 +71,7 @@ struct AudioInterface : Module, audio::Port {
}

void process(const ProcessArgs& args) override {
// Get inputs
// Push inputs to buffer
if (!inputBuffer.full()) {
dsp::Frame<NUM_AUDIO_INPUTS> inputFrame;
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
@@ -68,7 +80,7 @@ struct AudioInterface : Module, audio::Port {
inputBuffer.push(inputFrame);
}

// Set outputs
// Pull outputs from buffer
if (!outputBuffer.empty()) {
dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift();
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) {
@@ -81,12 +93,16 @@ struct AudioInterface : Module, audio::Port {
}
}

// Turn on light if at least one port is enabled in the nearby pair
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) {
lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1);
}
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) {
lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1);
if (lightDivider.process()) {
// Turn on light if at least one port is enabled in the nearby pair
int numInputs = getNumInputs();
int numOutputs = getNumOutputs();
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) {
lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1);
}
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) {
lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1);
}
}
}

@@ -102,46 +118,38 @@ struct AudioInterface : Module, audio::Port {
audio::Port::fromJson(audioJ);
}

void onReset() override {
setDeviceId(-1, 0);
}

// audio::Port

void processStream(const float* input, float* output, int frames) override {
void processInput(const float* input, int inputStride, int frames) override {
// Claim primary module if there is none
if (!APP->engine->getPrimaryModule()) {
APP->engine->setPrimaryModule(this);
}
bool isPrimary = (APP->engine->getPrimaryModule() == this);

// Clear output in case the audio driver uses this buffer in another thread before this method returns. (Not sure if any do this in practice.)
std::memset(output, 0, sizeof(float) * numOutputs * frames);

// Initialize sample rate converters
int numInputs = getNumInputs();
int engineSampleRate = (int) APP->engine->getSampleRate();
int sampleRate = getSampleRate();
double sampleRateRatio = (double) engineSampleRate / sampleRate;
outputSrc.setRates((int) sampleRate, engineSampleRate);
outputSrc.setRates(sampleRate, engineSampleRate);
outputSrc.setChannels(numInputs);
inputSrc.setRates(engineSampleRate, (int) sampleRate);
inputSrc.setChannels(numOutputs);

// Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate.
int maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5);
maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5);
// If this is a secondary audio module and the engine output buffer is too full, flush it.
if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) {
outputBuffer.clear();
// DEBUG("%p: flushing engine output", this);
}

int requestedEngineFrames;
if (numInputs > 0) {
// audio input -> engine output
dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames];
std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer));
for (int i = 0; i < frames; i++) {
for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) {
float v = input[i * numInputs + j];
float v = input[i * inputStride + j];
inputAudioBuffer[i].samples[j] = v;
}
}
@@ -156,11 +164,23 @@ struct AudioInterface : Module, audio::Port {
// Upper bound on number of frames so that `outputAudioFrames >= frames` at the end of this method.
requestedEngineFrames = (int) std::ceil(frames * sampleRateRatio) - inputBuffer.size();
}
}

void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override {
bool isPrimary = (APP->engine->getPrimaryModule() == this);
// Step engine
if (isPrimary && requestedEngineFrames > 0) {
APP->engine->step(requestedEngineFrames);
}
}

void processOutput(float* output, int outputStride, int frames) override {
bool isPrimary = (APP->engine->getPrimaryModule() == this);
int numOutputs = getNumOutputs();
int engineSampleRate = (int) APP->engine->getSampleRate();
int sampleRate = getSampleRate();
inputSrc.setRates(engineSampleRate, sampleRate);
inputSrc.setChannels(numOutputs);

if (numOutputs > 0) {
// engine input -> audio output
@@ -173,7 +193,7 @@ struct AudioInterface : Module, audio::Port {
for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) {
float v = outputAudioBuffer[i].samples[j];
v = clamp(v, -1.f, 1.f);
output[i * numOutputs + j] = v;
output[i * outputStride + j] = v;
}
}
}
@@ -196,9 +216,6 @@ struct AudioInterface : Module, audio::Port {
inputBuffer.clear();
outputBuffer.clear();
}

void onChannelsChange() override {
}
};




+ 5
- 0
src/main.cpp View File

@@ -1,6 +1,8 @@
#include <common.hpp>
#include <random.hpp>
#include <asset.hpp>
#include <audio.hpp>
#include <rtaudio.hpp>
#include <midi.hpp>
#include <rtmidi.hpp>
#include <keyboard.hpp>
@@ -155,6 +157,8 @@ int main(int argc, char* argv[]) {
INFO("Initializing environment");
random::init();
network::init();
audio::init();
rtaudioInit();
midi::init();
rtmidiInit();
keyboard::init();
@@ -215,6 +219,7 @@ int main(int argc, char* argv[]) {
}
plugin::destroy();
midi::destroy();
audio::destroy();
INFO("Destroying logger");
logger::destroy();



+ 26
- 25
src/midi.cpp View File

@@ -1,15 +1,14 @@
#include <midi.hpp>
#include <string.hpp>
#include <map>
#include <utility>


namespace rack {
namespace midi {


/** Preserves the order of IDs */
static std::vector<int> driverIds;
static std::map<int, Driver*> drivers;
static std::vector<std::pair<int, Driver*>> drivers;


////////////////////
@@ -41,7 +40,6 @@ void OutputDevice::subscribe(Output* output) {
}

void OutputDevice::unsubscribe(Output* output) {
// Remove Output from subscriptions
auto it = subscribed.find(output);
if (it != subscribed.end())
subscribed.erase(it);
@@ -52,30 +50,35 @@ void OutputDevice::unsubscribe(Output* output) {
////////////////////

std::vector<int> Port::getDriverIds() {
std::vector<int> driverIds;
for (auto& pair : drivers) {
driverIds.push_back(pair.first);
}
return driverIds;
}

std::string Port::getDriverName(int driverId) {
auto it = drivers.find(driverId);
if (it == drivers.end())
return "";

return it->second->getName();
for (auto& pair : drivers) {
if (pair.first == driverId) {
return pair.second->getName();
}
}
return "";
}

void Port::setDriverId(int driverId) {
// Unset device and driver
setDeviceId(-1);
if (driver) {
driver = NULL;
}
driver = NULL;
this->driverId = -1;

// Set driver
auto it = drivers.find(driverId);
if (it != drivers.end()) {
driver = it->second;
this->driverId = driverId;
for (auto& pair : drivers) {
if (pair.first == driverId) {
driver = pair.second;
this->driverId = driverId;
break;
}
}
}

@@ -137,8 +140,8 @@ Input::~Input() {
void Input::reset() {
channel = -1;
// Set first driver as default
if (driverIds.size() >= 1) {
setDriverId(driverIds[0]);
if (drivers.size() >= 1) {
setDriverId(drivers[0].first);
}
}

@@ -160,8 +163,8 @@ void Input::setDeviceId(int deviceId) {
// Destroy device
if (driver && this->deviceId >= 0) {
driver->unsubscribeInput(this->deviceId, this);
inputDevice = NULL;
}
inputDevice = NULL;
this->deviceId = -1;

// Create device
@@ -211,8 +214,8 @@ Output::~Output() {
void Output::reset() {
channel = 0;
// Set first driver as default
if (driverIds.size() >= 1) {
setDriverId(driverIds[0]);
if (drivers.size() >= 1) {
setDriverId(drivers[0].first);
}
}

@@ -234,8 +237,8 @@ void Output::setDeviceId(int deviceId) {
// Destroy device
if (driver && this->deviceId >= 0) {
driver->unsubscribeOutput(this->deviceId, this);
outputDevice = NULL;
}
outputDevice = NULL;
this->deviceId = -1;

// Create device
@@ -273,7 +276,6 @@ void init() {
}

void destroy() {
driverIds.clear();
for (auto& pair : drivers) {
delete pair.second;
}
@@ -282,8 +284,7 @@ void destroy() {

void addDriver(int driverId, Driver* driver) {
assert(driver);
driverIds.push_back(driverId);
drivers[driverId] = driver;
drivers.push_back(std::make_pair(driverId, driver));
}




+ 242
- 0
src/rtaudio.cpp View File

@@ -0,0 +1,242 @@
#include <rtaudio.hpp>
#include <audio.hpp>
#include <string.hpp>
#include <math.hpp>
#include <system.hpp>
#include <map>

#pragma GCC diagnostic push
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wsuggest-override"
#endif
#include <RtAudio.h>
#pragma GCC diagnostic pop


namespace rack {


struct RtAudioDevice : audio::Device {
RtAudio *rtAudio;
int deviceId;
RtAudio::DeviceInfo deviceInfo;
RtAudio::StreamParameters inputParameters;
RtAudio::StreamParameters outputParameters;
RtAudio::StreamOptions options;
int blockSize = 256;
int sampleRate = 44100;

RtAudioDevice(RtAudio::Api api, int deviceId) {
rtAudio = new RtAudio(api);
if (!rtAudio) {
throw Exception(string::f("Failed to create RtAudio driver %d", api));
}
try {
deviceInfo = rtAudio->getDeviceInfo(deviceId);
}
catch (RtAudioError& e) {
WARN("Failed to query RtAudio device: %s", e.what());
throw Exception(string::f("Failed to query RtAudio device: %s", e.what()));
}

this->deviceId = deviceId;
openStream();
}

~RtAudioDevice() {
closeStream();
delete rtAudio;
}

void openStream() {
// Open new device
if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) {
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId);
return;
}

inputParameters = RtAudio::StreamParameters();
inputParameters.deviceId = deviceId;
inputParameters.nChannels = deviceInfo.inputChannels;
inputParameters.firstChannel = 0;

outputParameters = RtAudio::StreamParameters();
outputParameters.deviceId = deviceId;
outputParameters.nChannels = deviceInfo.outputChannels;
outputParameters.firstChannel = 0;

options = RtAudio::StreamOptions();
options.flags |= RTAUDIO_JACK_DONT_CONNECT;
options.streamName = "VCV Rack";

int closestSampleRate = deviceInfo.preferredSampleRate;
for (int sr : deviceInfo.sampleRates) {
if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) {
closestSampleRate = sr;
}
}

try {
INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, inputParameters.nChannels, outputParameters.nChannels);
rtAudio->openStream(
outputParameters.nChannels == 0 ? NULL : &outputParameters,
inputParameters.nChannels == 0 ? NULL : &inputParameters,
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize,
&rtAudioCallback, this, &options, NULL);
}
catch (RtAudioError& e) {
WARN("Failed to open RtAudio stream: %s", e.what());
return;
}

try {
INFO("Starting RtAudio stream %d", deviceId);
rtAudio->startStream();
}
catch (RtAudioError& e) {
WARN("Failed to start RtAudio stream: %s", e.what());
return;
}

// Update sample rate to actual value
sampleRate = rtAudio->getStreamSampleRate();
onOpenStream();
}

void closeStream() {
if (rtAudio->isStreamRunning()) {
INFO("Stopping RtAudio stream %d", deviceId);
try {
rtAudio->stopStream();
}
catch (RtAudioError& e) {
WARN("Failed to stop RtAudio stream %s", e.what());
}
}
if (rtAudio->isStreamOpen()) {
INFO("Closing RtAudio stream %d", deviceId);
try {
rtAudio->closeStream();
}
catch (RtAudioError& e) {
WARN("Failed to close RtAudio stream %s", e.what());
}
}
onCloseStream();
}

std::vector<int> getSampleRates() override {
std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end());
return sampleRates;
}
int getSampleRate() override {
return sampleRate;
}
void setSampleRate(int sampleRate) override {
closeStream();
this->sampleRate = sampleRate;
openStream();
}

std::vector<int> getBlockSizes() override {
return {32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096};
}
int getBlockSize() override {
return blockSize;
}
void setBlockSize(int blockSize) override {
closeStream();
this->blockSize = blockSize;
openStream();
}

int getNumInputs() override {
return inputParameters.nChannels;
}
int getNumOutputs() override {
return outputParameters.nChannels;
}

static int rtAudioCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
RtAudioDevice* device = (RtAudioDevice*) userData;
assert(device);

int inputStride = device->getNumInputs();
int outputStride = device->getNumOutputs();
device->processBuffer((const float*) inputBuffer, inputStride, (float*) outputBuffer, outputStride, nFrames);
return 0;
}
};


struct RtAudioDriver : audio::Driver {
// Just for querying device IDs names
RtAudio *rtAudio;
// deviceId -> Device
std::map<int, RtAudioDevice*> devices;

RtAudioDriver(RtAudio::Api api) {
rtAudio = new RtAudio(api);
}

~RtAudioDriver() {
assert(devices.empty());
delete rtAudio;
}

std::string getName() override {
return RtAudio::getApiDisplayName(rtAudio->getCurrentApi());
}

std::vector<int> getDeviceIds() override {
int count = rtAudio->getDeviceCount();
std::vector<int> deviceIds;
for (int i = 0; i < count; i++)
deviceIds.push_back(i);
return deviceIds;
}

std::string getDeviceName(int deviceId) override {
if (deviceId >= 0) {
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId);
return deviceInfo.name;
}
return "";
}

audio::Device* subscribe(int deviceId, audio::Port* port) override {
RtAudioDevice* device = devices[deviceId];
if (!device) {
devices[deviceId] = device = new RtAudioDevice(rtAudio->getCurrentApi(), deviceId);
// TODO Error check
}

device->subscribe(port);
return device;
}

void unsubscribe(int deviceId, audio::Port* port) override {
auto it = devices.find(deviceId);
if (it == devices.end())
return;
RtAudioDevice* device = it->second;
device->unsubscribe(port);

if (device->subscribed.empty()) {
devices.erase(it);
delete device;
}
}
};


void rtaudioInit() {
std::vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
for (RtAudio::Api api : apis) {
RtAudioDriver* driver = new RtAudioDriver(api);
audio::addDriver((int) api, driver);
}
}

} // namespace rack

+ 2
- 0
src/rtmidi.cpp View File

@@ -90,6 +90,8 @@ struct RtMidiDriver : midi::Driver {
}

~RtMidiDriver() {
assert(inputDevices.empty());
assert(outputDevices.empty());
delete rtMidiIn;
delete rtMidiOut;
}


Loading…
Cancel
Save