#include #include #include #include "trowaSoft.hpp" #include "dsp/digital.hpp" #include "trowaSoftComponents.hpp" #include "trowaSoftUtilities.hpp" #include "TSSequencerModuleBase.hpp" #include "TSOSCSequencerListener.hpp" #include "TSOSCSequencerOutputMessages.hpp" #include "TSOSCCommunicator.hpp" #include "TSSequencerWidgetBase.hpp" // Static Variables: RandStructure TSSequencerModuleBase::RandomPatterns[TROWA_SEQ_NUM_RANDOM_PATTERNS] = { { 1,{ 0 } }, { 2,{ 0, 1 } }, { 2,{ 0,0,0,1 } }, { 2,{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } }, { 2,{ 0,0,1,1 } }, { 2,{ 0,1,1,1 } }, { 2,{ 0,1,1,0 } }, { 3,{ 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2 } }, { 3,{ 0,0,1,2 } }, { 3,{ 0,0,0,0,0,0,1,2 } }, { 3,{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2 } }, { 3,{ 0,1,0,2 } }, { 3,{ 0,1,2,0 } }, { 3,{ 0,1,1,2 } }, { 3,{ 0,2,1,2 } }, { 4,{ 0,1,2,3 } }, { 4,{ 0,0,1,1,2,2,3,3 } }, { 4,{ 0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3 } }, { 5,{ 0,1,0,1,0,1,0,2,0,3,0,3,0,3,0,4 } }, { 6,{ 0,1,0,2,0,1,0,3,0,1,0,4,0,1,0,5 } }, { 8,{ 0,0,0,1,2,3,4,5,0,0,0,1,2,3,6,7 } }, { 9,{ 0,1,0,2,0,1,0,3,0,1,0,4,5,6,7,8 } }, { 10,{ 0,1,1,2,3,4,5,6,0,1,1,2,3,7,8,9 } }, { 10,{ 0,1,1,2,3,4,5,6,1,1,1,2,3,7,8,9 } }, { 10,{ 0,1,2,3,4,5,6,7,0,1,2,3,4,5,8,9 } }, { 10,{ 0,1,2,3,4,5,6,7,0,1,8,3,4,5,6,9 } }, { 11,{ 0,1,2,3,4,5,6,7,8,1,2,3,4,5,9,10 } } }; //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // TSSequencerModuleBase() // Instantiate the abstract base class. // @numSteps: (IN) Maximum number of steps // @numRows: (IN) The number of rows (for layout). // @numCols: (IN) The number of columns (for layout). // @numRows * @numCols = @numSteps // @defStateVal : (IN) The default state value (i.e. 0/false for a boolean step sequencer or whatever float value you want). //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- TSSequencerModuleBase::TSSequencerModuleBase(/*in*/ int numSteps, /*in*/ int numRows, /*in*/ int numCols, /*in*/ float defStateVal) : Module(NUM_PARAMS + numSteps, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS + numSteps) { useOSC = false; oscInitialized = false; oscBuffer = NULL; oscTxSocket = NULL; oscListener = NULL; oscRxSocket = NULL; oscNamespace = OSC_DEFAULT_NS; oscId = TSOSCConnector::GetId(); for (int i = 0; i < SeqOSCOutputMsg::NUM_OSC_OUTPUT_MSGS; i++) { for (int j = 0; j < OSC_ADDRESS_BUFFER_SIZE; j++) oscAddrBuffer[i][j] = '\0'; } prevIndex = TROWA_INDEX_UNDEFINED; gateTriggers = NULL; lastStepWasExternalClock = false; defaultStateValue = defStateVal; currentChannelEditingIx = 0; currentPatternEditingIx = 0; currentPatternPlayingIx = 0; // Number of steps in not static at compile time anymore... maxSteps = numSteps; // Num Steps may vary now up to 64 currentNumberSteps = maxSteps; storedNumberSteps = maxSteps; this->numRows = numRows; this->numCols = numCols; stepLights = new float*[numRows]; gateLights = new float*[numRows]; padLightPtrs = new ColorValueLight**[numRows]; for (int r = 0; r < numRows; r++) { stepLights[r] = new float[numCols]; gateLights[r] = new float[numCols]; padLightPtrs[r] = new ColorValueLight*[numCols]; for (int c = 0; c < numCols; c++) { stepLights[r][c] = 0; gateLights[r][c] = 0; } } for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { copyBuffer[g] = new float[maxSteps]; } for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { triggerState[p][g] = new float[maxSteps]; for (int s = 0; s < maxSteps; s++) { triggerState[p][g][s] = defaultStateValue; } } } modeStrings[0] = "TRIG"; modeStrings[1] = "RTRG"; modeStrings[2] = "GATE"; // CONT/GATE copySourcePatternIx = -1; copySourceChannelIx = TROWA_SEQ_COPY_CHANNELIX_ALL; // Which trigger we are copying, -1 for all initialized = false; firstLoad = true; return; } // end TSSequencerModuleBase() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Delete our goodies. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- TSSequencerModuleBase::~TSSequencerModuleBase() { initialized = false; // Stop doing stuff cleanupOSC(); for (int r = 0; r < numRows; r++) { if (stepLights[r]) delete[] stepLights[r]; if (gateLights[r]) delete[] gateLights[r]; if (padLightPtrs[r]) delete[] padLightPtrs[r]; } if (stepLights != NULL) { delete[] stepLights; stepLights = NULL; } if (gateLights != NULL) { delete[] gateLights; gateLights = NULL; } if (padLightPtrs != NULL) { delete[] padLightPtrs; padLightPtrs = NULL; } for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { delete[] copyBuffer[g]; copyBuffer[g] = NULL; // We should be totally dead & unreferenced anyway, so I'm not sure we have NULL our ptrs??? } for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { delete[] triggerState[p][g]; triggerState[p][g] = NULL; } } this->copyGateLight = NULL; this->copyPatternLight = NULL; this->pasteLight = NULL; // Free our buffer if we had initialized it oscMutex.lock(); if (oscBuffer != NULL) { free(oscBuffer); oscBuffer = NULL; } oscMutex.unlock(); return; } // end ~TSSequencerModuleBase() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // reset(void) // Reset ALL step values to default. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::reset() { valuesChanging = true; for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { for (int c = 0; c < TROWA_SEQ_NUM_CHNLS; c++) { for (int s = 0; s < maxSteps; s++) { triggerState[p][c][s] = defaultStateValue; } } } /// TODO: Also clear our clipboard and turn off OSC? reloadEditMatrix = true; valuesChanging = false; return; } //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // randomize() // @patternIx : (IN) The index into our pattern matrix (0-63). Or TROWA_INDEX_UNDEFINED for all patterns. // @channelIx : (IN) The index of the channel (gate/trigger/voice) if any (0-15, or TROWA_SEQ_COPY_CHANNELIX_ALL/TROWA_INDEX_UNDEFINED for all). // @useStructured: (IN) Create a random sequence/pattern of random values. // Random all from : https://github.com/j4s0n-c/trowaSoft-VCV/issues/8 // Structured from : https://github.com/j4s0n-c/trowaSoft-VCV/issues/10 //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::randomize(int patternIx, int channelIx, bool useStructured) { if (patternIx == TROWA_INDEX_UNDEFINED) { // All patterns: for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { randomize(p, TROWA_INDEX_UNDEFINED, useStructured); // All channels } } else if (channelIx == TROWA_INDEX_UNDEFINED) { // This pattern: for (int c = 0; c < TROWA_SEQ_NUM_CHNLS; c++) { randomize(patternIx, c, useStructured); } } else { valuesChanging = true; // -- Randomize Channel Specified -- float val; if (useStructured) { // Use a pattern // A, AB, ABBA, ABAC int rIx = rand() % numStructuredRandomPatterns;// TROWA_SEQ_NUM_RANDOM_PATTERNS; int n = RandomPatterns[rIx].numDiffVals; float* randVals = new float[n]; int patternLen = RandomPatterns[rIx].pattern.size(); // Every Channel should get its own random pattern for (int i = 0; i < n; i++) randVals[i] = getRandomValue(); for (int s = 0; s < maxSteps; s++) { val = randVals[RandomPatterns[rIx].pattern[s % patternLen]]; triggerState[patternIx][channelIx][s] = val; if (patternIx == currentPatternEditingIx && channelIx == currentChannelEditingIx) onShownStepChange(s, val); } delete[] randVals; } // end if random pattern/structure else { // Every value is random for (int s = 0; s < maxSteps; s++) { val = getRandomValue(); triggerState[patternIx][channelIx][s] = val; if (patternIx == currentPatternEditingIx && channelIx == currentChannelEditingIx) onShownStepChange(s, val); } } // end else (normal Rand -- all values random) reloadEditMatrix = (patternIx == currentPatternEditingIx && channelIx == currentChannelEditingIx); valuesChanging = false; } // end else (channel and pattern specified) return; } //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Set the OSC namespace. // @oscNs: (IN) The namespace for OSC. // Sets the command address strings too. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::setOSCNamespace(const char* oscNs) { this->oscNamespace = oscNs; for (int i = 0; i < SeqOSCOutputMsg::NUM_OSC_OUTPUT_MSGS; i++) { // Create our array of output addresses based on the base format and the osc name space. sprintf(this->oscAddrBuffer[i], TSSeqOSCOutputFormats[i], oscNamespace.c_str()); } // Add %d (all this was changed for touchOSC's limitations) std::strcat(oscAddrBuffer[SeqOSCOutputMsg::EditStepString], "%d"); std::strcat(oscAddrBuffer[SeqOSCOutputMsg::EditStep], "%d"); std::strcat(oscAddrBuffer[SeqOSCOutputMsg::PlayStepLed], "%d"); // [touchOSC] Add some / std::strcat(oscAddrBuffer[SeqOSCOutputMsg::EditTOSC_GridStep], "%d/%d"); return; } // end setOSCNameSpace() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Initialize OSC on the given ip and ports. // @ipAddress: (IN) The ip address. // @outputPort: (IN) The output port. // @inputPort: (IN) The input port. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::initOSC(const char* ipAddress, int outputPort, int inputPort) { oscMutex.lock(); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::initOSC() - Initializing OSC"); #endif try { // Try to register these ports: if (TSOSCConnector::RegisterPorts(oscId, outputPort, inputPort)) { oscError = false; this->currentOSCSettings.oscTxIpAddress = ipAddress; this->setOSCNamespace(this->oscNamespace.c_str()); if (oscBuffer == NULL) { oscBuffer = (char*)malloc(OSC_OUTPUT_BUFFER_SIZE * sizeof(char)); } if (oscTxSocket == NULL) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::initOSC() - Create TRANS socket at %s, port %d.", ipAddress, outputPort); #endif oscTxSocket = new UdpTransmitSocket(IpEndpointName(ipAddress, outputPort)); this->currentOSCSettings.oscTxPort = outputPort; } if (oscRxSocket == NULL) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::initOSC() - Create RECV socket at any address, port %d.", inputPort); #endif oscListener = new TSOSCSequencerListener(); oscListener->sequencerModule = this; oscListener->oscNamespace = this->oscNamespace; oscRxSocket = new UdpListeningReceiveSocket(IpEndpointName(IpEndpointName::ANY_ADDRESS, inputPort), oscListener); this->currentOSCSettings.oscRxPort = inputPort; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::initOSC() - Starting listener thread..."); #endif oscListenerThread = std::thread(&UdpListeningReceiveSocket::Run, oscRxSocket); } //#if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW // debug("TSSequencerModuleBase::initOSC() - OSC Initialized"); //#endif info("TSSequencerModuleBase::initOSC() - OSC Initialized : %s :%d :%d", ipAddress, outputPort, inputPort); oscInitialized = true; } else { oscError = true; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::initOSC() - Ports in use already."); #endif } } catch (const std::exception& ex) { oscError = true; warn("TSSequencerModuleBase::initOSC() - Error initializing: %s.", ex.what()); } oscMutex.unlock(); return; } // end initOSC() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Clean up OSC. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::cleanupOSC() { oscMutex.lock(); try { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::cleanupOSC() - Cleaning up OSC"); #endif oscInitialized = false; oscError = false; TSOSCConnector::ClearPorts(oscId, currentOSCSettings.oscTxPort, currentOSCSettings.oscRxPort); if (oscRxSocket != NULL) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::cleanupOSC() - Cleaning up RECV socket."); #endif oscRxSocket->AsynchronousBreak(); oscListenerThread.join(); // Wait for him to finish delete oscRxSocket; oscRxSocket = NULL; } if (oscTxSocket != NULL) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::cleanupOSC() - Cleanup TRANS socket."); #endif delete oscTxSocket; oscTxSocket = NULL; } //if (oscBuffer != NULL) //{ // free(oscBuffer); // oscBuffer = NULL; //} #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::cleanupOSC() - OSC cleaned"); #endif } catch (const std::exception& ex) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("TSSequencerModuleBase::cleanupOSC() - Exception caught:\n%s", ex.what()); #endif } oscMutex.unlock(); return; } // end cleanupOSC() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // copy() // @patternIx : (IN) The index into our pattern matrix (0-15). // @channelIx : (IN) The index of the channel (gate/trigger/voice) to copy if any (0-15, or TROWA_SEQ_COPY_CHANNELIX_ALL for all). //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::copy(/*in*/ int patternIx, /*in*/ int channelIx) { copySourceChannelIx = channelIx; copySourcePatternIx = patternIx; if (copySourceChannelIx == TROWA_SEQ_COPY_CHANNELIX_ALL) { // Copy entire pattern (all gates/triggers/voices) for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { for (int s = 0; s < maxSteps; s++) { copyBuffer[g][s] = triggerState[copySourcePatternIx][g][s]; } } } else { // Copy just the gate: for (int s = 0; s < maxSteps; s++) { copyBuffer[copySourceChannelIx][s] = triggerState[copySourcePatternIx][copySourceChannelIx][s]; } } return; } // end copy() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // paste(void) // Paste our current clipboard Pattern/Gate to the currently selected Pattern/Gate. // @returns: True if the values were copied, false if not. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- bool TSSequencerModuleBase::paste() { if (copySourcePatternIx < 0) // Nothing to copy return false; valuesChanging = true; if (copySourceChannelIx == TROWA_SEQ_COPY_CHANNELIX_ALL) { // Copy entire pattern (all gates/triggers/voices) for (int g = 0; g < TROWA_SEQ_NUM_CHNLS; g++) { for (int s = 0; s < maxSteps; s++) { triggerState[currentPatternEditingIx][g][s] = copyBuffer[g][s]; } } } else { // Copy just the channel: for (int s = 0; s < maxSteps; s++) { triggerState[currentPatternEditingIx][currentChannelEditingIx][s] = copyBuffer[copySourceChannelIx][s]; } } valuesChanging = false; return true; } // end paste() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Set a single the step value // (i.e. this command probably comes from an external source) // @step : (IN) The step number to edit (0 to maxSteps). // @val : (IN) The step value. // @channel : (IN) The channel to edit (0 to TROWA_SEQ_NUM_CHNLS - 1). // @pattern: (IN) The pattern to edit (0 to TROWA_SEQ_NUM_PATTERNS - 1). //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::setStepValue(int step, float val, int channel, int pattern) { int r, c; if (channel == CURRENT_EDIT_CHANNEL_IX) { channel = currentChannelEditingIx; } if (pattern == CURRENT_EDIT_PATTERN_IX) { pattern = currentPatternEditingIx; } triggerState[pattern][channel][step] = val; r = step / this->numCols; c = step % this->numCols; if (pattern == currentPatternEditingIx && channel == currentChannelEditingIx) { if (triggerState[pattern][channel][step]) { gateLights[r][c] = 1.0f - stepLights[r][c]; if (gateTriggers != NULL) gateTriggers[step].state = SchmittTrigger::HIGH; } else { gateLights[r][c] = 0.0f; // Turn light off if (gateTriggers != NULL) gateTriggers[step].state = SchmittTrigger::LOW; } } oscMutex.lock(); if (useOSC && oscInitialized) { try { char addrBuff[50] = { 0 }; // Send the result back if (this->oscCurrentClient == OSCClient::touchOSCClient) { int gridRow, gridCol; touchOSC::stepIndex_to_mcRowCol(step, numRows, numCols, &gridRow, &gridCol); sprintf(addrBuff, oscAddrBuffer[SeqOSCOutputMsg::EditTOSC_GridStep], gridRow, gridCol); // Grid's // to accomodate touchOSC's lack of multi-parameter support. } else { sprintf(addrBuff, oscAddrBuffer[SeqOSCOutputMsg::EditStep], step + 1); // Changed to / to accomodate touchOSC's lack of multi-parameter support. } #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("setStepValue() - Received a msg (s=%d, v=%0.2f, c=%d, p=%d), sending back (%s).", step, val, channel, pattern, addrBuff); #endif osc::OutboundPacketStream oscStream(oscBuffer, OSC_OUTPUT_BUFFER_SIZE); oscStream << osc::BeginBundleImmediate << osc::BeginMessage(addrBuff) << triggerState[pattern][channel][step] << osc::EndMessage << osc::EndBundle; oscTxSocket->Send(oscStream.Data(), oscStream.Size()); } catch (const std::exception &e) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("seStepValue - Error sending back msg: %s.", e.what()); #endif } } oscMutex.unlock(); return; } // end setStepValue() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // getStepInputs() // Get the inputs shared between our Sequencers. // Now also processes our external message queue. // @pulse : (OUT) If gate pulse // @reloadMatrix: (OUT) If the edit matrix should be refreshed. // @valueModeChanged: (OUT) If the output value mode has changed. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::getStepInputs(/*out*/ bool* pulse, /*out*/ bool* reloadMatrix, /*out*/ bool* valueModeChanged) { // Track if we have changed these bool editPatternChanged = false; bool editChannelChanged = false; float lastBPM = currentBPM; bool playBPMChanged = false; bool lastRunning = running; int lastBPMNoteIx = this->selectedBPMNoteIx; int lastStepIndex = index; // Run if (runningTrigger.process(params[RUN_PARAM].value)) { running = !running; } lights[RUNNING_LIGHT].value = running ? 1.0 : 0.0; bool oscStarted = false; // If OSC just started to a new address this step. switch (this->oscCurrentAction) { case OSCAction::Disable: this->cleanupOSC(); // Try to clean up OSC break; case OSCAction::Enable: this->cleanupOSC(); // Try to clean up OSC if we already have something this->initOSC(this->oscNewSettings.oscTxIpAddress.c_str(), this->oscNewSettings.oscTxPort, this->oscNewSettings.oscRxPort); this->useOSC = true; oscStarted = this->useOSC && this->oscInitialized; break; case OSCAction::None: default: break; } this->oscCurrentAction = OSCAction::None; // OSC is Enabled and Active light lights[LightIds::OSC_ENABLED_LIGHT].value = (useOSC && oscInitialized) ? 1.0 : 0.0; if (!firstLoad) lastPatternPlayingIx = currentPatternPlayingIx; bool nextStep = false; // Now calculate BPM even if we are paused: // BPM calculation selection if (selectedBPMNoteTrigger.process(params[SELECTED_BPM_MULT_IX_PARAM].value)) { if (selectedBPMNoteIx < TROWA_TEMP_BPM_NUM_OPTIONS - 1) selectedBPMNoteIx++; else selectedBPMNoteIx = 0; // Wrap around lights[SELECTED_BPM_MULT_IX_LIGHT].value = 1.0; } float clockTime = 1.0; float input = 1.0; if (inputs[BPM_INPUT].active) { // Use whatever voltage we are getting (-10 TO 10 input) input = rescale(inputs[BPM_INPUT].value, TROWA_SEQ_PATTERN_MIN_V, TROWA_SEQ_PATTERN_MAX_V, TROWA_SEQ_BPM_KNOB_MIN, TROWA_SEQ_BPM_KNOB_MAX); } else { // Otherwise read our knob input = params[BPM_PARAM].value; // -2 to 6 } clockTime = powf(2.0, input); // -2 to 6 // Calculate his all the time now instead of just on next step: currentBPM = roundf(clockTime * BPMOptions[selectedBPMNoteIx]->multiplier); playBPMChanged = lastBPM != currentBPM; if (running) { if (inputs[EXT_CLOCK_INPUT].active) { // External clock input if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { realPhase = 0.0; nextStep = true; lastStepWasExternalClock = true; } } else { // Internal clock lastStepWasExternalClock = false; float dt = clockTime / engineGetSampleRate(); // Real dt realPhase += dt; // Real Time no matter what if (realPhase >= 1.0) { realPhase -= 1.0; nextStep = true; } //if (nextStep) //{ //currentBPM = roundf(clockTime * BPMOptions[selectedBPMNoteIx]->multiplier); //playBPMChanged = lastBPM != currentBPM; //} } } // end if running // Current Playing Pattern // If we get an input, then use that: if (inputs[SELECTED_PATTERN_PLAY_INPUT].active) { currentPatternPlayingIx = VoltsToPattern(inputs[SELECTED_PATTERN_PLAY_INPUT].value) - 1; } else { // Otherwise read our knob parameter and use that currentPatternPlayingIx = (int)clamp(static_cast(roundf(params[SELECTED_PATTERN_PLAY_PARAM].value)), 0, TROWA_SEQ_NUM_PATTERNS - 1); } if (currentPatternPlayingIx < 0) currentPatternPlayingIx = 0; else if (currentPatternPlayingIx > TROWA_SEQ_NUM_PATTERNS - 1) currentPatternPlayingIx = TROWA_SEQ_NUM_PATTERNS - 1; // Current Edit Pattern int lastEditPatternIx = currentPatternEditingIx; // From User Knob: currentPatternEditingIx = (int)clamp(static_cast(roundf(params[SELECTED_PATTERN_EDIT_PARAM].value)), 0, TROWA_SEQ_NUM_PATTERNS - 1); if (currentPatternEditingIx < 0) currentPatternEditingIx = 0; else if (currentPatternEditingIx > TROWA_SEQ_NUM_PATTERNS - 1) currentPatternEditingIx = TROWA_SEQ_NUM_PATTERNS - 1; // Gate inputs (which gate we are displaying & editing) int lastChannelIx = currentChannelEditingIx; currentChannelEditingIx = (int)clamp(static_cast(roundf(params[SELECTED_CHANNEL_PARAM].value)), 0, TROWA_SEQ_NUM_CHNLS - 1); if (currentChannelEditingIx < 0) currentChannelEditingIx = 0; else if (currentChannelEditingIx > TROWA_SEQ_NUM_CHNLS - 1) currentChannelEditingIx = TROWA_SEQ_NUM_CHNLS - 1; editChannelChanged = lastChannelIx != currentChannelEditingIx; int r = 0; int c = 0; // Current output value mode selectedOutputValueMode = static_cast((int)clamp(static_cast(roundf(params[SELECTED_OUTPUT_VALUE_MODE_PARAM].value)), 0, TROWA_SEQ_NUM_MODES - 1)); int lastNumberSteps = currentNumberSteps; if (inputs[STEPS_INPUT].active) { // Use the input if something is connected. // Some seqeuencers go to 64 steps, we want the same voltage to mean the same step number no matter how many max steps this one takes. // so voltage input is normalized to indicate step 1 to step 64, but we'll limit it to maxSteps. currentNumberSteps = (int)clamp(static_cast(roundf(rescale(inputs[STEPS_INPUT].value, TROWA_SEQ_STEPS_MIN_V, TROWA_SEQ_STEPS_MAX_V, 1.0, (float)TROWA_SEQ_MAX_NUM_STEPS))), 1, maxSteps); } else { // Otherwise read our knob currentNumberSteps = (int)clamp(static_cast(roundf(params[STEPS_PARAM].value)), 1, maxSteps); } //------------------------------------------------------------ // Check if we have any eternal messages. // (i.e. from OSC) //------------------------------------------------------------ /// TODO: Check performance hit from sending OSC in general /// TODO: Some thread safety and make sure that this queue never gets unruly? such that we start getting out of time. bool resetMsg = false; bool doPaste = false; int prevCopyPatternIx = copySourcePatternIx; int prevCopyChannelIx = copySourceChannelIx; bool storedPatternChanged = false; bool storedLengthChanged = false; bool storedBPMChanged = false; while (ctlMsgQueue.size() > 0) { TSExternalControlMessage recvMsg = (TSExternalControlMessage)(ctlMsgQueue.front()); ctlMsgQueue.pop(); float tmp; /// TODO: redorder switch for most common cases first. switch (recvMsg.messageType) { case TSExternalControlMessage::MessageType::ToggleEditStepValue: case TSExternalControlMessage::MessageType::SetEditStepValue: if (currentCtlMode == ExternalControllerMode::EditMode) { int p = (recvMsg.pattern == CURRENT_EDIT_PATTERN_IX) ? currentPatternEditingIx : recvMsg.pattern; int c = (recvMsg.channel == CURRENT_EDIT_CHANNEL_IX) ? currentChannelEditingIx : recvMsg.channel; float oldVal = this->triggerState[p][c][recvMsg.step]; float val = (recvMsg.messageType == TSExternalControlMessage::MessageType::ToggleEditStepValue) ? getToggleStepValue(recvMsg.step, recvMsg.val, /*channel*/ c, /*pattern*/ p) : recvMsg.val; if (oldVal != val) { // Value has changed: #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("[%d] Set Step Value (value changed): %d (P %d, C %d) = %.4f.", recvMsg.messageType, recvMsg.step, p, c, val); #endif this->setStepValue(recvMsg.step, val, /*channel*/ c, /*pattern*/ p); } #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED else { debug("[%d] Step value did not change -- Ignore : %d (P %d, C %d) = %.4f.", recvMsg.messageType, recvMsg.step, p, c, val); } #endif } else { // In performance mode, this will be interupted as jump to (playing): if (recvMsg.pattern != CURRENT_EDIT_PATTERN_IX) { currentPatternPlayingIx = recvMsg.pattern; // Jump to this pattern if sent // Update our knob controlKnobs[KnobIx::PlayPatternKnob]->value = currentPatternPlayingIx; controlKnobs[KnobIx::PlayPatternKnob]->dirty = true; params[ParamIds::SELECTED_PATTERN_PLAY_PARAM].value = currentPatternPlayingIx; } // Jump to this step: if (nextStep) { // We are already at beginning of a step, so we can sneak this in now. index = recvMsg.step - 1; // We will set nextStep to true to go to this step fresh nextIndex = TROWA_INDEX_UNDEFINED; } else { // Next time we are ready to go to the next step, this should be it. nextIndex = recvMsg.step; } #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Performance Mode: Jump to Step (index): %d (Pattern %d).", index, currentPatternPlayingIx); #endif } break; case TSExternalControlMessage::MessageType::SetPlayCurrentStep: // We want to wait until the 'next step' (finish the one we are currently doing so things are still in time). if (nextStep) { // We are already at beginning of a step, so we can sneak this in now. index = recvMsg.step - 1; // We will set nextStep to true to go to this step fresh nextIndex = TROWA_INDEX_UNDEFINED; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Play Step (index): %d [Immediate].", recvMsg.step); #endif } else { // Next time we are ready to go to the next step, this should be it. nextIndex = recvMsg.step; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Play Step (index): %d [Next, curr is %d].", nextIndex, index); #endif } break; case TSExternalControlMessage::MessageType::SetPlayReset: resetMsg = true; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Reset."); #endif break; case TSExternalControlMessage::MessageType::SetPlayPattern: if (recvMsg.pattern == TROWA_INDEX_UNDEFINED) recvMsg.pattern = storedPatternPlayingIx; // Check our stored pattern if (recvMsg.pattern > -1 && recvMsg.pattern < TROWA_SEQ_NUM_PATTERNS) { currentPatternPlayingIx = recvMsg.pattern; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Play Pattern: %d.", currentPatternPlayingIx); #endif // Update our knob controlKnobs[KnobIx::PlayPatternKnob]->value = currentPatternPlayingIx; controlKnobs[KnobIx::PlayPatternKnob]->dirty = true; params[ParamIds::SELECTED_PATTERN_PLAY_PARAM].value = currentPatternPlayingIx; } break; case TSExternalControlMessage::MessageType::StorePlayPattern: if (storedPatternPlayingIx != recvMsg.pattern) { storedPatternPlayingIx = recvMsg.pattern; storedPatternChanged = true; } break; case TSExternalControlMessage::MessageType::SetPlayOutputMode: // -- Set Ouput Mode: (TRIG, RTRIG, GATE) or (VOLT, NOTE, PATT) -- selectedOutputValueMode = (ValueMode)(recvMsg.mode); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Output Mode: %d (TRIG, RTRIG, GATE) or (VOLT, NOTE, PATT).", selectedOutputValueMode); #endif controlKnobs[KnobIx::OutputModeKnob]->value = selectedOutputValueMode; controlKnobs[KnobIx::OutputModeKnob]->dirty = true; params[ParamIds::SELECTED_OUTPUT_VALUE_MODE_PARAM].value = selectedOutputValueMode; break; case TSExternalControlMessage::MessageType::SetEditPattern: currentPatternEditingIx = recvMsg.pattern; *reloadMatrix = true; // Refresh our display matrix #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Edit Pattern: %d.", currentPatternEditingIx); #endif // Update our knob controlKnobs[KnobIx::EditPatternKnob]->value = currentPatternEditingIx; controlKnobs[KnobIx::EditPatternKnob]->dirty = true; params[ParamIds::SELECTED_PATTERN_EDIT_PARAM].value = currentPatternEditingIx; break; case TSExternalControlMessage::MessageType::SetEditChannel: currentChannelEditingIx = recvMsg.channel; *reloadMatrix = true; // Refresh our display matrix #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Edit Channel: %d.", currentChannelEditingIx); #endif // Update our knob controlKnobs[KnobIx::EditChannelKnob]->value = currentChannelEditingIx; controlKnobs[KnobIx::EditChannelKnob]->dirty = true; params[ParamIds::SELECTED_CHANNEL_PARAM].value = currentChannelEditingIx; break; case TSExternalControlMessage::MessageType::TogglePlayMode: // -- Control Mode: Edit / Performance Mode -- currentCtlMode = (currentCtlMode == ExternalControllerMode::EditMode) ? ExternalControllerMode::PerformanceMode : ExternalControllerMode::EditMode; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Toggle Control Mode: %d.", currentCtlMode); #endif break; case TSExternalControlMessage::MessageType::SetPlayMode: // -- Control Mode: Edit / Performance Mode -- currentCtlMode = static_cast(recvMsg.mode); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Control Mode: %d.", currentCtlMode); #endif break; case TSExternalControlMessage::MessageType::StorePlayBPM: if (storedBPM != recvMsg.mode) { storedBPMChanged = true; storedBPM = recvMsg.mode; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Store BPM (%d): %.2f.", recvMsg.mode, storedBPM); #endif } break; // Now BPM and Tempo are separate in OSC. /// TODO: Maybe move the calcs to the listener thread? But if we have other external messages from non-OSC, then it would have to repeated somewhere... case TSExternalControlMessage::MessageType::SetPlayBPM: // "BPM" is relative to the note //float bpm = recvMsg.mode; // currentBPM = 2^knob * mult // currentBPM / mult = 2 ^ knob // log2(currentBPM / mult) = knob if (recvMsg.mode == TROWA_INDEX_UNDEFINED) recvMsg.mode = storedBPM; tmp = clamp(std::log2f(recvMsg.mode / BPMOptions[selectedBPMNoteIx]->multiplier), static_cast(TROWA_SEQ_BPM_KNOB_MIN), static_cast(TROWA_SEQ_BPM_KNOB_MAX)); controlKnobs[KnobIx::BPMKnob]->value = tmp; controlKnobs[KnobIx::BPMKnob]->dirty = true; params[ParamIds::BPM_PARAM].value = tmp; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set BPM (%d): %.2f.", recvMsg.mode, tmp); #endif break; case TSExternalControlMessage::MessageType::AddPlayBPM: // "BPM" is relative to the note tmp = pow(2, controlKnobs[KnobIx::BPMKnob]->value) // Current BPM + recvMsg.mode / BPMOptions[selectedBPMNoteIx]->multiplier; tmp = std::log2f(tmp); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Add BPM (%d): Knob %.2f, End is %.2f", recvMsg.mode, controlKnobs[KnobIx::BPMKnob]->value, tmp); #endif controlKnobs[KnobIx::BPMKnob]->value = clamp(tmp, static_cast(TROWA_SEQ_BPM_KNOB_MIN), static_cast(TROWA_SEQ_BPM_KNOB_MAX)); controlKnobs[KnobIx::BPMKnob]->dirty = true; params[ParamIds::BPM_PARAM].value = tmp; break; case TSExternalControlMessage::MessageType::SetPlayTempo: // Tempo goes from 0 to 1 tmp = rescale(recvMsg.val, 0.0, 1.0, TROWA_SEQ_BPM_KNOB_MIN, TROWA_SEQ_BPM_KNOB_MAX); controlKnobs[KnobIx::BPMKnob]->value = tmp; controlKnobs[KnobIx::BPMKnob]->dirty = true; params[ParamIds::BPM_PARAM].value = tmp; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Tempo (%.2f): Knob %.2f.", recvMsg.val, tmp); #endif break; case TSExternalControlMessage::MessageType::AddPlayTempo: // Tempo goes from 0 to 1 tmp = rescale(recvMsg.val, 0.0, 1.0, TROWA_SEQ_BPM_KNOB_MIN, TROWA_SEQ_BPM_KNOB_MAX); controlKnobs[KnobIx::BPMKnob]->value = clamp(tmp + controlKnobs[KnobIx::BPMKnob]->value, static_cast(TROWA_SEQ_BPM_KNOB_MIN), static_cast(TROWA_SEQ_BPM_KNOB_MAX)); controlKnobs[KnobIx::BPMKnob]->dirty = true; params[ParamIds::BPM_PARAM].value = tmp; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Add Tempo (%.2f): Knob %.2f.", recvMsg.val, controlKnobs[KnobIx::BPMKnob]->value); #endif break; case TSExternalControlMessage::MessageType::AddPlayBPMNote: selectedBPMNoteIx = (selectedBPMNoteIx + recvMsg.step) % TROWA_TEMP_BPM_NUM_OPTIONS; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Add %d to BPM Note Ix: %d.", recvMsg.step, selectedBPMNoteIx); #endif break; case TSExternalControlMessage::MessageType::SetPlayBPMNote: selectedBPMNoteIx = recvMsg.step % TROWA_TEMP_BPM_NUM_OPTIONS; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set BPM Note Ix: %d.", selectedBPMNoteIx); #endif break; case TSExternalControlMessage::MessageType::StorePlayLength: if (recvMsg.step != storedNumberSteps) { storedNumberSteps = recvMsg.step; storedLengthChanged = true; } break; case TSExternalControlMessage::MessageType::SetPlayLength: // Use our stored value if sent is -1 recvMsg.step = (recvMsg.step == TROWA_INDEX_UNDEFINED) ? storedNumberSteps : recvMsg.step; if (recvMsg.step > 0 && recvMsg.step <= maxSteps) { currentNumberSteps = recvMsg.step; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Play Step Length: %d.", currentNumberSteps); #endif // Update our knob controlKnobs[KnobIx::StepLengthKnob]->value = currentNumberSteps; controlKnobs[KnobIx::StepLengthKnob]->dirty = true; params[ParamIds::STEPS_PARAM].value = currentNumberSteps; } break; case TSExternalControlMessage::MessageType::PasteEditClipboard: doPaste = true; #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Paste message."); #endif break; case TSExternalControlMessage::MessageType::CopyEditPattern: { int pat = (recvMsg.pattern == CURRENT_EDIT_PATTERN_IX) ? currentPatternEditingIx : recvMsg.pattern; if (copySourcePatternIx > -1 && copySourceChannelIx == TROWA_SEQ_COPY_CHANNELIX_ALL) { // Clear clipboard clearClipboard(); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("(clear clipboard) Copy Edit Pattern: %d.", pat); #endif } else { //int c = (recvMsg.channel == CURRENT_EDIT_CHANNEL_IX) ? currentChannelEditingIx : recvMsg.channel; copy(pat, TROWA_SEQ_COPY_CHANNELIX_ALL); lights[PASTE_LIGHT].value = 1; // Activate paste light to show there is something on the clipboard pasteLight->setColor(COLOR_WHITE); lights[COPY_PATTERN_LIGHT].value = 1; // Light up Pattern Copy as Active clipboard lights[COPY_CHANNEL_LIGHT].value = 0; // Inactivate Gate Copy light #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Copy Edit Pattern: %d.", pat); #endif } } break; case TSExternalControlMessage::MessageType::CopyEditChannel: { int pat = (recvMsg.pattern == CURRENT_EDIT_PATTERN_IX) ? currentPatternEditingIx : recvMsg.pattern; int ch = (recvMsg.channel == CURRENT_EDIT_CHANNEL_IX) ? currentChannelEditingIx : recvMsg.channel; if (copySourcePatternIx > -1 && copySourceChannelIx > -1) { // Clear clipboard clearClipboard(); #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("(clear clipboard) Copy Edit Channel: (P:%d, C:%d).", pat, ch); #endif } else { copy(pat, ch); lights[PASTE_LIGHT].value = 1; // Activate paste light to show there is something on the clipboard pasteLight->setColor(voiceColors[currentChannelEditingIx]); lights[COPY_CHANNEL_LIGHT].value = 1; // Light up Channel Copy Light as Active clipboard copyGateLight->setColor(voiceColors[currentChannelEditingIx]); // Match the color with our Channel color lights[COPY_PATTERN_LIGHT].value = 0; // Inactivate Pattern Copy Light #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Copy Edit Channel: (P:%d, C:%d).", pat, ch); #endif } } break; case TSExternalControlMessage::MessageType::SetPlayRunningState: case TSExternalControlMessage::MessageType::TogglePlayRunningState: if (recvMsg.messageType == TSExternalControlMessage::MessageType::TogglePlayRunningState) { running = !running; } else { running = recvMsg.val > 0; } #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Set Running to: %d.", running); #endif runningTrigger.state = (running) ? SchmittTrigger::HIGH : SchmittTrigger::LOW; params[RUN_PARAM].value = running; lights[RUNNING_LIGHT].value = running ? 1.0 : 0.0; break; case TSExternalControlMessage::MessageType::RandomizeEditStepValue: randomize(); break; case TSExternalControlMessage::MessageType::InitializeEditModule: reset(); for (int i = 0; i < KnobIx::NumKnobs; i++) { controlKnobs[i]->value = controlKnobs[i]->defaultValue; controlKnobs[i]->dirty = true; } // We also need to make sure our controls reset.... //Module::reset(); // Base method reset should do the knobs //_ParentWidget->reset(); /// TODO: We should also send our new values to OSC if OSC is enabled. We would have to re-read the vals though, /// I think the values should trigger that they changed next step()... TODO: double check that this happens break; default: #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_LOW debug("Ooops - didn't handle this control message type yet %d.", recvMsg.messageType); #endif break; } // end switch } // end loop through message queue //-- COPY / PASTE -- bool pasteCompleted = false; if (pasteTrigger.process(params[PASTE_PARAM].value) || doPaste) { pasteCompleted = paste(); // Paste whatever we have if we have anything } else { // Check Copy if (copyPatternTrigger.process(params[COPY_PATTERN_PARAM].value)) { if (copySourcePatternIx > -1 && copySourceChannelIx == TROWA_SEQ_COPY_CHANNELIX_ALL) { // Clear clipboard clearClipboard(); } else { copy(currentPatternEditingIx, TROWA_SEQ_COPY_CHANNELIX_ALL); lights[PASTE_LIGHT].value = 1; // Activate paste light to show there is something on the clipboard pasteLight->setColor(COLOR_WHITE); lights[COPY_PATTERN_LIGHT].value = 1; // Light up Pattern Copy as Active clipboard lights[COPY_CHANNEL_LIGHT].value = 0; // Inactivate Gate Copy light } } if (copyGateTrigger.process(params[COPY_CHANNEL_PARAM].value)) { if (copySourcePatternIx > -1 && copySourceChannelIx > -1) { // Clear clipboard clearClipboard(); } else { copy(currentPatternEditingIx, currentChannelEditingIx); lights[PASTE_LIGHT].value = 1; // Activate paste light to show there is something on the clipboard pasteLight->setColor(voiceColors[currentChannelEditingIx]); lights[COPY_CHANNEL_LIGHT].value = 1; // Light up Channel Copy Light as Active clipboard copyGateLight->setColor(voiceColors[currentChannelEditingIx]); // Match the color with our Channel color lights[COPY_PATTERN_LIGHT].value = 0; // Inactivate Pattern Copy Light } } // end if copyGateTrigger() } // Check value mode change after we have processed incoming messages. *valueModeChanged = (lastOutputValueMode != selectedOutputValueMode); lastOutputValueMode = selectedOutputValueMode; // Reset // [03/30/2018] So, now j4s0n wants RESET to wait until the next step is played... // So it's delayed reset. https://github.com/j4s0n-c/trowaSoft-VCV/issues/11 if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value) || resetMsg) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Reset"); #endif resetPaused = !running; // [03/30/2018] Delay reset until the next step. (https://github.com/j4s0n-c/trowaSoft-VCV/issues/11) // So it's more like JUMP TO step 0 (waits until the next step). resetQueued = true; // Flag that the reset has been queued. } // end check for reset if (resetQueued && nextStep) { resetQueued = false; realPhase = 0.0; swingAdjustedPhase = 0; // Reset swing index = 999; nextStep = true; lights[RESET_LIGHT].value = 1.0; nextIndex = TROWA_INDEX_UNDEFINED; // Reset our jump to index oscMutex.lock(); if (useOSC && oscInitialized) { osc::OutboundPacketStream oscStream(oscBuffer, OSC_OUTPUT_BUFFER_SIZE); oscStream << osc::BeginBundleImmediate << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayReset]) << "bang" << osc::EndMessage << osc::EndBundle; oscTxSocket->Send(oscStream.Data(), oscStream.Size()); } oscMutex.unlock(); } // end if resetQueued and it's time to reset // Next Step if (nextStep) { if (nextIndex == TROWA_INDEX_UNDEFINED) index++; // Advance step else { index = nextIndex; // Set to our 'jump to' value nextIndex = TROWA_INDEX_UNDEFINED; // Reset our jump to index } if (index >= currentNumberSteps || index < 0) { index = 0; // Reset (artifical limit) } // Show which step we are on: r = index / this->numCols;// TROWA_SEQ_STEP_NUM_COLS; c = index % this->numCols; //TROWA_SEQ_STEP_NUM_COLS; stepLights[r][c] = 1.0f; gatePulse.trigger(TROWA_PULSE_WIDTH); oscMutex.lock(); if (useOSC && oscInitialized) { // [01/06/2018] Changed to one-based for OSC (send index+1 instead of index) osc::OutboundPacketStream oscStream(oscBuffer, OSC_OUTPUT_BUFFER_SIZE); oscStream << osc::BeginBundleImmediate << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayClock]) << index + 1 << osc::EndMessage << osc::EndBundle; oscTxSocket->Send(oscStream.Data(), oscStream.Size()); } oscMutex.unlock(); } // end if next step // If we were just unpaused and we were reset during the pause, make sure we fire the first step. if (running && !lastRunning) { if (resetPaused) { gatePulse.trigger(TROWA_PULSE_WIDTH); } resetPaused = false; } // end if // Reset light lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / lightLambda / engineGetSampleRate(); // BPM Note Calc light: lights[SELECTED_BPM_MULT_IX_LIGHT].value -= lights[SELECTED_BPM_MULT_IX_LIGHT].value / lightLambda / engineGetSampleRate(); *pulse = gatePulse.process(engineGetSampleTime());// 1.0 / engineGetSampleRate()); editChannelChanged = currentChannelEditingIx != lastChannelIx; editPatternChanged = currentPatternEditingIx != lastEditPatternIx; // See if we should reload our matrix //*reloadMatrix = currentChannelEditingIx != lastChannelIx || currentPatternEditingIx != lastEditPatternIx || pasteCompleted || this->reloadEditMatrix || firstLoad; *reloadMatrix = editChannelChanged || editPatternChanged || pasteCompleted || this->reloadEditMatrix || firstLoad || oscStarted; // Send messages if needed /// TODO: Make a message sender to do this crap oscMutex.lock(); if (useOSC && oscInitialized) { bool bundleOpened = false; // If something has changed or we just started up osc, then send the status of our sequencer. osc::OutboundPacketStream oscStream(oscBuffer, OSC_OUTPUT_BUFFER_SIZE); if (lastRunning != running || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayRunningState]) << (int)(running) << osc::EndMessage; // Send another toggle message for touchOSC oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayToggleRun]) << (int)(running) << osc::EndMessage; } if (lastPatternPlayingIx != currentPatternPlayingIx || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayPattern]) << (currentPatternPlayingIx + 1) << osc::EndMessage; } if (playBPMChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayBPM]) << currentBPM << this->selectedBPMNoteIx << osc::EndMessage; } if (lastNumberSteps != currentNumberSteps || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayLength]) << currentNumberSteps << osc::EndMessage; } // end playLengthChanged if (*valueModeChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayOutputMode]) << (int)(this->selectedOutputValueMode) << osc::EndMessage; } // end playOutputModeChanged if (editPatternChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditPattern]) << (currentPatternEditingIx + 1) << osc::EndMessage; } // end editPatternChanged if (editChannelChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditChannel]) << (currentChannelEditingIx + 1) << osc::EndMessage; } // end editChannelChanged if (lastBPMNoteIx != this->selectedBPMNoteIx || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayBPMNote]) << selectedBPMNoteIx << osc::EndMessage; } // end bpmNoteChanged if (storedPatternChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayPatternSav]) << ((storedPatternPlayingIx > -1) ? storedPatternPlayingIx + 1 : currentPatternPlayingIx + 1) << osc::EndMessage; } // end storedPatternChanged if (storedLengthChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayLengthSav]) << ((storedNumberSteps > 0) ? storedNumberSteps : currentNumberSteps) << osc::EndMessage; } // end storedPatternChanged if (storedBPMChanged || oscStarted) { if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::PlayBPMSav]) << ((storedBPM > 0) ? storedBPM : (int)(currentBPM)) << osc::EndMessage; } if (copySourcePatternIx != prevCopyPatternIx || copySourceChannelIx != prevCopyChannelIx || oscStarted) { // Clipboard has changed if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } if (copySourcePatternIx == TROWA_INDEX_UNDEFINED) { #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Sending Clear Clipboard: %s %d.", oscAddrBuffer[SeqOSCOutputMsg::EditChannelCpyCurr], 0); #endif oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditClipboard]) << 0 << 0 << osc::EndMessage; // Clipboard was cleared oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditChannelCpyCurr]) << 0 << osc::EndMessage; oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditPatternCpyCurr]) << 0 << osc::EndMessage; } else { // Send clipboard message (pattern, channel) oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditClipboard]) << (copySourcePatternIx + 1) << (copySourceChannelIx + 1) << osc::EndMessage; if (copySourceChannelIx == TROWA_SEQ_COPY_CHANNELIX_ALL) { // Pattern copied (pattern) #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Sending Copied Pattern: %s %d.", oscAddrBuffer[SeqOSCOutputMsg::EditPatternCpyCurr], (copySourcePatternIx + 1)); #endif oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditPatternCpyCurr]) << (copySourcePatternIx + 1) << osc::EndMessage; oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditChannelCpyCurr]) << 0 << osc::EndMessage; } else { // Channel copied (channel) #if TROWA_DEBUG_MSGS >= TROWA_DEBUG_LVL_MED debug("Sending Copied Channel: %s %d.", oscAddrBuffer[SeqOSCOutputMsg::EditChannelCpyCurr], (copySourceChannelIx + 1)); #endif oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditPatternCpyCurr]) << 0 << osc::EndMessage; oscStream << osc::BeginMessage(oscAddrBuffer[SeqOSCOutputMsg::EditChannelCpyCurr]) << (copySourceChannelIx + 1) << osc::EndMessage; } }// end else } // end if clipboard change #if OSC_UPDATE_CURRENT_STEP_LED if (lastStepIndex != index) { // Turn off last led, turn on this led if (!bundleOpened) { oscStream << osc::BeginBundleImmediate; bundleOpened = true; } char addrBuff[50] = { 0 }; // Prev step should turn off: sprintf(addrBuff, oscAddrBuffer[SeqOSCOutputMsg::PlayStepLed], lastStepIndex + 1); oscStream << osc::BeginMessage(addrBuff) << 0 << osc::EndMessage; // Current step should turn on: sprintf(addrBuff, oscAddrBuffer[SeqOSCOutputMsg::PlayStepLed], index + 1); oscStream << osc::BeginMessage(addrBuff) << 1 << osc::EndMessage; } #endif //--- FINISH BUNDLE --- if (bundleOpened) { // Finish and send oscStream << osc::EndBundle; oscTxSocket->Send(oscStream.Data(), oscStream.Size()); } } // end send osc oscMutex.unlock(); firstLoad = false; return; } // end getStepInputs() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // toJson(void) // Save our junk to json. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- json_t *TSSequencerModuleBase::toJson() { json_t *rootJ = json_object(); // version json_object_set_new(rootJ, "version", json_integer(TROWA_INTERNAL_VERSION_INT)); // running json_object_set_new(rootJ, "running", json_boolean(running)); // Current Items: json_object_set_new(rootJ, "currentPatternEditIx", json_integer((int)currentPatternEditingIx)); json_object_set_new(rootJ, "currentTriggerEditIx", json_integer((int)currentChannelEditingIx)); // The current output / knob mode. json_object_set_new(rootJ, "selectedOutputValueMode", json_integer((int)selectedOutputValueMode)); // Current BPM calculation note (i.e. 1/4, 1/8, 1/8T, 1/16) json_object_set_new(rootJ, "selectedBPMNoteIx", json_integer((int)selectedBPMNoteIx)); // triggers json_t *triggersJ = json_array(); for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { for (int t = 0; t < TROWA_SEQ_NUM_CHNLS; t++) { for (int s = 0; s < maxSteps; s++) { json_t *gateJ = json_real((float)triggerState[p][t][s]); json_array_append_new(triggersJ, gateJ); } // end for (steps) } // end for (triggers) } // end for (patterns) json_object_set_new(rootJ, "triggers", triggersJ); // gateMode json_t *gateModeJ = json_integer((int)gateMode); json_object_set_new(rootJ, "gateMode", gateModeJ); // OSC Parameters json_t* oscJ = json_object(); json_object_set_new(oscJ, "IpAddress", json_string(this->currentOSCSettings.oscTxIpAddress.c_str())); json_object_set_new(oscJ, "TxPort", json_integer(this->currentOSCSettings.oscTxPort)); json_object_set_new(oscJ, "RxPort", json_integer(this->currentOSCSettings.oscRxPort)); json_object_set_new(oscJ, "Client", json_integer(this->oscCurrentClient)); json_object_set_new(oscJ, "AutoReconnectAtLoad", json_boolean(oscReconnectAtLoad)); // [v11, v0.6.3] json_object_set_new(oscJ, "Initialized", json_boolean(oscInitialized)); // [v11, v0.6.3] We know the settings are good at least at the time of save json_object_set_new(rootJ, "osc", oscJ); return rootJ; } // end toJson() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // fromJson(void) // Read in our junk from json. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void TSSequencerModuleBase::fromJson(json_t *rootJ) { // running json_t *runningJ = json_object_get(rootJ, "running"); if (runningJ) running = json_is_true(runningJ); // Current Items: json_t *currJ = NULL; currJ = json_object_get(rootJ, "currentPatternEditIx"); if (currJ) currentPatternEditingIx = json_integer_value(currJ); currJ = json_object_get(rootJ, "currentTriggerEditIx"); if (currJ) currentChannelEditingIx = json_integer_value(currJ); currJ = json_object_get(rootJ, "selectedOutputValueMode"); if (currJ) { selectedOutputValueMode = static_cast(json_integer_value(currJ)); modeString = modeStrings[selectedOutputValueMode]; } // Current BPM calculation note (i.e. 1/4, 1/8, 1/8T, 1/16) currJ = json_object_get(rootJ, "selectedBPMNoteIx"); if (currJ) selectedBPMNoteIx = json_integer_value(currJ); // triggers json_t *triggersJ = json_object_get(rootJ, "triggers"); if (triggersJ) { int i = 0; for (int p = 0; p < TROWA_SEQ_NUM_PATTERNS; p++) { for (int t = 0; t < TROWA_SEQ_NUM_CHNLS; t++) { for (int s = 0; s < maxSteps; s++) { json_t *gateJ = json_array_get(triggersJ, i++); if (gateJ) triggerState[p][t][s] = (float)json_real_value(gateJ); } // end for (steps) } // end for (triggers) } // end for (patterns) } // gateMode json_t *gateModeJ = json_object_get(rootJ, "gateMode"); if (gateModeJ) gateMode = (GateMode)json_integer_value(gateModeJ); json_t* oscJ = json_object_get(rootJ, "osc"); if (oscJ) { currJ = json_object_get(oscJ, "IpAddress"); if (currJ) this->currentOSCSettings.oscTxIpAddress = json_string_value(currJ); currJ = json_object_get(oscJ, "TxPort"); if (currJ) this->currentOSCSettings.oscTxPort = (uint16_t)(json_integer_value(currJ)); currJ = json_object_get(oscJ, "RxPort"); if (currJ) this->currentOSCSettings.oscRxPort = (uint16_t)(json_integer_value(currJ)); currJ = json_object_get(oscJ, "Client"); if (currJ) this->oscCurrentClient = static_cast((uint8_t)(json_integer_value(currJ))); currJ = json_object_get(oscJ, "AutoReconnectAtLoad"); if (currJ) oscReconnectAtLoad = json_boolean_value(currJ); if (oscReconnectAtLoad) { currJ = json_object_get(oscJ, "Initialized"); if (currJ && json_boolean_value(currJ)) { oscCurrentAction = OSCAction::Enable; // Will enable at next step //// Try to reconnect //cleanupOSC(); //this->initOSC(this->currentOSCSettings.oscTxIpAddress.c_str(), this->currentOSCSettings.oscTxPort, this->currentOSCSettings.oscRxPort); //if (oscError || !oscInitialized) //{ // warn("TSSequencerModuleBase::fromJson(): Error on auto-reconnect OSC %s :%d :%d.", this->currentOSCSettings.oscTxIpAddress.c_str(), this->currentOSCSettings.oscTxPort, this->currentOSCSettings.oscRxPort); //} //else //{ // info("TSSequencerModuleBase::fromJson(): Successful auto-reconnection of OSC %s :%d :%d.", this->currentOSCSettings.oscTxIpAddress.c_str(), this->currentOSCSettings.oscTxPort, this->currentOSCSettings.oscRxPort); //} } } } saveVersion = 0; currJ = NULL; currJ = json_object_get(rootJ, "version"); if (currJ) { saveVersion = (int)(json_integer_value(currJ)); } firstLoad = true; return; } // end fromJson()