/* ============================================================================== This file was auto-generated! It contains the basic startup code for a Juce application. ============================================================================== */ #include #include "PluginProcessor.h" #include "JaySynthAudioProcessorEditor.h" #include "synth/vector_utils.h" JaySynthAudioProcessorEditor* getEditor(JaySynthAudioProcessor *pThis) { return dynamic_cast(pThis->getActiveEditor()); } //============================================================================== JaySynthAudioProcessor::JaySynthAudioProcessor() { currProgram = 0; currpatch = &patches[currProgram]; the_editor = NULL; m_isWavesAvailable = isWavesAvailable(); // Initialise the synth... pSynth = new JaySynth(MAX_POLYPHONY, wavesGetPath()); pSynth->setNoteStealingEnabled(true); pSynth->addListener(this); setCurrentProgram(0); m_limiter_env = 0; startTimer (10); #ifdef DEBUG SynthDebug ("Jay Synth V %s (DEBUG) initialized.\n", JucePlugin_VersionString); #else SynthDebug ("Jay Synth V %s initialized.\n", JucePlugin_VersionString); #endif } JaySynthAudioProcessor::~JaySynthAudioProcessor() { delete (pSynth); } void JaySynthAudioProcessor::timerCallback() { if (getEditor(this)) actionListener.callLimiterChanged(m_limiter_active); } String JaySynthAudioProcessor::wavesGetPath() { bool isExistent, isValid; File pluginfile = File::getSpecialLocation (File::currentExecutableFile).getParentDirectory(); String wavespath = pluginfile.getFullPathName() + String("\\waves.bin"); File wavesfile = File(wavespath); isExistent = wavesfile.existsAsFile(); isValid = true; if (isExistent && isValid) return wavespath; return String::empty; } bool JaySynthAudioProcessor::isWavesAvailable() { return (wavesGetPath() != String::empty); } //============================================================================== const String JaySynthAudioProcessor::getName() const { return JucePlugin_Name; } int JaySynthAudioProcessor::getNumParameters() { return SYNTH_NUM_PARAMS; } void JaySynthAudioProcessor::applyCurrentMidiControllers(void) { JaySynth::midiCC_info_t *pMidiCC_info; int i; for (i=0; i < NUM_MIDI_CONTROLLERS; i++) { pMidiCC_info = pSynth->getLastMidiCC_info(i); if (pMidiCC_info) applyMidiController(pMidiCC_info, false); } } void JaySynthAudioProcessor::applyMidiController(JaySynth::midiCC_info_t *pMidiCC_info, bool forceApply) { int i, numDst, paramID; midiCC_container_t *pMidi_container; numDst = midCC_editBuffer.getNumDestinations(pMidiCC_info->ID); for (i=0; i < numDst; i++) { pMidi_container = midCC_editBuffer.getDestination(pMidiCC_info->ID, i); paramID = midCC_editBuffer.getParamID(pMidi_container); if (midCC_editBuffer.doApplyOnPatchInit(pMidi_container) || forceApply) { if (midCC_editBuffer.doChangeParameter(pMidi_container)) { setParam(paramID, toParam(midCC_editBuffer.getParam(pMidi_container), pMidiCC_info->value), false, false); } else { controlParam(midCC_editBuffer.getParamID(pMidi_container), toParam(midCC_editBuffer.getControl(pMidi_container), pMidiCC_info->value)); } } } } void JaySynthAudioProcessor::synthChanged(int type, void *pData) { JaySynth::midiCC_info_t *pMidiCC_info; JaySynth::midi_note_info_t *pMidi_note_info; switch(type) { case JaySynth::SYNTH_CHANGED_PARAM: if (getEditor(this) != NULL) actionListener.callParamChanged(*((int*)pData)); break; case JaySynth::SYNTH_CHANGED_MIDICC: pMidiCC_info = (JaySynth::midiCC_info_t*)pData; applyMidiController(pMidiCC_info, true); if (getEditor(this) != NULL) actionListener.callMidiControllerChanged(pMidiCC_info->channel, pMidiCC_info->ID, (float)pMidiCC_info->value); break; case JaySynth::SYNTH_CHANGED_NOTE_PRESSED: pMidi_note_info = (JaySynth::midi_note_info_t*)pData; if (getEditor(this) != NULL) actionListener.callMidiNotePressed(pMidi_note_info->note, (float)pMidi_note_info->velocity); break; case JaySynth::SYNTH_CHANGED_NOTE_RELEASED: pMidi_note_info = (JaySynth::midi_note_info_t*)pData; if (getEditor(this) != NULL) actionListener.callMidiNoteReleased(pMidi_note_info->note, (float)pMidi_note_info->velocity); break; case JaySynth::SYNTH_CHANGED_NUM_VOICES_PLAYING: if (getEditor(this) != NULL) actionListener.callNumVoicesPlaying(*((int*)pData)); break; case JaySynth::SYNTH_CHANGED_MIDICLOCK: if (getEditor(this) != NULL) actionListener.callMidiClock(*((uint32_t*)pData)); break; default: break; } }; float JaySynthAudioProcessor::getParameter (int index) { param_info_t *pParamInfo = getParamInfo(index); if (!pParamInfo) return 0; return (float)toSlider(pParamInfo, pSynth->getParameter(index)); } void JaySynthAudioProcessor::setParameter (int index, float newValue) { param_info_t *pParamInfo = getParamInfo(index); if (!pParamInfo) return; setParam(index, toParam(pParamInfo, (synth_float_t)newValue), true, false); } synth_float_t JaySynthAudioProcessor::getParam(int index) { return (synth_float_t)pSynth->getParameter(index); } void JaySynthAudioProcessor::setParam(int index, synth_float_t param, bool doModifyPatch, bool doNotifyHost) { param_info_t *pParamInfo = getParamInfo(index); if (!pParamInfo) return; if (doModifyPatch) { setPatchModified(); } if (doNotifyHost) sendParamChangeToHost(index, (float)toSlider(pParamInfo, param)); pSynth->setParameter(index, clampParam(pParamInfo, param)); } void JaySynthAudioProcessor::controlParam(int index, synth_float_t param) { pSynth->setControl(index, param); } const String JaySynthAudioProcessor::getParameterName (int index) { param_info_t *pParamInfo = getParamInfo(index); if (pParamInfo) return pParamInfo->pName; return String::empty; } const String JaySynthAudioProcessor::getParameterText (int index) { param_info_t *pParamInfo = getParamInfo(index); switch (index) { case SYNTH_PARAM_VCF_TYPE: switch ((int)pSynth->getParameter(index)) { case VCF_FILTERTYPE_LOWPASS: return "LPF"; case VCF_FILTERTYPE_HIGHPASS: return "HPF"; case VCF_FILTERTYPE_BANDPASS: return "BPF"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_NOISE_ENABLE: case SYNTH_PARAM_OSC_0_ENABLE: case SYNTH_PARAM_OSC_1_ENABLE: if (!(int)pSynth->getParameter(index)) return "Off"; return "On"; case SYNTH_PARAM_NOISE_SHAPE: switch ((int)pSynth->getParameter(index)) { case VOICE_NOISE_SHAPE_WHITE: return "Uniform"; case VOICE_NOISE_SHAPE_PINK: return "Gauss"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_OSC_0_WAVEFORM: case SYNTH_PARAM_OSC_1_WAVEFORM: switch ((int)pSynth->getParameter(index)) { case OSC_WAVEFORM_SAWTOOTH: return "Saw"; case OSC_WAVEFORM_SQUARE: return "Pulse"; case OSC_WAVEFORM_TRIANGLE: return "Tri"; case OSC_WAVEFORM_SINE: return "Sine"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_LFO_0_SHAPE: case SYNTH_PARAM_LFO_1_SHAPE: case SYNTH_PARAM_LFO_2_SHAPE: case SYNTH_PARAM_LFO_3_SHAPE: switch ((int)pSynth->getParameter(index)) { case LFO_WAVEFORM_SINE: return "Sine"; case LFO_WAVEFORM_COSINE: return "Cosine"; case LFO_WAVEFORM_SAW: return "Saw"; case LFO_WAVEFORM_SQUARE: return "Square"; case LFO_WAVEFORM_TRIANGLE: return "Tri"; case LFO_WAVEFORM_SH_UNI: return "S/H 1"; case LFO_WAVEFORM_SH_GAUSS: return "S/H 2"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_OSC_0_AM_SRC_0: case SYNTH_PARAM_OSC_1_AM_SRC_0: case SYNTH_PARAM_OSC_0_FM_SRC_0: case SYNTH_PARAM_OSC_1_FM_SRC_0: case SYNTH_PARAM_OSC_0_PWM_SRC_0: case SYNTH_PARAM_OSC_1_PWM_SRC_0: case SYNTH_PARAM_VCF_F_SRC_0: case SYNTH_PARAM_VCF_Q_SRC_0: case SYNTH_PARAM_VCA_AM_SRC_0: case SYNTH_PARAM_VCA_PAN_SRC_0: case SYNTH_PARAM_OSC_0_AM_SRC_1: case SYNTH_PARAM_OSC_1_AM_SRC_1: case SYNTH_PARAM_OSC_0_FM_SRC_1: case SYNTH_PARAM_OSC_1_FM_SRC_1: case SYNTH_PARAM_OSC_0_PWM_SRC_1: case SYNTH_PARAM_OSC_1_PWM_SRC_1: case SYNTH_PARAM_VCF_F_SRC_1: case SYNTH_PARAM_VCF_Q_SRC_1: switch ((int)pSynth->getParameter(index)) { case 0: return "Off"; case 1: return "LFO 1"; case 2: return "LFO 2"; case 3: return "LFO 3"; case 4: return "LFO 4"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_LFO_0_MODE: case SYNTH_PARAM_LFO_1_MODE: case SYNTH_PARAM_LFO_2_MODE: case SYNTH_PARAM_LFO_3_MODE: switch ((int)pSynth->getParameter(index)) { case VOICE_LFO_MODE_GLOBAL: return "Global"; case VOICE_LFO_MODE_INDIVIDUAL: return "Individual"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } case SYNTH_PARAM_LFO_0_SYNC: case SYNTH_PARAM_LFO_1_SYNC: case SYNTH_PARAM_LFO_2_SYNC: case SYNTH_PARAM_LFO_3_SYNC: switch ((int)pSynth->getParameter(index)) { case VOICE_LFO_SYNC_FREE: return "Free"; case VOICE_LFO_SYNC_RESTART: return "Restart"; default: return "Unknown (" + String((int)pSynth->getParameter(index)) + ")"; } default: if (pParamInfo->pinterval == 0.0) return String (pSynth->getParameter(index)); else return String (pSynth->getParameter(index), (int)(-log(pParamInfo->pinterval)/log(10.0) + 0.5)); } } const String JaySynthAudioProcessor::getInputChannelName (int channelIndex) const { return String (channelIndex + 1); } const String JaySynthAudioProcessor::getOutputChannelName (int channelIndex) const { return String (channelIndex + 1); } bool JaySynthAudioProcessor::isInputChannelStereoPair (int /*index*/) const { return true; } bool JaySynthAudioProcessor::isOutputChannelStereoPair (int /*index*/) const { return true; } bool JaySynthAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; #else return false; #endif } bool JaySynthAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; #else return false; #endif } int JaySynthAudioProcessor::getNumPrograms() { return NUM_PROGRAMS; } int JaySynthAudioProcessor::getCurrentProgram() { return currProgram; } void JaySynthAudioProcessor::setCurrentProgram (int index) { int i; param_info_t *pParamInfo; if ((unsigned)index >= NUM_PROGRAMS) index = 0; currProgram = index; currpatch = &patches[index]; // Set SYNTH_PARAM_TOTAL_NUM_VOICES first setParam(SYNTH_PARAM_TOTAL_NUM_VOICES, currpatch->getParameter(SYNTH_PARAM_TOTAL_NUM_VOICES), false, false); midCC_editBuffer = *currpatch->getMidiCC(); pSynth->setMidiCC_editBuffer(&midCC_editBuffer); pSynth->ClearControls(); for (i=0; i < SYNTH_NUM_PARAMS; i++) { pParamInfo = getParamInfo(i); setParam(i, currpatch->getParameter(i), false, true); } unisonoModeChanged(); humanizeModeChanged(); humanizeVarianceChanged_VCO(); humanizeVarianceChanged_VCF(); humanizeVarianceChanged_ENV(); perVoiceControlsChanged(); clearPatchModified(); // toDo: do this by synth->setParam via message if (getEditor(this)) { actionListener.callProgramChanged(); for (i=0; i < SYNTH_NUM_PARAMS; i++) getEditor(this)->paramUpdate(i, false); } applyCurrentMidiControllers(); updateHostDisplay(); } const String JaySynthAudioProcessor::getProgramName (int index) { return patches[index].getName(); } void JaySynthAudioProcessor::changeProgramName (int index, const String& newName) { patches[index].setName(newName); if (getEditor(this)) { actionListener.callProgramChanged(); } } //============================================================================== void JaySynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { // Use this method as the place to do any pre-playback // initialisation that you need.. m_fs = sampleRate; pSynth->setSampleRate (sampleRate); pSynth->setBufsize (samplesPerBlock); keyboardState.reset(); } void JaySynthAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. keyboardState.reset(); } void JaySynthAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); int i; float *buf1 = buffer.getWritePointer (0, 0); float *buf2 = buffer.getWritePointer (1, 0); synth_float_t limiter_env[SYNTH_MAX_BUFSIZE]; synth_float_t limiter_gain[SYNTH_MAX_BUFSIZE]; synth_float_t buf[2][SYNTH_MAX_BUFSIZE]; synth_float_t x, limiter_ar, limiter_af, limiter_threshold; float synth_gain; AudioPlayHead::CurrentPositionInfo pos; getPlayHead()->getCurrentPosition(pos); m_JK_MidiClock.generateMidiclock(pos, &midiMessages, numSamples, getSampleRate()); // Now pass any incoming midi messages to our keyboard state object, and let it // add messages to the buffer if the user is clicking on the on-screen keys keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); // First remove garbage from buffers buffer.clear (0, 0, numSamples); buffer.clear (1, 0, numSamples); // and now get the synth to process these midi events and generate its output. // Render individual voices pSynth->renderNextBlock (buffer, midiMessages, 0, numSamples); // apply master volume synth_gain = (float)pSynth->getVolume(); for (i=0; i paramUpdate(i, false); } return (AudioProcessorEditor*)the_editor; } //============================================================================== // magic number to identify memory blocks that we've stored as XML const uint32 magicXmlNumber = 0x21324356; void JaySynthAudioProcessor::copyXmlToBinary (const XmlElement& xml, juce::MemoryBlock& destData) { const String xmlString (xml.createDocument (String::empty, true, false)); const int stringLength = xmlString.getNumBytesAsUTF8(); destData.setSize ((size_t) stringLength + 10); char* const d = static_cast (destData.getData()); *(uint32*) d = ByteOrder::swapIfBigEndian ((const uint32) magicXmlNumber); *(uint32*) (d + 4) = ByteOrder::swapIfBigEndian ((const uint32) stringLength); xmlString.copyToUTF8 (d + 8, stringLength + 1); } XmlElement* JaySynthAudioProcessor::getXmlFromBinary (const void* data, const int sizeInBytes) { XmlElement *pResult = nullptr; if (sizeInBytes > 8 && ByteOrder::littleEndianInt (data) == magicXmlNumber) { const int stringLength = (int) ByteOrder::littleEndianInt (addBytesToPointer (data, 4)); XmlDocument doc(String::fromUTF8 ((static_cast (data)) + 8, jmin ((sizeInBytes - 8), stringLength))); if (stringLength > 0) pResult = doc.getDocumentElement(); if (doc.getLastParseError() != String::empty) { deleteAndZero(pResult); } } return pResult; } const String JaySynthAudioProcessor::getParameterNameXML (int index) { String name = getParameterName(index); name = name.removeCharacters (String(".")); name = name.replaceCharacter (' ','_'); return name; } void JaySynthAudioProcessor::getStateInformation (MemoryBlock& destData) { int i, progID; synth_float_t value; JaySynthSound *pPatch; XmlElement *pXML; // Create an outer XML element.. ScopedPointerxml = new XmlElement("JSYNTH_BANK"); MemoryOutputStream outputStream(destData, false); MemoryBlock tmpBlock(8192*1024, /*initialiseToZero*/true); GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0); if (isPatchModified() || midCC_editBuffer.isMidiCCmodified()) { for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = getParam(i); currpatch->setParameter(i, value); // Copy synth param to current program } currpatch->getMidiCC()->copyMidiCC(&midCC_editBuffer); // Copy MidiCC edit buffer to patch } for (progID=0; progID < NUM_PROGRAMS; progID++) { pXML = xml->createNewChildElement (String("PATCH_") + String(progID)); pPatch = &patches[progID]; pXML->setAttribute ("ID", progID); pXML->setAttribute ("NAME", pPatch->getName()); for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = pPatch->getParameter(i); pXML->setAttribute (getParameterNameXML(i), String(value, 10)); } pPatch->getMidiCC()->exportXML(pXML); } setCurrentProgram(getCurrentProgram()); // then use this helper function to stuff it into the binary blob and return it.. copyXmlToBinary (*xml, tmpBlock); GZIPEnc.write(tmpBlock.getData(), tmpBlock.getSize()); outputStream.flush(); #if 0 //#ifdef SYNTH_DEBUG { File file(String("C:\\Users\\jens\\JaySynth\\bank.xml")); file.create(); xml->writeToFile (/*const File &destinationFile*/file, /*const String &dtdToUse*/"JSYNTH_BANK_FILE" /*, const String &encodingType="UTF-8", int lineWrapLength=60*/); } #endif } void JaySynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { int i, progID, uncompressedSize; synth_float_t value; JaySynthSound *pPatch; XmlElement *pXML; String lastError; ScopedPointer xmlState = NULL; void* pData; int size; ScopedPointer pUncompressedData; pUncompressedData = new char[8192*1024]; MemoryInputStream inputStream(data, sizeInBytes, false); GZIPDecompressorInputStream GZIPDec(inputStream); uncompressedSize = GZIPDec.read(pUncompressedData, 8192*1024); pData = (void *)data; size = sizeInBytes; if (uncompressedSize) { pData = (void *)pUncompressedData; size = uncompressedSize; } xmlState = getXmlFromBinary(pData, size); if (xmlState != 0) { // make sure that it's actually our type of XML object.. if (xmlState->hasTagName ("JSYNTH_BANK")) { pXML = xmlState->getFirstChildElement(); while(pXML) { progID = pXML->getIntAttribute("ID"); if (progID >= NUM_PROGRAMS) break; pPatch = &patches[progID]; // ok, now pull out our parameters.. pPatch->setName(pXML->getStringAttribute("NAME")); for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = (synth_float_t) pXML->getDoubleAttribute (getParameterNameXML(i), pPatch->getParameter(i)); if (i==SYNTH_PARAM_TUNE) { if (value > 400.0) { value = 1200*log(value/440)/log(2.0); } } pPatch->setParameter(i, value); } pPatch->getMidiCC()->importXML(pXML); pXML = pXML->getNextElement(); } } setCurrentProgram(getCurrentProgram()); } } void JaySynthAudioProcessor::getCurrentProgramStateInformation (MemoryBlock& destData) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. // You should use this method to store your parameters in the memory block. // Here's an example of how you can use XML to make it easy and more robust: int i; synth_float_t value; MemoryOutputStream outputStream (destData, false); MemoryBlock tmpBlock(2*65536, /*initialiseToZero*/true); GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0); // Create an outer XML element.. ScopedPointerxml = new XmlElement("JSYNTH_PATCH"); if (isPatchModified() || midCC_editBuffer.isMidiCCmodified()) { for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = getParam(i); currpatch->setParameter(i, value); // Copy synth param to current program } currpatch->getMidiCC()->copyMidiCC(&midCC_editBuffer); // Copy MidiCC edit buffer to patch } xml->setAttribute ("NAME", currpatch->getName()); for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = currpatch->getParameter(i); xml->setAttribute (getParameterNameXML(i), String(value, 10)); } currpatch->getMidiCC()->exportXML(xml); setCurrentProgram(getCurrentProgram()); // then use this helper function to stuff it into the binary blob and return it.. copyXmlToBinary (*xml, tmpBlock); GZIPEnc.write(tmpBlock.getData(), tmpBlock.getSize()); #if 0 //#ifdef SYNTH_DEBUG { File file(String("C:\\Users\\jens\\JaySynth\\") + String(currpatch->getName()) + String(".xml")); file.create(); xml->writeToFile (/*const File &destinationFile*/file, /*const String &dtdToUse*/"JSYNTH_PATCH_FILE" /*, const String &encodingType="UTF-8", int lineWrapLength=60*/); } #endif } void JaySynthAudioProcessor::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. int i, uncompressedSize; synth_float_t value; ScopedPointer xmlState = NULL; void* pData; int size; ScopedPointer pUncompressedData; pUncompressedData = new char[2*65536]; MemoryInputStream inputStream(data, sizeInBytes, false); GZIPDecompressorInputStream GZIPDec(inputStream); uncompressedSize = GZIPDec.read(pUncompressedData, 2*65536); pData = (void *)data; size = sizeInBytes; if (uncompressedSize) { pData = (void *)pUncompressedData; size = uncompressedSize; } xmlState = getXmlFromBinary(pData, size); if (xmlState != 0) { // make sure that it's actually our type of XML object.. if (xmlState->hasTagName ("JSYNTH_PATCH")) { // ok, now pull out our parameters.. currpatch->setName(xmlState->getStringAttribute("NAME")); for (i=0; i < SYNTH_NUM_PARAMS; i++) { value = (synth_float_t) xmlState->getDoubleAttribute (getParameterNameXML(i), currpatch->getParameter(i)); if (i==SYNTH_PARAM_TUNE) { if (value > 400.0) { value = 1200*log(value/440)/log(2.0); } } currpatch->setParameter(i, value); } } currpatch->getMidiCC()->importXML(xmlState); setCurrentProgram(getCurrentProgram()); } } //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new JaySynthAudioProcessor(); }