commit a5be363b8dd886bc9dd437ddf7d03fa4af1ec73c parent 0a305af6395bb17a44908b243d7cfc1bbcd98fc8 Author: dsp56300 <87139854+dsp56300@users.noreply.github.com> Date: Fri, 14 Jan 2022 09:57:14 +0100 Merge pull request #32 from 790/ui UI fixes and improvements. Diffstat:
33 files changed, 908 insertions(+), 316 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") -project(gearmulator VERSION 1.2.2) +project(gearmulator VERSION 1.2.3) include(base.cmake) diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -41,6 +41,7 @@ PRIVATE ui/Virus_LfoEditor.cpp ui/Virus_OscEditor.cpp ui/Virus_PatchBrowser.cpp + ui/Virus_Parts.cpp ui/Virus_Buttons.h ui/Virus_LookAndFeel.h ui/VirusEditor.h @@ -49,6 +50,7 @@ PRIVATE ui/Virus_LfoEditor.h ui/Virus_OscEditor.h ui/Virus_PatchBrowser.h + ui/Virus_Parts.h ui/Ui_Utils.h VirusController.cpp VirusController.h @@ -80,8 +82,8 @@ juce_add_binary_data(jucePlugin_BinaryData "assets/buttons/presets_btn_43_15.png" "assets/buttons/Handle_18x47.png" "assets/buttons/sync2_54x25.png" - "assets/buttons/part_select_btn_39x72.png" - "assets/buttons/arphold_btn_28_22.png" + "assets/buttons/part_select_btn_36x36.png" + "assets/buttons/arphold_btn_36x36.png" "assets/knobs/Gen_70x70_100.png" "assets/knobs/Gen_pol_70x70_100.png" "assets/knobs/GenBlue_70x70_100.png" diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -9,7 +9,7 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() : AudioProcessor(BusesProperties() .withInput("Input", juce::AudioChannelSet::stereo(), true) .withOutput("Output", juce::AudioChannelSet::stereo(), true)), - MidiInputCallback(), m_device(synthLib::findROM()), m_plugin(&m_device) + MidiInputCallback(), m_romName(synthLib::findROM()), m_device(synthLib::findROM()), m_plugin(&m_device) { getController(); // init controller } @@ -191,9 +191,18 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::AudioPlayHead::CurrentPositionInfo pos{}; auto* playHead = getPlayHead(); - if(playHead) + if(playHead) { playHead->getCurrentPosition(pos); + if(pos.bpm > 0) { // sync virus interal clock to host + const uint8_t bpmValue = juce::jmin(127, juce::jmax(0, (int)pos.bpm-63)); // clamp to virus range, 63-190 + auto clockParam = getController().getParameter(Virus::Param_ClockTempo, 0); + if (clockParam != nullptr && (int)clockParam->getValueObject().getValue() != bpmValue) { + clockParam->getValueObject().setValue(bpmValue); + } + } + } + m_plugin.process(inputs, outputs, buffer.getNumSamples(), static_cast<float>(pos.bpm), static_cast<float>(pos.ppqPosition), pos.isPlaying); diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h @@ -60,6 +60,9 @@ public: juce::MidiInput* getMidiInput() const; void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; + std::string getRomName() { + return juce::File(juce::String(m_romName)).getFileNameWithoutExtension().toStdString(); + } // _____________ // private: @@ -74,4 +77,5 @@ private: virusLib::Device m_device; synthLib::Plugin m_plugin; std::vector<synthLib::SMidiEvent> m_midiOut; + std::string m_romName; }; diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -20,13 +20,13 @@ namespace Virus (findSynthParam(0, 0x72, 0x7a))->onValueChanged = [this] { const uint8_t prg = isMultiMode() ? 0x0 : 0x40; sendSysEx(constructMessage({MessageType::REQUEST_SINGLE, 0x0, prg})); + sendSysEx(constructMessage({MessageType::REQUEST_MULTI, 0x0, prg})); }; sendSysEx(constructMessage({MessageType::REQUEST_TOTAL})); sendSysEx(constructMessage({MessageType::REQUEST_ARRANGEMENT})); for(uint8_t i=3; i<=8; ++i) sendSysEx(constructMessage({MessageType::REQUEST_BANK_SINGLE, i})); - startTimer(5); } @@ -227,7 +227,10 @@ namespace Virus ignored */ } - + uint8_t Controller::getVirusModel() + { + return parseAsciiText(m_singles[2][0].data, 128 + 112) == "Taurus JS" ? virusLib::VirusModel::B : virusLib::VirusModel::C; + } juce::StringArray Controller::getSinglePresetNames(virusLib::BankNumber _bank) const { if (_bank == virusLib::BankNumber::EditBuffer) @@ -256,7 +259,13 @@ namespace Virus bankNames.add(parseAsciiText(m_multis[i].data, 0)); return bankNames; } - + void Controller::setSinglePresetName(uint8_t part, juce::String _name) { + constexpr uint8_t asciiStart = 112; + _name = _name.substring(0,kNameLength).paddedRight(' ', kNameLength); + for (int i = 0; i < kNameLength; i++) { + getParamValue(part, 1, asciiStart+i)->setValue((uint8_t)_name[i]); + } + } juce::String Controller::getCurrentPartPresetName(uint8_t part) { // expensive but fresh! @@ -290,7 +299,8 @@ namespace Virus for (auto i = 0; i < kDataSizeInBytes; ++i) patch.push_back(preset.data[i]); sendSysEx(constructMessage(patch)); - sendSysEx(constructMessage({MessageType::REQUEST_ARRANGEMENT})); + //sendSysEx(constructMessage({MessageType::REQUEST_ARRANGEMENT})); + sendSysEx(constructMessage({MessageType::REQUEST_SINGLE, 0x0, pt})); m_currentBank[part] = _bank; m_currentProgram[part] = prg; } @@ -360,6 +370,23 @@ namespace Virus patch.progNumber = msg[startPos + 1]; auto progName = parseAsciiText(msg, startPos + 2 + 3); [[maybe_unused]] auto dataSum = copyData(msg, startPos + 2, patch.data); + + /* If it's a multi edit buffer, set the part page C single parameters to their multi equivalents */ + if (patch.bankNumber == 0) { + for (auto pt = 0; pt < 16; pt++) { + for(int i = 0; i < 8; i++) { + if (auto* p = findSynthParam(pt, virusLib::PAGE_C, virusLib::PART_MIDI_CHANNEL+i)) { + p->setValueFromSynth(patch.data[virusLib::MD_PART_MIDI_CHANNEL + (i*16) + pt], true); + } + } + if (auto* p = findSynthParam(pt, virusLib::PAGE_B, virusLib::CLOCK_TEMPO)) { + p->setValueFromSynth(patch.data[virusLib::MD_CLOCK_TEMPO], true); + } + if (auto* p = findSynthParam(pt, virusLib::PAGE_A, virusLib::EFFECT_SEND)) { + p->setValueFromSynth(patch.data[virusLib::MD_PART_EFFECT_SEND], true); + } + } + } if (hasChecksum) { const int expectedChecksum = msg[msg.size() - 2]; @@ -367,7 +394,11 @@ namespace Virus const int checksum = (msgDeviceId + 0x11 + patch.bankNumber + patch.progNumber + dataSum) & 0x7f; assert(checksum == expectedChecksum); } - m_multis[patch.progNumber] = patch; + if (patch.bankNumber == 0) { + m_currentMulti = patch; + } else { + m_multis[patch.progNumber] = patch; + } } void Controller::parseControllerDump(synthLib::SMidiEvent &m) @@ -637,7 +668,20 @@ namespace Virus default: return juce::String(idx); } } - + juce::String numToDelayLfoShape(float idx, Parameter::Description) + { + const auto ridx = juce::roundToInt(idx); + switch (ridx) + { + case 0: return "Sine"; + case 1: return "Triangle"; + case 2: return "Saw"; + case 3: return "Square"; + case 4: return "S&H"; + case 5: return "S&G"; + default: return juce::String(""); + } + } juce::String numToOsc3Mode(float idx, Parameter::Description) { const auto ridx = juce::roundToInt(idx); @@ -763,7 +807,7 @@ namespace Virus } } - juce::String numToOutputDelaySelect(float idx, Parameter::Description) + juce::String numToOutputSelect(float idx, Parameter::Description) { const auto ridx = juce::roundToInt(idx); switch (ridx) @@ -771,19 +815,19 @@ namespace Virus case 0: return "OUT 1 L"; case 1: return "OUT 1 L+R"; case 2: return "OUT 1 R"; - case 3: return "OUT 2 L"; +/* case 3: return "OUT 2 L"; case 4: return "OUT 2 L+R"; case 5: return "OUT 2 R"; case 6: return "OUT 3 L"; case 7: return "OUT 3 L+R"; - case 8: return "OUT 3 R"; + case 8: return "OUT 3 R";*/ case 9: return "AUX 1 L"; case 10: return "AUX 1 L+R"; case 11: return "AUX 1 R"; case 12: return "AUX 2 L"; case 13: return "AUX 2 L+R"; case 14: return "AUX 2 R"; - default: return juce::String(idx); + default: return juce::String(""); } } @@ -797,8 +841,28 @@ namespace Virus case 2: return "Reverb"; case 3: return "Reverb + Feedback 1"; case 4: return "Reverb + Feedback 2"; - case 5: return "Delay X:Y"; - case 6: return "Pattern X+Y"; + case 5: return "Delay2:1"; + case 6: return "Delay4:3"; + case 7: return "Delay4:1"; + case 8: return "Delay8:7"; + case 9: return "Pattern1+1"; + case 10: return "Pattern2+1"; + case 11: return "Pattern3+1"; + case 12: return "Pattern4+1"; + case 13: return "Pattern5+1"; + case 14: return "Pattern2+3"; + case 15: return "Pattern2+5"; + case 16: return "Pattern3+2"; + case 17: return "Pattern3+3"; + case 18: return "Pattern3+4"; + case 19: return "Pattern3+5"; + case 20: return "Pattern4+3"; + case 21: return "Pattern4+5"; + case 22: return "Pattern5+2"; + case 23: return "Pattern5+3"; + case 24: return "Pattern5+4"; + case 25: return "Pattern5+5"; + case 26: return "Pattern X+Y"; default: return juce::String(idx); } } @@ -1168,6 +1232,25 @@ namespace Virus } } + juce::String numToSoftKnobName(float idx, Parameter::Description) { + const char *softKnobNames[] = {"~Para","+3rds","+4ths","+5ths", + "+7ths","+Octave","Access","ArpMode","ArpOct","Attack", + "Balance","Chorus","Cutoff","Decay","Delay","Depth", + "Destroy","Detune","Disolve","Distort","Dive","Effects", + "Elevate","Energy","EqHigh","EqLow","EqMid","Fast","Fear", + "Filter","FM","Glide","Hold","Hype","Infect","Length","Mix", + "Morph","Mutate","Noise","Open","Orbit","Pan","Phaser","Phatter", + "Pitch","Pulsate","Push","PWM","Rate","Release","Reso","Reverb", + "Scream","Shape","Sharpen","Slow","Soften","Speed","SubOsc", + "Sustain","Sweep","Swing","Tempo","Thinner","Tone","Tremolo", + "Vibrato","WahWah","Warmth","Warp","Width"}; + const auto ridx = juce::roundToInt(idx); + if (ridx < sizeof(softKnobNames)-1) { + return softKnobNames[ridx]; + } + return juce::String(idx); + } + juce::String numToUnisonMode(float v, Parameter::Description) { const auto ridx = juce::roundToInt(v); @@ -1292,7 +1375,7 @@ namespace Virus const auto ridx = juce::roundToInt(v); switch (ridx) { - case 0: return "Off"; +// case 0: return "Off"; case 1: return "1 Stage"; case 2: return "2 Stages"; case 3: return "3 Stages"; @@ -1315,6 +1398,56 @@ namespace Virus } } + juce::String numToCategory(float idx, Parameter::Description) + { + const juce::Array<juce::String> categories = {"--", "Lead", "Bass", "Pad", "Decay", "Pluck", + "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", + "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3"}; + + const auto ridx = juce::roundToInt(idx); + if(ridx < categories.size()) + return categories[ridx]; + return "Undefined"; + } + + juce::String numToBendRange(float idx, Parameter::Description) { + const auto ridx = juce::roundToInt(idx); + switch (ridx) + { + case 40: return "-2oct"; + case 52: return "-1oct"; + case 57: return "-7st"; + case 58: return "-6st"; + case 59: return "-5st"; + case 60: return "-4st"; + case 61: return "-3st"; + case 62: return "-2st"; + case 63: return "-1st"; + case 64: return "None"; + case 65: return "+1st"; + case 66: return "+2st"; + case 67: return "+3st"; + case 68: return "+4st"; + case 69: return "+5st"; + case 70: return "+6st"; + case 71: return "+7st"; + case 76: return "+1oct"; + case 88: return "+2oct"; + default: return juce::String(""); + } + } + juce::String numToReverbRoomSize(float idx, Parameter::Description) { + const auto ridx = juce::roundToInt(idx); + + switch (ridx) { + case 0: return "Ambience"; + case 1: return "Small Room"; + case 2: return "Large Room"; + case 3: return "Hall"; + default: return juce::String(""); + } + } + const std::initializer_list<Parameter::Description> Controller::m_paramsDescription = { {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 0, "Bank Select", {0, 3 + 26}, numToBank, {}, false, true, false}, // The Virus TI contains 4 banks of RAM, followed by 26 banks of ROM @@ -1327,45 +1460,45 @@ namespace Virus {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 7, "Channel Volume", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 8, "Balance", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 9, "Contr 9", {0,127}, {},{}, false, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 10, "Panorama", {0,127}, numToPan,{}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 10, "Panorama", {0,127}, numToPan,{}, true, false, false, true}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 11, "Expression", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 12, "Contr 12", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 13, "Contr 13", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 14, "Contr 14", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 15, "Contr 15", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 16, "Contr 16", {0,127}, {},{}, false, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 17, "Osc1 Shape", {0,127}, numToOscShape,{}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 17, "Osc1 Shape", {0,127}, numToOscShape,{}, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 18, "Osc1 Pulsewidth", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 19, "Osc1 Wave Select", {0,64}, numToOscWaveSelect, {}, true, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 20, "Osc1 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 21, "Osc1 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 22, "Osc2 Shape", {0,127}, numToOscShape, {}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 20, "Osc1 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 21, "Osc1 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 22, "Osc2 Shape", {0,127}, numToOscShape, {}, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 23, "Osc2 Pulsewidth", {0,127}, {}, {}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 24, "Osc2 Wave Select", {0,64}, numToOscWaveSelect,{}, true, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 25, "Osc2 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 25, "Osc2 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 26, "Osc2 Detune", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 27, "Osc2 FM Amount", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 28, "Osc2 Sync", {0,1}, {},{}, true, false, true}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 29, "Osc2 Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 30, "FM Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 31, "Osc2 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 29, "Osc2 Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 30, "FM Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 31, "Osc2 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 32, "Bank Select", {0, 3 + 26}, numToBank,{}, false, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 33, "Osc Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 33, "Osc Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 34, "Suboscillator Volume", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 35, "Suboscillator Shape", {0,1}, [](float v, Parameter::Description){ return juce::roundToInt(v) == 0 ? "Square" : "Triangle"; },{}, true, true, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 36, "Osc Mainvolume", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 37, "Noise Volume", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 38, "Ringmodulator Volume", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::VIRUS_C, 39, "Noise Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::VIRUS_C, 39, "Noise Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 40, "Cutoff", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 41, "Cutoff2", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 42, "Filter1 Resonance", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 43, "Filter2 Resonance", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 44, "Filter1 Env Amt", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 45, "Filter2 Env Amt", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 46, "Filter1 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 47, "Filter2 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 48, "Filter Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 46, "Filter1 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 47, "Filter2 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 48, "Filter Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 49, "Saturation Curve", {0,14}, numToSatCurv, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 51, "Filter1 Mode", {0,7}, numToFilterMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 52, "Filter2 Mode", {0,3}, numToFilter2Mode, {}, true, true, false}, @@ -1373,12 +1506,12 @@ namespace Virus {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 54, "Filter Env Attack", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 55, "Filter Env Decay", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 56, "Filter Env Sustain", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 57, "Filter Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 57, "Filter Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 58, "Filter Env Release", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 59, "Amp Env Attack", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 60, "Amp Env Decay", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 61, "Amp Env Sustain", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 62, "Amp Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 62, "Amp Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 63, "Amp Env Release", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 64, "Hold Pedal", {0,127}, {},{}, false, false, false}, {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 65, "Portamento Pedal", {0,127}, {},{}, false, false, false}, @@ -1387,50 +1520,50 @@ namespace Virus {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 68, "Lfo1 Shape", {0,67}, numToLfoShape, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 69, "Lfo1 Env Mode", {0,1}, {},{}, true, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 70, "Lfo1 Mode", {0,1}, numToLfoMode, {}, true, false, true}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 71, "Lfo1 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 71, "Lfo1 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 72, "Lfo1 Keyfollow", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 73, "Lfo1 Keytrigger", {0,127}, {}, {}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 74, "Osc1 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 75, "Osc2 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 76, "PW Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 77, "Reso Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 78, "FiltGain Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 74, "Osc1 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 75, "Osc2 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 76, "PW Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 77, "Reso Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 78, "FiltGain Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 79, "Lfo2 Rate", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 80, "Lfo2 Shape", {0,67}, numToLfoShape,{}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 81, "Lfo2 Env Mode", {0,1}, {},{}, true, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 82, "Lfo2 Mode", {0,1}, numToLfoMode, {}, true, false, true}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 83, "Lfo2 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 83, "Lfo2 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 84, "Lfo2 Keyfollow", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 85, "Lfo2 Keytrigger", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 86, "OscShape Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 87, "FmAmount Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 88, "Cutoff1 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 89, "Cutoff2 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 90, "Panorama Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 86, "Shape Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 87, "FM Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 88, "Cutoff1 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 89, "Cutoff2 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 90, "Pan Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 91, "Patch Volume", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 93, "Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 93, "Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 94, "Key Mode", {0,5}, numToKeyMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 97, "Unison Mode", {0,15}, numToUnisonMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 98, "Unison Detune", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 99, "Unison Panorama Spread", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 100, "Unison Lfo Phase", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 100, "Unison Lfo Phase", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 101, "Input Mode", {0,3}, numToInputMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 102, "Input Select", {0,8}, numToInputSelect,{}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 105, "Chorus Mix", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 106, "Chorus Rate", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 107, "Chorus Depth", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 108, "Chorus Delay", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 109, "Chorus Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 109, "Chorus Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 110, "Chorus Lfo Shape", {0,67}, numToLfoShape, {}, true, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 112, "Delay/Reverb Mode", {0,6}, numToDelayReverbMode, {}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 112, "Delay/Reverb Mode", {0,26}, numToDelayReverbMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE, 113, "Effect Send", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 114, "Delay Time", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 115, "Delay Feedback", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 116, "Delay Rate / Reverb Decay Time", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 117, "Delay Depth / Reverb Room Size", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Delay Lfo Shape", {0,5}, numToLfoShape, {}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 117, "Delay Depth / Reverb Room Size", {0,127}, numToReverbRoomSize,{}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Delay Lfo Shape", {0,127}, numToDelayLfoShape, {}, true, true, false}, // {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Reverb Damping", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 119, "Delay Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 119, "Delay Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 122, "Keyb Local", {0,1}, {},{}, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 123, "All Notes Off", {0,127}, {},{}, false, false, false}, @@ -1438,7 +1571,7 @@ namespace Virus {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 2, "Arp Pattern Selct", {0,63}, numToNumPlusOne, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 3, "Arp Octave Range", {0,3}, numToNumPlusOne,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 4, "Arp Hold Enable", {0,1}, {},{}, true, false, true}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 5, "Arp Note Length", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 5, "Arp Note Length", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 6, "Arp Swing", {0,127}, numToArpSwing, {}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 7, "Lfo3 Rate", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 8, "Lfo3 Shape", {0,67}, numToLfoShape, {}, true, true, false}, @@ -1447,16 +1580,16 @@ namespace Virus {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 11, "Lfo3 Destination", {0,5}, numToLfoDest, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 12, "Osc Lfo3 Amount", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 13, "Lfo3 Fade-In Time", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 16, "Clock Tempo", {0,127}, numToClockTempo, {}, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 16, "Clock Tempo", {0,127}, numToClockTempo, {}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 17, "Arp Clock", {0,17}, numToMusicDivision, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 18, "Lfo1 Clock", {0,21}, numToMusicDivision, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 19, "Lfo2 Clock", {0,21}, numToMusicDivision, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 20, "Delay Clock", {0,16}, numToMusicDivision, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 21, "Lfo3 Clock", {0,21}, numToMusicDivision, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 25, "Control Smooth Mode", {0,3}, numToControlSmoothMode,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 26, "Bender Range Up", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 27, "Bender Range Down", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 28, "Bender Scale", {0,1}, numToLinExp, {}, true, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 26, "Bender Range Up", {0,127}, numToBendRange, {}, true, true, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 27, "Bender Range Down", {0,127}, numToBendRange, {}, true, true, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 28, "Bender Scale", {0,1}, numToLinExp, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 30, "Filter1 Env Polarity", {0,1}, numToNegPos, {}, true, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 31, "Filter1 Env Polarity", {0,1}, numToNegPos, {}, true, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 32, "Filter2 Cutoff Link", {0,1}, {},{}, true, false, true}, @@ -1468,50 +1601,50 @@ namespace Virus {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 39, "Vocoder Mode", {0,12}, numToVocoderMode,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 41, "Osc3 Mode", {0,67}, numToOsc3Mode,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 42, "Osc3 Volume", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 43, "Osc3 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 43, "Osc3 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 44, "Osc3 Detune", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 45, "LowEQ Frequency", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 46, "HighEQ Frequency", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 47, "Osc1 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 48, "Osc2 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 49, "PulseWidth Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 50, "Fm Amount Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 51, "Soft Knob1 ShortName", {0,127}, {},{}, false, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 52, "Soft Knob2 ShortName", {0,127}, {},{}, false, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 54, "Filter1 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 55, "Filter2 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 56, "Resonance1 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 57, "Resonance2 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 47, "Osc1 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 48, "Osc2 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 49, "PulseWidth Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 50, "Fm Amount Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 51, "Soft Knob1 ShortName", {0,71}, numToSoftKnobName,{}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 52, "Soft Knob2 ShortName", {0,71}, numToSoftKnobName,{}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 54, "Flt1 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 55, "Flt2 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 56, "Resonance1 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 57, "Resonance2 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 58, "Second Output Balance", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 60, "Amp Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 61, "Panorama Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 60, "Amp Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 61, "Panorama Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 62, "Soft Knob-1 Single", {0,117}, numToSoftKnobDest, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 63, "Soft Knob-2 Single", {0,117}, numToSoftKnobDest, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 64, "Assign1 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 65, "Assign1 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 66, "Assign1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 66, "Assign1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 67, "Assign2 Source", {0,27}, numToModMatrixSource,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 68, "Assign2 Destination1", {0,122}, numToModMatrixDest,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 69, "Assign2 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 69, "Assign2 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 70, "Assign2 Destination2", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 71, "Assign2 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 71, "Assign2 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 72, "Assign3 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 73, "Assign3 Destination1", {0,122}, numToModMatrixDest,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 74, "Assign3 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 74, "Assign3 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 75, "Assign3 Destination2", {0,122}, numToModMatrixDest,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 76, "Assign3 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 76, "Assign3 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 77, "Assign2 Destination3", {0,122}, numToModMatrixDest,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 78, "Assign2 Amount3", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 78, "Assign2 Amount3", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 79, "LFO1 Assign Dest", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 80, "LFO1 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 80, "LFO1 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 81, "LFO2 Assign Dest", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 82, "LFO2 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 82, "LFO2 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 84, "Phaser Mode", {0,6}, numToPhaserMode, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 85, "Phaser Mix", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 86, "Phaser Rate", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 87, "Phaser Depth", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 88, "Phaser Frequency", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 89, "Phaser Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 89, "Phaser Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 90, "Phaser Spread", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 92, "MidEQ Gain", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 93, "MidEQ Frequency", {0,127}, {},{}, true, false, false}, @@ -1523,34 +1656,35 @@ namespace Virus {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 99, "Input Ringmodulator", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 100, "Distortion Curve", {0,11}, numToDistortionCurv,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 101, "Distortion Intensity", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 102, "Assign 4 Source", {0,27}, numToModMatrixSource,{}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 103, "Assign 4 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C,104, "Assign 4 Amount", {0,127}, {},{}, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 105, "Assign 5 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 106, "Assign 5 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 107, "Assign 5 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 108, "Assign 6 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 109, "Assign 6 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 110, "Assign 6 Amount", {0,127}, paramTo7bitSigned, {}, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 103, "Assign 4 Source", {0,27}, numToModMatrixSource,{}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 104, "Assign 4 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 105, "Assign 4 Amount", {0,127}, {},{}, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 106, "Assign 5 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 107, "Assign 5 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 108, "Assign 5 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 109, "Assign 6 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 110, "Assign 6 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 111, "Assign 6 Amount", {0,127}, paramTo7bitSigned, {}, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 122, "Filter Select", {0,2}, numToFilterSelect, {}, true, true, false}, - - {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::NON_PART_SENSITIVE, 22, "Delay Output Select", {0,14}, numToOutputDelaySelect, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 123, "Category1", {0,16}, numToCategory, {}, true, true, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 124, "Category2", {0,16}, numToCategory, {}, true, true, false}, + {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::NON_PART_SENSITIVE, 22, "Delay Output Select", {0,14}, numToOutputSelect, {}, false, true, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 31, "Part Bank Select", {0, 3 + 26}, {},{}, false, true, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 32, "Part Bank Change", {0, 3 + 26}, {},{}, false, true, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 33, "Part Program Change", {0,127}, {},{}, false, false, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 34, "Part Midi Channel", {0,15}, {},{}, false, true, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 35, "Part Low Key", {0,127}, {},{}, false, true, false}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 36, "Part High Key", {0,127}, {},{}, false, true, false}, - {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 37, "Part Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false}, - {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 38, "Part Detune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false}, - {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 39, "Part Volume", {0,127}, {},{}, true, false, false}, + {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 37, "Part Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false, true}, + {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 38, "Part Detune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false, true}, + {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 39, "Part Volume", {0,127}, paramTo7bitSigned,textTo7bitSigned, true, false, false, true}, {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 40, "Part Midi Volume Init", {0,127}, {},{}, false, true, false}, - {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 41, "Part Output Select", {0,14}, {},{}, false, true, false}, + {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 41, "Part Output Select", {0,14}, numToOutputSelect,{}, false, true, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 45, "Second Output Select", {0,15}, {},{}, false, true, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 63, "Keyb Transpose Buttons", {0,1}, {},{}, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 64, "Keyb Local", {0,1}, {},{}, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 65, "Keyb Mode", {0,1}, {},{}, false, false, true}, - {Parameter::Page::C, Parameter::Class::GLOBAL, 66, "Keyb Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false}, + {Parameter::Page::C, Parameter::Class::GLOBAL, 66, "Keyb Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 67, "Keyb ModWheel Contr", {0,127}, {},{}, false, false, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 68, "Keyb Pedal 1 Contr", {0,127}, {},{}, false, false, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 69, "Keyb Pedal 2 Contr", {0,127}, {},{}, false, false, false}, @@ -1566,7 +1700,7 @@ namespace Virus {Parameter::Page::C, Parameter::Class::GLOBAL, 87, "Glob Midi Volume Enable", {0,1}, {},{}, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 90, "Input Thru Level", {0,127}, {},{}, false, false, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 91, "Input Boost", {0,127}, {},{}, false, false, false}, - {Parameter::Page::C, Parameter::Class::GLOBAL, 92, "Master Tune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false}, + {Parameter::Page::C, Parameter::Class::GLOBAL, 92, "Master Tune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 93, "Device ID", {0,16}, {},{}, true, true, false}, {Parameter::Page::C, Parameter::Class::GLOBAL, 94, "Midi Control Low Page", {0,1}, {},{}, false, false, true}, {Parameter::Page::C, Parameter::Class::GLOBAL, 95, "Midi Control High Page", {0,1}, {},{}, false, false, true}, diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -231,7 +231,8 @@ namespace Virus Param_Assign6Destination, Param_Assign6Amount, Param_FilterSelect, - + Param_Category1, + Param_Category2, Param_DelayOutputSelect, Param_PartBankSelect, Param_PartBankChange, @@ -408,10 +409,11 @@ namespace Virus juce::Value *getParamValue(ParameterType _param); Parameter* getParameter(ParameterType _param); Parameter *getParameter(ParameterType _param, uint8_t _part); - + uint8_t getVirusModel(); // bank - 0-1 (AB) juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; juce::StringArray getMultiPresetsName() const; + void setSinglePresetName(uint8_t part, juce::String _name); bool isMultiMode() { return getParamValue(0, 2, 0x7a)->getValue(); } // part 0 - 15 (ignored when single! 0x40...) void setCurrentPartPreset(uint8_t part, virusLib::BankNumber bank, uint8_t prg); @@ -419,6 +421,8 @@ namespace Virus uint8_t getCurrentPartProgram(uint8_t part); juce::String getCurrentPartPresetName(uint8_t part); uint32_t getBankCount() const { return static_cast<uint32_t>(m_singles.size()); } + uint8_t getCurrentPart() const { return m_currentPart; } + void setCurrentPart(uint8_t _part) { m_currentPart = _part; } void parseMessage(const SysEx &); void sendSysEx(const SysEx &); void onStateLoaded(); @@ -442,7 +446,7 @@ namespace Virus MultiPatch m_multis[128]; // RAM has 128 Multi 'snapshots' std::array<std::array<SinglePatch, 128>, 8> m_singles; - + MultiPatch m_currentMulti; static const std::initializer_list<Parameter::Description> m_paramsDescription; struct ParamIndex @@ -489,5 +493,6 @@ namespace Virus unsigned char m_deviceId; virusLib::BankNumber m_currentBank[16]; uint8_t m_currentProgram[16]; + uint8_t m_currentPart; }; }; // namespace Virus diff --git a/source/jucePlugin/VirusParameter.h b/source/jucePlugin/VirusParameter.h @@ -42,6 +42,7 @@ namespace Virus bool isPublic; bool isDiscrete; bool isBool; + bool isBipolar; }; Parameter(Controller &, const Description desc, uint8_t partNum = 0x40); @@ -56,9 +57,10 @@ namespace Virus float getValue() const override { return convertTo0to1(m_value.getValue()); } void setValue(float newValue) override { return m_value.setValue(convertFrom0to1(newValue)); }; void setValueFromSynth(int newValue, bool notifyHost = true); - float getDefaultValue() const override { return 0; /* maybe return from ROM state? */ } + float getDefaultValue() const override { return m_desc.isBipolar ? 64.0f : 0.0f; /* maybe return from ROM state? */ } bool isDiscrete() const override { return m_desc.isDiscrete; } bool isBoolean() const override { return m_desc.isBool; } + bool isBipolar() const { return m_desc.isBipolar; } float getValueForText(const juce::String &text) const override { diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp @@ -2,53 +2,78 @@ #include "VirusParameter.h" #include "PluginProcessor.h" -void VirusParameterBinding::setPart(uint8_t _part) { - m_part = _part; +VirusParameterBinding::~VirusParameterBinding() +{ + clearBindings(); +} +void VirusParameterBinding::clearBindings() { for (const auto b : m_bindings) { b->onValueChanged = nullptr; } m_bindings.clear(); - +} +void VirusParameterBinding::setPart(uint8_t _part) +{ + clearBindings(); + m_processor.getController().setCurrentPart(_part); } void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param) { - const auto v = m_processor.getController().getParameter(_param, m_part); + bind(_slider, _param, m_processor.getController().getCurrentPart()); +} +void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param, uint8_t part) +{ + const auto v = m_processor.getController().getParameter(_param, part); if (!v) { assert(false && "Failed to find parameter"); return; } const auto range = v->getNormalisableRange(); + _slider.setRange(range.start, range.end, range.interval); _slider.setDoubleClickReturnValue(true, v->getDefaultValue()); _slider.getValueObject().referTo(v->getValueObject()); + _slider.getProperties().set("type", "slider"); + _slider.getProperties().set("name", v->getDescription().name); + if (v->isBipolar()) { + _slider.getProperties().set("bipolar", true); + } } -void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) +void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) { + bind(_combo, _param, m_processor.getController().getCurrentPart()); +} +void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param, uint8_t _part) { - const auto v = m_processor.getController().getParameter(_param, m_part); + const auto v = m_processor.getController().getParameter(_param, _part); if (!v) { assert(false && "Failed to find parameter"); return; } _combo.setTextWhenNothingSelected("--"); - _combo.addItemList(v->getAllValueStrings(), 1); - _combo.setSelectedItemIndex(v->getValueObject().getValueSource().getValue()); + int idx = 1; + for (auto vs : v->getAllValueStrings()) { + if(vs.isNotEmpty()) + _combo.addItem(vs, idx); + idx++; + } + //_combo.addItemList(v->getAllValueStrings(), 1); + _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); _combo.onChange = [this, &_combo, v]() { - //v->setValue(_combo.getSelectedId() - 1); - v->getValueObject().getValueSource().setValue(_combo.getSelectedItemIndex()); + v->getValueObject().getValueSource().setValue(_combo.getSelectedId() - 1); }; - v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedItemIndex(v->getValueObject().getValueSource().getValue(), juce::NotificationType::dontSendNotification); }; + v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); }; m_bindings.add(v); } void VirusParameterBinding::bind(juce::DrawableButton &_btn, Virus::ParameterType _param) { - const auto v = m_processor.getController().getParameter(_param, m_part); + const auto v = m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart()); if (!v) { assert(false && "Failed to find parameter"); @@ -59,7 +84,7 @@ void VirusParameterBinding::bind(juce::DrawableButton &_btn, Virus::ParameterTyp void VirusParameterBinding::bind(juce::Component &_btn, Virus::ParameterType _param) { - const auto v = m_processor.getController().getParameter(_param, m_part); + const auto v = m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart()); if (!v) { assert(false && "Failed to find parameter"); diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h @@ -1,6 +1,7 @@ #pragma once #include "VirusController.h" #include "VirusParameter.h" +#include "ui/Virus_Buttons.h" namespace juce { class Value; } @@ -10,16 +11,19 @@ class AudioPluginAudioProcessor; class VirusParameterBinding { public: - VirusParameterBinding(AudioPluginAudioProcessor& _processor, uint8_t _part = 0) : m_processor(_processor) + VirusParameterBinding(AudioPluginAudioProcessor& _processor) : m_processor(_processor) { - m_part = _part; + } + ~VirusParameterBinding(); + void clearBindings(); void setPart(uint8_t _part); void bind(juce::Slider& _control, Virus::ParameterType _param); + void bind(juce::Slider& _control, Virus::ParameterType _param, uint8_t _part); void bind(juce::ComboBox &_control, Virus::ParameterType _param); + void bind(juce::ComboBox &_control, Virus::ParameterType _param, uint8_t _part); void bind(juce::DrawableButton &_control, Virus::ParameterType _param); void bind(juce::Component &_control, Virus::ParameterType _param); AudioPluginAudioProcessor& m_processor; - uint8_t m_part; juce::Array<Virus::Parameter*> m_bindings; }; diff --git a/source/jucePlugin/assets/bg_1377x800.png b/source/jucePlugin/assets/bg_1377x800.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/arphold_btn_36x36.png b/source/jucePlugin/assets/buttons/arphold_btn_36x36.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/part_select_btc_18x36.png b/source/jucePlugin/assets/buttons/part_select_btc_18x36.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/part_select_btn_36x36.png b/source/jucePlugin/assets/buttons/part_select_btn_36x36.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_arp_1018x620.png b/source/jucePlugin/assets/panels/bg_arp_1018x620.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_fx_1018x620.png b/source/jucePlugin/assets/panels/bg_fx_1018x620.png Binary files differ. diff --git a/source/jucePlugin/ui/VirusEditor.cpp b/source/jucePlugin/ui/VirusEditor.cpp @@ -6,6 +6,7 @@ #include "Virus_LfoEditor.h" #include "Virus_OscEditor.h" #include "Virus_PatchBrowser.h" +#include "Virus_Parts.h" #include "../VirusParameterBinding.h" #include "../VirusController.h" @@ -13,9 +14,10 @@ using namespace juce; constexpr auto kPanelWidth = 1377; constexpr auto kPanelHeight = 800; - +static uint8_t currentTab = 3; VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAudioProcessor &_processorRef) : - m_parameterBinding(_parameterBinding), processorRef(_processorRef), m_controller(processorRef.getController()), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode") + m_parameterBinding(_parameterBinding), processorRef(_processorRef), m_controller(processorRef.getController()), + m_controlLabel("ctrlLabel", "") { setLookAndFeel(&m_lookAndFeel); @@ -30,117 +32,57 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu m_lfoEditor = std::make_unique<LfoEditor>(_parameterBinding); m_oscEditor = std::make_unique<OscEditor>(_parameterBinding); m_patchBrowser = std::make_unique<PatchBrowser>(_parameterBinding, m_controller); + m_partList = std::make_unique<Parts>(_parameterBinding, m_controller); + + m_partList->setBounds(0,0, 338, kPanelHeight); + m_partList->setVisible(true); + addChildComponent(m_partList.get()); applyToSections([this](Component *s) { addChildComponent(s); }); // show/hide section from buttons.. m_mainButtons.updateSection = [this]() { + if (m_mainButtons.m_arpSettings.getToggleState()) { + currentTab = 0; + } + else if (m_mainButtons.m_effects.getToggleState()) { + currentTab = 1; + } + else if (m_mainButtons.m_lfoMatrix.getToggleState()) { + currentTab = 2; + } + else if (m_mainButtons.m_oscFilter.getToggleState()) { + currentTab = 3; + } + else if (m_mainButtons.m_patches.getToggleState()) { + currentTab = 4; + } m_arpEditor->setVisible(m_mainButtons.m_arpSettings.getToggleState()); m_fxEditor->setVisible(m_mainButtons.m_effects.getToggleState()); m_lfoEditor->setVisible(m_mainButtons.m_lfoMatrix.getToggleState()); m_oscEditor->setVisible(m_mainButtons.m_oscFilter.getToggleState()); m_patchBrowser->setVisible(m_mainButtons.m_patches.getToggleState()); }; - m_oscEditor->setVisible(true); - m_mainButtons.m_oscFilter.setToggleState(true, NotificationType::dontSendNotification); + uint8_t index = 0; + applyToSections([this, index](Component *s) mutable { + if (currentTab == index) { + s->setVisible(true); + } + index++; + }); + index = 0; + m_mainButtons.applyToMainButtons([this, &index](DrawableButton *s) mutable { + if (currentTab == index) { + s->setToggleState(true, juce::dontSendNotification); + } + index++; + }); + //m_oscEditor->setVisible(true); + //m_mainButtons.m_oscFilter.setToggleState(true, NotificationType::dontSendNotification); addAndMakeVisible(m_presetButtons); m_presetButtons.m_load.onClick = [this]() { loadFile(); }; m_presetButtons.m_save.onClick = [this]() { saveFile(); }; - for (auto pt = 0; pt < 16; pt++) - { - m_partLabels[pt].setBounds(34, 161 + pt * (36), 24, 36); - m_partLabels[pt].setText(juce::String(pt + 1), juce::dontSendNotification); - m_partLabels[pt].setColour(0, juce::Colours::white); - m_partLabels[pt].setColour(1, juce::Colour(45, 24, 24)); - m_partLabels[pt].setJustificationType(Justification::centred); - addAndMakeVisible(m_partLabels[pt]); - - m_partSelect[pt].setBounds(34, 161 + pt*(36), 39, 36); - m_partSelect[pt].setButtonText(juce::String(pt)); - m_partSelect[pt].setRadioGroupId(kPartGroupId); - m_partSelect[pt].setClickingTogglesState(true); - m_partSelect[pt].onClick = [this, pt]() { - this->changePart(pt); - }; - addAndMakeVisible(m_partSelect[pt]); - - m_presetNames[pt].setBounds(80, 171 + pt * (36) - 2, 136, 16 + 4); - m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); - m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255,113,128)); - m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); - //m_presetNames[pt].setColour(0, juce::Colours::white); - - m_presetNames[pt].onClick = [this, pt]() { - juce::PopupMenu selector; - - for (uint8_t b = 0; b < m_controller.getBankCount(); ++b) - { - const auto bank = virusLib::fromArrayIndex(b); - auto presetNames = m_controller.getSinglePresetNames(bank); - juce::PopupMenu p; - for (uint8_t j = 0; j < 128; j++) - { - const auto presetName = presetNames[j]; - p.addItem(presetNames[j], [this, bank, j, pt, presetName] { - m_controller.setCurrentPartPreset(pt, bank, j); - m_presetNames[pt].setButtonText(presetName); - }); - } - std::stringstream bankName; - bankName << "Bank " << static_cast<char>('A' + b); - selector.addSubMenu(std::string(bankName.str()), p); - } - selector.showMenu(juce::PopupMenu::Options()); - }; - addAndMakeVisible(m_presetNames[pt]); - - m_prevPatch[pt].setBounds(228, 173 + 36*pt - 2, 16, 14); - m_nextPatch[pt].setBounds(247, 173 + 36*pt - 2, 16, 14); - m_prevPatch[pt].setButtonText("<"); - m_nextPatch[pt].setButtonText(">"); - m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); - m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); - m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); - m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); - m_prevPatch[pt].onClick = [this, pt]() { - m_controller.setCurrentPartPreset( - pt, m_controller.getCurrentPartBank(pt), - std::max(0, m_controller.getCurrentPartProgram(pt) - 1)); - }; - m_nextPatch[pt].onClick = [this, pt]() { - m_controller.setCurrentPartPreset( - pt, m_controller.getCurrentPartBank(pt), - std::min(127, m_controller.getCurrentPartProgram(pt) + 1)); - }; - addAndMakeVisible(m_prevPatch[pt]); - addAndMakeVisible(m_nextPatch[pt]); - } - m_partSelect[0].setToggleState(true, NotificationType::sendNotification); - - m_btSingleMode.setRadioGroupId(0x3cf); - m_btMultiMode.setRadioGroupId(0x3cf); - addAndMakeVisible(m_btSingleMode); - addAndMakeVisible(m_btMultiMode); - m_btSingleMode.setTopLeftPosition(102, 756); - m_btSingleMode.setSize(100, 30); - //m_btMultiMode.getToggleStateValue().referTo(*m_controller.getParamValue(Virus::Param_PlayMode)); - const auto isMulti = m_controller.isMultiMode(); - m_btSingleMode.setToggleState(!isMulti, juce::dontSendNotification); - m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification); - m_btSingleMode.setClickingTogglesState(true); - m_btMultiMode.setClickingTogglesState(true); - m_btSingleMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); - m_btSingleMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); - m_btMultiMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); - m_btMultiMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); - m_btSingleMode.onClick = [this]() { setPlayMode(0); }; - m_btMultiMode.onClick = [this]() { setPlayMode(1); }; - - m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, - m_btSingleMode.getY()); - m_btMultiMode.setSize(100, 30); - juce::PropertiesFile::Options opts; opts.applicationName = "DSP56300 Emulator"; opts.filenameSuffix = ".settings"; @@ -205,6 +147,8 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu if (!processorRef.isPluginValid()) message += "\n\nNo ROM, no sound!\nCopy ROM next to plugin, must end with .bin"; message += "\n\nTo donate: paypal.me/dsp56300"; + std::string model(m_controller.getVirusModel() == virusLib::VirusModel::B ? "B" : "C"); + message += "\n\nROM Loaded: " + _processorRef.getRomName(); m_version.setText(message, NotificationType::dontSendNotification); m_version.setBounds(94, 2, 220, 150); m_version.setColour(juce::Label::textColourId, juce::Colours::white); @@ -215,10 +159,38 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu m_patchName.setJustificationType(Justification::left); m_patchName.setFont(juce::Font(32.0f, juce::Font::bold)); m_patchName.setColour(juce::Label::textColourId, juce::Colour(255, 113, 128)); + m_patchName.setEditable(false, true, true); + m_patchName.onTextChange = [this]() { + auto text = m_patchName.getText(); + if(text.trim().length() > 0) { + if (text == "/pv") { // stupid debug thing to remove later + m_paramDisplayLocal = !m_paramDisplayLocal; + return; + } + m_controller.setSinglePresetName(m_controller.getCurrentPart(), text); + } + }; addAndMakeVisible(m_patchName); + m_controlLabel.setBounds(m_patchName.getBounds().translated(m_controlLabel.getWidth(), 0)); + m_controlLabel.setSize(m_patchName.getWidth()/2, m_patchName.getHeight()); + m_controlLabel.setJustificationType(Justification::topRight); + m_controlLabel.setColour(juce::Label::textColourId, juce::Colour(255, 113, 128)); + m_controlLabel.setFont(juce::Font(16.0f, juce::Font::bold)); + //m_controlLabel.setColour(juce::Label::ColourIds::backgroundColourId, juce::Colours::black); + //m_controlLabel.setColour(juce::Label::ColourIds::outlineColourId, juce::Colours::white); + + addAndMakeVisible(m_controlLabel); + + m_controller.getBankCount(); + addMouseListener(this, true); + startTimerHz(5); setSize (kPanelWidth, kPanelHeight); + + // without this some combobox parameters are wrong on first load, no idea why. + //m_controller.getParameter(Virus::Param_PlayMode)->setValue(virusLib::PlayModeSingle); + recreateControls(); } VirusEditor::~VirusEditor() { setLookAndFeel(nullptr); } @@ -230,15 +202,12 @@ void VirusEditor::timerCallback() for (auto pt = 0; pt < 16; pt++) { bool singlePartOrInMulti = pt == 0 || multiMode; - m_presetNames[pt].setVisible(singlePartOrInMulti); - m_prevPatch[pt].setVisible(singlePartOrInMulti); - m_nextPatch[pt].setVisible(singlePartOrInMulti); - if (singlePartOrInMulti) - m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); - if (pt == m_parameterBinding.m_part) + if (pt == m_controller.getCurrentPart()) { - m_patchName.setText(m_controller.getCurrentPartPresetName(pt), - NotificationType::dontSendNotification); + const auto patchName = m_controller.getCurrentPartPresetName(pt); + if(m_patchName.getText() != patchName) { + m_patchName.setText(patchName, NotificationType::dontSendNotification); + } } } @@ -302,13 +271,6 @@ void VirusEditor::updateMidiOutput(int index) m_cmbMidiOutput.setSelectedItemIndex(index + 1, juce::dontSendNotification); m_lastOutputIndex = index; } -void VirusEditor::updatePartsPresetNames() -{ - for (auto i = 0; i < 16; i++) - { - m_presetNames[i].setButtonText(m_controller.getCurrentPartPresetName(i)); - } -} void VirusEditor::applyToSections(std::function<void(Component *)> action) { for (auto *section : {static_cast<Component *>(m_arpEditor.get()), static_cast<Component *>(m_fxEditor.get()), @@ -318,6 +280,53 @@ void VirusEditor::applyToSections(std::function<void(Component *)> action) action(section); } } +void VirusEditor::MainButtons::applyToMainButtons(std::function<void(DrawableButton *)> action) +{ + for (auto *section : {static_cast<juce::DrawableButton *>(&m_arpSettings), static_cast<DrawableButton *>(&m_effects), + static_cast<DrawableButton *>(&m_lfoMatrix), static_cast<DrawableButton *>(&m_oscFilter), + static_cast<DrawableButton *>(&m_patches)}) + { + action(section); + } +} +void VirusEditor::mouseDrag(const juce::MouseEvent & event) +{ + auto props = event.eventComponent->getProperties(); + if(props.contains("type") && props["type"] == "slider") { + m_controlLabel.setVisible(true); + auto comp = dynamic_cast<juce::Slider*>(event.eventComponent); + if(comp) { + auto name = props["name"]; + + if(m_paramDisplayLocal) { + m_controlLabel.setTopLeftPosition(getTopLevelComponent()->getLocalPoint(comp->getParentComponent(), comp->getPosition().translated(0, -16))); + m_controlLabel.setSize(comp->getWidth(), 20); + m_controlLabel.setColour(juce::Label::ColourIds::backgroundColourId, juce::Colours::black); + m_controlLabel.setColour(juce::Label::ColourIds::outlineColourId, juce::Colours::white); + if (props.contains("bipolar") && props["bipolar"]) { + m_controlLabel.setText(juce::String(juce::roundToInt(comp->getValue())-64), juce::dontSendNotification); + } else { + m_controlLabel.setText(juce::String(juce::roundToInt(comp->getValue())), juce::dontSendNotification); + } + } + else { + m_controlLabel.setBounds(m_patchName.getBounds().translated(m_controlLabel.getWidth(), 0)); + m_controlLabel.setSize(m_patchName.getWidth()/2, m_patchName.getHeight()); + if (props.contains("bipolar") && props["bipolar"]) { + m_controlLabel.setText(name.toString() + "\n" + juce::String(juce::roundToInt(comp->getValue())-64), juce::dontSendNotification); + } else { + m_controlLabel.setText(name.toString() + "\n" + juce::String(juce::roundToInt(comp->getValue())), juce::dontSendNotification); + } + } + } + } +} + +void VirusEditor::mouseUp(const juce::MouseEvent & event) +{ + m_controlLabel.setText("", juce::dontSendNotification); + m_controlLabel.setVisible(false); +} void VirusEditor::resized() { @@ -328,20 +337,14 @@ void VirusEditor::resized() applyToSections([this](Component *s) { s->setTopLeftPosition(338, 133); }); } -void VirusEditor::setPlayMode(uint8_t _mode) { - m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); - changePart(0); +void VirusEditor::handleCommandMessage(int commandId) { + if (commandId == Commands::Rebind) { + recreateControls(); + } } -void VirusEditor::changePart(uint8_t _part) +void VirusEditor::recreateControls() { - for (auto &p : m_partSelect) - { - p.setToggleState(false, juce::dontSendNotification); - } - m_partSelect[_part].setToggleState(true, juce::dontSendNotification); - m_parameterBinding.setPart(_part); - removeChildComponent(m_oscEditor.get()); removeChildComponent(m_lfoEditor.get()); removeChildComponent(m_fxEditor.get()); @@ -454,7 +457,11 @@ void VirusEditor::loadFile() { if ((uint8_t)*it == (uint8_t)0xf0 && (it + 267) < end) { - if ((uint8_t) * (it + 1) == (uint8_t)0x00) + if ((uint8_t)*(it+1) == 0x00 + && (uint8_t)*(it+2) == 0x20 + && (uint8_t)*(it+3) == 0x33 + && (uint8_t)*(it+4) == 0x01 + && (uint8_t)*(it+6) == virusLib::DUMP_SINGLE) { auto syx = Virus::SysEx(it, it + 267); syx[7] = 0x01; // force to bank a @@ -464,7 +471,11 @@ void VirusEditor::loadFile() it += 266; } - else // some midi files have two bytes after the 0xf0 + else if((uint8_t)*(it+3) == 0x00 + && (uint8_t)*(it+4) == 0x20 + && (uint8_t)*(it+5) == 0x33 + && (uint8_t)*(it+6) == 0x01 + && (uint8_t)*(it+8) == virusLib::DUMP_SINGLE)// some midi files have two bytes after the 0xf0 { auto syx = Virus::SysEx(); syx.push_back(0xf0); @@ -501,14 +512,13 @@ void VirusEditor::saveFile() { const auto result = chooser.getResult(); m_previousPath = result.getParentDirectory().getFullPathName(); const auto ext = result.getFileExtension().toLowerCase(); - const uint8_t syxHeader[9] = {0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x01, 0x00}; const uint8_t syxEof[1] = {0xF7}; uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; uint8_t data[256]; for (int i = 0; i < 256; i++) { - auto param = m_controller.getParamValue(m_parameterBinding.m_part, i < 128 ? 0 : 1, i % 128); + auto param = m_controller.getParamValue(m_controller.getCurrentPart(), i < 128 ? 0 : 1, i % 128); data[i] = param ? (int)param->getValue() : 0; cs += data[i]; diff --git a/source/jucePlugin/ui/VirusEditor.h b/source/jucePlugin/ui/VirusEditor.h @@ -12,6 +12,7 @@ class LfoEditor; class FxEditor; class ArpEditor; class PatchBrowser; +class Parts; class VirusEditor : public juce::Component, private juce::Timer { @@ -19,26 +20,25 @@ public: VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAudioProcessor &_processorRef); ~VirusEditor() override; void resized() override; - void changePart(uint8_t _part); + void recreateControls(); void updatePartsPresetNames(); void loadFile(); void saveFile(); - void setPlayMode(uint8_t _mode); + enum Commands { + None, + Rebind = 0x100 + }; private: void timerCallback() override; + void handleCommandMessage(int commandId) override; void updateMidiInput(int index); void updateMidiOutput(int index); juce::Label m_version; juce::Label m_patchName; - Buttons::PartSelectButton m_partSelect[16]; - juce::Label m_partLabels[16]; - juce::TextButton m_presetNames[16]; - juce::TextButton m_nextPatch[16]; - juce::TextButton m_prevPatch[16]; - juce::TextButton m_btSingleMode; - juce::TextButton m_btMultiMode; + juce::Label m_controlLabel; + juce::ComboBox m_cmbMidiInput; juce::ComboBox m_cmbMidiOutput; juce::AudioDeviceManager deviceManager; @@ -46,7 +46,6 @@ private: int m_lastInputIndex = 0; int m_lastOutputIndex = 0; - static constexpr auto kPartGroupId = 0x3FBBC; struct MainButtons : juce::Component, juce::Value::Listener { MainButtons(); @@ -59,6 +58,7 @@ private: static constexpr auto kButtonWidth = 141; static constexpr auto kButtonHeight = 26; static constexpr auto kGroupId = 0x3FBBA; + void applyToMainButtons(std::function<void(juce::DrawableButton *)>); } m_mainButtons; struct PresetButtons : juce::Component @@ -81,9 +81,15 @@ private: std::unique_ptr<ArpEditor> m_arpEditor; std::unique_ptr<PatchBrowser> m_patchBrowser; + std::unique_ptr<Parts> m_partList; std::unique_ptr<juce::Drawable> m_background; Virus::LookAndFeel m_lookAndFeel; juce::String m_previousPath; + bool m_paramDisplayLocal = false; + + void mouseDrag (const juce::MouseEvent &event) override; + void mouseUp (const juce::MouseEvent &event) override; + }; diff --git a/source/jucePlugin/ui/Virus_ArpEditor.cpp b/source/jucePlugin/ui/Virus_ArpEditor.cpp @@ -83,15 +83,16 @@ ArpEditor::Arpeggiator::Arpeggiator(VirusParameterBinding &_parameterBinding) constexpr auto comboBoxHeight = 15; constexpr auto comboTopY = 35; - m_mode.setBounds(35, 40, 100, 18); + m_mode.setBounds(35, 40, 90, 15); + m_mode.setJustificationType(Justification::topLeft); m_pattern.setBounds(114, comboTopY, comboBoxWidth, comboBoxHeight); m_resolution.setBounds(220, comboTopY, comboBoxWidth, comboBoxHeight); m_octaveRange.setBounds(m_pattern.getBounds().translated(0, comboBoxHeight + 18)); addAndMakeVisible(m_arpHold); - m_arpHold.setBounds(222, m_octaveRange.getY()+2, 28, 11); + m_arpHold.setBounds(218, m_octaveRange.getY()-2, 36, 18); - _parameterBinding.bind(m_globalTempo, Virus::Param_ClockTempo); + _parameterBinding.bind(m_globalTempo, Virus::Param_ClockTempo, 0); _parameterBinding.bind(m_noteLength, Virus::Param_ArpNoteLength); _parameterBinding.bind(m_noteSwing, Virus::Param_ArpSwing); _parameterBinding.bind(m_mode, Virus::Param_ArpMode); @@ -111,6 +112,11 @@ ArpEditor::SoftKnobs::SoftKnobs(VirusParameterBinding &_parameterBinding) addAndMakeVisible(m_name[i]); m_name[i].setBounds(m_funcAs[i].getX() + distance, 42, comboBoxWidth, comboBoxHeight); } + _parameterBinding.bind(m_name[0], Virus::Param_SoftKnob1ShortName); + _parameterBinding.bind(m_name[1], Virus::Param_SoftKnob2ShortName); + + _parameterBinding.bind(m_funcAs[0], Virus::Param_SoftKnob1Single); + _parameterBinding.bind(m_funcAs[1], Virus::Param_SoftKnob2Single); } ArpEditor::PatchSettings::PatchSettings(VirusParameterBinding &_parameterBinding) @@ -125,9 +131,11 @@ ArpEditor::PatchSettings::PatchSettings(VirusParameterBinding &_parameterBinding m_transpose.setBounds(m_panning.getX(), y2, knobSize, knobSize); for (auto *cb : - {&m_keyMode, &m_secondaryOutput, &m_bendUp, &m_bendDown, &m_bendScale, &m_smoothMode, &m_cat1, &m_cat2}) + {&m_keyMode, &m_secondaryOutput, &m_bendScale, &m_smoothMode, &m_cat1, &m_cat2}) addAndMakeVisible(cb); - + + addAndMakeVisible(m_bendUp); + addAndMakeVisible(m_bendDown); constexpr auto yDist = 50; m_keyMode.setBounds(18, 42, comboBoxWidth, comboBoxHeight); @@ -146,9 +154,12 @@ ArpEditor::PatchSettings::PatchSettings(VirusParameterBinding &_parameterBinding //_parameterBinding.bind(m_outputBalance, Virus::Param_SecondOutputBalance); _parameterBinding.bind(m_transpose, Virus::Param_Transpose); _parameterBinding.bind(m_keyMode, Virus::Param_KeyMode); - //_parameterBinding.bind(m_secondaryOutput, Virus::Param_KeyMode); _parameterBinding.bind(m_bendUp, Virus::Param_BenderRangeUp); _parameterBinding.bind(m_bendDown, Virus::Param_BenderRangeDown); _parameterBinding.bind(m_bendScale, Virus::Param_BenderScale); _parameterBinding.bind(m_smoothMode, Virus::Param_ControlSmoothMode); + _parameterBinding.bind(m_cat1, Virus::Param_Category1); + _parameterBinding.bind(m_cat2, Virus::Param_Category2); + + _parameterBinding.bind(m_secondaryOutput, Virus::Param_PartOutputSelect); } diff --git a/source/jucePlugin/ui/Virus_ArpEditor.h b/source/jucePlugin/ui/Virus_ArpEditor.h @@ -56,7 +56,8 @@ private: juce::Slider m_outputBalance; juce::Slider m_transpose; juce::ComboBox m_keyMode, m_secondaryOutput; - juce::ComboBox m_bendUp, m_bendDown, m_bendScale, m_smoothMode, m_cat1, m_cat2; + juce::ComboBox m_bendScale, m_smoothMode, m_cat1, m_cat2; + juce::ComboBox m_bendUp, m_bendDown; } m_patchSettings; std::unique_ptr<juce::Drawable> m_background; diff --git a/source/jucePlugin/ui/Virus_Buttons.cpp b/source/jucePlugin/ui/Virus_Buttons.cpp @@ -29,12 +29,12 @@ namespace Buttons Buttons::ArpHoldButton::ArpHoldButton() : DrawableButton("ArpHoldButton", DrawableButton::ImageRaw) { - auto off = Drawable::createFromImageData(BinaryData::arphold_btn_28_22_png, BinaryData::arphold_btn_28_22_pngSize); + auto off = Drawable::createFromImageData(BinaryData::arphold_btn_36x36_png, BinaryData::arphold_btn_36x36_pngSize); auto on = off->createCopy(); setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); setClickingTogglesState(true); - on->setOriginWithOriginalSize({0, -11}); + on->setOriginWithOriginalSize({0, -17}); setImages(off.get(), nullptr, on.get(), nullptr, on.get()); } @@ -106,12 +106,10 @@ namespace Buttons Buttons::PartSelectButton::PartSelectButton() : DrawableButton("PartSelectButton", DrawableButton::ButtonStyle::ImageRaw) { - auto normal = - Drawable::createFromImageData(BinaryData::part_select_btn_39x72_png, BinaryData::part_select_btn_39x72_pngSize); - auto pressed = normal->createCopy(); - pressed->setOriginWithOriginalSize({0, -36}); + auto on = + Drawable::createFromImageData(BinaryData::part_select_btn_36x36_png, BinaryData::part_select_btn_36x36_pngSize); setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); - setImages(normal.get(), nullptr, pressed.get(), nullptr, pressed.get(), nullptr, normal.get()); + setImages(nullptr, nullptr, on.get(), nullptr, on.get(), nullptr, nullptr); } }; // namespace Buttons diff --git a/source/jucePlugin/ui/Virus_FxEditor.cpp b/source/jucePlugin/ui/Virus_FxEditor.cpp @@ -135,6 +135,10 @@ FxEditor::EnvelopeFollower::EnvelopeFollower(VirusParameterBinding &_parameterBi addAndMakeVisible(m_input); m_input.setBounds(17, 37, comboBoxWidth, comboBoxHeight); + _parameterBinding.bind(m_input, Virus::Param_InputFollowMode); + _parameterBinding.bind(m_attack, Virus::Param_FilterEnvAttack); + _parameterBinding.bind(m_release, Virus::Param_FilterEnvDecay); + _parameterBinding.bind(m_gain, Virus::Param_FilterEnvSustain); } FxEditor::Punch::Punch(VirusParameterBinding &_parameterBinding) @@ -162,17 +166,18 @@ FxEditor::DelayAndReverb::DelayAndReverb(VirusParameterBinding &_parameterBindin m_sync.setBounds(0, 116 + 2, 481, 116); addAndMakeVisible(m_sync); - _parameterBinding.bind(m_time, Virus::Param_DelayTime); - _parameterBinding.bind(m_rate, Virus::Param_DelayRateReverbDecayTime); - _parameterBinding.bind(m_depth, Virus::Param_DelayDepthReverbRoomSize); - _parameterBinding.bind(m_color, Virus::Param_DelayColor); - _parameterBinding.bind(m_feedback, Virus::Param_DelayFeedback); - _parameterBinding.bind(m_fxMode, Virus::Param_DelayReverbMode); + _parameterBinding.bind(m_time, Virus::Param_DelayTime, 0); + _parameterBinding.bind(m_rate, Virus::Param_DelayRateReverbDecayTime, 0); + _parameterBinding.bind(m_depth, Virus::Param_DelayDepthReverbRoomSize, 0); + _parameterBinding.bind(m_color, Virus::Param_DelayColor, 0); + _parameterBinding.bind(m_feedback, Virus::Param_DelayFeedback, 0); + _parameterBinding.bind(m_fxMode, Virus::Param_DelayReverbMode, 0); } FxEditor::DelayAndReverb::Sync::Sync(VirusParameterBinding &_parameterBinding) { setupRotary(*this, m_mix); + setupRotary(*this, m_damping); m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); m_mix.setBounds(376, -2, knobSize, knobSize); @@ -181,9 +186,18 @@ FxEditor::DelayAndReverb::Sync::Sync(VirusParameterBinding &_parameterBinding) addAndMakeVisible(m_lfoShape); m_lfoShape.setBounds(m_clock.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_reverbMode); + m_reverbMode.setBounds(m_lfoShape.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); + + m_damping.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_damping.setBounds(m_reverbMode.getBounds().getRight() + 2, -2, knobSize, knobSize); + addAndMakeVisible(m_damping); + _parameterBinding.bind(m_mix, Virus::Param_EffectSend); - _parameterBinding.bind(m_clock, Virus::Param_DelayClock); - _parameterBinding.bind(m_lfoShape, Virus::Param_DelayLfoShape); + _parameterBinding.bind(m_clock, Virus::Param_DelayClock, 0); + _parameterBinding.bind(m_lfoShape, Virus::Param_DelayLfoShape, 0); + _parameterBinding.bind(m_damping, Virus::Param_DelayLfoShape, 0); + _parameterBinding.bind(m_reverbMode, Virus::Param_DelayDepthReverbRoomSize, 0); } FxEditor::Vocoder::Vocoder(VirusParameterBinding &_parameterBinding) : diff --git a/source/jucePlugin/ui/Virus_FxEditor.h b/source/jucePlugin/ui/Virus_FxEditor.h @@ -90,6 +90,8 @@ private: Sync(VirusParameterBinding &_parameterBinding); juce::Slider m_mix; juce::ComboBox m_clock, m_lfoShape; + juce::ComboBox m_reverbMode; + juce::Slider m_damping; } m_sync; } m_delayReverb; diff --git a/source/jucePlugin/ui/Virus_LfoEditor.cpp b/source/jucePlugin/ui/Virus_LfoEditor.cpp @@ -70,14 +70,9 @@ LfoEditor::LfoTwoOneShared::LfoTwoOneShared(VirusParameterBinding& _parameterBin m_link.setBounds(293, 8, 36, 12); m_assignDest.setBounds(393, 122, comboBoxWidth, comboBoxHeight); - //_parameterBinding.bind(m_rate, Virus::Param_Lfo1Rate); - //_parameterBinding.bind(m_keytrack, Virus::Param_Lfo1Keyfollow); _parameterBinding.bind(m_contour, _lfoIndex == 0 ? Virus::Param_Lfo1Symmetry : Virus::Param_Lfo2Symmetry); _parameterBinding.bind(m_phase, _lfoIndex == 0 ? Virus::Param_Lfo1KeyTrigger : Virus::Param_Lfo2Keytrigger); - //parameterBinding.bind(m_amount, Virus::Param_Lfo1AssignAmount); _parameterBinding.bind(m_envMode, _lfoIndex == 0 ? Virus::Param_Lfo1EnvMode : Virus::Param_Lfo2EnvMode); - //_parameterBinding.bind(m_link, _lfoIndex == 0 ? Virus::Param_Lfo1Mode : Virus::Param_Lfo2Mode); - //_parameterBinding.bind(m_assignDest, Virus::Param_Lfo1AssignDest); } LfoEditor::LfoOne::LfoOne(VirusParameterBinding& _parameterBinding) : LfoTwoOneShared(_parameterBinding, 0) diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.cpp b/source/jucePlugin/ui/Virus_LookAndFeel.cpp @@ -39,6 +39,9 @@ namespace Virus case KnobStyle::GENERIC_BLUE: knob = m_genBlue.get(); break; + case KnobStyle::GENERIC_MULTI: + knob = m_multi.get(); + break; case KnobStyle::GENERIC: default: knob = m_genKnob.get(); @@ -49,7 +52,13 @@ namespace Virus // g.fillAll (Colours::pink.withAlpha (0.4f)); const auto step = roundToInt(m_knobImageSteps.convertFrom0to1(sliderPosProportional)); // take relevant pos - knob->setOriginWithOriginalSize({0.0f, -70.0f * step}); + if (knob == m_multi.get()) { + knob->setOriginWithOriginalSize({0.0f, -18.0f * step}); + } + else { + knob->setOriginWithOriginalSize({0.0f, -70.0f * step}); + } + } // this won't support scaling! knob->drawAt(g, x, y, 1.0f); diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.h b/source/jucePlugin/ui/Virus_LookAndFeel.h @@ -16,7 +16,8 @@ namespace Virus GENERIC, GENERIC_POL, GENERIC_BLUE, - GENERIC_RED + GENERIC_RED, + GENERIC_MULTI }; static const char *KnobStyleProp; diff --git a/source/jucePlugin/ui/Virus_OscEditor.cpp b/source/jucePlugin/ui/Virus_OscEditor.cpp @@ -26,6 +26,8 @@ OscEditor::OscEditor(VirusParameterBinding& _parameterBinding) addAndMakeVisible(m_filterEnv); addAndMakeVisible(m_ampEnv); addAndMakeVisible(m_oscSync); + + _parameterBinding.bind(m_oscSync, Virus::Param_Osc2Sync); } void OscEditor::resized() diff --git a/source/jucePlugin/ui/Virus_Parts.cpp b/source/jucePlugin/ui/Virus_Parts.cpp @@ -0,0 +1,172 @@ +#include "Virus_Parts.h" +#include "BinaryData.h" +#include "Ui_Utils.h" +#include "../VirusParameterBinding.h" +#include "VirusEditor.h" +using namespace juce; + +static uint8_t g_playMode = 0; +Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _controller) : m_parameterBinding(_parameterBinding), m_controller(_controller), + m_btSingleMode("Single\nMode"), m_btMultiSingleMode("Multi\nSingle"), m_btMultiMode("Multi\nMode") +{ + for (auto pt = 0; pt < 16; pt++) + { + m_partLabels[pt].setBounds(34, 161 + pt * (36), 24, 36); + m_partLabels[pt].setText(juce::String(pt + 1), juce::dontSendNotification); + m_partLabels[pt].setColour(0, juce::Colours::white); + m_partLabels[pt].setColour(1, juce::Colour(45, 24, 24)); + m_partLabels[pt].setJustificationType(Justification::centred); + addAndMakeVisible(m_partLabels[pt]); + + m_partSelect[pt].setBounds(35, 161 + pt*(36), 36, 36); + m_partSelect[pt].setButtonText(juce::String(pt)); + m_partSelect[pt].setRadioGroupId(kPartGroupId); + m_partSelect[pt].setClickingTogglesState(true); + m_partSelect[pt].onClick = [this, pt]() { + this->changePart(pt); + }; + addAndMakeVisible(m_partSelect[pt]); + + m_presetNames[pt].setBounds(80, 171 + pt * (36) - 2, 136, 16 + 4); + m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); + m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255,113,128)); + m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + + m_presetNames[pt].onClick = [this, pt]() { + juce::PopupMenu selector; + + for (uint8_t b = 0; b < m_controller.getBankCount(); ++b) + { + const auto bank = virusLib::fromArrayIndex(b); + auto presetNames = m_controller.getSinglePresetNames(bank); + juce::PopupMenu p; + for (uint8_t j = 0; j < 128; j++) + { + const auto presetName = presetNames[j]; + p.addItem(presetNames[j], [this, bank, j, pt, presetName] { + m_controller.setCurrentPartPreset(pt, bank, j); + m_presetNames[pt].setButtonText(presetName); + }); + } + std::stringstream bankName; + bankName << "Bank " << static_cast<char>('A' + b); + selector.addSubMenu(std::string(bankName.str()), p); + } + selector.showMenu(juce::PopupMenu::Options()); + }; + addAndMakeVisible(m_presetNames[pt]); + + m_prevPatch[pt].setBounds(228, 173 + 36*pt - 2, 16, 14); + m_nextPatch[pt].setBounds(247, 173 + 36*pt - 2, 16, 14); + m_prevPatch[pt].setButtonText("<"); + m_nextPatch[pt].setButtonText(">"); + m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); + m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); + m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + m_prevPatch[pt].onClick = [this, pt]() { + m_controller.setCurrentPartPreset( + pt, m_controller.getCurrentPartBank(pt), + std::max(0, m_controller.getCurrentPartProgram(pt) - 1)); + }; + m_nextPatch[pt].onClick = [this, pt]() { + m_controller.setCurrentPartPreset( + pt, m_controller.getCurrentPartBank(pt), + std::min(127, m_controller.getCurrentPartProgram(pt) + 1)); + }; + addAndMakeVisible(m_prevPatch[pt]); + addAndMakeVisible(m_nextPatch[pt]); + + m_partVolumes[pt].setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + m_partVolumes[pt].setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + m_partVolumes[pt].setBounds(m_nextPatch[pt].getBounds().translated(m_nextPatch[pt].getWidth()+8, 0)); + m_partVolumes[pt].setSize(18,18); + m_partVolumes[pt].getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_MULTI); + m_partPans[pt].setTooltip("Part Volume"); + _parameterBinding.bind(m_partVolumes[pt], Virus::Param_PartVolume, pt); + addAndMakeVisible(m_partVolumes[pt]); + + m_partPans[pt].setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + m_partPans[pt].setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + m_partPans[pt].setBounds(m_partVolumes[pt].getBounds().translated(m_partVolumes[pt].getWidth()+4, 0)); + m_partPans[pt].setSize(18,18); + m_partPans[pt].getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_MULTI); + m_partPans[pt].setTooltip("Part Pan"); + _parameterBinding.bind(m_partPans[pt], Virus::Param_Panorama, pt); + addAndMakeVisible(m_partPans[pt]); + } + m_partSelect[m_controller.getCurrentPart()].setToggleState(true, NotificationType::sendNotification); + + m_btSingleMode.setRadioGroupId(0x3cf); + m_btMultiMode.setRadioGroupId(0x3cf); + m_btMultiSingleMode.setRadioGroupId(0x3cf); + addAndMakeVisible(m_btSingleMode); + addAndMakeVisible(m_btMultiMode); + addAndMakeVisible(m_btMultiSingleMode); + m_btSingleMode.setTopLeftPosition(102, 756); + m_btSingleMode.setSize(70, 30); + + //m_btMultiMode.getToggleStateValue().referTo(*m_controller.getParamValue(Virus::Param_PlayMode)); + m_btSingleMode.setClickingTogglesState(true); + m_btMultiMode.setClickingTogglesState(true); + m_btMultiSingleMode.setClickingTogglesState(true); + + m_btSingleMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); + m_btSingleMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); + m_btMultiMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); + m_btMultiMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); + m_btMultiSingleMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); + m_btMultiSingleMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); + m_btSingleMode.onClick = [this]() { setPlayMode(virusLib::PlayMode::PlayModeSingle); }; + m_btMultiSingleMode.onClick = [this]() { setPlayMode(virusLib::PlayMode::PlayModeMultiSingle); }; + m_btMultiMode.onClick = [this]() { setPlayMode(virusLib::PlayMode::PlayModeMulti); }; + + m_btMultiSingleMode.setBounds(m_btSingleMode.getBounds().translated(m_btSingleMode.getWidth()+4, 0)); + m_btMultiMode.setBounds(m_btMultiSingleMode.getBounds().translated(m_btMultiSingleMode.getWidth()+4, 0)); + + const uint8_t playMode = g_playMode; + if (playMode == virusLib::PlayModeSingle) { + m_btSingleMode.setToggleState(true, juce::dontSendNotification); + } + else if (playMode == virusLib::PlayModeMultiSingle) { + m_btMultiSingleMode.setToggleState(true, juce::dontSendNotification); + } + else if (playMode == virusLib::PlayModeMulti) { + m_btMultiMode.setToggleState(true, juce::dontSendNotification); + } + startTimerHz(5); + setSize(338, 800); +} + +void Parts::changePart(uint8_t _part) +{ + for (auto &p : m_partSelect) + { + p.setToggleState(false, juce::dontSendNotification); + } + m_partSelect[_part].setToggleState(true, juce::dontSendNotification); + m_parameterBinding.setPart(_part); + getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); +} + +void Parts::setPlayMode(uint8_t _mode) { + m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); + g_playMode = _mode; + getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); +} + +void Parts::timerCallback() +{ + // ugly (polling!) way for refreshing presets names as this is temporary ui + const auto multiMode = m_controller.isMultiMode(); + for (auto pt = 0; pt < 16; pt++) + { + bool singlePartOrInMulti = pt == 0 || multiMode; + m_presetNames[pt].setVisible(singlePartOrInMulti); + m_prevPatch[pt].setVisible(singlePartOrInMulti); + m_nextPatch[pt].setVisible(singlePartOrInMulti); + if (singlePartOrInMulti) + m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); + } + +} diff --git a/source/jucePlugin/ui/Virus_Parts.h b/source/jucePlugin/ui/Virus_Parts.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../PluginProcessor.h" +#include "Virus_Buttons.h" +#include <juce_gui_extra/juce_gui_extra.h> +#include "../VirusController.h" +class VirusParameterBinding; + +class Parts : public juce::Component, private juce::Timer +{ + public: + Parts(VirusParameterBinding& _parameterBinding, Virus::Controller& _controller); + static constexpr auto kPartGroupId = 0x3FBBC; + private: + void changePart(uint8_t _part); + void setPlayMode(uint8_t _mode); + void timerCallback() override; + Virus::Controller &m_controller; + VirusParameterBinding &m_parameterBinding; + + Buttons::PartSelectButton m_partSelect[16]; + juce::Label m_partLabels[16]; + juce::TextButton m_presetNames[16]; + juce::TextButton m_nextPatch[16]; + juce::TextButton m_prevPatch[16]; + juce::Slider m_partVolumes[16]; + juce::Slider m_partPans[16]; + juce::TextButton m_btSingleMode; + juce::TextButton m_btMultiSingleMode; + juce::TextButton m_btMultiMode; +}; diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.cpp b/source/jucePlugin/ui/Virus_PatchBrowser.cpp @@ -4,6 +4,7 @@ #include "Virus_PatchBrowser.h" #include <juce_gui_extra/juce_gui_extra.h> using namespace juce; +using namespace virusLib; constexpr auto comboBoxWidth = 98; const juce::Array<juce::String> categories = {"", "Lead", "Bass", "Pad", "Decay", "Pluck", "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", @@ -34,11 +35,13 @@ PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Con m_patchList.setBounds(m_bankList.getBounds().translated(m_bankList.getWidth(), 0)); - m_patchList.getHeader().addColumn("#", 0, 32); - m_patchList.getHeader().addColumn("Name", 1, 150); - m_patchList.getHeader().addColumn("Category1", 2, 100); - m_patchList.getHeader().addColumn("Category2", 3, 100); - m_patchList.getHeader().addColumn("Arp", 4, 32); + m_patchList.getHeader().addColumn("#", Columns::INDEX, 32); + m_patchList.getHeader().addColumn("Name", Columns::NAME, 140); + m_patchList.getHeader().addColumn("Category1", Columns::CAT1, 90); + m_patchList.getHeader().addColumn("Category2", Columns::CAT2, 90); + m_patchList.getHeader().addColumn("Arp", Columns::ARP, 32); + m_patchList.getHeader().addColumn("Uni", Columns::UNI, 32); + m_patchList.getHeader().addColumn("Ver", Columns::VER, 32); addAndMakeVisible(m_bankList); addAndMakeVisible(m_patchList); @@ -48,6 +51,25 @@ PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Con void PatchBrowser::selectionChanged() {} +VirusModel guessVersion(uint8_t *data) { + if (data[51] > 3) { + // check extra filter modes + return VirusModel::C; + } + if(data[179] == 0x40 && data[180] == 0x40) // soft knobs don't exist on B so they have fixed value + return VirusModel::B; + /*if (data[232] != 0x03 || data[235] != 0x6c || data[238] != 0x01) { // extra mod slots + return VirusModel::C; + }*/ + /*if(data[173] != 0x00 || data[174] != 0x00) // EQ + return VirusModel::C;*/ + /*if (data[220] != 0x40 || data[221] != 0x54 || data[222] != 0x20 || data[223] != 0x40 || data[224] != 0x40) { + // eq controls + return VirusModel::C; + }*/ + return VirusModel::C; + +} void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e) { auto ext = file.getFileExtension().toLowerCase(); @@ -58,11 +80,18 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e m_patches.clear(); juce::MemoryBlock data; - file.loadFileAsData(data); - uint8_t index = 0; + if (!file.loadFileAsData(data)) { + return; + } + int index = 0; for (auto it = data.begin(); it != data.end(); it += 267) { - if ((it + 267) <= data.end()) + if ((uint8_t)*it == (uint8_t)0xf0 + && (uint8_t)*(it+1) == (uint8_t)0x00 + && (uint8_t)*(it+2) == (uint8_t)0x20 + && (uint8_t)*(it+3) == (uint8_t)0x33 + && (uint8_t)*(it+4) == (uint8_t)0x01 + && (uint8_t)*(it+6) == (uint8_t)virusLib::DUMP_SINGLE) { Patch patch; patch.progNumber = index; @@ -70,11 +99,19 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.name = parseAsciiText(patch.data, 128 + 112); patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; + patch.unison = patch.data[97]; + if ((uint8_t)*(it + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 266) != (uint8_t)0xf8) { + patch.model = VirusModel::TI; + } + else { + patch.model = guessVersion(patch.data); + } m_patches.add(patch); index++; } } m_patchList.updateContent(); + m_patchList.deselectAllRows(); m_patchList.repaint(); // force repaint since row number doesn't often change } else if (ext == ".mid" || ext == ".midi") @@ -92,9 +129,13 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e for (auto it = ptr; it < end; it += 1) { - if ((uint8_t)*it == (uint8_t)0xf0 && (it + 267) < end) + if ((uint8_t)*it == (uint8_t)0xf0 && (it + 267) < end) // we don't check for sysex eof so we can load TI banks { - if ((uint8_t) * (it + 1) == (uint8_t)0x00) + if ((uint8_t) *(it+1) == (uint8_t)0x00 + && (uint8_t)*(it+2) == 0x20 + && (uint8_t)*(it+3) == 0x33 + && (uint8_t)*(it+4) == 0x01 + && (uint8_t)*(it+6) == virusLib::DUMP_SINGLE) { auto syx = Virus::SysEx(it, it + 267); syx[7] = 0x01; // force to bank a @@ -106,11 +147,22 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.name = parseAsciiText(patch.data, 128 + 112); patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; + patch.unison = patch.data[97]; + if ((uint8_t)*(it + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 266) != (uint8_t)0xf8) { + patch.model = VirusModel::TI; + } + else { + patch.model = guessVersion(patch.data); + } m_patches.add(patch); index++; it += 266; } - else // some midi files have two bytes after the 0xf0 + else if((uint8_t)*(it+3) == 0x00 // some midi files have two bytes after the 0xf0 + && (uint8_t)*(it+4) == 0x20 + && (uint8_t)*(it+5) == 0x33 + && (uint8_t)*(it+6) == 0x01 + && (uint8_t)*(it+8) == virusLib::DUMP_SINGLE) { auto syx = Virus::SysEx(); syx.push_back(0xf0); @@ -127,6 +179,13 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.name = parseAsciiText(patch.data, 128 + 112); patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; + patch.unison = patch.data[97]; + if ((uint8_t)*(it + 2 + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 2 + 266) != (uint8_t)0xf8) { + patch.model = VirusModel::TI; + } + else { + patch.model = guessVersion(patch.data); + } m_patches.add(patch); index++; @@ -136,6 +195,7 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e } } m_patchList.updateContent(); + m_patchList.deselectAllRows(); m_patchList.repaint(); } } @@ -163,25 +223,34 @@ void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width auto rowElement = m_patches[rowNumber]; //auto text = rowElement.name; juce::String text = ""; - if (columnId == 0) + if (columnId == Columns::INDEX) text = juce::String(rowElement.progNumber); - else if (columnId == 1) + else if (columnId == Columns::NAME) text = rowElement.name; - else if (columnId == 2) + else if (columnId == Columns::CAT1) text = categories[rowElement.category1]; - else if (columnId == 3) + else if (columnId == Columns::CAT2) text = categories[rowElement.category2]; - else if (columnId == 4) + else if (columnId == Columns::ARP) text = rowElement.data[129] != 0 ? "Y" : " "; + else if(columnId == Columns::UNI) + text = rowElement.unison == 0 ? " " : juce::String(rowElement.unison+1); + else if (columnId == Columns::VER) { + if(rowElement.model < ModelList.size()) + text = ModelList[rowElement.model]; + } g.drawText(text, 2, 0, width - 4, height, juce::Justification::centredLeft, true); // [6] g.setColour(getLookAndFeel().findColour(juce::ListBox::backgroundColourId)); g.fillRect(width - 1, 0, 1, height); // [7] } -void PatchBrowser::selectedRowsChanged(int lastRowSelected) { +void PatchBrowser::selectedRowsChanged(int lastRowSelected) { auto idx = m_patchList.getSelectedRow(); + if (idx == -1) { + return; + } uint8_t syxHeader[9] = {0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x00, 0x00}; - syxHeader[8] = m_controller.isMultiMode() ? m_parameterBinding.m_part : virusLib::ProgramType::SINGLE; // set edit buffer + syxHeader[8] = m_controller.isMultiMode() ? m_controller.getCurrentPart() : virusLib::ProgramType::SINGLE; // set edit buffer const uint8_t syxEof = 0xF7; uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; uint8_t data[256]; @@ -205,3 +274,59 @@ void PatchBrowser::selectedRowsChanged(int lastRowSelected) { m_controller.sendSysEx(syx); // send to edit buffer m_controller.parseMessage(syx); // update ui } + +void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const juce::MouseEvent &) +{ + if(rowNumber == m_patchList.getSelectedRow()) { + selectedRowsChanged(0); + } +} + +class PatchBrowser::PatchBrowserSorter +{ +public: + PatchBrowserSorter (int attributeToSortBy, bool forwards) + : attributeToSort (attributeToSortBy), + direction (forwards ? 1 : -1) + {} + + int compareElements (Patch first, Patch second) const + { + if(attributeToSort == Columns::INDEX) { + return direction * (first.progNumber - second.progNumber); + } + else if (attributeToSort == Columns::NAME) { + return direction * first.name.compareIgnoreCase(second.name); + } + else if (attributeToSort == Columns::CAT1) { + return direction * (first.category1 - second.category1); + } + else if (attributeToSort == Columns::CAT2) { + return direction * (first.category2 - second.category2); + } + else if (attributeToSort == Columns::ARP) { + return direction * (first.data[129]- second.data[129]); + } + else if (attributeToSort == Columns::UNI) { + return direction * (first.unison - second.unison); + } + else if (attributeToSort == Columns::VER) { + return direction * (first.model - second.model); + } + return direction * (first.progNumber - second.progNumber); + } + +private: + int attributeToSort; + int direction; +}; + +void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards) +{ + if (newSortColumnId != 0) + { + PatchBrowser::PatchBrowserSorter sorter (newSortColumnId, isForwards); + m_patches.sort(sorter); + m_patchList.updateContent(); + } +} diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.h b/source/jucePlugin/ui/Virus_PatchBrowser.h @@ -6,6 +6,18 @@ #include "../VirusController.h" class VirusParameterBinding; +const juce::Array<juce::String> ModelList = {"A","B","C","TI"}; +struct Patch +{ + int progNumber; + juce::String name; + uint8_t category1; + uint8_t category2; + uint8_t data[256]; + virusLib::VirusModel model; + uint8_t unison; +}; + class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel { @@ -15,15 +27,6 @@ public: private: VirusParameterBinding &m_parameterBinding; Virus::Controller& m_controller; - struct Patch - { - uint8_t progNumber; - juce::String name; - uint8_t category1; - uint8_t category2; - uint8_t data[256]; - - }; template <typename T> juce::String parseAsciiText(const T &msg, const int start) const { char text[Virus::Controller::kNameLength + 1]; @@ -52,4 +55,16 @@ private: bool rowIsSelected) override; virtual void selectedRowsChanged(int lastRowSelected) override; + virtual void cellDoubleClicked (int rowNumber, int columnId, const juce::MouseEvent &) override; + void sortOrderChanged(int newSortColumnId, bool isForwards) override; + class PatchBrowserSorter; + enum Columns { + INDEX = 1, + NAME = 2, + CAT1 = 3, + CAT2 = 4, + ARP = 5, + UNI = 6, + VER = 7 + }; }; diff --git a/source/jucePlugin/version.h b/source/jucePlugin/version.h @@ -1,4 +1,4 @@ #pragma once -static constexpr const char* const g_pluginVersionString = "1.2.1"; -static constexpr uint32_t g_pluginVersion = 121; +static constexpr const char* const g_pluginVersionString = "1.2.3"; +static constexpr uint32_t g_pluginVersion = 123; diff --git a/source/virusLib/microcontroller.cpp b/source/virusLib/microcontroller.cpp @@ -492,7 +492,7 @@ bool Microcontroller::sendSysex(const std::vector<uint8_t>& _data, bool _cancelI const auto param = _data[8]; const auto value = _data[9]; - if(page == PAGE_C) + if(page == PAGE_C || (page == PAGE_B && param == CLOCK_TEMPO)) { applyToMultiEditBuffer(part, param, value); @@ -903,7 +903,13 @@ void Microcontroller::applyToSingleEditBuffer(TPreset& _single, const Page _page void Microcontroller::applyToMultiEditBuffer(const uint8_t _part, const uint8_t _param, const uint8_t _value) { - // TODO: This is horrible. We need to remap everything + // remap page C parameters into the multi edit buffer + if (_part == PAGE_C && _param >= PART_MIDI_CHANNEL && _param <= PART_OUTPUT_SELECT) { + m_multiEditBuffer[MD_PART_MIDI_CHANNEL + ((_param-PART_MIDI_CHANNEL)*16) + _part] = _value; + } + else if (_part == PAGE_B && _param == CLOCK_TEMPO) { + m_multiEditBuffer[MD_CLOCK_TEMPO] = _value; + } } } diff --git a/source/virusLib/microcontrollerTypes.h b/source/virusLib/microcontrollerTypes.h @@ -38,6 +38,8 @@ namespace virusLib MULTI_NAME_CHAR_8 = 13, MULTI_NAME_CHAR_9 = 14, + CLOCK_TEMPO = 16, // this is actually in page B + MULTI_DELAY_OUTPUT_SELECT = 22, UNK1a = 26, @@ -81,6 +83,7 @@ namespace virusLib MIDI_ARPEGGIATOR_SEND = 96, MULTI_PROGRAM_CHANGE = 105, MIDI_CLOCK_RX = 106, + EFFECT_SEND = 113, // actually in bank A UNK6d = 109, PLAY_MODE = 122, PART_NUMBER = 123, @@ -174,7 +177,13 @@ namespace virusLib H, Count }; - + enum VirusModel : uint8_t + { + A, + B, + C, + TI + }; uint8_t toMidiByte(BankNumber _bank); BankNumber fromMidiByte(uint8_t _byte); uint32_t toArrayIndex(BankNumber _bank);