zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit fcdfb79d527edb27fb8117bb111df61af04ffc2c
parent a0f5fe2d7357febd8688209870fddc3507b1a6a7
Author: friedolino78 <[email protected]>
Date:   Sun, 31 Jul 2022 00:17:03 +0200

Add sympathetic resonance effect (#141)

This uses a comb filter bank to emulate the resonance
of passive strings in a piano.
Diffstat:
Msrc/DSP/CombFilter.cpp | 2+-
Msrc/Effects/CMakeLists.txt | 2++
Asrc/Effects/CombFilterBank.cpp | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Effects/CombFilterBank.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Effects/EffectMgr.cpp | 7++++++-
Asrc/Effects/Sympathetic.cpp | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Effects/Sympathetic.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/UI/EffUI.fl | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/UI/MasterUI.fl | 146++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/globals.h | 4++++
10 files changed, 864 insertions(+), 132 deletions(-)

diff --git a/src/DSP/CombFilter.cpp b/src/DSP/CombFilter.cpp @@ -38,7 +38,7 @@ inline float CombFilter::tanhX(const float x) { // Pade approximation of tanh(x) bound to [-1 .. +1] // https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html - float x2 = x*x; + const float x2 = x*x; return (x*(105.0f+10.0f*x2)/(105.0f+(45.0f+x2)*x2)); // } diff --git a/src/Effects/CMakeLists.txt b/src/Effects/CMakeLists.txt @@ -2,6 +2,7 @@ set(zynaddsubfx_effect_SRCS Effects/Alienwah.cpp Effects/Chorus.cpp Effects/Distortion.cpp + Effects/CombFilterBank.cpp Effects/DynamicFilter.cpp Effects/Echo.cpp Effects/Effect.cpp @@ -10,5 +11,6 @@ set(zynaddsubfx_effect_SRCS Effects/EQ.cpp Effects/Phaser.cpp Effects/Reverb.cpp + Effects/Sympathetic.cpp PARENT_SCOPE ) diff --git a/src/Effects/CombFilterBank.cpp b/src/Effects/CombFilterBank.cpp @@ -0,0 +1,130 @@ +#include <cmath> +#include "../Misc/Allocator.h" +#include "../Misc/Util.h" +#include "CombFilterBank.h" + +namespace zyn { + + CombFilterBank::CombFilterBank(Allocator *alloc, unsigned int samplerate_, int buffersize_, float initgain): + inputgain(1.0f), + outgain(1.0f), + gainbwd(initgain), + baseFreq(110.0f), + nrOfStrings(0), + memory(*alloc), + samplerate(samplerate_), + buffersize(buffersize_) + { + // setup the smoother for gain parameter + gain_smoothing.sample_rate(samplerate/16); + gain_smoothing.thresh(0.02f); // TBD: 2% jump audible? + gain_smoothing.cutoff(1.0f); + gain_smoothing.reset(gainbwd); + pos_writer = 0; + } + + CombFilterBank::~CombFilterBank() + { + setStrings(0, baseFreq); + } + + void CombFilterBank::setStrings(unsigned int nrOfStringsNew, const float baseFreqNew) + { + // limit nrOfStringsNew + nrOfStringsNew = min(NUM_SYMPATHETIC_STRINGS,nrOfStringsNew); + + if(nrOfStringsNew == nrOfStrings && baseFreqNew == baseFreq) + return; + + const unsigned int mem_size_new = (int)ceilf(( (float)samplerate/baseFreqNew*1.03f + buffersize + 2)/16) * 16; + if(mem_size_new == mem_size) + { + if(nrOfStringsNew>nrOfStrings) + { + for(unsigned int i = nrOfStrings; i < nrOfStringsNew; ++i) + { + string_smps[i] = memory.valloc<float>(mem_size); + memset(string_smps[i], 0, mem_size*sizeof(float)); + } + } + else if(nrOfStringsNew<nrOfStrings) + for(unsigned int i = nrOfStringsNew; i < nrOfStrings; ++i) + memory.devalloc(string_smps[i]); + } else + { + // free the old buffers (wrong size for baseFreqNew) + for(unsigned int i = 0; i < nrOfStrings; ++i) + memory.devalloc(string_smps[i]); + + // allocate buffers with new size + for(unsigned int i = 0; i < nrOfStringsNew; ++i) + { + string_smps[i] = memory.valloc<float>(mem_size_new); + memset(string_smps[i], 0, mem_size_new*sizeof(float)); + } + // update mem_size and baseFreq + mem_size = mem_size_new; + baseFreq = baseFreqNew; + } + // update nrOfStrings + nrOfStrings = nrOfStringsNew; + } + + inline float CombFilterBank::tanhX(const float x) + { + // Pade approximation of tanh(x) bound to [-1 .. +1] + // https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html + const float x2 = x*x; + return (x*(105.0f+10.0f*x2)/(105.0f+(45.0f+x2)*x2)); + } + + inline float CombFilterBank::sampleLerp(const float *smp, const float pos) const { + int poshi = (int)pos; // integer part (pos >= 0) + float poslo = pos - (float) poshi; // decimal part + // linear interpolation between samples + return smp[poshi] + poslo * (smp[(poshi+1)%mem_size]-smp[poshi]); + } + + void CombFilterBank::filterout(float *smp) + { + // no string -> no sound + if (nrOfStrings==0) return; + + // interpolate gainbuf values over buffer length using value smoothing filter (lp) + // this should prevent popping noise when controlled binary with 0 / 127 + // new control rate = samplerate / 16 + const unsigned int gainbufsize = buffersize / 16; + float gainbuf[gainbufsize]; // buffer for value smoothing filter + if (!gain_smoothing.apply( gainbuf, gainbufsize, gainbwd ) ) // interpolate the gain value + std::fill(gainbuf, gainbuf+gainbufsize, gainbwd); // if nothing to interpolate (constant value) + + for (unsigned int i = 0; i < buffersize; ++i) + { + // apply input gain + const float input_smp = smp[i]*inputgain; + + for (unsigned int j = 0; j < nrOfStrings; ++j) + { + assert(float(mem_size)>delays[j]); + // calculate the feedback sample positions in the buffer + const float pos_reader = fmodf(float(pos_writer+mem_size) - delays[j], float(mem_size)); + + // sample at that position + const float sample = sampleLerp(string_smps[j], pos_reader); + string_smps[j][pos_writer] = input_smp + tanhX(sample*gainbuf[i/16]); + } + // mix output buffer samples to output sample + smp[i]=0.0f; + for (unsigned int j = 0; j < nrOfStrings; ++j) + smp[i] += string_smps[j][pos_writer]; + + // apply output gain to sum of strings and + // divide by nrOfStrings to get mean value + // division by zero is catched at the beginning filterOut() + smp[i] *= outgain / (float)nrOfStrings; + + // increment writing position + ++pos_writer %= mem_size; + } + } +} diff --git a/src/Effects/CombFilterBank.h b/src/Effects/CombFilterBank.h @@ -0,0 +1,46 @@ +#include "../Misc/Allocator.h" +#include "../globals.h" +#include "../DSP/Value_Smoothing_Filter.h" + +#pragma once + + +namespace zyn { + +/**Comb Filter Bank for sympathetic Resonance*/ +class CombFilterBank +{ + public: + CombFilterBank(Allocator *alloc, unsigned int samplerate_, int buffersize_, float initgain); + ~CombFilterBank(); + void filterout(float *smp); + + float delays[NUM_SYMPATHETIC_STRINGS]={}; + float inputgain; + float outgain; + float gainbwd; + + void setStrings(unsigned int nr, const float basefreq); + + + private: + static float tanhX(const float x); + float sampleLerp(const float *smp, const float pos) const; + + float* string_smps[NUM_SYMPATHETIC_STRINGS] = {}; + float baseFreq; + unsigned int nrOfStrings=0; + unsigned int pos_writer = 0; + + /* for smoothing gain jump when using binary valued sustain pedal */ + Value_Smoothing_Filter gain_smoothing; + + Allocator &memory; + unsigned int mem_size=0; + int samplerate=0; + unsigned int buffersize=0; + + +}; + +} diff --git a/src/Effects/EffectMgr.cpp b/src/Effects/EffectMgr.cpp @@ -26,6 +26,7 @@ #include "EQ.h" #include "DynamicFilter.h" #include "Phaser.h" +#include "Sympathetic.h" #include "../Misc/XMLwrapper.h" #include "../Misc/Util.h" #include "../Misc/Time.h" @@ -227,7 +228,7 @@ static const rtosc::Ports local_ports = { d.reply(d.loc, "bb", sizeof(a), a, sizeof(b), b); }}, {"efftype::i:c:S", rOptions(Disabled, Reverb, Echo, Chorus, - Phaser, Alienwah, Distortion, EQ, DynFilter) rDefault(Disabled) + Phaser, Alienwah, Distortion, EQ, DynFilter, Sympathetic) rDefault(Disabled) rProp(parameter) rDoc("Get Effect Type"), NULL, rCOptionCb(obj->nefx, obj->changeeffectrt(var))}, {"efftype:b", rProp(internal) rDoc("Pointer swap EffectMgr"), NULL, @@ -255,6 +256,7 @@ static const rtosc::Ports local_ports = { rSubtype(EQ), rSubtype(Phaser), rSubtype(Reverb), + rSubtype(Sympathetic), }; const rtosc::Ports &EffectMgr::ports = local_ports; @@ -335,6 +337,9 @@ void EffectMgr::changeeffectrt(int _nefx, bool avoidSmash) case 8: efx = memory.alloc<DynamicFilter>(pars); break; + case 9: + efx = memory.alloc<Sympathetic>(pars); + break; //put more effect here default: efx = NULL; diff --git a/src/Effects/Sympathetic.cpp b/src/Effects/Sympathetic.cpp @@ -0,0 +1,326 @@ +/* + ZynAddSubFX - a software synthesizer + + Sympathetic.cpp - Distorsion effect + Copyright (C) 2002-2005 Nasca Octavian Paul + Author: Nasca Octavian Paul + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. +*/ + +#include "Sympathetic.h" +#include "../DSP/AnalogFilter.h" +#include "CombFilterBank.h" +#include "../Misc/Allocator.h" +#include <cmath> +#include <rtosc/ports.h> +#include <rtosc/port-sugar.h> +#include "../globals.h" + +namespace zyn { + +#define rObject Sympathetic +#define rBegin [](const char *msg, rtosc::RtData &d) { +#define rEnd } + +#define rEffParRange(name, idx, ...) \ + {STRINGIFY(name) "::i", rProp(parameter) rDefaultDepends(preset) \ + DOC(__VA_ARGS__), NULL, rEffParCb(idx)} + +rtosc::Ports Sympathetic::ports = { + {"preset::i", rProp(parameter) + rOptions(Piano, Grand, Guitar, 12-String) + rDoc("Instrument Presets"), 0, + rBegin; + rObject *o = (rObject*)d.obj; + if(rtosc_narguments(msg)) + o->setpreset(rtosc_argument(msg, 0).i); + else + d.reply(d.loc, "i", o->Ppreset); + rEnd}, + rEffParVol(rDefault(127)), + rEffParPan(rDefault(64)), + rEffPar(Pq, 2, rShort("q"), rDefault(65), + "Resonance"), + rEffPar(Pdrive, 3, rShort("dr"), rDefault(65), + "Input Amplification"), + rEffPar(Plevel, 4, rShort("lev"), rDefault(65), + "Output Amplification"), + rEffPar(Punison_frequency_spread, 5, rShort("detune"), rDefault(30), + "Unison String Detune"), + rEffParTF(Pnegate, 6, rShort("neg"), rDefault(false), "Negate Signal"), + rEffPar(Plpf, 7, rShort("lpf"), rDefault(127), "Low Pass Cutoff"), + rEffPar(Phpf, 8, rShort("hpf"), rDefault(0), "High Pass Cutoff"), + rEffParRange(Punison_size, 9, rShort("uni"), rLinear(1,3), rDefault(1), + "Number of Unison Strings"), + rEffParRange(Pstrings, 10, rShort("str"), rLinear(0,76), rDefault(0), + "Number of Strings"), + rEffPar(Pbasenote, 11, rShort("base"), rDefault(57), // basefreq = powf(2.0f, (basenote-69)/12)*440; 57->220Hz + "Midi Note of Lowest String"), + rArrayF(freqs, 88, rLinear(27.50f,4186.01f), + "String Frequencies"), +}; + +#undef rBegin +#undef rEnd +#undef rObject + +Sympathetic::Sympathetic(EffectParams pars) + :Effect(pars), + Pvolume(127), + Pdrive(65), + Plevel(65), + Ptype(0), + Pnegate(0), + Plpf(127), + Phpf(0), + Pstereo(0), + Pq(65), + Punison_size(1), + Punison_frequency_spread(30), + baseFreq(220.0f) +{ + lpfl = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize); + lpfr = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize); + hpfl = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize); + hpfr = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize); + + // precalc gainbwd_init = gainbwd_offset + gainbwd_factor * Pq + // 0.873f + 0.001f * 65 = 0.873f + 0.065f = 0.938f + filterBank = memory.alloc<CombFilterBank>(&memory, pars.srate, pars.bufsize, 0.938f); + calcFreqs(); // sets freqs + cleanup(); +} + +Sympathetic::~Sympathetic() +{ + memory.dealloc(lpfl); + memory.dealloc(lpfr); + memory.dealloc(hpfl); + memory.dealloc(hpfr); + memory.dealloc(filterBank); +} + +//Cleanup the effect +void Sympathetic::cleanup(void) +{ + lpfl->cleanup(); + hpfl->cleanup(); + lpfr->cleanup(); + hpfr->cleanup(); +} + + +//Apply the filters +void Sympathetic::applyfilters(float *efxoutl, float *efxoutr) +{ + if(Plpf!=127) lpfl->filterout(efxoutl); + if(Phpf!=0) hpfl->filterout(efxoutl); + if(Pstereo != 0) { //stereo + if(Plpf!=127) lpfr->filterout(efxoutr); + if(Phpf!=0) hpfr->filterout(efxoutr); + } +} + + +//Effect output +void Sympathetic::out(const Stereo<float *> &smp) +{ + float inputvol = powf(2.0f, (Pdrive - 65.0f) / 128.0f) / 2.0f; + if(Pnegate) + inputvol *= -1.0f; + + if(Pstereo) //Stereo + for(int i = 0; i < buffersize; ++i) { + efxoutl[i] = smp.l[i] * inputvol * pangainL; + efxoutr[i] = smp.r[i] * inputvol * pangainR; + } + else //Mono + for(int i = 0; i < buffersize; ++i) + efxoutl[i] = (smp.l[i] * pangainL + smp.r[i] * pangainR) * inputvol; + + filterBank->filterout(efxoutl); + if(Pstereo) + filterBank->filterout(efxoutr); + + applyfilters(efxoutl, efxoutr); + + if(!Pstereo) + memcpy(efxoutr, efxoutl, bufferbytes); + + float level = dB2rap(60.0f * Plevel / 127.0f - 40.0f); + for(int i = 0; i < buffersize; ++i) { + float lout = efxoutl[i]; + float rout = efxoutr[i]; + float l = lout * (1.0f - lrcross) + rout * lrcross; + float r = rout * (1.0f - lrcross) + lout * lrcross; + lout = l; + rout = r; + + efxoutl[i] = lout * 2.0f * level; + efxoutr[i] = rout * 2.0f * level; + } +} + +//Parameter control +void Sympathetic::setvolume(unsigned char _Pvolume) +{ + Pvolume = _Pvolume; + + if(insertion == 0) { + outvolume = powf(0.01f, (1.0f - Pvolume / 127.0f)) * 4.0f; + volume = 1.0f; + } + else + volume = outvolume = Pvolume / 127.0f; + if(Pvolume == 0) + cleanup(); +} + +void Sympathetic::setlpf(unsigned char _Plpf) +{ + Plpf = _Plpf; + float fr = expf(sqrtf(Plpf / 127.0f) * logf(25000.0f)) + 40.0f; + lpfl->setfreq(fr); + lpfr->setfreq(fr); +} + +void Sympathetic::sethpf(unsigned char _Phpf) +{ + Phpf = _Phpf; + float fr = expf(sqrtf(Phpf / 127.0f) * logf(25000.0f)) + 20.0f; + hpfl->setfreq(fr); + hpfr->setfreq(fr); +} + +void Sympathetic::calcFreqs() +{ + const float unison_spread_semicent = powf(Punison_frequency_spread / 63.5f, 2.0f) * 25.0f; + const float unison_real_spread_up = powf(2.0f, (unison_spread_semicent * 0.5f) / 1200.0f); + const float unison_real_spread_down = 1.0f/unison_real_spread_up; + + for(unsigned int i = 0; i < Punison_size*Pstrings; i+=Punison_size) + { + const float centerFreq = powf(2.0f, (float)i / 12.0f) * baseFreq; + filterBank->delays[i] = ((float)samplerate)/centerFreq; + if (Punison_size > 1) filterBank->delays[i+1] = ((float)samplerate)/(centerFreq * unison_real_spread_up); + if (Punison_size > 2) filterBank->delays[i+2] = ((float)samplerate)/(centerFreq * unison_real_spread_down); + } + filterBank->setStrings(Pstrings*Punison_size,baseFreq); + +} + +unsigned char Sympathetic::getpresetpar(unsigned char npreset, unsigned int npar) +{ +#define PRESET_SIZE 13 +#define NUM_PRESETS 4 + static const unsigned char presets[NUM_PRESETS][PRESET_SIZE] = { + //Vol Pan Q Drive Lev Spr neg lp hp sz strings note cross + //Piano 12-String + {100, 64, 125, 5, 80, 10, 0, 127, 0, 3, 12, 57}, + //Piano 60-String + {80, 64, 125, 5, 90, 5, 0, 127, 0, 1, 60, 33}, + //Guitar 6-String + {100, 64, 110, 5, 65, 0, 0, 127, 0, 1, 6, 52}, + //Guitar 12-String + {90, 64, 110, 5, 77, 10, 0, 127, 0, 2, 6, 52}, + }; + if(npreset < NUM_PRESETS && npar < PRESET_SIZE) { + if(npar == 0 && insertion == 0) { + /* lower the volume if this is system effect */ + return (3 * presets[npreset][npar]) / 2; + } + return presets[npreset][npar]; + } + return 0; +} + +void Sympathetic::setpreset(unsigned char npreset) +{ + if(npreset >= NUM_PRESETS) + npreset = NUM_PRESETS - 1; + for(int n = 0; n != 128; n++) + changepar(n, getpresetpar(npreset, n)); + Ppreset = npreset; + cleanup(); +} + + +void Sympathetic::changepar(int npar, unsigned char value) +{ + switch(npar) { + case 0: + setvolume(value); + break; + case 1: + setpanning(value); + break; + case 2: + Pq = value; + filterBank->gainbwd = gainbwd_offset + (float)Pq * gainbwd_factor; + break; + case 3: + Pdrive = value; + filterBank->inputgain = (float)Pdrive/65.0f; + break; + case 4: + Plevel = value; + filterBank->outgain = (float)Plevel/65.0f; + break; + case 5: + Punison_frequency_spread = value; + calcFreqs(); + break; + case 6: + if(value > 1) + Pnegate = 1; + else + Pnegate = value; + break; + case 7: + setlpf(value); + break; + case 8: + sethpf(value); + break; + case 9: + Punison_size = limit(value, (unsigned char) 1, (unsigned char) 3); + calcFreqs(); + break; + case 10: + Pstrings = limit(value, (unsigned char) 0, (unsigned char) 76); + calcFreqs(); + break; + case 11: + Pbasenote = value; + baseFreq = powf(2.0f, ((float)Pbasenote-69.0f)/12.0f)*440.0f; + calcFreqs(); + break; + default: + break; + } +} + +unsigned char Sympathetic::getpar(int npar) const +{ + switch(npar) { + case 0: return Pvolume; + case 1: return Ppanning; + case 2: return Pq; + case 3: return Pdrive; + case 4: return Plevel; + case 5: return Punison_frequency_spread; + case 6: return Pnegate; + case 7: return Plpf; + case 8: return Phpf; + case 9: return Punison_size; + case 10: return Pstrings; + case 11: return Pbasenote; + default: return 0; //in case of bogus parameter number + } +} + +} diff --git a/src/Effects/Sympathetic.h b/src/Effects/Sympathetic.h @@ -0,0 +1,76 @@ +/* + ZynAddSubFX - a software synthesizer + + Sympathetic.cpp - Distorsion effect + Copyright (C) 2002-2005 Nasca Octavian Paul + Author: Nasca Octavian Paul + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. +*/ + +#ifndef SYMPATHETIC_H +#define SYMPATHETIC_H + +#include "Effect.h" + +namespace zyn { + +// coefficients for calculating gainbwd from Pq +// gainbwd = gainbwd_offset + Pq * gainbwd_factor +// designed for gainbwd range up to 1.0 at Pq==127 +const float gainbwd_offset = 0.873f; +const float gainbwd_factor = 0.001f; + +class Sympathetic:public Effect +{ + + public: + Sympathetic(EffectParams pars); + ~Sympathetic(); + void out(const Stereo<float *> &smp); + unsigned char getpresetpar(unsigned char npreset, unsigned int npar); + void setpreset(unsigned char npreset); + void changepar(int npar, unsigned char value); + unsigned char getpar(int npar) const; + void cleanup(void); + void applyfilters(float *efxoutl, float *efxoutr); + + static rtosc::Ports ports; + private: + //Parameters + unsigned char Pvolume; //Volume or E/R + unsigned char Pdrive; //the input amplification + unsigned char Plevel; //the output amplification + unsigned char Ptype; //Distorsion type + unsigned char Pnegate; //if the input is negated + unsigned char Plpf; //lowpass filter + unsigned char Phpf; //highpass filter + unsigned char Pstereo; //0=mono, 1=stereo + unsigned char Pq; //0=0.95 ... 127=1.05 + unsigned char Punison_size; //number of unison strings + unsigned char Punison_frequency_spread; + unsigned char Pstrings; //number of strings + unsigned char Pbasenote; //midi note of lowest string + + unsigned char spread, spread_old; + + float freqs[NUM_SYMPATHETIC_STRINGS]; + float baseFreq; + + void setvolume(unsigned char _Pvolume); + void setlpf(unsigned char _Plpf); + void sethpf(unsigned char _Phpf); + void calcFreqs(); + + //Real Parameters + class AnalogFilter * lpfl, *lpfr, *hpfl, *hpfr; + + class CombFilterBank * filterBank; +}; + +} + +#endif diff --git a/src/UI/EffUI.fl b/src/UI/EffUI.fl @@ -1,45 +1,45 @@ # data file for the Fltk User Interface Designer (fluid) -version 1.0302 -header_name {.h} +version 1.0302 +header_name {.h} code_name {.cc} decl {//Copyright (c) 2002-2005 Nasca Octavian Paul} {private local -} +} decl {//License: GNU GPL version 2 or later} {private local -} +} decl {\#include <stdlib.h>} {public local -} +} decl {\#include <stdio.h>} {public local -} +} decl {\#include <string.h>} {public local -} +} decl {\#include "../globals.h"} {public local -} +} decl {\#include "Fl_Osc_Dial.H"} {public local -} +} decl {\#include "Fl_Osc_Check.H"} {public local -} +} decl {\#include "Fl_EQGraph.H"} {public local -} +} decl {\#include "Fl_Osc_Pane.H"} {public local } decl {\#include "EnvelopeUI.h"} {public local -} +} decl {\#include "FilterUI.h"} {public local -} +} decl {\#include "../Misc/Util.h"} {public local -} +} decl {\#include "../Effects/EffectMgr.h"} {public local } @@ -51,7 +51,7 @@ decl {\#include "../Effects/Alienwah.h" /* for macros only, TODO */ } {public lo } decl {\#include "PresetsUI.h"} {public local -} +} decl {\#include "common.H"} {public local } @@ -74,10 +74,11 @@ effalienwahwindow->hide();//delete (effalienwahwindow); effdistortionwindow->hide();//delete (effdistortionwindow); effeqwindow->hide();//delete (effeqwindow); effdynamicfilterwindow->hide();//delete (effdynamicfilterwindow); +effsympatheticwindow->hide();//delete (effsympatheticwindow); if (filterwindow!=NULL){ - filterwindow->hide(); - delete(filterwindow); + filterwindow->hide(); + delete(filterwindow); };} {} } Function {make_null_window()} {} { @@ -155,7 +156,7 @@ if (filterwindow!=NULL){ Fl_Choice revp10 { label Type callback {if(o->value()==2) revp12->activate(); - else revp12->deactivate();} + else revp12->deactivate();} xywh {110 15 85 15} box UP_BOX down_box BORDER_BOX color 14 selection_color 7 labelfont 1 labelsize 10 align 5 textfont 1 textsize 10 code0 {o->init("parameter10");} class Fl_Osc_Choice @@ -1157,6 +1158,63 @@ eqgraph->update();} } } } + Function {make_sympathetic_window()} {} { + Fl_Window effsympatheticwindow { + xywh {974 596 380 100} type Double box UP_BOX color 51 labelfont 1 labelsize 19 + code3 {set_module_parameters(o);} + class Fl_Group visible + } { + Fl_Dial symp0 { + label Vol + tooltip {Effect Volume} xywh {10 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter0");} + class Fl_Osc_Dial + } + Fl_Dial symp2 { + label Q + tooltip {Resonance} xywh {45 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter2");} + class Fl_Osc_Dial + } + Fl_Dial symp3 { + label Drive + tooltip {Drive} xywh {80 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter3");} + class Fl_Osc_Dial + } + Fl_Dial symp4 { + label Lev + tooltip {Level} xywh {115 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter4");} + class Fl_Osc_Dial + } + Fl_Dial symp5 { + label Spr + tooltip {Spread} xywh {150 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter5");} + class Fl_Osc_Dial + } + Fl_Dial symp9 { + label Uni + tooltip {number of unisono strings} xywh {185 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 3 + code0 {o->init("parameter9");} + class Fl_Osc_Dial + } + Fl_Dial symp10 { + label Str + tooltip {number of strings} xywh {220 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 48 + code0 {o->init("parameter10");} + class Fl_Osc_Dial + } + Fl_Dial symp11 { + label B.note + tooltip {lowest midi note } xywh {255 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter11");} + class Fl_Osc_Dial + } + + } + } Function {make_filter_window()} {} { Fl_Window filterwindow { label {Filter Parameters for DynFilter Eff.} @@ -1191,6 +1249,7 @@ make_alienwah_window(); make_distortion_window(); make_eq_window(); make_dynamicfilter_window(); +make_sympathetic_window(); int px=this->parent()->x(); int py=this->parent()->y(); @@ -1204,6 +1263,7 @@ effalienwahwindow->position(px,py); effdistortionwindow->position(px,py); effeqwindow->position(px,py); effdynamicfilterwindow->position(px,py); +effsympatheticwindow->position(px,py); refresh();} {} } @@ -1222,15 +1282,16 @@ effalienwahwindow->hide(); effdistortionwindow->hide(); effeqwindow->hide(); effdynamicfilterwindow->hide(); +effsympatheticwindow->hide(); eqband=0; if (filterwindow){ - filterwindow->hide(); - delete(filterwindow); - filterwindow=NULL; + filterwindow->hide(); + delete(filterwindow); + filterwindow=NULL; }; - + if(insertion) { revp0->label("D/W"); echop0->label("D/W"); @@ -1244,34 +1305,36 @@ if (filterwindow){ switch(efftype){ case 1: effreverbwindow->show(); - break; + break; case 2: - effechowindow->show(); - break; + effechowindow->show(); + break; case 3: - effchoruswindow->show(); - break; + effchoruswindow->show(); + break; case 4: - effphaserwindow->show(); - break; + effphaserwindow->show(); + break; case 5: - effalienwahwindow->show(); - break; + effalienwahwindow->show(); + break; case 6: effdistortionwindow->show(); break; case 7:eqband=0; - bandcounter->value(eqband); - bandcounter->do_callback(); - eqgraph->redraw(); - effeqwindow->show(); + bandcounter->value(eqband); + bandcounter->do_callback(); + eqgraph->redraw(); + effeqwindow->show(); break; case 8:make_filter_window(); - - effdynamicfilterwindow->show(); - break; + effdynamicfilterwindow->show(); + break; + case 9: + effsympatheticwindow->show(); + break; default:effnullwindow->show(); - break; + break; }; this->show();} {selected @@ -1283,7 +1346,7 @@ this->show();} {selected } decl {int efftype;} {public local } -} +} class SimpleEffUI {open : {public Fl_Osc_Group,public PresetsUI_} } { @@ -1299,7 +1362,8 @@ effphaserwindow->hide();//delete (effphaserwindow); effalienwahwindow->hide();//delete (effalienwahwindow); effdistortionwindow->hide();//delete (effdistortionwindow); effeqwindow->hide();//delete (effeqwindow); -effdynamicfilterwindow->hide();//delete (effdynamicfilterwindow);} {} +effdynamicfilterwindow->hide();//delete (effdynamicfilterwindow); +effsympatheticwindow->hide();//delete (effsympatheticwindow);} {} } Function {make_null_window()} {} { Fl_Window effnullwindow { @@ -2021,6 +2085,63 @@ eqgraph->redraw();} } } } + Function {make_sympathetic_window()} {} { + Fl_Window effsympatheticwindow { + xywh {974 596 380 100} type Double box UP_BOX color 51 labelfont 1 labelsize 19 + code3 {set_module_parameters(o);} + class Fl_Group visible + } { + Fl_Dial symp0 { + label Vol + tooltip {Effect Volume} xywh {10 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter0");} + class Fl_Osc_Dial + } + Fl_Dial symp2 { + label Q + tooltip {Resonance} xywh {45 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter2");} + class Fl_Osc_Dial + } + Fl_Dial symp3 { + label Drive + tooltip {Drive} xywh {80 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter3");} + class Fl_Osc_Dial + } + Fl_Dial symp4 { + label Level + tooltip {Level} xywh {115 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter4");} + class Fl_Osc_Dial + } + Fl_Dial symp5 { + label Spread + tooltip {Spread} xywh {150 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter5");} + class Fl_Osc_Dial + } + Fl_Dial symp9 { + label Drive + tooltip {Drive} xywh {185 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter9");} + class Fl_Osc_Dial + } + Fl_Dial symp10 { + label Strings + tooltip {number of strings} xywh {220 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter10");} + class Fl_Osc_Dial + } + Fl_Dial symp11 { + label Basenote + tooltip {lowest midi note } xywh {255 40 30 30} box ROUND_UP_BOX labelfont 1 labelsize 11 maximum 127 + code0 {o->init("parameter11");} + class Fl_Osc_Dial + } + + } + } Function {init(bool ins_)} {open } { code {efftype = 0; @@ -2034,6 +2155,7 @@ make_alienwah_window(); make_distortion_window(); make_eq_window(); make_dynamicfilter_window(); +make_sympathetic_window(); int px=this->parent()->x(); int py=this->parent()->y(); @@ -2046,7 +2168,8 @@ effphaserwindow->position(px,py); effalienwahwindow->position(px,py); effdistortionwindow->position(px,py); effeqwindow->position(px,py); -effdynamicfilterwindow->position(px,py);} {} +effdynamicfilterwindow->position(px,py); +effsympatheticwindow->position(px,py);} {} } Function {refresh()} {open } { @@ -2064,48 +2187,52 @@ effalienwahwindow->hide(); effdistortionwindow->hide(); effeqwindow->hide(); effdynamicfilterwindow->hide(); +effsympatheticwindow->hide(); eqband=0; if(insertion) { - revp0->label("D/W"); - echop0->label("D/W"); - chorusp0->label("D/W"); - phaserp0->label("D/W"); - awp0->label("D/W"); - distp0->label("D/W"); - dfp0->label("D/W"); + revp0->label("D/W"); + echop0->label("D/W"); + chorusp0->label("D/W"); + phaserp0->label("D/W"); + awp0->label("D/W"); + distp0->label("D/W"); + dfp0->label("D/W"); } switch(efftype){ case 1: effreverbwindow->show(); - break; + break; case 2: - effechowindow->show(); - break; + effechowindow->show(); + break; case 3: - effchoruswindow->show(); - break; + effchoruswindow->show(); + break; case 4: - effphaserwindow->show(); - break; + effphaserwindow->show(); + break; case 5: - effalienwahwindow->show(); - break; + effalienwahwindow->show(); + break; case 6: effdistortionwindow->show(); break; case 7: - bandcounter->value(eqband); - bandcounter->do_callback(); - effeqwindow->show(); + bandcounter->value(eqband); + bandcounter->do_callback(); + effeqwindow->show(); break; case 8: - effdynamicfilterwindow->show(); - break; + effdynamicfilterwindow->show(); + break; + case 9: + effsympatheticwindow->show(); + break; default:effnullwindow->show(); - break; + break; }; this->show();} {} @@ -2116,4 +2243,4 @@ this->show();} {} } decl {int efftype;} {public local } -} +} diff --git a/src/UI/MasterUI.fl b/src/UI/MasterUI.fl @@ -1,86 +1,86 @@ # data file for the Fltk User Interface Designer (fluid) -version 1.0302 -header_name {.h} +version 1.0302 +header_name {.h} code_name {.cc} decl {//Copyright (c) 2002-2009 Nasca Octavian Paul - (c) 2009-2021 Mark McCurry} {private local -} +} decl {//License: GNU GPL version 2 or later} {private local -} +} decl {\#include <stdlib.h>} {public local -} +} decl {\#include <stdio.h>} {public local -} +} decl {\#include <string.h>} {public local -} +} decl {\#if ! defined(PLUGINVERSION) && HAS_X11 \#include "zynaddsubfx.xpm" \#endif} {private local -} +} decl {\#include "WidgetPDial.h"} {public local -} +} decl {\#include "ADnoteUI.h"} {public local -} +} decl {\#include "SUBnoteUI.h"} {public local -} +} decl {\#include "EffUI.h"} {public local -} +} decl {\#include "VirKeyboard.h"} {public local -} +} decl {\#include "ConfigUI.h"} {public local -} +} decl {\#include "BankUI.h"} {public local -} +} decl {\#include "PartUI.h"} {public local -} +} decl {\#include "MicrotonalUI.h"} {public local -} +} decl {\#include "PresetsUI.h"} {public local -} +} decl {\#include "NioUI.h"} {public global -} +} decl {\#include "VuPartMeter.h"} {public local -} +} decl {\#include "Fl_Osc_Dial.H"} {private local -} +} decl {\#include "Osc_DataModel.h"} {private local -} +} decl {\#include "Fl_Osc_TSlider.H"} {private local -} +} decl {\#include "VuMasterMeter.h"} {public local -} +} decl {\#include "PartNameButton.h"} {public local -} +} decl {\#include "common.H"} {public local -} +} decl {\#if USE_NSM \#include "NSM.H" extern NSM_Client *nsm; \#endif} {public local -} +} decl {\#if !defined(PLUGINVERSION) && defined(NTK_GUI) \#include <X11/xpm.h> @@ -90,25 +90,25 @@ decl {\#if !defined(PLUGINVERSION) && defined(NTK_GUI) decl {\#if !defined(PLUGINVERSION) && HAS_X11 \#include <FL/Fl_RGB_Image.H> \#endif} {private local -} +} decl {\#if !defined(PLUGINVERSION) && HAS_X11 \#include <FL/Fl.H> \#endif} {private local -} +} decl {\#if !defined(PLUGINVERSION) && HAS_X11 \#include <FL/Enumerations.H> \#endif} {private local -} +} decl {\#if !defined(PLUGINVERSION) && HAS_X11 \#include <FL/Fl_Pixmap.H> \#endif} {private local -} +} decl {\#include "../globals.h"} {public local -} +} class SysEffSend {: {public Fl_Osc_Dial} } { @@ -131,7 +131,7 @@ this->copy_label(tmp);} {} Function {~SysEffSend()} {} { code {hide();} {} } -} +} class Panellistitem {open : {public Fl_Osc_Group} } { @@ -233,10 +233,10 @@ end();} {} partpanning->update(); partvolume->update(); partname->update(); - + if ((int)bankui->cbwig->value()!=(npart+1)) panellistitemgroup->color(fl_rgb_color(160,160,160)); -else +else panellistitemgroup->color(fl_rgb_color(50,190,240)); panellistitemgroup->redraw();} {} @@ -250,7 +250,7 @@ panellistitemgroup->redraw();} {} } decl {PartUI *partui;} {private local } -} +} class MasterUI {open } { @@ -377,7 +377,7 @@ result=fileexists(filename); if (result) { result=0; if (!fl_choice("The file exists. \\nOverwrite it?","No","Yes",NULL)) return; - + }; @@ -453,7 +453,7 @@ int result=fileexists(filename); if (result) { result=0; if (!fl_choice("The file exists. \\nOverwrite it?","No","Yes",NULL)) return; - + }; osc->write("/save_xiz", "is", npart, filename); @@ -503,9 +503,9 @@ if (fl_choice("The file *might* exist. \\nOverwrite it?","No","Yes",NULL)) { MenuItem {} { label {Switch User Interface Mode} callback {if (fl_choice("Switch the User Interface to Beginner mode ?","No","Yes",NULL)){ - masterwindow->hide(); - refresh_master_ui(); - simplemasterwindow->show(); + masterwindow->hide(); + refresh_master_ui(); + simplemasterwindow->show(); osc->write("/config/cfg.UserInterfaceMode", "i", 2); };} xywh {15 15 100 20} @@ -608,6 +608,10 @@ syseffectui->refresh();} label DynFilter xywh {95 95 100 20} labelfont 1 labelsize 10 } + MenuItem {} { + label Sympathetic + xywh {105 105 100 20} labelfont 1 labelsize 10 + } } Fl_Group syseffectuigroup { xywh {5 203 380 95} color 48 @@ -657,11 +661,11 @@ inseffectui->refresh(); if (master->Pinsparts[ninseff]!=-1) { insefftype->activate(); - inseffectui->activate(); + inseffectui->activate(); inseffectuigroup->activate(); } else { - insefftype->deactivate(); - inseffectui->deactivate(); + insefftype->deactivate(); + inseffectui->deactivate(); inseffectuigroup->deactivate(); };*/} xywh {5 183 80 22} type Simple labelfont 1 labelsize 10 align 1 minimum 0 maximum 127 step 1 value 1 textfont 1 @@ -718,6 +722,10 @@ inseffectui->show();} label DynFilter xywh {105 105 100 20} labelfont 1 labelsize 10 } + MenuItem {} { + label Sympathetic + xywh {115 115 100 20} labelfont 1 labelsize 10 + } } Fl_Group inseffectuigroup {open xywh {5 205 380 95} box FLAT_BOX color 48 @@ -893,11 +901,11 @@ panelwindow->show();} xywh {15 35 335 55} labeltype EMBOSSED_LABEL labelsize 15 align 208 } Fl_Box {} { - label {This is free software; you may redistribute it and/or modify it under the terms of the + label {This is free software; you may redistribute it and/or modify it under the terms of the version 2 (or any later version) of the GNU General Public License as published by the Free Software Foundation. This program comes with - ABSOLUTELY NO WARRANTY. - See the version 2 (or any later version) of the + ABSOLUTELY NO WARRANTY. + See the version 2 (or any later version) of the GNU General Public License for details.} xywh {15 90 335 145} labelfont 1 labelsize 11 align 144 } @@ -977,7 +985,7 @@ updatepanel();} callback { if (isPlugin || fl_choice("Exit and leave the unsaved data?","No","Yes",NULL)) { - *exitprogram=1; + *exitprogram=1; };} open xywh {655 378 600 335} type Double class Fl_Osc_Window visible @@ -1069,9 +1077,9 @@ if (result==-10) fl_alert("Error: Could not load the file\\nbecause it is not an MenuItem {} { label {Switch User Interface Mode} callback {if (fl_choice("Switch the User Interface to Advanced mode ?","No","Yes",NULL)){ - simplemasterwindow->hide(); - refresh_master_ui(); - masterwindow->show(); + simplemasterwindow->hide(); + refresh_master_ui(); + masterwindow->show(); osc->write("/config/cfg.UserInterfaceMode", "i", 1); };} xywh {0 0 100 20} @@ -1271,6 +1279,10 @@ simplesyseffectui->refresh(); label DynFilter xywh {100 100 100 20} labelfont 1 labelsize 10 } + MenuItem {} { + label Sympathetic + xywh {110 110 100 20} labelfont 1 labelsize 10 + } } Fl_Group simplesyseffectuigroup { xywh {350 95 235 95} color 48 @@ -1316,11 +1328,11 @@ simpleinseffectui->refresh(); /* if (master->Pinsparts[ninseff]!=-1) { simpleinsefftype->activate(); - simpleinseffectui->activate(); + simpleinseffectui->activate(); simpleinseffectuigroup->activate(); } else { - simpleinsefftype->deactivate(); - simpleinseffectui->deactivate(); + simpleinsefftype->deactivate(); + simpleinseffectui->deactivate(); simpleinseffectuigroup->deactivate(); };*/} xywh {350 75 80 20} type Simple labelfont 1 labelsize 10 align 1 minimum 0 maximum 127 step 1 value 1 textfont 1 @@ -1377,6 +1389,10 @@ simpleinseffectui->show();} label DynFilter xywh {110 110 100 20} labelfont 1 labelsize 10 } + MenuItem {} { + label Sympathetic + xywh {120 120 100 20} labelfont 1 labelsize 10 + } } Fl_Group simpleinseffectuigroup { xywh {350 95 234 95} box FLAT_BOX color 48 @@ -1531,7 +1547,7 @@ osc->write("/config/cfg.UserInterfaceMode", "i", 2);} } } Function {updatesendwindow()} {} { - code {/*for (int neff1=0;neff1<NUM_SYS_EFX;neff1++) + code {/*for (int neff1=0;neff1<NUM_SYS_EFX;neff1++) for (int neff2=neff1+1;neff2<NUM_SYS_EFX;neff2++) syseffsend[neff1][neff2]->value(master->Psysefxsend[neff1][neff2]);*/} {} } @@ -1634,12 +1650,12 @@ delete selectuiwindow;} {} } Function {showUI(int UIMode)} {} { code {switch (UIMode){ - case 0:selectuiwindow->show(); - break; - case 1:masterwindow->show(); - break; - case 2:simplemasterwindow->show(); - break; + case 0:selectuiwindow->show(); + break; + case 1:masterwindow->show(); + break; + case 2:simplemasterwindow->show(); + break; };} {} } Function {simplerefresh()} {} { @@ -1692,7 +1708,7 @@ return 1;} {} updatepanel(); setfilelabel(display_name); - + return 1;} {} } Function {do_load_master(const char* file = NULL)} {} { @@ -1723,7 +1739,7 @@ char *tmp; if (result) { result=0; if (!fl_choice("The file exists. Overwrite it?","No","Yes",NULL)) return; - + } } else { @@ -1733,7 +1749,7 @@ char *tmp; osc->write("/save_xmz", "s", filename); if (result<0) fl_alert("Error: Could not save the file."); - else + else { osc->write("/last_xmz", "s", filename); \#if USE_NSM @@ -1806,4 +1822,4 @@ bankui->hide();} {} } { code {*exitprogram=1;} {} } -} +} diff --git a/src/globals.h b/src/globals.h @@ -136,6 +136,10 @@ typedef std::complex<fftwf_real> fft_t; */ #define NUM_KIT_ITEMS 16 +/* + * Maximum number of "strings" in Sympathetic Resonance Effect + */ +#define NUM_SYMPATHETIC_STRINGS 228U // 76*3 /* * How is applied the velocity sensing