@@ -43,17 +43,18 @@ struct AudioIO { | |||||
void setSampleRate(int sampleRate); | void setSampleRate(int sampleRate); | ||||
void setBlockSize(int blockSize); | void setBlockSize(int blockSize); | ||||
void setChannels(int numOutputs, int numInputs); | |||||
/** Must close the stream before opening */ | /** Must close the stream before opening */ | ||||
void openStream(); | void openStream(); | ||||
void closeStream(); | void closeStream(); | ||||
/** Returns whether the audio stream is open and running */ | |||||
bool isActive(); | |||||
std::vector<int> getSampleRates(); | std::vector<int> getSampleRates(); | ||||
virtual void processStream(const float *input, float *output, int frames) {} | virtual void processStream(const float *input, float *output, int frames) {} | ||||
virtual void onCloseStream() {} | virtual void onCloseStream() {} | ||||
virtual void onOpenStream() {} | virtual void onOpenStream() {} | ||||
virtual void onChannelsChange() {} | |||||
json_t *toJson(); | json_t *toJson(); | ||||
void fromJson(json_t *rootJ); | void fromJson(json_t *rootJ); | ||||
}; | }; | ||||
@@ -5,14 +5,13 @@ | |||||
namespace rack { | namespace rack { | ||||
static const int BRIDGE_CHANNELS = 16; | |||||
static const int BRIDGE_NUM_PORTS = 16; | |||||
void bridgeInit(); | void bridgeInit(); | ||||
void bridgeDestroy(); | void bridgeDestroy(); | ||||
void bridgeAudioSubscribe(int channel, AudioIO *audio); | void bridgeAudioSubscribe(int channel, AudioIO *audio); | ||||
void bridgeAudioUnsubscribe(int channel, AudioIO *audio); | void bridgeAudioUnsubscribe(int channel, AudioIO *audio); | ||||
bool bridgeAudioIsActive(int channel, AudioIO *audio); | |||||
} // namespace rack | } // namespace rack |
@@ -43,10 +43,10 @@ struct AudioInterfaceIO : AudioIO { | |||||
for (int i = 0; i < frames; i++) { | for (int i = 0; i < frames; i++) { | ||||
if (inputBuffer.full()) | if (inputBuffer.full()) | ||||
break; | break; | ||||
Frame<INPUTS> f; | |||||
memset(&f, 0, sizeof(f)); | |||||
memcpy(&f, &input[numInputs * i], numInputs * sizeof(float)); | |||||
inputBuffer.push(f); | |||||
Frame<INPUTS> inputFrame; | |||||
memset(&inputFrame, 0, sizeof(inputFrame)); | |||||
memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); | |||||
inputBuffer.push(inputFrame); | |||||
} | } | ||||
} | } | ||||
@@ -71,7 +71,7 @@ struct AudioInterfaceIO : AudioIO { | |||||
} | } | ||||
// Notify engine when finished processing | // Notify engine when finished processing | ||||
engineCv.notify_all(); | |||||
engineCv.notify_one(); | |||||
} | } | ||||
void onCloseStream() override { | void onCloseStream() override { | ||||
@@ -127,12 +127,6 @@ struct AudioInterface : Module { | |||||
} | } | ||||
void onSampleRateChange() override { | void onSampleRateChange() override { | ||||
// for (int i = 0; i < INPUTS; i++) { | |||||
// inputSrc[i].setRates(audioIO.sampleRate, engineGetSampleRate()); | |||||
// } | |||||
// for (int i = 0; i < OUTPUTS; i++) { | |||||
// outputSrc[i].setRates(engineGetSampleRate(), audioIO.sampleRate); | |||||
// } | |||||
inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); | inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); | ||||
outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); | outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); | ||||
} | } | ||||
@@ -154,6 +148,7 @@ void AudioInterface::step() { | |||||
} | } | ||||
if (audioIO.numInputs > 0) { | if (audioIO.numInputs > 0) { | ||||
// Convert inputs if needed | |||||
if (inputBuffer.empty()) { | if (inputBuffer.empty()) { | ||||
int inLen = audioIO.inputBuffer.size(); | int inLen = audioIO.inputBuffer.size(); | ||||
int outLen = inputBuffer.capacity(); | int outLen = inputBuffer.capacity(); | ||||
@@ -163,25 +158,27 @@ void AudioInterface::step() { | |||||
} | } | ||||
} | } | ||||
// Take input from buffer | |||||
if (!inputBuffer.empty()) { | if (!inputBuffer.empty()) { | ||||
inputFrame = inputBuffer.shift(); | inputFrame = inputBuffer.shift(); | ||||
} | } | ||||
for (int i = 0; i < INPUTS; i++) { | for (int i = 0; i < INPUTS; i++) { | ||||
outputs[AUDIO_OUTPUT + i].value = 10.0 * inputFrame.samples[i]; | |||||
outputs[AUDIO_OUTPUT + i].value = 10.f * inputFrame.samples[i]; | |||||
} | } | ||||
if (audioIO.numOutputs > 0) { | if (audioIO.numOutputs > 0) { | ||||
// Get and push output SRC frame | // Get and push output SRC frame | ||||
if (!outputBuffer.full()) { | if (!outputBuffer.full()) { | ||||
Frame<OUTPUTS> f; | |||||
for (int i = 0; i < audioIO.numOutputs; i++) { | |||||
f.samples[i] = inputs[AUDIO_INPUT + i].value / 10.0; | |||||
Frame<OUTPUTS> outputFrame; | |||||
for (int i = 0; i < OUTPUTS; i++) { | |||||
outputFrame.samples[i] = inputs[AUDIO_INPUT + i].value / 10.f; | |||||
} | } | ||||
outputBuffer.push(f); | |||||
outputBuffer.push(outputFrame); | |||||
} | } | ||||
if (outputBuffer.full()) { | if (outputBuffer.full()) { | ||||
// Wait until outputs are needed | |||||
// Wait until outputs are needed. | |||||
// Give up after a timeout in case the audio device is being unresponsive. | |||||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | std::unique_lock<std::mutex> lock(audioIO.engineMutex); | ||||
auto cond = [&] { | auto cond = [&] { | ||||
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | ||||
@@ -200,14 +197,16 @@ void AudioInterface::step() { | |||||
debug("Audio Interface underflow"); | debug("Audio Interface underflow"); | ||||
} | } | ||||
} | } | ||||
// Notify audio thread that an output is potentially ready | |||||
audioIO.audioCv.notify_one(); | |||||
} | } | ||||
// Turn on light if at least one port is enabled in the nearby pair | |||||
for (int i = 0; i < INPUTS / 2; i++) | for (int i = 0; i < INPUTS / 2; i++) | ||||
lights[INPUT_LIGHT + i].value = (audioIO.numOutputs >= 2*i+1); | lights[INPUT_LIGHT + i].value = (audioIO.numOutputs >= 2*i+1); | ||||
for (int i = 0; i < OUTPUTS / 2; i++) | for (int i = 0; i < OUTPUTS / 2; i++) | ||||
lights[OUTPUT_LIGHT + i].value = (audioIO.numInputs >= 2*i+1); | lights[OUTPUT_LIGHT + i].value = (audioIO.numInputs >= 2*i+1); | ||||
audioIO.audioCv.notify_all(); | |||||
} | } | ||||
@@ -46,8 +46,10 @@ std::string AudioIO::getDriverName(int driver) { | |||||
} | } | ||||
void AudioIO::setDriver(int driver) { | void AudioIO::setDriver(int driver) { | ||||
// Close device | |||||
setDevice(-1, 0); | setDevice(-1, 0); | ||||
// Close driver | |||||
if (rtAudio) { | if (rtAudio) { | ||||
delete rtAudio; | delete rtAudio; | ||||
rtAudio = NULL; | rtAudio = NULL; | ||||
@@ -69,7 +71,7 @@ int AudioIO::getDeviceCount() { | |||||
return rtAudio->getDeviceCount(); | return rtAudio->getDeviceCount(); | ||||
} | } | ||||
if (driver == BRIDGE_DRIVER) { | if (driver == BRIDGE_DRIVER) { | ||||
return BRIDGE_CHANNELS; | |||||
return BRIDGE_NUM_PORTS; | |||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -90,13 +92,11 @@ bool AudioIO::getDeviceInfo(int device, RtAudio::DeviceInfo *deviceInfo) { | |||||
} | } | ||||
catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
warn("Failed to query RtAudio device: %s", e.what()); | warn("Failed to query RtAudio device: %s", e.what()); | ||||
return false; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
else { | |||||
return false; | |||||
} | |||||
return false; | |||||
} | } | ||||
int AudioIO::getDeviceChannels(int device) { | int AudioIO::getDeviceChannels(int device) { | ||||
@@ -148,7 +148,7 @@ std::string AudioIO::getDeviceDetail(int device, int offset) { | |||||
} | } | ||||
} | } | ||||
if (driver == BRIDGE_DRIVER) { | if (driver == BRIDGE_DRIVER) { | ||||
return stringf("Channel %d", device + 1); | |||||
return stringf("Port %d", device + 1); | |||||
} | } | ||||
return ""; | return ""; | ||||
} | } | ||||
@@ -172,6 +172,12 @@ void AudioIO::setBlockSize(int blockSize) { | |||||
openStream(); | openStream(); | ||||
} | } | ||||
void AudioIO::setChannels(int numOutputs, int numInputs) { | |||||
this->numOutputs = numOutputs; | |||||
this->numInputs = numInputs; | |||||
onChannelsChange(); | |||||
} | |||||
static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | ||||
AudioIO *audioIO = (AudioIO*) userData; | AudioIO *audioIO = (AudioIO*) userData; | ||||
@@ -252,8 +258,8 @@ void AudioIO::openStream() { | |||||
onOpenStream(); | onOpenStream(); | ||||
} | } | ||||
if (driver == BRIDGE_DRIVER) { | if (driver == BRIDGE_DRIVER) { | ||||
numOutputs = 2; | |||||
numInputs = 2; | |||||
numOutputs = 0; | |||||
numInputs = 0; | |||||
// TEMP | // TEMP | ||||
sampleRate = 44100; | sampleRate = 44100; | ||||
blockSize = 256; | blockSize = 256; | ||||
@@ -293,17 +299,6 @@ void AudioIO::closeStream() { | |||||
onCloseStream(); | onCloseStream(); | ||||
} | } | ||||
bool AudioIO::isActive() { | |||||
if (rtAudio) { | |||||
return rtAudio->isStreamRunning(); | |||||
} | |||||
if (driver == BRIDGE_DRIVER) { | |||||
bridgeAudioIsActive(device, this); | |||||
} | |||||
return false; | |||||
} | |||||
std::vector<int> AudioIO::getSampleRates() { | std::vector<int> AudioIO::getSampleRates() { | ||||
if (rtAudio) { | if (rtAudio) { | ||||
try { | try { | ||||
@@ -25,11 +25,13 @@ enum BridgeCommand { | |||||
NO_COMMAND = 0, | NO_COMMAND = 0, | ||||
START_COMMAND, | START_COMMAND, | ||||
QUIT_COMMAND, | QUIT_COMMAND, | ||||
CHANNEL_SET_COMMAND, | |||||
PORT_SET_COMMAND, | |||||
MIDI_MESSAGE_SEND_COMMAND, | |||||
AUDIO_SAMPLE_RATE_SET_COMMAND, | AUDIO_SAMPLE_RATE_SET_COMMAND, | ||||
AUDIO_CHANNELS_SET_COMMAND, | AUDIO_CHANNELS_SET_COMMAND, | ||||
AUDIO_BUFFER_SEND_COMMAND, | AUDIO_BUFFER_SEND_COMMAND, | ||||
MIDI_MESSAGE_SEND_COMMAND, | |||||
AUDIO_ACTIVATE, | |||||
AUDIO_DEACTIVATE, | |||||
NUM_COMMANDS | NUM_COMMANDS | ||||
}; | }; | ||||
@@ -37,7 +39,9 @@ enum BridgeCommand { | |||||
static const int RECV_BUFFER_SIZE = (1<<13); | static const int RECV_BUFFER_SIZE = (1<<13); | ||||
static const int RECV_QUEUE_SIZE = (1<<17); | static const int RECV_QUEUE_SIZE = (1<<17); | ||||
static AudioIO *audioListeners[BRIDGE_CHANNELS]; | |||||
struct BridgeClientConnection; | |||||
static BridgeClientConnection *connections[BRIDGE_NUM_PORTS] = {}; | |||||
static AudioIO *audioListeners[BRIDGE_NUM_PORTS] = {}; | |||||
static std::thread serverThread; | static std::thread serverThread; | ||||
static bool serverQuit; | static bool serverQuit; | ||||
@@ -47,10 +51,11 @@ struct BridgeClientConnection { | |||||
RingBuffer<uint8_t, RECV_QUEUE_SIZE> recvQueue; | RingBuffer<uint8_t, RECV_QUEUE_SIZE> recvQueue; | ||||
BridgeCommand currentCommand = START_COMMAND; | BridgeCommand currentCommand = START_COMMAND; | ||||
bool closeRequested = false; | bool closeRequested = false; | ||||
int channel = -1; | |||||
int port = -1; | |||||
int sampleRate = -1; | int sampleRate = -1; | ||||
int audioChannels = 0; | int audioChannels = 0; | ||||
int audioBufferLength = -1; | int audioBufferLength = -1; | ||||
bool audioActive = false; | |||||
void send(const uint8_t *buffer, int length) { | void send(const uint8_t *buffer, int length) { | ||||
if (length <= 0) | if (length <= 0) | ||||
@@ -80,6 +85,35 @@ struct BridgeClientConnection { | |||||
return x; | return x; | ||||
} | } | ||||
void run() { | |||||
info("Bridge client connected"); | |||||
while (!closeRequested) { | |||||
uint8_t buffer[RECV_BUFFER_SIZE]; | |||||
#ifdef ARCH_LIN | |||||
int recvFlags = MSG_NOSIGNAL; | |||||
#else | |||||
int recvFlags = 0; | |||||
#endif | |||||
ssize_t received = ::recv(client, (char*) buffer, sizeof(buffer), recvFlags); | |||||
if (received <= 0) | |||||
break; | |||||
// Make sure we can fill the buffer | |||||
if (recvQueue.capacity() < (size_t) received) { | |||||
// If we can't accept it, future messages will be incomplete | |||||
break; | |||||
} | |||||
recvQueue.pushBuffer(buffer, received); | |||||
// Loop the state machine until it returns false | |||||
while (step()) {} | |||||
} | |||||
info("Bridge client closed"); | |||||
} | |||||
/** Steps the state machine | /** Steps the state machine | ||||
Returns true if step() should be called again | Returns true if step() should be called again | ||||
*/ | */ | ||||
@@ -115,10 +149,21 @@ struct BridgeClientConnection { | |||||
debug("Quitting!"); | debug("Quitting!"); | ||||
} break; | } break; | ||||
case CHANNEL_SET_COMMAND: { | |||||
case PORT_SET_COMMAND: { | |||||
if (recvQueue.size() >= 1) { | if (recvQueue.size() >= 1) { | ||||
channel = shift<uint8_t>(); | |||||
debug("Set channel %d", channel); | |||||
int port = shift<uint8_t>(); | |||||
setPort(port); | |||||
debug("Set port %d", port); | |||||
currentCommand = NO_COMMAND; | |||||
return true; | |||||
} | |||||
} break; | |||||
case MIDI_MESSAGE_SEND_COMMAND: { | |||||
if (recvQueue.size() >= 3) { | |||||
uint8_t midiBuffer[3]; | |||||
recvQueue.shiftBuffer(midiBuffer, 3); | |||||
debug("MIDI: %02x %02x %02x", midiBuffer[0], midiBuffer[1], midiBuffer[2]); | |||||
currentCommand = NO_COMMAND; | currentCommand = NO_COMMAND; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -136,7 +181,7 @@ struct BridgeClientConnection { | |||||
case AUDIO_CHANNELS_SET_COMMAND: { | case AUDIO_CHANNELS_SET_COMMAND: { | ||||
if (recvQueue.size() >= 1) { | if (recvQueue.size() >= 1) { | ||||
audioChannels = shift<uint8_t>(); | audioChannels = shift<uint8_t>(); | ||||
debug("Set audio channels %d", channel); | |||||
debug("Set audio channels %d", audioChannels); | |||||
currentCommand = NO_COMMAND; | currentCommand = NO_COMMAND; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -177,14 +222,16 @@ struct BridgeClientConnection { | |||||
} | } | ||||
} break; | } break; | ||||
case MIDI_MESSAGE_SEND_COMMAND: { | |||||
if (recvQueue.size() >= 3) { | |||||
uint8_t midiBuffer[3]; | |||||
recvQueue.shiftBuffer(midiBuffer, 3); | |||||
debug("MIDI: %02x %02x %02x", midiBuffer[0], midiBuffer[1], midiBuffer[2]); | |||||
currentCommand = NO_COMMAND; | |||||
return true; | |||||
} | |||||
case AUDIO_ACTIVATE: { | |||||
audioActive = true; | |||||
refreshAudioActive(); | |||||
return true; | |||||
} break; | |||||
case AUDIO_DEACTIVATE: { | |||||
audioActive = false; | |||||
refreshAudioActive(); | |||||
return true; | |||||
} break; | } break; | ||||
default: { | default: { | ||||
@@ -192,44 +239,47 @@ struct BridgeClientConnection { | |||||
closeRequested = true; | closeRequested = true; | ||||
} break; | } break; | ||||
} | } | ||||
// Stop looping the state machine | |||||
return false; | return false; | ||||
} | } | ||||
void setPort(int newPort) { | |||||
if (!(0 <= newPort && newPort < BRIDGE_NUM_PORTS)) | |||||
return; | |||||
// Unbind from existing port | |||||
if (connections[port] == this) { | |||||
if (audioListeners[port]) | |||||
audioListeners[port]->setChannels(0, 0); | |||||
connections[port] = NULL; | |||||
} | |||||
port = newPort; | |||||
// Bind to new port | |||||
if (!connections[port]) { | |||||
connections[port] = this; | |||||
refreshAudioActive(); | |||||
} | |||||
else { | |||||
port = -1; | |||||
} | |||||
} | |||||
void processStream(const float *input, float *output, int frames) { | void processStream(const float *input, float *output, int frames) { | ||||
if (!(0 <= channel && channel < BRIDGE_CHANNELS)) | |||||
if (!(0 <= port && port < BRIDGE_NUM_PORTS)) | |||||
return; | return; | ||||
if (!audioListeners[channel]) | |||||
if (!audioListeners[port]) | |||||
return; | return; | ||||
audioListeners[channel]->processStream(input, output, frames); | |||||
audioListeners[port]->processStream(input, output, frames); | |||||
} | } | ||||
void run() { | |||||
info("Bridge client connected"); | |||||
while (!closeRequested) { | |||||
uint8_t buffer[RECV_BUFFER_SIZE]; | |||||
#ifdef ARCH_LIN | |||||
int recvFlags = MSG_NOSIGNAL; | |||||
#else | |||||
int recvFlags = 0; | |||||
#endif | |||||
ssize_t received = ::recv(client, (char*) buffer, sizeof(buffer), recvFlags); | |||||
if (received <= 0) | |||||
break; | |||||
// Make sure we can fill the buffer | |||||
if (recvQueue.capacity() < (size_t) received) { | |||||
// If we can't accept it, future messages will be incomplete | |||||
break; | |||||
} | |||||
recvQueue.pushBuffer(buffer, received); | |||||
// Loop the state machine until it returns false | |||||
while (step()) {} | |||||
} | |||||
info("Bridge client closed"); | |||||
void refreshAudioActive() { | |||||
if (!audioListeners[port]) | |||||
return; | |||||
if (audioActive) | |||||
audioListeners[port]->setChannels(2, 2); | |||||
else | |||||
audioListeners[port]->setChannels(0, 0); | |||||
} | } | ||||
}; | }; | ||||
@@ -373,26 +423,23 @@ void bridgeDestroy() { | |||||
serverThread.join(); | serverThread.join(); | ||||
} | } | ||||
void bridgeAudioSubscribe(int channel, AudioIO *audio) { | |||||
if (!(0 <= channel && channel < BRIDGE_CHANNELS)) | |||||
void bridgeAudioSubscribe(int port, AudioIO *audio) { | |||||
if (!(0 <= port && port < BRIDGE_NUM_PORTS)) | |||||
return; | return; | ||||
if (audioListeners[channel]) | |||||
if (audioListeners[port]) | |||||
return; | return; | ||||
audioListeners[channel] = audio; | |||||
audioListeners[port] = audio; | |||||
if (connections[port]) | |||||
connections[port]->refreshAudioActive(); | |||||
} | } | ||||
void bridgeAudioUnsubscribe(int channel, AudioIO *audio) { | |||||
if (!(0 <= channel && channel < BRIDGE_CHANNELS)) | |||||
void bridgeAudioUnsubscribe(int port, AudioIO *audio) { | |||||
if (!(0 <= port && port < BRIDGE_NUM_PORTS)) | |||||
return; | return; | ||||
if (audioListeners[channel] != audio) | |||||
if (audioListeners[port] != audio) | |||||
return; | return; | ||||
audioListeners[channel] = NULL; | |||||
} | |||||
bool bridgeAudioIsActive(int channel, AudioIO *audio) { | |||||
if (!(0 <= channel && channel < BRIDGE_CHANNELS)) | |||||
return false; | |||||
return (audioListeners[channel] == audio); | |||||
audioListeners[port] = NULL; | |||||
audio->setChannels(0, 0); | |||||
} | } | ||||