@@ -43,17 +43,18 @@ struct AudioIO { | |||
void setSampleRate(int sampleRate); | |||
void setBlockSize(int blockSize); | |||
void setChannels(int numOutputs, int numInputs); | |||
/** Must close the stream before opening */ | |||
void openStream(); | |||
void closeStream(); | |||
/** Returns whether the audio stream is open and running */ | |||
bool isActive(); | |||
std::vector<int> getSampleRates(); | |||
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); | |||
}; | |||
@@ -5,14 +5,13 @@ | |||
namespace rack { | |||
static const int BRIDGE_CHANNELS = 16; | |||
static const int BRIDGE_NUM_PORTS = 16; | |||
void bridgeInit(); | |||
void bridgeDestroy(); | |||
void bridgeAudioSubscribe(int channel, AudioIO *audio); | |||
void bridgeAudioUnsubscribe(int channel, AudioIO *audio); | |||
bool bridgeAudioIsActive(int channel, AudioIO *audio); | |||
} // namespace rack |
@@ -43,10 +43,10 @@ struct AudioInterfaceIO : AudioIO { | |||
for (int i = 0; i < frames; i++) { | |||
if (inputBuffer.full()) | |||
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 | |||
engineCv.notify_all(); | |||
engineCv.notify_one(); | |||
} | |||
void onCloseStream() override { | |||
@@ -127,12 +127,6 @@ struct AudioInterface : Module { | |||
} | |||
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()); | |||
outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); | |||
} | |||
@@ -154,6 +148,7 @@ void AudioInterface::step() { | |||
} | |||
if (audioIO.numInputs > 0) { | |||
// Convert inputs if needed | |||
if (inputBuffer.empty()) { | |||
int inLen = audioIO.inputBuffer.size(); | |||
int outLen = inputBuffer.capacity(); | |||
@@ -163,25 +158,27 @@ void AudioInterface::step() { | |||
} | |||
} | |||
// Take input from buffer | |||
if (!inputBuffer.empty()) { | |||
inputFrame = inputBuffer.shift(); | |||
} | |||
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) { | |||
// Get and push output SRC frame | |||
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()) { | |||
// 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); | |||
auto cond = [&] { | |||
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | |||
@@ -200,14 +197,16 @@ void AudioInterface::step() { | |||
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++) | |||
lights[INPUT_LIGHT + i].value = (audioIO.numOutputs >= 2*i+1); | |||
for (int i = 0; i < OUTPUTS / 2; i++) | |||
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) { | |||
// Close device | |||
setDevice(-1, 0); | |||
// Close driver | |||
if (rtAudio) { | |||
delete rtAudio; | |||
rtAudio = NULL; | |||
@@ -69,7 +71,7 @@ int AudioIO::getDeviceCount() { | |||
return rtAudio->getDeviceCount(); | |||
} | |||
if (driver == BRIDGE_DRIVER) { | |||
return BRIDGE_CHANNELS; | |||
return BRIDGE_NUM_PORTS; | |||
} | |||
return 0; | |||
} | |||
@@ -90,13 +92,11 @@ bool AudioIO::getDeviceInfo(int device, RtAudio::DeviceInfo *deviceInfo) { | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query RtAudio device: %s", e.what()); | |||
return false; | |||
} | |||
} | |||
} | |||
else { | |||
return false; | |||
} | |||
return false; | |||
} | |||
int AudioIO::getDeviceChannels(int device) { | |||
@@ -148,7 +148,7 @@ std::string AudioIO::getDeviceDetail(int device, int offset) { | |||
} | |||
} | |||
if (driver == BRIDGE_DRIVER) { | |||
return stringf("Channel %d", device + 1); | |||
return stringf("Port %d", device + 1); | |||
} | |||
return ""; | |||
} | |||
@@ -172,6 +172,12 @@ void AudioIO::setBlockSize(int blockSize) { | |||
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) { | |||
AudioIO *audioIO = (AudioIO*) userData; | |||
@@ -252,8 +258,8 @@ void AudioIO::openStream() { | |||
onOpenStream(); | |||
} | |||
if (driver == BRIDGE_DRIVER) { | |||
numOutputs = 2; | |||
numInputs = 2; | |||
numOutputs = 0; | |||
numInputs = 0; | |||
// TEMP | |||
sampleRate = 44100; | |||
blockSize = 256; | |||
@@ -293,17 +299,6 @@ void AudioIO::closeStream() { | |||
onCloseStream(); | |||
} | |||
bool AudioIO::isActive() { | |||
if (rtAudio) { | |||
return rtAudio->isStreamRunning(); | |||
} | |||
if (driver == BRIDGE_DRIVER) { | |||
bridgeAudioIsActive(device, this); | |||
} | |||
return false; | |||
} | |||
std::vector<int> AudioIO::getSampleRates() { | |||
if (rtAudio) { | |||
try { | |||
@@ -25,11 +25,13 @@ enum BridgeCommand { | |||
NO_COMMAND = 0, | |||
START_COMMAND, | |||
QUIT_COMMAND, | |||
CHANNEL_SET_COMMAND, | |||
PORT_SET_COMMAND, | |||
MIDI_MESSAGE_SEND_COMMAND, | |||
AUDIO_SAMPLE_RATE_SET_COMMAND, | |||
AUDIO_CHANNELS_SET_COMMAND, | |||
AUDIO_BUFFER_SEND_COMMAND, | |||
MIDI_MESSAGE_SEND_COMMAND, | |||
AUDIO_ACTIVATE, | |||
AUDIO_DEACTIVATE, | |||
NUM_COMMANDS | |||
}; | |||
@@ -37,7 +39,9 @@ enum BridgeCommand { | |||
static const int RECV_BUFFER_SIZE = (1<<13); | |||
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 bool serverQuit; | |||
@@ -47,10 +51,11 @@ struct BridgeClientConnection { | |||
RingBuffer<uint8_t, RECV_QUEUE_SIZE> recvQueue; | |||
BridgeCommand currentCommand = START_COMMAND; | |||
bool closeRequested = false; | |||
int channel = -1; | |||
int port = -1; | |||
int sampleRate = -1; | |||
int audioChannels = 0; | |||
int audioBufferLength = -1; | |||
bool audioActive = false; | |||
void send(const uint8_t *buffer, int length) { | |||
if (length <= 0) | |||
@@ -80,6 +85,35 @@ struct BridgeClientConnection { | |||
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 | |||
Returns true if step() should be called again | |||
*/ | |||
@@ -115,10 +149,21 @@ struct BridgeClientConnection { | |||
debug("Quitting!"); | |||
} break; | |||
case CHANNEL_SET_COMMAND: { | |||
case PORT_SET_COMMAND: { | |||
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; | |||
return true; | |||
} | |||
@@ -136,7 +181,7 @@ struct BridgeClientConnection { | |||
case AUDIO_CHANNELS_SET_COMMAND: { | |||
if (recvQueue.size() >= 1) { | |||
audioChannels = shift<uint8_t>(); | |||
debug("Set audio channels %d", channel); | |||
debug("Set audio channels %d", audioChannels); | |||
currentCommand = NO_COMMAND; | |||
return true; | |||
} | |||
@@ -177,14 +222,16 @@ struct BridgeClientConnection { | |||
} | |||
} 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; | |||
default: { | |||
@@ -192,44 +239,47 @@ struct BridgeClientConnection { | |||
closeRequested = true; | |||
} break; | |||
} | |||
// Stop looping the state machine | |||
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) { | |||
if (!(0 <= channel && channel < BRIDGE_CHANNELS)) | |||
if (!(0 <= port && port < BRIDGE_NUM_PORTS)) | |||
return; | |||
if (!audioListeners[channel]) | |||
if (!audioListeners[port]) | |||
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(); | |||
} | |||
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; | |||
if (audioListeners[channel]) | |||
if (audioListeners[port]) | |||
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; | |||
if (audioListeners[channel] != audio) | |||
if (audioListeners[port] != audio) | |||
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); | |||
} | |||