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