1049 lines
29 KiB
C++
1049 lines
29 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file was auto-generated!
|
|
|
|
It contains the basic startup code for a Juce application.
|
|
|
|
==============================================================================
|
|
*/
|
|
#include <math.h>
|
|
|
|
#include "PluginProcessor.h"
|
|
#include "JaySynthAudioProcessorEditor.h"
|
|
#include "synth/vector_utils.h"
|
|
|
|
using namespace juce;
|
|
|
|
JaySynthAudioProcessorEditor* getEditor(JaySynthAudioProcessor *pThis)
|
|
{
|
|
return dynamic_cast<JaySynthAudioProcessorEditor*>(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();
|
|
#if LINUX
|
|
String wavespath = pluginfile.getFullPathName() + String("/waves.bin");
|
|
#else
|
|
// that'S probably windows
|
|
String wavespath = pluginfile.getFullPathName() + String("\\waves.bin");
|
|
#endif
|
|
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;
|
|
JaySynth::midi_quarter_clock_info_t *pMidi_quarter_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:
|
|
pMidi_quarter_info = (JaySynth::midi_quarter_clock_info_t*)pData;
|
|
if (getEditor(this) != NULL)
|
|
actionListener.callMidiClock(pMidi_quarter_info->bpm, pMidi_quarter_info->type);
|
|
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)) + ")";
|
|
}
|
|
|
|
case SYNTH_PARAM_LFO_0_MIDISYNC_MODE:
|
|
case SYNTH_PARAM_LFO_1_MIDISYNC_MODE:
|
|
case SYNTH_PARAM_LFO_2_MIDISYNC_MODE:
|
|
case SYNTH_PARAM_LFO_3_MIDISYNC_MODE:
|
|
switch ((int)pSynth->getParameter(index))
|
|
{
|
|
case VOICE_LFO_MIDISYNC_OFF:
|
|
return "Off";
|
|
case VOICE_LFO_MIDISYNC_F:
|
|
return "Freq";
|
|
case VOICE_LFO_MIDISYNC_P:
|
|
return "Phase";
|
|
case VOICE_LFO_MIDISYNC_F_P:
|
|
return "Freq+Phase";
|
|
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 = *((JaySynthMidiCC*)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 <numSamples; i++)
|
|
buf[0][i] = (synth_float_t)buf1[i] * synth_gain;
|
|
|
|
for (i=0; i <numSamples; i++)
|
|
buf[1][i] = (synth_float_t)buf2[i] * synth_gain;
|
|
|
|
// Limiter
|
|
limiter_ar = 5.0/(SYNTH_LIMITER_RISE_TIME*m_fs);
|
|
limiter_af = 5.0/(SYNTH_LIMITER_FALL_TIME*m_fs);
|
|
limiter_threshold = pow((synth_float_t)10.0, (synth_float_t)SYNTH_LIMITER_THRESHOLD/20);
|
|
|
|
for (i=0; i <numSamples; i++)
|
|
{
|
|
x = jsy_max(fabs(buf[0][i]), fabs(buf[1][i]));
|
|
if (x > m_limiter_env)
|
|
{
|
|
m_limiter_env = (1.0-limiter_ar)*m_limiter_env + limiter_ar*x;
|
|
}
|
|
else
|
|
{
|
|
m_limiter_env = (1.0-limiter_af)*m_limiter_env;
|
|
}
|
|
|
|
limiter_env[i] = m_limiter_env;
|
|
}
|
|
|
|
m_limiter_active = 0;
|
|
for (i=0; i <numSamples; i++)
|
|
{
|
|
limiter_gain[i] = jsy_min(1.0, 1.0/(limiter_env[i] + 1.0e-6));
|
|
if (limiter_gain[i] < 1.0)
|
|
{
|
|
m_limiter_active = 1;
|
|
}
|
|
}
|
|
|
|
for (i=0; i <numSamples; i++)
|
|
buf1[i] = (float)(buf[0][i]*limiter_gain[i]);
|
|
|
|
for (i=0; i <numSamples; i++)
|
|
buf2[i] = (float)(buf[1][i]*limiter_gain[i]);
|
|
|
|
// In case we have more outputs than inputs, we'll clear any output
|
|
// channels that didn't contain input data, (because these aren't
|
|
// guaranteed to be empty - they may contain garbage).
|
|
// for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
|
|
// {
|
|
// buffer.clear (i, 0, buffer.getNumSamples());
|
|
// }
|
|
}
|
|
|
|
//==============================================================================
|
|
bool JaySynthAudioProcessor::hasEditor() const
|
|
{
|
|
return true; // (change this to false if you choose to not supply an editor)
|
|
}
|
|
|
|
AudioProcessorEditor* JaySynthAudioProcessor::createEditor()
|
|
{
|
|
int i;
|
|
|
|
the_editor = (void*) new JaySynthAudioProcessorEditor (this, m_isWavesAvailable);
|
|
|
|
if (the_editor)
|
|
{
|
|
actionListener.callProgramChanged();
|
|
for (i=0; i < SYNTH_NUM_PARAMS; i++)
|
|
((JaySynthAudioProcessorEditor*)the_editor)->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<char*> (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<const char*> (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::commitEditBuffer()
|
|
{
|
|
if (isPatchModified() || midCC_editBuffer.isMidiCCmodified())
|
|
{
|
|
for (int i=0; i < SYNTH_NUM_PARAMS; i++)
|
|
{
|
|
synth_float_t value = getParam(i);
|
|
currpatch->setParameter(i, value); // Copy synth param to current program
|
|
}
|
|
JaySynthMidiCC *midiCC = (JaySynthMidiCC*)currpatch->getMidiCC();
|
|
midiCC->copyMidiCC(&midCC_editBuffer); // Copy MidiCC edit buffer to patch
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::patchEncodeXml(XmlElement *xml, JaySynthSound const *patch)
|
|
{
|
|
XmlElement *xml_patch = xml->createNewChildElement("Patch");
|
|
xml_patch->setAttribute ("NAME", patch->getName());
|
|
for (int i=0; i < SYNTH_NUM_PARAMS; i++)
|
|
{
|
|
synth_float_t value = patch->getParameter(i);
|
|
xml_patch->setAttribute (getParameterNameXML(i), String(value, 10));
|
|
}
|
|
patch->getMidiCC()->exportXML(xml_patch);
|
|
}
|
|
|
|
void JaySynthAudioProcessor::patchDecodeXml(const XmlElement *xml, JaySynthSound *patch)
|
|
{
|
|
// ok, now pull out our parameters..
|
|
patch->setName(xml->getStringAttribute("NAME"));
|
|
for (int i=0; i < SYNTH_NUM_PARAMS; i++)
|
|
{
|
|
synth_float_t value = (synth_float_t) xml->getDoubleAttribute (getParameterNameXML(i), patch->getParameter(i));
|
|
if (i==SYNTH_PARAM_TUNE)
|
|
{
|
|
if (value > 400.0)
|
|
{
|
|
value = 1200*log(value/440)/log(2.0);
|
|
}
|
|
}
|
|
patch->setParameter(i, value);
|
|
}
|
|
((JaySynthMidiCC*)patch->getMidiCC())->importXML(xml);
|
|
|
|
}
|
|
|
|
void JaySynthAudioProcessor::bankEncodeXml(XmlElement *xml)
|
|
{
|
|
XmlElement *xml_bank = xml->createNewChildElement("Bank");
|
|
for (int i=0; i < NUM_PROGRAMS; i++)
|
|
{
|
|
XmlElement *xml_program = xml_bank->createNewChildElement (String("Program_") + String(i));
|
|
JaySynthSound *pPatch = &patches[i];
|
|
xml_program->setAttribute ("ID", i);
|
|
patchEncodeXml(xml_program, pPatch);
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::bankDecodeXml(const XmlElement *_xml)
|
|
{
|
|
XmlElement *xml = (XmlElement*)_xml;
|
|
while(xml)
|
|
{
|
|
// Read ID
|
|
int progID = xml->getIntAttribute("ID");
|
|
if (progID >= NUM_PROGRAMS)
|
|
{
|
|
break;
|
|
}
|
|
JaySynthSound *pPatch = &patches[progID];
|
|
{
|
|
XmlElement *xml2 = xml->getFirstChildElement();
|
|
if (xml2->hasTagName ("Patch"))
|
|
{
|
|
// Read patch
|
|
patchDecodeXml(xml2, pPatch);
|
|
}
|
|
}
|
|
xml = xml->getNextElement();
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::getStateInformation (MemoryBlock& destData)
|
|
{
|
|
commitEditBuffer();
|
|
|
|
// Create an outer XML element..
|
|
ScopedPointer <XmlElement> xml = new XmlElement("JSynth");
|
|
bankEncodeXml(xml);
|
|
|
|
setCurrentProgram(getCurrentProgram());
|
|
|
|
MemoryOutputStream outputStream(destData, false);
|
|
MemoryBlock tmpBlock(destData.getSize(), /*initialiseToZero*/true);
|
|
GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0);
|
|
|
|
// 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();
|
|
}
|
|
|
|
void JaySynthAudioProcessor::loadBankFromFile (const File &file)
|
|
{
|
|
String ext = file.getFileExtension();
|
|
if (String(".fxb") == ext)
|
|
{
|
|
MemoryBlock mem;
|
|
file.loadFileAsData(mem);
|
|
fxBank *bank = (fxBank*)mem.getData();
|
|
if (String((const char*)&bank->chunkMagic) == String(cMagic))
|
|
{
|
|
bool is_regular = String((const char*)&bank->fxMagic) == String(bankMagic);
|
|
bool is_opaque = String((const char*)&bank->fxMagic) == String(chunkBankMagic);
|
|
if (is_opaque)
|
|
{
|
|
uint32_t size = ByteOrder::swap((uint32_t)bank->content.data.size);
|
|
setStateInformation(bank->content.data.chunk, (int)size);
|
|
}
|
|
}
|
|
}
|
|
else if (String(".xmb") == ext)
|
|
{
|
|
ScopedPointer <XmlElement> xml = XmlDocument::parse(file);
|
|
bankImportXml(xml);
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::bankImportXml (const XmlElement *xml)
|
|
{
|
|
if (xml != 0)
|
|
{
|
|
if (xml->hasTagName ("JSynth"))
|
|
{
|
|
XmlElement *xml2 = xml->getFirstChildElement();
|
|
if (xml2->hasTagName ("Bank"))
|
|
{
|
|
XmlElement *xml3 = xml2->getFirstChildElement();
|
|
bankDecodeXml(xml3);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Legacy
|
|
if (xml->hasTagName ("JSYNTH_BANK"))
|
|
{
|
|
XmlElement *xml2 = xml->getFirstChildElement();
|
|
bankDecodeXml_legacy(*xml2);
|
|
|
|
}
|
|
}
|
|
setCurrentProgram(getCurrentProgram());
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
|
{
|
|
ScopedPointer<char> pUncompressedData = new char[8192*1024];
|
|
|
|
MemoryInputStream inputStream(data, sizeInBytes, false);
|
|
GZIPDecompressorInputStream GZIPDec(inputStream);
|
|
int uncompressedSize = GZIPDec.read(pUncompressedData, 8192*1024);
|
|
|
|
void* pData = (void *)data;
|
|
int size = sizeInBytes;
|
|
if (uncompressedSize)
|
|
{
|
|
pData = (void *)pUncompressedData;
|
|
size = uncompressedSize;
|
|
}
|
|
|
|
ScopedPointer<XmlElement> xml = getXmlFromBinary(pData, size);
|
|
bankImportXml(xml);
|
|
}
|
|
|
|
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:
|
|
|
|
commitEditBuffer();
|
|
|
|
MemoryOutputStream outputStream (destData, false);
|
|
MemoryBlock tmpBlock(2*65536, /*initialiseToZero*/true);
|
|
GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0);
|
|
|
|
// Create an outer XML element..
|
|
ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
|
|
patchEncodeXml(xml, currpatch);
|
|
|
|
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());
|
|
}
|
|
|
|
void JaySynthAudioProcessor::loadPatchFromFile (const File &file, int index)
|
|
{
|
|
String ext = file.getFileExtension();
|
|
if (String(".fxp") == ext)
|
|
{
|
|
MemoryBlock mem;
|
|
file.loadFileAsData(mem);
|
|
fxProgram *program = (fxProgram*)mem.getData();
|
|
if (String((const char*)&program->chunkMagic) == String(cMagic))
|
|
{
|
|
bool is_regular = String((const char*)&program->fxMagic) == String(fMagic);
|
|
bool is_opaque = String((const char*)&program->fxMagic) == String(chunkPresetMagic);
|
|
if (is_opaque)
|
|
{
|
|
uint32_t size = ByteOrder::swap((uint32_t)program->content.data.size);
|
|
setCurrentProgramStateInformation(program->content.data.chunk, (int)size);
|
|
}
|
|
}
|
|
}
|
|
else if (String(".xmp") == ext)
|
|
{
|
|
ScopedPointer<XmlElement> xml = XmlDocument::parse(file);
|
|
patchImportXml(xml, currpatch);
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::patchImportXml (const XmlElement *xml, JaySynthSound *patch)
|
|
{
|
|
if (xml->hasTagName ("JSynth"))
|
|
{
|
|
XmlElement *xml2 = xml->getFirstChildElement();
|
|
if (xml2->hasTagName ("Patch"))
|
|
{
|
|
patchDecodeXml(xml2, patch);
|
|
}
|
|
}
|
|
else if (xml->hasTagName ("JSYNTH_PATCH"))
|
|
{
|
|
patchDecodeXml_legacy(*xml, patch);
|
|
}
|
|
setCurrentProgram(getCurrentProgram());
|
|
}
|
|
|
|
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;
|
|
void* pData;
|
|
int size;
|
|
|
|
ScopedPointer<char> 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;
|
|
}
|
|
ScopedPointer<XmlElement> xml = getXmlFromBinary(pData, size);
|
|
if (xml)
|
|
{
|
|
patchImportXml(xml, currpatch);
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::savePatchToFile (const File &file, int index)
|
|
{
|
|
String ext = file.getFileExtension();
|
|
if (String(".fxp") == ext)
|
|
{
|
|
// ToDo: implement fxp save
|
|
}
|
|
else if (String(".xmp") == ext)
|
|
{
|
|
commitEditBuffer();
|
|
|
|
// Create an outer XML element..
|
|
ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
|
|
patchEncodeXml(xml, currpatch);
|
|
|
|
// Write XML to file
|
|
xml->writeToFile (/*const File &destinationFile*/file, /*dtdToUse*/"");
|
|
}
|
|
}
|
|
|
|
void JaySynthAudioProcessor::saveBankToFile (const File &file)
|
|
{
|
|
String ext = file.getFileExtension();
|
|
if (String(".fxb") == ext)
|
|
{
|
|
// ToDo: implement fxp save
|
|
}
|
|
else if (String(".xmb") == ext)
|
|
{
|
|
commitEditBuffer();
|
|
|
|
// Create an outer XML element..
|
|
ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
|
|
bankEncodeXml(xml);
|
|
|
|
// Write XML to file
|
|
xml->writeToFile (/*const File &destinationFile*/file, /*dtdToUse*/"");
|
|
}
|
|
}
|
|
void JaySynthAudioProcessor::patchDecodeXml_legacy(const XmlElement& xml, JaySynthSound *patch)
|
|
{
|
|
// ok, now pull out our parameters..
|
|
patch->setName(xml.getStringAttribute("NAME"));
|
|
for (int i=0; i < SYNTH_NUM_PARAMS; i++)
|
|
{
|
|
synth_float_t value = (synth_float_t) xml.getDoubleAttribute (getParameterNameXML(i), patch->getParameter(i));
|
|
if (i==SYNTH_PARAM_TUNE)
|
|
{
|
|
if (value > 400.0)
|
|
{
|
|
value = 1200*log(value/440)/log(2.0);
|
|
}
|
|
}
|
|
patch->setParameter(i, value);
|
|
}
|
|
((JaySynthMidiCC*)patch->getMidiCC())->importXML(&xml);
|
|
}
|
|
|
|
void JaySynthAudioProcessor::bankDecodeXml_legacy(const XmlElement& xml)
|
|
{
|
|
// make sure that it's actually our type of XML object..
|
|
XmlElement *pXML = (XmlElement*)&xml;
|
|
|
|
while(pXML)
|
|
{
|
|
int progID = pXML->getIntAttribute("ID");
|
|
if (progID >= NUM_PROGRAMS)
|
|
break;
|
|
|
|
JaySynthSound *pPatch = &patches[progID];
|
|
patchDecodeXml_legacy(*pXML, pPatch);
|
|
((JaySynthMidiCC*)pPatch->getMidiCC())->importXML(pXML);
|
|
pXML = pXML->getNextElement();
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// This creates new instances of the plugin..
|
|
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
|
{
|
|
return new JaySynthAudioProcessor();
|
|
}
|