diff options
author | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
---|---|---|
committer | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
commit | 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch) | |
tree | 291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/app/twirl | |
download | apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.gz apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.bz2 apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.zip |
initial
Diffstat (limited to 'site/app/twirl')
58 files changed, 4345 insertions, 0 deletions
diff --git a/site/app/twirl/appdata.js b/site/app/twirl/appdata.js new file mode 100644 index 0000000..b417c3f --- /dev/null +++ b/site/app/twirl/appdata.js @@ -0,0 +1,1427 @@ +twirl.appdata = {
+ version: 1.0,
+ modulations: [
+ {
+ name: "LFO",
+ instr: "twst_mod_lfo",
+ parameters: [
+ {name: "Rate", description: "Rate in Hz", dfault: 1, min: 0.1, max: 20},
+ {name: "Base value", description: "Base value", hostrange: true},
+ {name: "Gain", description: "Gain", dfault: 0.2},
+ {preset: "wave"},
+ {name: "Min", preset: "hostrangemin", hidden: true},
+ {name: "Max", preset: "hostrangemax", hidden: true}
+ ]
+ },
+ {
+ name: "Line",
+ instr: "twst_mod_line",
+ parameters: [
+ {name: "First point", channel: "first", description: "First point value", hostrange: true, automatable: false},
+ {name: "Last point", channel: "last", description: "Last point value", hostrange: true, automatable: false},
+ ]
+ },
+ {
+ name: "Random",
+ instr: "twst_mod_random",
+ parameters: [
+ {name: "Rate", description: "Rate in Hz", min: 0.1, max: 20, dfault: 1},
+ {name: "Min", hostrange: true, dfault: "hostrangemin"},
+ {name: "Max", hostrange: true, dfault: "hostrangemax"},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0.1, min: 0, max: 0.5},
+
+ ]
+ },
+ {
+ name: "Jitter",
+ instr: "twst_mod_jitter",
+ parameters: [
+ {name: "Base value", description: "Base value", hostrange: true},
+ {name: "Amplitude", channel: "amp", dfault: 1},
+ {name: "Rate minimum", description: "Rate in Hz", channel: "freqmin", dfault: 1, min: 0.1, max: 20},
+ {name: "Rate maximum", description: "Rate in Hz", channel: "freqmax", dfault: 1, min: 0.1, max: 20},
+ {name: "Min", hostrange: true, dfault: "hostrangemin"},
+ {name: "Max", hostrange: true, dfault: "hostrangemax"}
+
+ ]
+ },
+ {
+ name: "Parametric jitter",
+ instr: "twst_mod_jitter2",
+ parameters: [
+ {name: "Base value", description: "Base value", hostrange: true},
+ {name: "Total amplitude", channel: "totalamp", dfault: 1},
+ {name: "Amp 1", channel: "amp1", dfault: 0.5},
+ {name: "Rate 1", description: "Rate in Hz", channel: "freq1", dfault: 1, min: 0.1, max: 20},
+ {name: "Amp 2", channel: "amp2", dfault: 0.5},
+ {name: "Rate 2", description: "Rate in Hz", channel: "freq2", dfault: 1, min: 0.1, max: 20},
+ {name: "Amp 3", channel: "amp3", dfault: 0.5},
+ {name: "Rate 3", description: "Rate in Hz", channel: "freq3", dfault: 1, min: 0.1, max: 20},
+ {name: "Min", preset: "hostrangemin", hidden: true},
+ {name: "Max", preset: "hostrangemax", hidden: true}
+ ]
+ },
+ {
+ name: "Crossadaptive RMS",
+ instr: "twst_xa_rms",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", channel: "xrmsinstance"},
+ {preset: "instanceloop", channel: "xrmslooptype"},
+ {name: "Scaling", channel: "xrmsscale", description: "Scaling", hostrange: true},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0, min: 0, max: 0.2}
+ ]
+ },
+ {
+ name: "Crossadaptive AMDF pitch",
+ instr: "twst_xa_pitchamdf",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", channel: "xpitchinstance"},
+ {preset: "instanceloop", channel: "xpitchlooptype"},
+ {name: "Scaling", channel: "xpitchscale", description: "Scaling", hostrange: true},
+ {name: "Minimum frequency", channel: "xpitchmin", description: "Minimum frequency in analysis", min: 20, max: 1000, step: 1, dfault: 20, automatable: false},
+ {name: "Maximum frequency", channel: "xpitchmax", description: "Maximum frequency in analysis", min: 1000, max: 10000, step: 1, dfault: 10000, automatable: false},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0, min: 0, max: 0.2},
+ ]
+ },
+ {
+ name: "Crossadaptive pitch 1",
+ instr: "twst_xa_pitch1",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", channel: "xpitchinstance"},
+ {preset: "instanceloop", channel: "xpitchlooptype"},
+ {name: "Scaling", channel: "xpitchscale", description: "Scaling", hostrange: true},
+ {name: "Hop size root", channel: "xpitchhopsize", description: "Square root of hop size", min: 6, max: 12, dfault: 8, step: 1, automatable: false},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0, min: 0, max: 0.2},
+ ]
+ },
+ {
+ name: "Crossadaptive pitch 2",
+ instr: "twst_xa_pitch2",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", channel: "xpitchinstance"},
+ {preset: "instanceloop", channel: "xpitchlooptype"},
+ {name: "Scaling", channel: "xpitchscale", description: "Scaling", hostrange: true},
+ {name: "Minimum frequency", channel: "xpitchmin", description: "Minimum frequency in analysis", min: 20, max: 1000, step: 1, dfault: 20, automatable: false},
+ {name: "Maximum frequency", channel: "xpitchmax", description: "Maximum frequency in analysis", min: 1000, max: 10000, step: 1, dfault: 10000, automatable: false},
+ {name: "Analysis period", channel: "xpitchperiod", description: "Analysis period in seconds", min: 0.001, max: 0.1, dfault: 0.05, automatable: false},
+ {name: "Amplitude threshold", channel: "xpitchampthresh", description: "Analysis amplitude threshold in decibels", min: 1, max: 30, dfault: 10, automatable: false},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0, min: 0, max: 0.2},
+ ]
+ },
+ {
+ name: "Crossadaptive spectral centroid",
+ instr: "twst_xa_centroid",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", channel: "xcentroidinstance"},
+ {preset: "instanceloop", channel: "xcentroidlooptype"},
+ {name: "Scaling", channel: "xcentroidscale", description: "Scaling", hostrange: true},
+ {name: "Analysis period", channel: "xcentroidperiod", description: "Analysis period in seconds", min: 0.001, max: 0.3, dfault: 0.05},
+ {preset: "fftsize", channel: "xcentroidfftsize"},
+ {name: "Portamento time", channel: "porttime", description: "Value glide time in seconds", dfault: 0, min: 0, max: 0.2}
+ ]
+ },
+ ],
+ transforms: [
+ {
+ name: "General",
+ contents: [
+ {
+ name: "Reverse",
+ description: "Reverse sample region",
+ instr: "twst_tfi_reverse",
+ parameters: []
+ }
+ ]
+ },
+ {
+ name: "Generate",
+ contents: [
+ {
+ name: "Silence",
+ instr: "twst_tf_gensilence",
+ description: "Replace region with silence",
+ parameters: []
+ },
+ {
+ name: "Oscillator",
+ instr: "twst_tf_gentone",
+ description: "Simple interpolating oscillator",
+ parameters: [
+ {presetgroup: "notefreq"},
+ {preset: "amp"},
+ {preset: "wave", automatable: true},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "FM",
+ instr: "twst_tf_genfm",
+ description: "Frequency modulation synthesis",
+ parameters: [
+ {presetgroup: "notefreq"},
+ {preset: "amp"},
+ {name: "Carrier factor", channel: "carrier", min: 0.1, max: 8, dfault: 1},
+ {name: "Modulator factor", channel: "modulator", min: 0.1, max: 8, dfault: 1},
+ {name: "Modulation index", channel: "index", min: 0.1, max: 10, dfault: 2},
+ {preset: "wave"},
+ {name: "Stereo variance", channel: "stereovar", min: 0.5, max: 1.5, dfault: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "FM model",
+ instr: "twst_tf_genfmmodel",
+ description: "Frequency modulation physical models",
+ parameters: [
+ {name: "Type", channel: "fmtype", options: ["Organ", "Bell", "Flute", "Rhodes", "Wurlitzer", "Random"]},
+ {presetgroup: "notefreq"},
+ {preset: "amp"},
+ {name: "Modulation index", channel: "control1", min: 0.1, max: 10, dfault: 2},
+ {name: "Oscillator crossfade", channel: "control2", min: 0.1, max: 10, dfault: 2},
+ {name: "Vibrato depth", channel: "vibdepth", min: 0, max: 1, dfault: 0.05},
+ {name: "Vibrato rate", channel: "vibrate", min: 0, max: 20, dfault: 2},
+ {preset: "wave", channel: "wave1", name: "Wave 1"},
+ {preset: "wave", channel: "wave2", name: "Wave 2"},
+ {preset: "wave", channel: "wave3", name: "Wave 3"},
+ {preset: "wave", channel: "wave4", name: "Wave 4"},
+ {preset: "wave", channel: "vibwave", name: "Vibrato wave"},
+ {name: "Stereo variance", channel: "stereovar", min: 0.5, max: 1.5, dfault: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "String pluck",
+ instr: "twst_tf_genrepluck",
+ description: "String pluck physical model",
+ parameters: [
+ {presetgroup: "notefreq"},
+ {preset: "amp"},
+ {name: "Pluck point", channel: "pluckpoint", description: "String pluck point ratio", dfault: 0.3, automatable: false},
+ {name: "Pickup point", channel: "pickpoint", description: "Pickup point ratio", dfault: 0.6, automatable: false},
+ {name: "Reflection", channel: "reflection", description: "Reflection coefficient", min: 0.0001, max: 0.9999, dfault: 0.4},
+ {name: "Exciter mode", channel: "excitemode", options: ["Noise", "Oscillator"], dfault: 1},
+ {preset: "wave", channel: "excitewave", name: "Exciter waveform", conditions:[{channel: "excitemode", operator: "eq", value: 1}]},
+ {presetgroup: "notefreq", channelprepend: "excite", nameprepend: "Exciter", conditions: [{channel: "excitemode", operator: "eq", value: 1}]},
+ {name: "Exciter amplitude", channel: "exciteamp", description: "Exciter amplitude", dfault: 1, min: 0, max: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Stochastic additive",
+ instr: "twst_tf_genadditive",
+ description: "Aleatoric additive synthesis",
+ parameters: [
+ {preset: "amp", automatable: false},
+ {presetgroup: "notefreq", nameprepend: "Minimum", dfault: 440, channelprepend: "min", automatable: false, lagHint: 1},
+ {presetgroup: "notefreq", nameprepend: "Maximum", dfault: 8000, channelprepend: "max", automatable: false, lagHint: -1},
+ {name: "Frequency increment", channel: "step", automatable: false, min: 1.001, max: 1.5, dfault: 1.1, lagHint: -1},
+ {name: "Frequency randomness", channel: "steprand", automatable: false, min: 1, max: 1.5, dfault: 1},
+ {name: "Amplitude increment", channel: "ampmult", automatable: false, min: 0.2, max: 1, dfault: 0.6},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Noise",
+ instr: "twst_tf_gennoise",
+ description: "Noise generation",
+ parameters: [
+ {preset: "amp"},
+ {name: "Type", options: ["White", "Pink"], description: "Type of noise"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Bamboo",
+ instr: "twst_tf_genbamboo",
+ description: "Bamboo shaker physical model",
+ parameters: [
+ {preset: "amp"},
+ {name: "Number of resonators", channel: "number", min: 0, max: 10, step: 1, dfault: 0, automatable: false},
+ {presetgroup: "notefreq", nameprepend: "Resonator 1", dfault: 440, channelprepend: "r1", automatable: false},
+ {presetgroup: "notefreq", nameprepend: "Resonator 2", dfault: 440, channelprepend: "r2", automatable: false},
+ {presetgroup: "notefreq", nameprepend: "Resonator 3", dfault: 440, channelprepend: "r3", automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "WG bow",
+ instr: "twst_tf_genwgbow",
+ description: "Physical modelling waveguide bowed string",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "notefreq"},
+ {name: "Bow pressure", channel: "pressure", min: 0, max: 5, dfault: 3},
+ {name: "Bow position", channel: "position", min: 0.025, max: 0.23, dfault: 0.127236},
+ {name: "Vibrato rate", channel: "vibfreq", min: 0, max: 12, dfault: 0},
+ {name: "Vibrato rate", channel: "vibamp", min: 0, max: 1, dfault: 0},
+ {name: "Vibrato waveform", preset: "wave"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Feedback",
+ instr: "twst_tf_genfeedback",
+ description: "Feedback modelling",
+ added: "2025-04-17",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "notefreq"},
+ {name: "Feedback", min: 0.1, max: 10, dfault: 1.1},
+ {name: "Filter bandwidth", channel: "bandwidth", min: 10, max: 500, dfault: 125},
+ {name: "Post gain", channel: "postgain", min: 0.1, max: 2, dfault: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Additive",
+ instr: "twst_tf_gensimpleadditive",
+ description: "Simple additive synthesis",
+ added: "2025-04-17",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "notefreq"},
+ {name: "Frequency step multiplier", channel: "multiplier", min: 1.0001, max: 2, dfault: 1.1},
+ {name: "Frequency multiplier", channel: "stepmultiplier", min: 0.5, max: 2, dfault: 1},
+ {name: "Amplitude smoothing", channel: "ampprofile", min: 0, max: 1, step: 1, dfault: 1},
+ {name: "Harmonics", min: 2, max: 256, dfault: 64, automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ }
+ /* not quite right, don't create sound as expected
+ {
+ name: "WG bowed bar",
+ instr: "twst_tf_genwgbowedbar",
+ description: "Physical modelling waveguide bowed bar",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "notefreq"},
+ {name: "Bow pressure", channel: "pressure", min: 0, max: 5, dfault: 3},
+ {name: "Bow position", channel: "position", min: 0.025, max: 0.23, dfault: 0.127236},
+ {name: "Filter gain", channel: "filtergain", min: 0, max: 1, dfault: 0.809},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "WG brass",
+ instr: "twst_tf_genwgbrass",
+ description: "Physical modelling waveguide brass",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "notefreq"},
+ {name: "Lip tension", channel: "tension", min: 0, max: 1, dfault: 0.4},
+ {name: "Attack", channel: "position", min: 0, max: 1, dfault: 0.1, automatable: false},
+ {name: "Vibrato rate", channel: "vibfreq", min: 0, max: 12, dfault: 0},
+ {name: "Vibrato rate", channel: "vibamp", min: 0, max: 1, dfault: 0},
+ {name: "Vibrato waveform", preset: "wave"},
+ {presetgroup: "applymode"}
+ ]
+ },*/
+ ]
+ },
+ {
+ name: "Amplitude",
+ contents: [
+ {
+ name: "Normalise",
+ instr: "twst_tf_normalise",
+ description: "Normalise region",
+ parameters: [
+ {name: "Scale", description: "Scaling of normalisation", dfault: 1, min: 0, max: 1, automatable: false},
+ {name: "Stereo equal", channel: "equal", description: "Normalise stereo channels equally", dfault: 1, min: 0, max: 1, step: 1, automatable: false},
+ ]
+ },
+ {
+ name: "Amplitude",
+ instr: "twst_tf_amplitude",
+ description: "Gain alteration",
+ twine: true,
+ parameters: [
+ {name: "Gain", description: "Gain amount", dfault: 1, min: 0, max: 2},
+ {name: "Balance", description: "Left and right balance", dfault: 0.5, min: 0, max: 1}
+ ]
+ },
+ {
+ name: "Bit crush",
+ instr: "twst_tf_bitcrush",
+ description: "Sample level bit reduction",
+ twine: true,
+ parameters: [
+ {name: "Crush depth", channel: "crush", description: "Bit depth", dfault: 16, min: 1, max: 64, step: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Suppressor",
+ instr: "twst_tf_suppress",
+ description: "Wrap, mirror or limit amplitude",
+ twine: true,
+ parameters: [
+ {name: "Mode", channel: "mode", options: ["Limit", "Wrap", "Mirror"], automatable: true, dfault: 0},
+ {name: "Threshold", dfault: 0.8},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Linear clip",
+ instr: "twst_tf_pdclip",
+ description: "Phase distortion linear clipping",
+ twine: true,
+ parameters: [
+ {name: "Width", min: 0, max: 1, dfault: 0.2},
+ {name: "Centre", description: "Clipping offset", min: -1, max: 1, dfault: 0},
+ {name: "Bipolar", min: 0, max: 1, step: 1, dfault: 0, automatable: false},
+ {name: "Fullscale", min: 0, max: 1, dfault: 1, automatable: false},
+ ]
+ },
+ {
+ name: "Distortion",
+ instr: "twst_tf_distort",
+ description: "Waveshaping distortion",
+ twine: true,
+ parameters: [
+ {name: "Amount", min: 0, max: 1, dfault: 0.2},
+ {preset: "wave"},
+ {name: "Half power point", channel: "halfpower", min: 0, max: 100, dfault: 10, automatable: false},
+ ]
+ },
+ {
+ name: "HT Distortion",
+ instr: "twst_tf_distort1",
+ description: "Hyperbolic tangent distortion",
+ twine: true,
+ parameters: [
+ {name: "Pre gain", channel: "pregain", min: 0, max: 5, dfault: 1},
+ {name: "Post gain", channel: "postgain", min: 0, max: 1, dfault: 1},
+ {name: "Positive shape", channel: "shape1", min: 0, max: 1, dfault: 0.2},
+ {name: "Negative shape", channel: "shape2", min: 0, max: 1, dfault: 0.2},
+ ]
+ },
+ {
+ name: "Strobe",
+ instr: "twst_tf_strobe",
+ description: "Automatic amplitude attenuation",
+ twine: true,
+ added: "2025-04-17",
+ parameters: [
+ {name: "Rate", description: "Rate of action in Hz", dfault: 4, min: 0.1, max: 30},
+ {name: "Hold time", channel: "holdtime", description: "Hold time in seconds", dfault: 0.1, min: 0.01, max: 0.5},
+ {name: "Windowed", description: "Whether to apply windowing", dfault: 0, min: 0, max: 1, step: 1}
+ ]
+ },
+ ]
+ },
+ {
+ name: "Filter",
+ contents: [
+ {
+ name: "Low pass",
+ instr: "twst_tf_lpf",
+ description: "Butterworth low pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", description: "Filter frequency", max: 22000, min: 20, dfault: 1000},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "High pass",
+ instr: "twst_tf_hpf",
+ description: "Butterworth high pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", description: "Filter frequency", max: 22000, min: 20, dfault: 1000},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Band pass",
+ instr: "twst_tf_bpf",
+ description: "Butterworth band pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", description: "Filter frequency", max: 22000, min: 20, dfault: 1000},
+ {name: "Bandwidth", description: "Filter bandwidth", max: 5000, min: 1, dfault: 200},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Parametric",
+ instr: "twst_tf_pareq",
+ description: "Parametric EQ",
+ twine: true,
+ parameters: [
+ {name: "Frequency", description: "Filter frequency", max: 22000, min: 20, dfault: 1000},
+ {name: "Gain", description: "Filter gain factor", max: 4, min: 0, dfault: 1},
+ {name: "Q", description: "Filter Q", max: 0.9, min: 0.5, dfault: 0.707},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "DC Block",
+ instr: "twst_tf_dcblock",
+ description: "Remove DC offset from signal",
+ twine: true,
+ parameters: [
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Moog high pass",
+ instr: "twst_tf_mooghpf",
+ description: "Emulated Moog high pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 10, max: 10000, dfault: 800},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Moog low pass",
+ instr: "twst_tf_mooglpf",
+ description: "Emulated Moog low pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 10, max: 10000, dfault: 800},
+ {name: "Resonance", min: 0, max: 1, dfault: 0.6},
+ {name: "Mode", min: 0, max: 2, dfault: 1, step: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "TB303 low pass",
+ instr: "twst_tf_tbvcf",
+ description: "Emulated TB303 low pass filter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 1000, max: 15000, dfault: 1500},
+ {name: "Resonance", min: 0, max: 2, dfault: 0.6},
+ {name: "Distortion", min: 0.5, max: 3, dfault: 2},
+ {name: "Resonance asymmetry ", min: 0, max: 1, dfault: 0.5},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Waveguide",
+ instr: "twst_tf_waveguide1",
+ description: "Waveguide filter",
+ twine: true,
+ parameters: [
+ {name: "Rate", channel: "freq", min: 10, max: 4000, dfault: 100},
+ {name: "Filter cutoff", channel: "cutoff", min: 400, max: 20000, dfault: 4000},
+ {name: "Feedback", dfault: 0.5},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Frequency", contents: [
+ {
+ name: "Frequency shift 1",
+ instr: "twst_tf_freqshift1",
+ description: "Hilbert frequency shifter",
+ twine: true,
+ parameters: [
+ {name: "Shift", description: "Shift frequency in Hz", dfault: 0, min: -1000, max: 1000},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Frequency shift 2",
+ instr: "twst_tf_freqshift2",
+ description: "Biquadratic frequency shifter",
+ twine: true,
+ parameters: [
+ {name: "Shift", description: "Shift frequency in Hz", dfault: 0, min: -1000, max: 1000},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Ring modulator",
+ instr: "twst_tf_ringmod",
+ description: "Ring modulator",
+ twine: true,
+ parameters: [
+ {name: "Frequency", description: "Modulation frequency in Hz", dfault: 440, min: 20, max: 18000},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Exciter",
+ instr: "twst_tf_exciter",
+ description: "Non-linear signal excitation",
+ twine: true,
+ parameters: [
+ {presetgroup: "notefreq", nameprepend: "Lower", channelprepend: "low"},
+ {presetgroup: "notefreq", nameprepend: "Upper", channelprepend: "high"},
+ {name: "Harmonics", description: "Number of harmonics", min: 0.1, max: 10, dfault: 6, lagHint: -1},
+ {name: "Blend", description: "Harmonic blending", min: -10, max: 10, dfault: 10},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Delay", contents: [
+ {
+ name: "Dynamic delay",
+ instr: "twst_tf_vdelay",
+ description: "Variable delay",
+ twine: true,
+ parameters: [
+ {name: "Delay time", channel: "delay", description: "Delay time in seconds", dfault: 0.3, min: 0.001, max: 1, lagHint: -1},
+ {name: "Feedback", description: "Feedback gain", dfault: 0.5},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Flanger",
+ instr: "twst_tf_flanger",
+ twine: true,
+ parameters: [
+ {name: "Delay", max: 0.02, dfault: 0.01, lagHint: -1},
+ {name: "Feedback", dfault: 0.3},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "First order phaser",
+ instr: "twst_tf_phaser1",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 10, max: 10000, dfault: 3000},
+ {name: "Order", min: 1, max: 24, step: 1, dfault: 4, automatable: false, lagHint: -1},
+ {name: "Feedback", dfault: 0.3},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Second order phaser",
+ instr: "twst_tf_phaser2",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 10, max: 10000, dfault: 3000},
+ {name: "Q", min: 0.2, max: 1.5, dfault: 0.67},
+ {name: "Order", min: 1, max: 2499, step: 1, dfault: 800, automatable: false, lagHint: -1},
+ {name: "Mode", min: 1, max: 2, dfault: 1, step: 1, automatable: false},
+ {name: "Separation", channel: "sep", min: 0.01, max: 4, dfault: 1, lagHint: 1},
+ {name: "Feedback", min: -1, max: 1, dfault: 0.3},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Reverb", contents: [
+ {
+ name: "Room",
+ instr: "twst_tf_reverb3",
+ twine: true,
+ parameters: [
+ {name: "Room size", channel: "roomsize", description: "Room size", dfault: 0.5, min: 0, max: 1, lagHint: -1},
+ {name: "Damping", channel: "hfdamp", description: "High frequency damping", dfault: 0.5, min: 0, max: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Midverb",
+ instr: "twst_tf_reverb2",
+ twine: true,
+ parameters: [
+ {name: "Length", channel: "time", description: "Decay time", dfault: 1, min: 0.1, max: 10, lagHint: -1},
+ {name: "Damping", channel: "hfdamp", description: "High frequency damping", dfault: 0.5, min: 0, max: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Basicverb",
+ instr: "twst_tf_reverb1",
+ twine: true,
+ parameters: [
+ {name: "Length", channel: "time", description: "Decay time", dfault: 1, min: 0.1, max: 10, lagHint: -1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "FDNverb",
+ instr: "twst_tf_reverb4",
+ twine: true,
+ parameters: [
+ {name: "Feedback", channel: "feedback", description: "Feedback amount equating to decay time", dfault: 0.5, min: 0.1, max: 1, lagHint: -1},
+ {name: "Damping", channel: "hfdamp", description: "High frequency damping", dfault: 0.5, min: 0, max: 1},
+ {name: "Pitch variation", channel: "pitchmod", description: "Pitch variation", dfault: 1, min: 0, max: 10, automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Baboverb",
+ instr: "twst_tf_reverb5",
+ description: "Physical model reverberator",
+ twine: true,
+ parameters: [
+ {name: "Room width", channel: "width", description: "Room width in metres", dfault: 14.39, min: 2, max: 100, automatable: false, lagHint: -1},
+ {name: "Room depth", channel: "depth", description: "Room depth in metres", dfault: 11.86, min: 2, max: 100, automatable: false, lagHint: -1},
+ {name: "Room height", channel: "height", description: "Room height in metres", dfault: 10, min: 2, max: 100, automatable: false, lagHint: -1},
+ {name: "X position", channel: "posx", description: "X position of listener", dfault: 0.5, min: 0, max: 1},
+ {name: "Y position", channel: "posy", description: "Y position of listener", dfault: 0.5, min: 0, max: 1},
+ {name: "Z position", channel: "posz", description: "Z position of listener", dfault: 0.5, min: 0, max: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ ]
+ },
+ {
+ name: "Granular", contents: [
+ {
+ name: "Autoglitch",
+ instr: "twst_tf_autoglitch",
+ description: "Automatic aleatoric sample reading with modifications",
+ twine: true,
+ parameters: [
+ {name: "Minimum ratio", channel: "minratio", description: "Minimum time ratio of original", dfault: 0.5, min: 0, max: 1, lagHint: 1},
+ {name: "Change rate", channel: "changerate", description: "Rate of change in Hz", dfault: 0.5, min: 0.1, max: 10, lagHint: -1},
+ {name: "Change chance", channel: "changechance", description: "Chance of change", dfault: 0.5, min: 0, max: 1},
+ {name: "Portamento time", channel: "porttime", description: "Reading position glide", dfault: 0.1, min: 0.001, max: 0.5},
+ {name: "Distortion", channel: "distortion", description: "Apply distortion", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Amp change", channel: "ampchange", description: "Apply amplitude changes", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Buffer size", channel: "buflens", description: "Buffer size in seconds", dfault: 0.5, min: 0.1, max: 4, automatable: false, lagHint: -1},
+ {name: "Read mode", channel: "readmode", description: "Reading mode", dfault: 0, options: ["Direct", "Texture", "FFT"], automatable: false, lagHint: {option: 0}},
+ {name: "Stereo unique", channel: "stereounique", description: "Whether channels are glitched independently", dfault: 1, min: 0, max: 1, step: 1, automatable: false},
+ {presetgroup: "applymode"}
+
+ ]
+ },
+ {
+ name: "Syncgrain",
+ instr: "twst_tfi_syncgrain",
+ description: "Synchronous granular synthesis",
+ parameters: [
+ {preset: "amp"},
+ {name: "Frequency", description: "Grains per second", channel: "freq", step: 1, min: 1, max: 100, dfault: 20, lagHint: -1},
+ {presetgroup: "pitchscale"},
+ {name: "Grain size", channel: "grainsize", min: 0.01, max: 0.5, dfault: 0.1, lagHint: 1},
+ {name: "Overlaps", min: 1, max: 8, step: 1, dfault: 1},
+ {name: "Time scale", min: 0.1, max: 10, dfault: 1, channel: "timescale"},
+ {preset: "wintype"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Grain",
+ instr: "twst_tfi_grain",
+ description: "Granular synthesis",
+ parameters: [
+ {preset: "amp"},
+ {presetgroup: "pitchscale"},
+ {name: "Density", description: "Grains per second", channel: "density", step: 1, min: 1, max: 100, dfault: 20, lagHint: -1},
+ {name: "Grain size", channel: "grainsize", min: 0.01, max: 0.5, dfault: 0.1, lagHint: 1},
+ {name: "Amplitude variation", channel: "ampvar", min: 0, max: 1, dfault: 0},
+ {name: "Pitch variation", channel: "pitchvar", min: 0, max: 1, dfault: 0},
+ {name: "Random readpoint", min: 0, max: 1, step: 1, dfault: 1},
+ {preset: "wintype"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Retrigger",
+ instr: "twst_tfi_retriglitch",
+ description: "Repeated window reader",
+ parameters: [
+ {name: "Read mode", channel: "readmode", description: "Sample reading type", options: ["Linear", "Direct", "Scale", "Reverse", "Random"], dfault: 0},
+ {name: "Read position", channel: "readtime", description: "Read time ratio", dfault: 0.5, conditions: [{channel: "readmode", operator: "eq", value: 1}]},
+ {name: "Time scaling", channel: "timescale", description: "Time scaling factor", dfault: 1, min: 0.1, max: 16, conditions: [{channel: "readmode", operator: "eq", value: 2}], automatable: false},
+ {presetgroup: "pitchscale"},
+ {name: "Retrigger length", channel: "triglen", min: 0.01, max: 1, dfault: 0.2, lagHint: 1},
+ {name: "Apply windowing", channel: "applywindowing", step: 1, dfault: 0},
+ {preset: "wintype", conditions:[{channel: "applywindowing", operator: "eq", value: 1}]},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Sample rearrange",
+ instr: "twst_tfi_rearrange",
+ description: "Aleatoric sample range rearrangement",
+ parameters: [
+ {name: "Number of chops", description: "Number of sample rearrangements to apply", channel: "chopnumber", step: 1, min: 1, max: 128, dfault: 16, automatable: false},
+ {name: "Minimum samples", description: "Minimum number of samples to rearrange with each chop", channel: "chopmin", step: 1, min: 1, max: 1000, dfault: 400, automatable: false},
+ {name: "Maximum samples", description: "Maximum number of samples to rearrange with each chop", channel: "chopmax", step: 1, min: 64, max: 10000, dfault: 5000, automatable: false},
+ {name: "Stereo unique", description: "Whether to chop channels independently", channel: "stereounique", step: 1, min: 0, max: 1, dfault: 0, automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Harmonic", contents: [
+ {
+ name: "Parallel resonators",
+ instr: "twst_tf_resony",
+ twine: true,
+ parameters: [
+ {presetgroup: "notefreq"},
+ {name: "Bandwidth", description: "Bandwidth in Hz", dfault: 100, min: 1, max: 500},
+ {name: "Number", channel: "num", description: "Number of resonators", dfault: 8, min: 1, max: 64, step: 1, automatable: false, lagHint: -1},
+ {name: "Separation", description: "Band separation octaves", dfault: 1, min: 1, max: 6, step: 1, lagHint: 1},
+ {name: "Separation mode", channel: "sepmode", description: "Separation mode", options: ["Logarithmic", "Linear"], dfault: 0},
+ {name: "Balance output", channel: "balance", description: "Balance output level to input level", dfault: 1, min: 0, max: 1, step: 1, automatable: 0},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Resonator stack",
+ instr: "twst_tf_resonx",
+ twine: true,
+ parameters: [
+ {presetgroup: "notefreq"},
+ {name: "Bandwidth", description: "Bandwidth in Hz", dfault: 100, min: 1, max: 500},
+ {name: "Number", channel: "num", description: "Number of resonators", dfault: 8, min: 1, max: 64, step: 1, automatable: false, lagHint: -1},
+ {name: "Balance output", channel: "balance", description: "Balance output level to input level", dfault: 1, min: 0, max: 1, step: 1, automatable: 0},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "String resonator",
+ instr: "twst_tf_streson",
+ twine: true,
+ parameters: [
+ {presetgroup: "notefreq"},
+ {name: "Feedback", description: "Feedback gain", dfault: 0.6, min: 0, max: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "MVM resonator",
+ instr: "twst_tf_mvmfilter",
+ twine: true,
+ parameters: [
+ {name: "Frequency", channel: "freq", min: 10, max: 10000, dfault: 800},
+ {name: "Decay time", channel: "decay", min: 0.1, max: 0.8, dfault: 0.2, lagHint: -1},
+ {name: "Balance", channel: "balance", min: 0, max: 1, step: 1, dfault: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Autoharmoniser",
+ instr: "twst_tf_harmon",
+ twine: true,
+ parameters: [
+ {name: "Estimated frequency", channel: "estfreq", min: 10, max: 4000, dfault: 440},
+ {name: "Maximum variance", channel: "maxvar", dfault: 0.2},
+ {name: "Frequency 1 ratio", channel: "genfreq1", min: 0, max: 8, dfault: 2},
+ {name: "Frequency 2 ratio", channel: "genfreq2", min: 0, max: 8, dfault: 4},
+ {name: "Minimum analysis frequency", channel: "minfreq", min: 200, max: 4000, step: 1, dfault: 300, automatable: false, lagHint: 1},
+ {name: "Analysis time", channel: "minfreq", min: 0.01, max: 0.1, dfault: 0.03, automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Formant autoharmoniser",
+ instr: "twst_tf_formantharmon",
+ twine: true,
+ parameters: [
+ {name: "Frequency 1 ratio", channel: "genfreq1", min: 0, max: 8, dfault: 2},
+ {name: "Frequency 2 ratio", channel: "genfreq2", min: 0, max: 8, dfault: 3},
+ {name: "Frequency 3 ratio", channel: "genfreq3", min: 0, max: 8, dfault: 4},
+ {name: "Frequency 4 ratio", channel: "genfreq4", min: 0, max: 8, dfault: 5},
+ {name: "Minimum frequency", channel: "minfreq", min: 20, max: 4000, step: 1, dfault: 100, automatable: false},
+ {name: "Polarity", min: 0, max: 1, step: 1, dfault: 1, automatable: false},
+ {name: "Analysis window time", channel: "pupdate", min: 0.001, max: 0.1, dfault: 0.01, automatable: false, lagHint: 1},
+ {name: "Analysis bottom frequency", channel: "plowfreq", min: 20, max: 1000, dfault: 100, automatable: false, lagHint: 1},
+ {name: "Analysis top frequency", channel: "phighfreq", min: 1000, max: 22000, dfault: 20000, automatable: false, lagHint: -1},
+ {name: "Analysis threshold", channel: "pthresh", min: 0, max: 1, dfault: 0.4, automatable: false},
+ {name: "Analysis octave divisions", channel: "pfrqs", min: 1, max: 120, dfault: 12, step: 1, automatable: false, lagHint: 1},
+ {name: "Analysis confirmations", channel: "pconfirms", min: 1, max: 40, dfault: 10, step: 1, automatable: false, lagHint: -1},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Warping", contents: [
+ {
+ name: "Paulstretch",
+ instr: "twst_tfi_paulstretch",
+ description: "Extreme timestretch",
+ parameters: [
+ {name: "Stretch ratio", channel: "stretch", description: "Ratio to stretch by", dfault: 2, min: 1, max: 20, automatable: false},
+ {name: "Window size", channel: "winsize", description: "Window size used for stretch", dfault: 0.5, min: 0.1, max: 5, automatable: false, lagHint: -1}
+ ]
+ },
+ {
+ name: "Freeze",
+ instr: "twst_tf_freeze",
+ description: "Frequency domain freezing",
+ twine: true,
+ parameters: [
+ {name: "Freeze amp", channel: "freezeamp", description: "Freeze amplitudes", min: 0, max: 1, dfault: 1, step: 1},
+ {name: "Freeze frequency", channel: "freezefreq", description: "Freeze frequencies", min: 0, max: 1, dfault: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Waveset",
+ instr: "twst_tf_waveset",
+ description: "Repeat wave cycles",
+ twine: true,
+ parameters: [
+ {name: "Repetitions", channel: "reps", description: "Number of samples repetition in stretch", min: 1, max: 64, dfault: 2, step: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Blur",
+ instr: "twst_tf_blur",
+ description: "Frequency domain time blurring",
+ twine: true,
+ parameters: [
+ {name: "Blur time", channel: "time", description: "Blur time in seconds", min: 0.01, max: 3, dfault: 0.4, lagHint: -1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Spectral pitch scale",
+ instr: "twst_tf_fftpitchscale",
+ description: "Formant preserving pitch shifter",
+ twine: true,
+ parameters: [
+ {presetgroup: "pitchscale"},
+ {name: "Keep formants", channel: "formants", description: "Preserve formants", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Formant coefficients", channel: "formantcoefs", description: "Number of formants in preservation", dfault: 80, min: 1, max: 120, step: 1, conditions: [{channel: "formants", operator: "eq", value: 1}]},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Hilbert pitch scale",
+ instr: "twst_tf_hilbertpitchscale",
+ description: "Hilbert transform based pitch shifter",
+ twine: true,
+ parameters: [
+ {presetgroup: "pitchscale"},
+ {preset: "fftsize"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Autotune",
+ instr: "twst_tf_autotune",
+ description: "Formant preserving autotune pitch shifter",
+ twine: true,
+ parameters: [
+ {name: "Threshold", channel: "threshold", description: "Pitch detection threshold", dfault: 0.01, min: 0.001, max: 0.2},
+ {name: "Keep formants", channel: "formants", description: "Preserve formants", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Formant coefficients", channel: "formantcoefs", description: "Number of formants in preservation", dfault: 80, min: 1, max: 120, step: 1, conditions: [{channel: "formants", operator: "eq", value: 1}]},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Window reader",
+ instr: "twst_tfi_sndwarp",
+ description: "Windowed playback",
+ parameters: [
+ {name: "Read mode", channel: "readmode", description: "Sample reading type", options: ["Linear", "Direct", "Scale", "Reverse"], dfault: 0},
+ {name: "Read position", channel: "readtime", description: "Read time ratio", dfault: 0.5, conditions: [{channel: "readmode", operator: "eq", value: 1}]},
+ {name: "Time scaling", channel: "timescale", description: "Time scaling factor", dfault: 1, min: 0.1, max: 16, conditions: [{channel: "readmode", operator: "eq", value: 2}], automatable: false},
+ {presetgroup: "pitchscale"},
+ {name: "Window size", channel: "winsize", description: "Window size", min: 256, max: 10000, dfault: 4410, step: 1, automatable: false, lagHint: -1},
+ {name: "Window randomness", channel: "winrand", description: "Randomness in window size", min: 0, max: 1000, dfault: 441, step: 1, automatable: false},
+ {name: "Window overlap", channel: "overlap", description: "Overlap number", min: 1, max: 32, dfault: 4, step: 1, automatable: false, lagHint: -1},
+ {preset: "wintype"}
+ ]
+ },
+ {
+ name: "FFT reader",
+ instr: "twst_tfi_mincer",
+ description: "Frequency domain playback",
+ parameters: [
+ {name: "Read mode", channel: "readmode", description: "Sample reading type", options: ["Linear", "Direct", "Scale", "Reverse"], dfault: 0},
+ {name: "Read position", channel: "readtime", description: "Read time ratio", dfault: 0.5, conditions: [{channel: "readmode", operator: "eq", value: 1}]},
+ {name: "Time scaling", channel: "timescale", description: "Time scaling factor", dfault: 1, min: 0.1, max: 16, conditions: [{channel: "readmode", operator: "eq", value: 2}], automatable: false},
+ {presetgroup: "pitchscale"},
+ {name: "Phase lock", channel: "phaselock", description: "Lock phase when resynthesising", dfault: 0, min: 0, max: 1, step: 1},
+ {preset: "fftsize"},
+ {name: "Overlap decimation", options: [2, 4, 8, 16], asvalue: true, dfault: 1, channel: "decimation", automatable: false, lagHint: -1}
+ ]
+ },
+ {
+ name: "Sample holder",
+ instr: "twst_tf_smphold",
+ twine: true,
+ parameters: [
+ {name: "Ratio", description: "Inverse ratio of samples to be held in a block period", min: 0.000001},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Cross processing", contents: [
+ {
+ name: "Cross synth",
+ instr: "twst_tf_crosssynth",
+ description: "Amplitude based cross synthesis",
+ inputs: 2,
+ parameters: [
+ {preset: "instance"},
+ {preset: "instanceloop"},
+ {name: "Current amp", channel: "amp1", description: "Ratio of current amplitude to use", dfault: 1, min: 0, max: 1},
+ {name: "Target amp", channel: "amp2", description: "Ratio of target amplitude to use", dfault: 1, min: 0, max: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Direct convolution",
+ instr: "twst_tf_directconvolve",
+ description: "Convolution",
+ inputs: 2,
+ parameters: [
+ {preset: "instance"},
+ {preset: "amp"},
+ {name: "Size ratio", min: 0.00001, max: 1, dfault: 0.1, lagHint: -1, channel: "sizeratio", automatable: false},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Block convolution",
+ instr: "twst_tf_blockconvolve",
+ description: "Short term frequency domain convolution",
+ inputs: 2,
+ parameters: [
+ {preset: "instance"},
+ {preset: "instanceloop"},
+ {preset: "fftsize", dfault: 2},
+ {name: "Overlap", min: 1, max: 8, step: 1, dfault: 2, lagHint: -1},
+ {presetgroup: "applymode"}
+ ]
+ }, /* not in WASM at current
+ {
+ name: "DFT/FIR convolve",
+ instr: "twst_tf_tvconv",
+ inputs: 2,
+ parameters: [
+ {preset: "instance"},
+ {preset: "instanceloop"},
+ {name: "Update source", channel: "apply1", min: 0, max: 1, step: 1, dfault: 1},
+ {name: "Update destination", channel: "apply2", min: 0, max: 1, step: 1, dfault: 1},
+ {name: "Mode", options: ["DFT", "FIR"]},
+ {name: "Partition size", channel: "parts", options: [16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192], dfault: 5, asvalue: true, conditions: [{channel: "mode", operator: "eq", value: 0}]},
+ {name: "Filter size", channel: "dftfiltersize", options: [16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192], dfault: 5, asvalue: true, conditions: [{channel: "mode", operator: "eq", value: 0}]},
+ {name: "Filter size", channel: "firfiltersize", min: 2, max: 8192, dfault: 512, conditions: [{channel: "mode", operator: "eq", value: 1}]},
+ {presetgroup: "applymode"}
+ ]
+ }, */
+ {
+ name: "Morph",
+ instr: "twst_tf_morph",
+ description: "Amplitude and frequency cross synthesis",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", name: "Cross instance"},
+ {preset: "instanceloop"},
+ {name: "Amplitude amount", channel: "amp", description: "Amplitude interpolation", dfault: 1, min: 0, max: 1},
+ {name: "Frequency amount", channel: "freq", description: "Frequency interpolation", dfault: 1, min: 0, max: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Concatenative resynthesis",
+ instr: "twst_tf_mfccmatch",
+ description: "Map nearest perceptually similar segments",
+ inputs: 2,
+ parameters: [
+ {preset: "instance", name: "Corpus"},
+ {preset: "instanceloop"},
+ {preset: "fftsize"},
+ {name: "Minimum frequency", channel: "freqmin", description: "Minimum frequency to account for", dfault: 100, min: 30, max: 1000, automatable: false, lagHint: 1},
+ {name: "Maximum frequency", channel: "freqmax", description: "Maximum frequency to account for", dfault: 4000, min: 1000, max: 20000, automatable: false, lagHint: -1},
+ {name: "Bands", channel: "bands", description: "Number of frequency bands to use", dfault: 3, options:[2, 4, 8, 16, 32, 64], asvalue: true, automatable: false, lagHint: -1},
+ {name: "Stretch", channel: "stretch", description: "Read stretch", dfault: 0.35, min: 0.1, max: 2.5},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Cross rearrange",
+ instr: "twst_tf_crossrearrange",
+ description: "Select chunks from all open instances",
+ added: "2025-04-17",
+ parameters: [
+ {name: "Minimum samples", channel: "minsamples", description: "Minimum number of samples to chop", dfault: 4410, min: 10, max: 10000},
+ {name: "Maximum samples", channel: "maxsamples", description: "Maximum number of samples to chop", dfault: 10000, min: 1000, max: 50000},
+ {name: "Change rate", channel: "rate", description: "Rate of rearrangement change", dfault: 4, min: 0.1, max: 20},
+ {name: "Stereo unique", channel: "stereounique", description: "Stereo independent rearrangement", dfault: 1, min: 0, max: 1, step: 1},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ },
+ {
+ name: "Spectral", contents: [
+ {
+ name: "Spectral autoglitch",
+ instr: "twst_tf_spectralautoglitch",
+ description: "Automatic frequency domain glitcher",
+ twine: true,
+ parameters: [
+ {name: "Change rate", channel: "changerate", description: "Rate of change", dfault: 1, min: 0.1, max: 10, lagHint: -1},
+ {name: "Change chance", channel: "changechance", description: "Chance of change", dfault: 0.5, min: 0, max: 1, lagHint: -1},
+ {name: "Portamento time", channel: "porttime", description: "Reading position glide", dfault: 0.1, min: 0.001, max: 0.5},
+ {name: "Alter pitch", channel: "pitchalter", description: "Apply pitch alterations", dfault: 0, min: 0, max: 1, step: 1},
+ {preset: "fftsize"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Delay",
+ instr: "twst_tf_spectraldelay",
+ description: "Frequency domain bin-independent delay",
+ unstable: true,
+ parameters: [
+ {name: "Delay time", channel: "time", description: "Delay time in seconds", dfault: 0.03, min: 0.01, max: 0.4, lagHint: -1},
+ {name: "Delay time add", channel: "add", description: "Delay time addition with each step", dfault: 0.001, min: 0, max: 0.2, lagHint: -1},
+ {name: "Portamento", channel: "porttime", description: "Resynthesis oscillator portamento time in seconds", dfault: 0.01, min: 0.01, max: 0.1},
+ {presetgroup: "pitchscale"},
+ {name: "Frequency bottom ratio", automatable: false, channel: "start", description: "Resynthesis bin start", dfault: 0, lagHint: 1},
+ {name: "Frequency top ratio", automatable: false, channel: "end", description: "Resynthesis bin end", dfault: 0.3, lagHint: -1},
+ {name: "Resynthesis waveform", preset: "wave"},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },/* not in WASM
+ {
+ name: "Trace",
+ instr: "twst_tf_trace",
+ twine: true,
+ description: "Retain only a selected number of the loudest bins",
+ parameters: [
+ {name: "Number of bins (ratio)", description: "Number of frequency bins to retain", dfault: 0.5, min: 0, max: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },*/
+ {
+ name: "Isolator",
+ instr: "twst_tf_isolator",
+ description: "Isolate an analysis bin",
+ twine: true,
+ parameters: [
+ {name: "Bin (ratio)", description: "Bin to use", dfault: 0.1, min: 0, max: 1, channel: "bin"},
+ {name: "Attenuation", description: "Bin attenuation", dfault: 1, min: 0, max: 1},
+ {name: "Accentuation", description: "Bin accentuation", dfault: 1, min: 0, max: 2},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Shift",
+ instr: "twst_tf_spectralshift",
+ description: "Frequency shifter in the spectral domain",
+ twine: true,
+ parameters: [
+ {name: "Shift", channel: "freqincr", description: "Frequency addition per bin", dfault: 0, min: -100, max: 100},
+ {name: "Portamento", channel: "porttime", description: "Resynthesis oscillator portamento time in seconds", dfault: 0.01, min: 0.01, max: 0.1},
+ {presetgroup: "pitchscale"},
+ {name: "Frequency bottom ratio", automatable: false, channel: "start", description: "Resynthesis bin start", dfault: 0, lagHint: 1},
+ {name: "Frequency top ratio", dfault: 0.3, automatable: false, channel: "end", description: "Resynthesis bin end", lagHint: -1},
+ {name: "Resynthesis waveform", preset: "wave"},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Gate",
+ instr: "twst_tf_spectralgate",
+ description: "Analysis bin-independent gate",
+ twine: true,
+ parameters: [
+ {name: "Threshold", description: "Gate threshold", dfault: 0.01, min: 0, max: 0.2},
+ {name: "Hold", description: "Hold or decay frequencies", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Portamento", channel: "porttime", description: "Resynthesis oscillator portamento time in seconds", dfault: 0.01, min: 0.01, max: 0.1},
+ {presetgroup: "pitchscale"},
+ {name: "Frequency bottom ratio", automatable: false, channel: "start", description: "Resynthesis bin start", dfault: 0, lagHint: 1},
+ {name: "Frequency top ratio", dfault: 0.3, automatable: false, channel: "end", description: "Resynthesis bin end", lagHint: -1},
+ {name: "Resynthesis waveform", preset: "wave"},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Granular",
+ instr: "twst_tfi_spectralgrain1",
+ description: "Frequency domain granular synthesis",
+ parameters: [
+ {name: "Grain duration", channel: "graindur", description: "Duration of grains in seconds", min: 0.05, max: 2, dfault: 0.2},
+ {name: "Overlaps", channel: "layers", description: "Layers of grains", min: 1, max: 6, dfault: 1, step: 1, automatable: false},
+ {name: "Rate randomness", channel: "freqrand", description: "Read rate randomness", min: 1, max: 3, dfault: 1},
+ {name: "Duration randomness", channel: "durrand", description: "Grain duration randomness", min: 1, max: 3, dfault: 1},
+ {name: "Pitch randomness", channel: "pitchrand", description: "Grain pitch randomness", min: 1, max: 3, dfault: 1},
+ {name: "Read mode", channel: "readmode", description: "Grain reading mode", options: ["Linear", "Direct", "Scale", "Reverse"], dfault: 0},
+ {name: "Read position", channel: "readtime", description: "Read time ratio", dfault: 0.5, conditions: [{channel: "readmode", operator: "eq", value: 1}]},
+ {name: "Time scaling", channel: "timescale", description: "Time scaling factor", dfault: 1, min: 0.1, max: 10, conditions: [{channel: "readmode", operator: "eq", value: 2}], automatable: false},
+ {name: "Portamento", channel: "porttime", description: "Resynthesis oscillator portamento time in seconds", dfault: 0.01, min: 0.01, max: 0.1},
+ {presetgroup: "pitchscale"},
+ {name: "Frequency bottom ratio", automatable: false, channel: "start", description: "Resynthesis bin start", dfault: 0, lagHint: 1},
+ {name: "Frequency top ratio", dfault: 0.3, automatable: false, channel: "end", description: "Resynthesis bin end", lagHint: -1},
+ {name: "Resynthesis waveform", preset: "wave"},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Residuals",
+ instr: "twst_tf_residual",
+ description: "Spectral resynthesis residuals",
+ twine: true,
+ parameters: [
+ {preset: "fftsize"}
+ ]
+ },
+ /*{
+ name: "Stencil",
+ instr: "twst_tf_stencil",
+ unstable: true,
+ inputs: 2,
+ parameters: [
+ {preset: "instance"},
+ {preset: "instanceloop"},
+ {name: "Gain", description: "Pre processing gain", dfault: 1},
+ {name: "Level", description: "Level of transformation", dfault: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+
+ },*/
+ {
+ name: "Bubble",
+ instr: "twst_tf_tpvbubble",
+ description: "Randomly reset bin amplitudes to zero",
+ twine: true,
+ parameters: [
+ {name: "Chance", description: "Chance of resetting bin amplitudes to 0", dfault: 0.5},
+ {name: "Stereo unique", channel: "stereounique", description: "Whether to apply channel independently", dfault: 1, min: 0, max: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Smear",
+ instr: "twst_tf_tpvsmear",
+ description: "Delay bin amplitudes",
+ twine: true,
+ parameters: [
+ {name: "Maximum frames", channel: "maxframes", description: "Maximum number of frames to be used", automatable: false, min: 4, max: 32, step: 1, dfault: 16},
+ {name: "Frames", description: "Number of frames to be used", min: 4, max: 32, step: 1, dfault: 16},
+ {name: "Average frequencies", channel: "avgfreqs", description: "Average frequencies as well as smearing amplitudes", min: 0, max: 1, step: 1, dfault: 1},
+ {name: "Include original", channel: "includeoriginal", description: "Include original frames in output", min: 0, max: 1, step: 1, dfault: 0},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Read",
+ instr: "twst_tf_spectralread",
+ description: "Frequency domain sample reader",
+ parameters: [
+ {name: "Read position", channel: "readtime", description: "Read time ratio", dfault: 0},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Partial reconstruction",
+ instr: "twst_tf_partialreconstruction",
+ description: "Streaming partials resynthesis",
+ twine: true,
+ parameters: [
+ {name: "Threshold", description: "Analysis threshold factor", dfault: 0.5},
+ {name: "Minimum points", channel: "minpoints", description: "Minimum analysis points", dfault: 1, min: 1, max: 10, lagHint: 1},
+ {name: "Maximum gap", channel: "maxgap", description: "Maximum gap between analysis points", dfault: 3, min: 1, max: 10, lagHint: 1},
+ {name: "Maximum analysis tracks", channel: "anlmaxtracks", description: "Maximum number of frequency tracks in analysis", automatable: false, lagHint: -1},
+ {name: "Amplitude scale", channel: "ampscale", description: "Amplitude scaling"},
+ {presetgroup: "pitchscale"},
+ {name: "Maximum resynth tracks", channel: "resmaxtracks", description: "Maximum number of frequency tracks in resynthesis", lagHint: -1},
+ {preset: "fftsize"},
+ {preset: "wave"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Invert",
+ instr: "twst_tf_tpvinvert",
+ description: "Invert frequency and/or amplitude",
+ twine: true,
+ parameters: [
+ {name: "Invert frequency", channel: "invertfreq", description: "Perform inversion of frequency", dfault: 1, min: 0, max: 1, step: 1},
+ {name: "Invert amplitude", channel: "invertamp", description: "Perform inversion of amplitude", dfault: 1, min: 0, max: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth", dfault: 1},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Scramble",
+ instr: "twst_tf_tpvscramble",
+ twine: true,
+ description: "Randomly reassign amplitude and/or frequency",
+ parameters: [
+ {name: "Sanity", channel: "step", description: "Retention of sanity in scrambling", dfault: 0, min: 0, max: 1},
+ {name: "Amplitude", channel: "scrambleamp", description: "Apply scrambling to amplitude", dfault: 1, min: 0, max: 1, step: 1},
+ {name: "Frequency", channel: "scramblefreq", description: "Apply scrambling to frequency", dfault: 1, min: 0, max: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Frame gate",
+ instr: "twst_tf_tpvthreshold",
+ twine: true,
+ description: "Bin-independent spectral gate",
+ parameters: [
+ {name: "Threshold", description: "Amplitude threshold", dfault: 0.5, min: 0, max: 1},
+ {name: "Direction", description: "Apply to above or below threshold", dfault: 0, min: 0, max: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Frame freeze",
+ instr: "twst_tf_tpvfreeze",
+ twine: true,
+ description: "Freeze analysis frames",
+ parameters: [
+ {name: "Freeze amount", channel: "freeze", description: "Freeze amount", dfault: 1, min: 0, max: 1},
+ {name: "Freeze amplitude", channel: "freezeamp", description: "Apply amplitude freeze", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Freeze frequency", channel: "freezefreq", description: "Apply amplitude freeze", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Apply crossfades", channel: "crossfade", dfault: 0, min: 0, max: 1, step: 1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Average",
+ instr: "twst_tf_tpvaverage",
+ twine: true,
+ description: "Time-average frequency and/or amplitudes",
+ parameters: [
+ {name: "Average amplitudes", channel: "avgamp", dfault: 0, min: 0, max: 1, step: 1},
+ {name: "Average frequencies", channel: "avgfreq", dfault: 1, min: 0, max: 1, step: 1},
+ {name: "Maximum frames", description: "Maximum frames in average window", channel: "maxframes", dfault: 16, min: 2, max: 512, step: 1, lagHint: -1},
+ {name: "Rate", description: "Rate of averaging in Hz", dfault: 1, min: 0.1, max: 10, lagHint: -1},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Wrap",
+ instr: "twst_tf_tpvwrap",
+ twine: true,
+ description: "Wrap amplitude and/or frequency bins",
+ parameters: [
+ {name: "Amplitude wrap bin ratio", channel: "wrapampbin", dfault: 0, min: 0, max: 3},
+ {name: "Frequency wrap bin ratio", channel: "wrapfreqbin", dfault: 1, min: 0, max: 3},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Swap",
+ instr: "twst_tf_tpvswap",
+ twine: true,
+ description: "Swap amplitude and/or frequency bin ranges",
+ parameters: [
+ {name: "Amplitude start bin ratio", channel: "ampstart"},
+ {name: "Amplitude length bin ratio", channel: "amplength", dfault: 0.1},
+ {name: "Amplitude target bin ratio", channel: "amptarget"},
+ {name: "Frequency start bin ratio", channel: "freqstart"},
+ {name: "Frequency length bin ratio", channel: "freqlength", dfault: 0.1},
+ {name: "Frequency target bin ratio", channel: "freqtarget"},
+ {name: "Wrap mode", channel: "wrapmode", options: ["Limit", "Wrap"], dfault: 1, automatable: true},
+ {presetgroup: "pvanal"},
+ {presetgroup: "pvsynth"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Centroid oscillator",
+ instr: "twst_tf_centroidoscillator",
+ twine: true,
+ description: "Oscillate at the analysed spectral centroid",
+ parameters: [
+ {preset: "wave", automatable: true},
+ {presetgroup: "pitchscale"},
+ {name: "Portamento time", channel: "porttime", description: "Reading position glide", dfault: 0.01, min: 0.001, max: 0.3},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Bin oscillator",
+ instr: "twst_tf_binoscillator",
+ twine: true,
+ description: "Resynthesise a single analysis bin",
+ parameters: [
+ {name: "Bin ratio", channel: "bin", description: "Frequency bin ratio", dfault: 0.1, min: 0, max: 1},
+ {preset: "wave", automatable: true},
+ {presetgroup: "pitchscale"},
+ {name: "Portamento time", channel: "porttime", description: "Reading position glide", dfault: 0.01, min: 0.001, max: 0.3},
+ {presetgroup: "pvanal"},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Stochastic subtractive",
+ instr: "twst_tf_subtractive",
+ description: "Parametric aleatoric subtractive synthesis",
+ twine: true,
+ unstable: true,
+ parameters: [
+ {preset: "amp", automatable: false},
+ {presetgroup: "notefreq", nameprepend: "Minimum", dfault: 440, channelprepend: "min", automatable: false, lagHint: 1},
+ {presetgroup: "notefreq", nameprepend: "Maximum", dfault: 8000, channelprepend: "max", automatable: false, lagHint: -1},
+ {name: "Frequency increment", channel: "step", automatable: false, min: 1.1, max: 2, dfault: 1, lagHint: 1},
+ {name: "Frequency randomness", channel: "steprand", automatable: false, min: 1, max: 1.5, dfault: 1},
+ {name: "Amplitude increment", channel: "ampmult", automatable: false, min: 0.2, max: 1, dfault: 0.6},
+ {presetgroup: "applymode"}
+ ]
+ },
+ {
+ name: "Phase masher",
+ instr: "twst_tf_phasemash",
+ twine: true,
+ description: "Phase modification and reset",
+ parameters: [
+ {name: "Replace phase", min: 0, max: 1, step: 1, dfault: 0, channel: "phasereplace"},
+ {name: "Phase alteration", min: 0, max: 1, dfault: 0.5, channel: "phasevalue"},
+ {preset: "fftsize"},
+ {presetgroup: "applymode"}
+ ]
+ }
+ ]
+ }
+ ]
+};
\ No newline at end of file diff --git a/site/app/twirl/font/Chicago.woff b/site/app/twirl/font/Chicago.woff Binary files differnew file mode 100644 index 0000000..0ae3393 --- /dev/null +++ b/site/app/twirl/font/Chicago.woff diff --git a/site/app/twirl/font/JosefinSans.ttf b/site/app/twirl/font/JosefinSans.ttf Binary files differnew file mode 100644 index 0000000..00ea1e7 --- /dev/null +++ b/site/app/twirl/font/JosefinSans.ttf diff --git a/site/app/twirl/font/NationalPark-Regular.woff b/site/app/twirl/font/NationalPark-Regular.woff Binary files differnew file mode 100644 index 0000000..7766e9b --- /dev/null +++ b/site/app/twirl/font/NationalPark-Regular.woff diff --git a/site/app/twirl/font/Nouveau_IBM.woff b/site/app/twirl/font/Nouveau_IBM.woff Binary files differnew file mode 100644 index 0000000..0912b18 --- /dev/null +++ b/site/app/twirl/font/Nouveau_IBM.woff diff --git a/site/app/twirl/icon/areaSelect.svg b/site/app/twirl/icon/areaSelect.svg new file mode 100644 index 0000000..dc0549c --- /dev/null +++ b/site/app/twirl/icon/areaSelect.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-dashed-mouse-pointer"><path d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"/><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h2"/><path d="M14 3h1"/><path d="M3 9v1"/><path d="M21 9v2"/><path d="M3 14v1"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/arrowsUpDown.svg b/site/app/twirl/icon/arrowsUpDown.svg new file mode 100644 index 0000000..8ecaa91 --- /dev/null +++ b/site/app/twirl/icon/arrowsUpDown.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-down"><path d="m21 16-4 4-4-4"/><path d="M17 20V4"/><path d="m3 8 4-4 4 4"/><path d="M7 4v16"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/audition.svg b/site/app/twirl/icon/audition.svg new file mode 100644 index 0000000..d4a3dea --- /dev/null +++ b/site/app/twirl/icon/audition.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/automate.svg b/site/app/twirl/icon/automate.svg new file mode 100644 index 0000000..16a9705 --- /dev/null +++ b/site/app/twirl/icon/automate.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-spline"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16c.5-2 1.5-7 4-7 2 0 2 3 4 3 2.5 0 4.5-5 5-7"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/brightnessDecrease.svg b/site/app/twirl/icon/brightnessDecrease.svg new file mode 100644 index 0000000..4685dae --- /dev/null +++ b/site/app/twirl/icon/brightnessDecrease.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-dim"><circle cx="12" cy="12" r="4"/><path d="M12 4h.01"/><path d="M20 12h.01"/><path d="M12 20h.01"/><path d="M4 12h.01"/><path d="M17.657 6.343h.01"/><path d="M17.657 17.657h.01"/><path d="M6.343 17.657h.01"/><path d="M6.343 6.343h.01"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/brightnessIncrease.svg b/site/app/twirl/icon/brightnessIncrease.svg new file mode 100644 index 0000000..12f688b --- /dev/null +++ b/site/app/twirl/icon/brightnessIncrease.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-medium"><circle cx="12" cy="12" r="4"/><path d="M12 3v1"/><path d="M12 20v1"/><path d="M3 12h1"/><path d="M20 12h1"/><path d="m18.364 5.636-.707.707"/><path d="m6.343 17.657-.707.707"/><path d="m5.636 5.636.707.707"/><path d="m17.657 17.657.707.707"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/close.svg b/site/app/twirl/icon/close.svg new file mode 100644 index 0000000..fa22a65 --- /dev/null +++ b/site/app/twirl/icon/close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-x"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/commit.svg b/site/app/twirl/icon/commit.svg new file mode 100644 index 0000000..b00c67b --- /dev/null +++ b/site/app/twirl/icon/commit.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-import"><path d="M12 3v12"/><path d="m8 11 4 4 4-4"/><path d="M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/copy.svg b/site/app/twirl/icon/copy.svg new file mode 100644 index 0000000..84d777d --- /dev/null +++ b/site/app/twirl/icon/copy.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard-copy"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/><path d="M16 4h2a2 2 0 0 1 2 2v4"/><path d="M21 14H11"/><path d="m15 10-4 4 4 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/crossfade.svg b/site/app/twirl/icon/crossfade.svg new file mode 100644 index 0000000..ca5f34e --- /dev/null +++ b/site/app/twirl/icon/crossfade.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flip-horizontal-2"><path d="m3 7 5 5-5 5V7"/><path d="m21 7-5 5 5 5V7"/><path d="M12 20v2"/><path d="M12 14v2"/><path d="M12 8v2"/><path d="M12 2v2"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/cut.svg b/site/app/twirl/icon/cut.svg new file mode 100644 index 0000000..ca0a485 --- /dev/null +++ b/site/app/twirl/icon/cut.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scissors"><circle cx="6" cy="6" r="3"/><path d="M8.12 8.12 12 12"/><path d="M20 4 8.12 15.88"/><circle cx="6" cy="18" r="3"/><path d="M14.8 14.8 20 20"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/delete.svg b/site/app/twirl/icon/delete.svg new file mode 100644 index 0000000..b1d974f --- /dev/null +++ b/site/app/twirl/icon/delete.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-delete"><path d="M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z"/><path d="m12 9 6 6"/><path d="m18 9-6 6"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/develop.svg b/site/app/twirl/icon/develop.svg new file mode 100644 index 0000000..9d3f535 --- /dev/null +++ b/site/app/twirl/icon/develop.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-codepen"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/><line x1="12" x2="12" y1="22" y2="15.5"/><polyline points="22 8.5 12 15.5 2 8.5"/><polyline points="2 15.5 12 8.5 22 15.5"/><line x1="12" x2="12" y1="2" y2="8.5"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/ear.svg b/site/app/twirl/icon/ear.svg new file mode 100644 index 0000000..d2cb61a --- /dev/null +++ b/site/app/twirl/icon/ear.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ear"><path d="M6 8.5a6.5 6.5 0 1 1 13 0c0 6-6 6-6 10a3.5 3.5 0 1 1-7 0"/><path d="M15 8.5a2.5 2.5 0 0 0-5 0v1a2 2 0 1 1 0 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/fileVolume.svg b/site/app/twirl/icon/fileVolume.svg new file mode 100644 index 0000000..ee81c71 --- /dev/null +++ b/site/app/twirl/icon/fileVolume.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-volume-2"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M8 15h.01"/><path d="M11.5 13.5a2.5 2.5 0 0 1 0 3"/><path d="M15 12a5 5 0 0 1 0 6"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/hand.svg b/site/app/twirl/icon/hand.svg new file mode 100644 index 0000000..b258330 --- /dev/null +++ b/site/app/twirl/icon/hand.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand"><path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"/><path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"/><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/hide.svg b/site/app/twirl/icon/hide.svg new file mode 100644 index 0000000..1b66ce0 --- /dev/null +++ b/site/app/twirl/icon/hide.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye-off"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/horizontalArrows.svg b/site/app/twirl/icon/horizontalArrows.svg new file mode 100644 index 0000000..3187142 --- /dev/null +++ b/site/app/twirl/icon/horizontalArrows.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-move-horizontal"><path d="m18 8 4 4-4 4"/><path d="M2 12h20"/><path d="m6 8-4 4 4 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/horizontalFold.svg b/site/app/twirl/icon/horizontalFold.svg new file mode 100644 index 0000000..4c80762 --- /dev/null +++ b/site/app/twirl/icon/horizontalFold.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-fold-horizontal"><path d="M2 12h6"/><path d="M22 12h-6"/><path d="M12 2v2"/><path d="M12 8v2"/><path d="M12 14v2"/><path d="M12 20v2"/><path d="m19 9-3 3 3 3"/><path d="m5 15 3-3-3-3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/lasso.svg b/site/app/twirl/icon/lasso.svg new file mode 100644 index 0000000..bbf7602 --- /dev/null +++ b/site/app/twirl/icon/lasso.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lasso"><path d="M7 22a5 5 0 0 1-2-4"/><path d="M3.3 14A6.8 6.8 0 0 1 2 10c0-4.4 4.5-8 10-8s10 3.6 10 8-4.5 8-10 8a12 12 0 0 1-5-1"/><path d="M5 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/loop.svg b/site/app/twirl/icon/loop.svg new file mode 100644 index 0000000..b5eef47 --- /dev/null +++ b/site/app/twirl/icon/loop.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-repeat-2"><path d="m2 9 3-3 3 3"/><path d="M13 18H7a2 2 0 0 1-2-2V6"/><path d="m22 15-3 3-3-3"/><path d="M11 6h6a2 2 0 0 1 2 2v10"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/modulate.svg b/site/app/twirl/icon/modulate.svg new file mode 100644 index 0000000..891ded8 --- /dev/null +++ b/site/app/twirl/icon/modulate.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-audio-waveform"><path d="M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/move.svg b/site/app/twirl/icon/move.svg new file mode 100644 index 0000000..1f839f4 --- /dev/null +++ b/site/app/twirl/icon/move.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-move"><path d="M12 2v20"/><path d="m15 19-3 3-3-3"/><path d="m19 9 3 3-3 3"/><path d="M2 12h20"/><path d="m5 9-3 3 3 3"/><path d="m9 5 3-3 3 3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/paste.svg b/site/app/twirl/icon/paste.svg new file mode 100644 index 0000000..0f94433 --- /dev/null +++ b/site/app/twirl/icon/paste.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard-paste"><path d="M15 2H9a1 1 0 0 0-1 1v2c0 .6.4 1 1 1h6c.6 0 1-.4 1-1V3c0-.6-.4-1-1-1Z"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2M16 4h2a2 2 0 0 1 2 2v2M11 14h10"/><path d="m17 10 4 4-4 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/pasteSpecial.svg b/site/app/twirl/icon/pasteSpecial.svg new file mode 100644 index 0000000..387b56b --- /dev/null +++ b/site/app/twirl/icon/pasteSpecial.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard-pen"><rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5.5"/><path d="M4 13.5V6a2 2 0 0 1 2-2h2"/><path d="M13.378 15.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/pencil.svg b/site/app/twirl/icon/pencil.svg new file mode 100644 index 0000000..8d5e050 --- /dev/null +++ b/site/app/twirl/icon/pencil.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/play.svg b/site/app/twirl/icon/play.svg new file mode 100644 index 0000000..f089aa1 --- /dev/null +++ b/site/app/twirl/icon/play.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play"><polygon points="6 3 20 12 6 21 6 3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/pointer.svg b/site/app/twirl/icon/pointer.svg new file mode 100644 index 0000000..2180fb9 --- /dev/null +++ b/site/app/twirl/icon/pointer.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mouse-pointer"><path d="M12.586 12.586 19 19"/><path d="M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/randomise.svg b/site/app/twirl/icon/randomise.svg new file mode 100644 index 0000000..73e0c43 --- /dev/null +++ b/site/app/twirl/icon/randomise.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-dices"><rect width="12" height="12" x="2" y="10" rx="2" ry="2"/><path d="m17.92 14 3.5-3.5a2.24 2.24 0 0 0 0-3l-5-4.92a2.24 2.24 0 0 0-3 0L10 6"/><path d="M6 18h.01"/><path d="M10 14h.01"/><path d="M15 6h.01"/><path d="M18 9h.01"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/record.svg b/site/app/twirl/icon/record.svg new file mode 100644 index 0000000..726d9f1 --- /dev/null +++ b/site/app/twirl/icon/record.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/redo.svg b/site/app/twirl/icon/redo.svg new file mode 100644 index 0000000..c6dbd45 --- /dev/null +++ b/site/app/twirl/icon/redo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-redo"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/reset.svg b/site/app/twirl/icon/reset.svg new file mode 100644 index 0000000..f28f3db --- /dev/null +++ b/site/app/twirl/icon/reset.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-restart"><path d="M21 6H3"/><path d="M7 12H3"/><path d="M7 18H3"/><path d="M12 18a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L11 14"/><path d="M11 10v4h4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/rewind.svg b/site/app/twirl/icon/rewind.svg new file mode 100644 index 0000000..4aa6f48 --- /dev/null +++ b/site/app/twirl/icon/rewind.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rewind"><polygon points="11 19 2 12 11 5 11 19"/><polygon points="22 19 13 12 22 5 22 19"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/save.svg b/site/app/twirl/icon/save.svg new file mode 100644 index 0000000..1fec9b0 --- /dev/null +++ b/site/app/twirl/icon/save.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-save"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"/><path d="M7 3v4a1 1 0 0 0 1 1h7"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/script.svg b/site/app/twirl/icon/script.svg new file mode 100644 index 0000000..85069c6 --- /dev/null +++ b/site/app/twirl/icon/script.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/show.svg b/site/app/twirl/icon/show.svg new file mode 100644 index 0000000..8ca30ae --- /dev/null +++ b/site/app/twirl/icon/show.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/showAll.svg b/site/app/twirl/icon/showAll.svg new file mode 100644 index 0000000..ceb57c5 --- /dev/null +++ b/site/app/twirl/icon/showAll.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-fullscreen"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect width="10" height="8" x="7" y="8" rx="1"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/stop.svg b/site/app/twirl/icon/stop.svg new file mode 100644 index 0000000..f5e9c24 --- /dev/null +++ b/site/app/twirl/icon/stop.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square"><rect width="18" height="18" x="3" y="3" rx="2"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/trim.svg b/site/app/twirl/icon/trim.svg new file mode 100644 index 0000000..d7664d1 --- /dev/null +++ b/site/app/twirl/icon/trim.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-scissors"><rect width="20" height="20" x="2" y="2" rx="2"/><circle cx="8" cy="8" r="2"/><path d="M9.414 9.414 12 12"/><path d="M14.8 14.8 18 18"/><circle cx="8" cy="16" r="2"/><path d="m18 6-8.586 8.586"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/undo.svg b/site/app/twirl/icon/undo.svg new file mode 100644 index 0000000..0bc90dd --- /dev/null +++ b/site/app/twirl/icon/undo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/verticalArrows.svg b/site/app/twirl/icon/verticalArrows.svg new file mode 100644 index 0000000..518673d --- /dev/null +++ b/site/app/twirl/icon/verticalArrows.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-move-vertical"><path d="M12 2v20"/><path d="m8 18 4 4 4-4"/><path d="m8 6 4-4 4 4"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/verticalFold.svg b/site/app/twirl/icon/verticalFold.svg new file mode 100644 index 0000000..a21f94b --- /dev/null +++ b/site/app/twirl/icon/verticalFold.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-fold-vertical"><path d="M12 22v-6"/><path d="M12 8V2"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/><path d="m15 19-3-3-3 3"/><path d="m15 5-3 3-3-3"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/waves.svg b/site/app/twirl/icon/waves.svg new file mode 100644 index 0000000..5cccfbf --- /dev/null +++ b/site/app/twirl/icon/waves.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-waves"><path d="M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M2 12c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/zoomIn.svg b/site/app/twirl/icon/zoomIn.svg new file mode 100644 index 0000000..deaf812 --- /dev/null +++ b/site/app/twirl/icon/zoomIn.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zoom-in"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/zoomOut.svg b/site/app/twirl/icon/zoomOut.svg new file mode 100644 index 0000000..0dab381 --- /dev/null +++ b/site/app/twirl/icon/zoomOut.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zoom-out"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="8" x2="14" y1="11" y2="11"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/icon/zoomSelection.svg b/site/app/twirl/icon/zoomSelection.svg new file mode 100644 index 0000000..76100e1 --- /dev/null +++ b/site/app/twirl/icon/zoomSelection.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scan-search"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/></svg>
\ No newline at end of file diff --git a/site/app/twirl/notedata.json b/site/app/twirl/notedata.json new file mode 100644 index 0000000..696eae0 --- /dev/null +++ b/site/app/twirl/notedata.json @@ -0,0 +1 @@ +{"notes": [[0, "", 8.18],[1, "", 8.66],[2, "", 9.18],[3, "", 9.72],[4, "", 10.30],[5, "", 10.91],[6, "", 11.56],[7, "", 12.25],[8, "", 12.98],[9, "", 13.75],[10, "", 14.57],[11, "", 15.43],[12, "", 16.35],[13, "", 17.32],[14, "", 18.35],[15, "", 19.45],[16, "", 20.60],[17, "", 21.83],[18, "", 23.12],[19, "", 24.50],[20, "", 25.96],[21, "A0", 27.50],[22, "A#0/Bb0", 29.14],[23, "B0", 30.87],[24, "C1", 32.70],[25, "C#1/Db1", 34.65],[26, "D1", 36.71],[27, "D#1/Eb1", 38.89],[28, "E1", 41.20],[29, "F1", 43.65],[30, "F#1/Gb1", 46.25],[31, "G1", 49.00],[32, "G#1/Ab1", 51.91],[33, "A1", 55.00],[34, "A#1/Bb1", 58.27],[35, "B1", 61.74],[36, "C2", 65.41],[37, "C#2/Db2", 69.30],[38, "D2", 73.42],[39, "D#2/Eb2", 77.78],[40, "E2", 82.41],[41, "F2", 87.31],[42, "F#2/Gb2", 92.50],[43, "G2", 98.00],[44, "G#2/Ab2", 103.83],[45, "A2", 110.00],[46, "A#2/Bb2", 116.54],[47, "B2", 123.47],[48, "C3", 130.81],[49, "C#3/Db3", 138.59],[50, "D3", 146.83],[51, "D#3/Eb3", 155.56],[52, "E3", 164.81],[53, "F3", 174.61],[54, "F#3/Gb3", 185.00],[55, "G3", 196.00],[56, "G#3/Ab3", 207.65],[57, "A3", 220.00],[58, "A#3/Bb3", 233.08],[59, "B3", 246.94],[60, "C4", 261.63],[61, "C#4/Db4", 277.18],[62, "D4", 293.66],[63, "D#4/Eb4", 311.13],[64, "E4", 329.63],[65, "F4", 349.23],[66, "F#4/Gb4", 369.99],[67, "G4", 392.00],[68, "G#4/Ab4", 415.30],[69, "A4", 440.00],[70, "A#4/Bb4", 466.16],[71, "B4", 493.88],[72, "C5", 523.25],[73, "C#5/Db5", 554.37],[74, "D5", 587.33],[75, "D#5/Eb5", 622.25],[76, "E5", 659.26],[77, "F5", 698.46],[78, "F#5/Gb5", 739.99],[79, "G5", 783.99],[80, "G#5/Ab5", 830.61],[81, "A5", 880.00],[82, "A#5/Bb5", 932.33],[83, "B5", 987.77],[84, "C6", 1046.50],[85, "C#6/Db6", 1108.73],[86, "D6", 1174.66],[87, "D#6/Eb6", 1244.51],[88, "E6", 1318.51],[89, "F6", 1396.91],[90, "F#6/Gb6", 1479.98],[91, "G6", 1567.98],[92, "G#6/Ab6", 1661.22],[93, "A6", 1760.00],[94, "A#6/Bb6", 1864.66],[95, "B6", 1975.53],[96, "C7", 2093.00],[97, "C#7/Db7", 2217.46],[98, "D7", 2349.32],[99, "D#7/Eb7", 2489.02],[100, "E7", 2637.02],[101, "F7", 2793.83],[102, "F#7/Gb7", 2959.96],[103, "G7", 3135.96],[104, "G#7/Ab7", 3322.44],[105, "A7", 3520.00],[106, "A#7/Bb7", 3729.31],[107, "B7", 3951.07],[108, "C8", 4186.01],[109, "C#8/Db8", 4434.92],[110, "D8", 4698.64],[111, "D#8/Eb8", 4978.03],[112, "E8", 5274.04],[113, "F8", 5587.65],[114, "F#8/Gb8", 5919.91],[115, "G8", 6271.93],[116, "G#8/Ab8", 6644.88],[117, "A8", 7040.00],[118, "A#8/Bb8", 7458.62],[119, "B8", 7902.13],[120, "C9", 8372.02],[121, "C#9/Db9", 8869.84],[122, "D9", 9397.27],[123, "D#9/Eb9", 9956.06],[124, "E9", 10548.08],[125, "F9", 11175.30],[126, "F#9/Gb9", 11839.82],[127, "G9", 12543.85],[128, "G#9/Ab9", 13289.75]],"chords": [{"intervals": [0, 4, 8], "name": "Augmented"}, {"intervals": [0, 4, 7, 10, 2, 6], "name": "Augmented 11th"}, {"intervals": [0, 4, 8, 11], "name": "Augmented major 7th"}, {"intervals": [0, 4, 8, 10], "name": "Augmented 7th"}, {"intervals": [0, 6, 8], "name": "Augmented 6th"}, {"intervals": [0, 3, 6], "name": "Diminished"}, {"intervals": [0, 3, 6, 11], "name": "Diminished major 7th"}, {"intervals": [0, 3, 6, 9], "name": "Diminished 7th"}, {"intervals": [0, 4, 7], "name": "Dominant"}, {"intervals": [0, 4, 7, 10, 2, 5], "name": "Dominant 11th"}, {"intervals": [0, 4, 7, 10, 1], "name": "Dominant minor 9th"}, {"intervals": [0, 4, 7, 10, 2], "name": "Dominant 9th"}, {"intervals": [0, 3, 7], "name": "Dominant parallel"}, {"intervals": [0, 4, 7, 10], "name": "Dominant 7th"}, {"intervals": [0, 4, 6, 10], "name": "Dominant 7th b5"}, {"intervals": [0, 4, 7, 10, 2, 5, 9], "name": "Dominant 13th"}, {"intervals": [0, 5, 6, 7], "name": "Dream"}, {"intervals": [0, 7, 9, 1, 4], "name": "Elektra"}, {"intervals": [0, 8, 11, 4, 9], "name": "Farben"}, {"intervals": [0, 4, 7, 10], "name": "Harmonic 7th"}, {"intervals": [0, 4, 7, 10, 3], "name": "Augmented 9th"}, {"intervals": [0, 3, 6], "name": "Leading-tone"}, {"intervals": [0, 4, 7, 11, 6], "name": "Lydian"}, {"intervals": [0, 4, 7], "name": "Major"}, {"intervals": [0, 4, 7, 11, 2, 5], "name": "Major 11th"}, {"intervals": [0, 4, 7, 11], "name": "Major 7th"}, {"intervals": [0, 4, 7, 11, 6], "name": "Major 7th #11th"}, {"intervals": [0, 4, 7, 9], "name": "Major 6th"}, {"intervals": [0, 4, 7, 11, 2], "name": "Major 9th"}, {"intervals": [0, 4, 7, 11, 2, 6, 9], "name": "Major 13th"}, {"intervals": [0, 3, 7], "name": "Mediant"}, {"intervals": [0, 3, 7], "name": "Minor"}, {"intervals": [0, 3, 7, 10, 2, 5], "name": "Minor 11th"}, {"intervals": [0, 3, 7, 11], "name": "Minor major 7th"}, {"intervals": [0, 3, 7, 10, 2], "name": "Minor 9th"}, {"intervals": [0, 3, 7, 10], "name": "Minor 7th"}, {"intervals": [0, 3, 6, 10], "name": "Half-diminished 7th"}, {"intervals": [0, 3, 7, 9], "name": "Minor 6th"}, {"intervals": [0, 3, 7, 10, 2, 5, 9], "name": "Minor 13th"}, {"intervals": [0, 2, 4, 7], "name": "Mu"}, {"intervals": [0, 6, 10, 4, 9, 2], "name": "Mystic"}, {"intervals": [1, 5, 8], "name": "Neapolitan"}, {"intervals": [0, 4, 8, 10, 2], "name": "Ninth augmented 5th"}, {"intervals": [0, 4, 6, 10, 2], "name": "Ninth b5th"}, {"intervals": [1, 2, 8, 0, 3, 6, 7, 10, 11, 4, 7], "name": "Northern Lights"}, {"intervals": [0, 1, 4, 5, 8, 9], "name": "Napoleon hexachord"}, {"intervals": [0, 1, 4, 6, 7, 10], "name": "Petrushka"}, {"intervals": [0, 7], "name": "Power"}, {"intervals": [0, 3, 7], "name": "Psalms"}, {"intervals": [0, 4, 7], "name": "Secondary dominant"}, {"intervals": [0, 3, 6], "name": "Secondary leading-tone"}, {"intervals": [0, 3, 7], "name": "Secondary supertonic"}, {"intervals": [0, 4, 7, 9, 10], "name": "Seven six"}, {"intervals": [0, 4, 7, 10, 1], "name": "7th b9"}, {"intervals": [0, 5, 7, 10], "name": "7th suspension 4"}, {"intervals": [0, 4, 7, 9, 2], "name": "Sixth 9th"}, {"intervals": [0, 5, 7], "name": "Suspended"}, {"intervals": [0, 4, 7], "name": "Subdominant"}, {"intervals": [0, 3, 7], "name": "Subdominant parallel"}, {"intervals": [0, 3, 7], "name": "Submediant"}, {"intervals": [0, 4, 7], "name": "Subtonic"}, {"intervals": [0, 3, 7], "name": "Supertonic"}, {"intervals": [0, 5, 10, 3, 7], "name": "So What"}, {"intervals": [0, 4, 7, 10, 1, 9], "name": "Thirteenth b9th"}, {"intervals": [0, 4, 6, 10, 1, 9], "name": "Thirteenth b9th b5th"}, {"intervals": [0, 3, 7], "name": "Tonic counter parallel"}, {"intervals": [0, 4, 7], "name": "Tonic"}, {"intervals": [0, 3, 7], "name": "Tonic parallel"}, {"intervals": [0, 3, 6, 10], "name": "Tristan"}, {"intervals": [0, 1, 6], "name": "Viennese trichord 1"}, {"intervals": [0, 6, 7], "name": "Viennese trichord 2"}]}
\ No newline at end of file diff --git a/site/app/twirl/stdui.js b/site/app/twirl/stdui.js new file mode 100644 index 0000000..8141912 --- /dev/null +++ b/site/app/twirl/stdui.js @@ -0,0 +1,494 @@ +twirl.stdui = {
+ PlayButton: function(options) {
+ var self = this;
+ var txtPlay = "\u25b6";
+ var txtStop = "\u23f9";
+
+ this.state = false;
+
+ this.element = $("<p />").text(txtPlay).css("cursor", "pointer");
+
+ if (options.hasOwnProperty("fontsize")) {
+ this.element.css("font-size", options.fontsize);
+ }
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.element.click(function() {
+ if (!self.state) {
+ self.element.text(txtStop);
+ self.state = true;
+ } else {
+ self.element.text(txtPlay);
+ self.state = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(self.state, self);
+ }
+ if (options.hasOwnProperty("change")) {
+ options.change(self.state, self);
+ }
+ });
+
+ this.setValue = function(v, runChange) {
+ if (v) {
+ self.element.text(txtStop);
+ self.state = true;
+ } else {
+ self.element.text(txtPlay);
+ self.state = false;
+ }
+
+ if (runChange && options.hasOwnProperty("change")) {
+ options.change(self.state, self);
+ }
+ };
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(this.element);
+ }
+ },
+ StandardButton: function(options) {
+ var self = this;
+ this.element = $("<button />").addClass("smbut").text(options.label);
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.element.click(function(e) {
+ options.change(e);
+ });
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(this.element);
+ }
+ },
+ StandardToggle: function(options) {
+ var self = this;
+ var doChange = true;
+ this.element = $("<button />").addClass("smbut").attr(
+ "value",
+ options.hasOwnProperty("value") && options.value == 1 ? 1 : 0
+ ).text(options.label);
+
+ function setAppearance() {
+ var col;
+ if (self.element.attr("value") == 0) {
+ col = "#b5b01d";
+ } else {
+ col = "#f2e30c";
+ }
+ self.element.css("background-color", col);
+ };
+
+ setAppearance();
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.element.click(function() {
+ var val = (self.element.attr("value") == 0) ? 1 : 0;
+ self.element.attr("value", val);
+ setAppearance();
+ if (options.stateAlter) {
+ options.stateAlter(val, self);
+ }
+ if (doChange && options.hasOwnProperty("change")) {
+ options.change(val, self);
+ }
+ });
+
+ this.setValue = function(v, runChange) {
+ if (!runChange) {
+ doChange = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(v, self);
+ }
+ self.element.attr("value", v);
+ setAppearance();
+ if (runChange && options.hasOwnProperty("change")) {
+ options.change(v, self);
+ }
+ doChange = true;
+ };
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(this.element);
+ }
+ },
+ ComboBox: function(options) {
+ var self = this;
+ var doChange = true;
+ var tr = $("<tr />");
+ this.element = $("<select />");
+
+ for (var v in options.options) {
+ var val = (options.asValue) ? options.options[v] : v;
+ $("<option />").attr("value", val).text(options.options[v]).appendTo(self.element);
+ }
+
+ this.element.change(function() {
+ var val;
+ val = (options.asValue) ? self.element.find(":selected").text() : self.element.val();
+ if (options.stateAlter) {
+ options.stateAlter(val, self);
+ }
+ if (doChange && options.hasOwnProperty("change")) {
+ options.change(val, self);
+ }
+ });
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ tr.show();
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ tr.hide();
+ };
+
+ this.setValue = function(v, runChange) {
+ if (!runChange) {
+ doChange = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(v, self);
+ }
+ self.element.val(v);
+ doChange = true;
+ };
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ if (options.asRow) {
+ tr.appendTo($("#" + options.target));
+ var tdl = $("<td />").appendTo(tr);
+ if (options.label) tdl.addClass("transparentinput").text(options.label);
+ $("<td />").append(self.element).appendTo(tr);
+ $("<td />").appendTo(tr);
+ } else {
+ $("#" + options.target).append(self.element);
+ }
+ }
+ },
+ TextArea: function(options) {
+ var self = this;
+ this.element = $("<textarea />").css({
+ "background-color": "var(--codeBgColor)",
+ "color": "var(--codeFgColor)",
+ "font-size": "var(--codeFontSize)",
+ "font-family": "var(--codeFontFace)"
+ });
+
+ if (options.width) {
+ self.element.css("width", options.width);
+ }
+ if (options.height) {
+ self.element.css("height", options.height);
+ }
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.setValue = function(v) {
+ self.element.val(v);
+ };
+ if (options.target) {
+ $("#" + options.target).append(self.element);
+ }
+ },
+ TextInput: function(options) {
+ var self = this;
+ var doChange = true;
+ this.element = $("<input />").css({
+ "background-color": "var(--bgColor1)",
+ "color": "var(--fgColor1)"
+ });
+
+ if (options.css) {
+ self.element.css(options.css);
+ }
+
+ this.element.change(function() {
+ var val = self.element.val();
+ if (options.stateAlter) {
+ options.stateAlter(val, self);
+ }
+ if (doChange && options.hasOwnProperty("change")) {
+ options.change(val, self);
+ }
+ });
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.setValue = function(v, runChange) {
+ if (!runChange) {
+ doChange = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(v, self);
+ }
+ self.element.val(v);
+ if (runChange && options.hasOwnProperty("change")) {
+ options.change(v, self);
+ }
+ doChange = true;
+ };
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(this.element);
+ }
+ },
+ ColourInput: function(options) {
+ var self = this;
+ var doChange = true;
+ this.element = $("<input />").attr("type", "color");
+
+ if (options.css) {
+ self.element.css(options.css);
+ }
+
+ this.element.change(function() {
+ var val = self.element.val();
+ if (options.stateAlter) {
+ options.stateAlter(val, self);
+ }
+ if (doChange && options.hasOwnProperty("change")) {
+ options.change(val, self);
+ }
+ });
+
+ this.show = function() {
+ self.element.css("visibility", "visible");
+ };
+
+ this.hide = function() {
+ self.element.css("visibility", "hidden");
+ };
+
+ this.setValue = function(v, runChange) {
+ if (!runChange) {
+ doChange = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(v, self);
+ }
+ self.element.val(v);
+ if (runChange && options.hasOwnProperty("change")) {
+ options.change(v, self);
+ }
+ doChange = true;
+ };
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(this.element);
+ }
+ },
+ Slider: function(options) {
+ var self = this;
+ var doChange = true;
+ var label;
+ var tr = $("<tr />");
+
+ if (!options.hasOwnProperty("min")) {
+ options.min = 0;
+ }
+
+ if (!options.hasOwnProperty("max")) {
+ options.max = 1;
+ }
+
+ if (!options.hasOwnProperty("step")) {
+ options.step = 0.000001;
+ }
+
+ if (!options.hasOwnProperty("value")) {
+ options.value = 0;
+ }
+
+ if (!options.hasOwnProperty("size")) {
+ options.size = 64;
+ }
+
+ this.element = $("<input />")
+ .attr("type", "range")
+ .attr("min", options.min)
+ .attr("max", options.max)
+ .attr("step", options.step)
+ .attr("value", options.value)
+ .addClass("slider")
+ .addClass("transparentinput");
+
+ this.valueLabel = null;
+
+ this.show = function() {
+ tr.show();
+ };
+
+ this.hide = function() {
+ tr.hide();
+ };
+
+ var valueLabelUpdate = true;
+ if (options.hasOwnProperty("valueLabel") && options.valueLabel) {
+ self.valueLabel = $("<input />")
+ .attr("type", "number")
+ .attr("min", options.min)
+ .attr("max", options.max)
+ .attr("step", options.step)
+ .attr("value", options.value)
+ .addClass("transparentinput")
+ .val(options.value).change(function(){
+ valueLabelUpdate = false;
+ self.setValue($(this).val());
+ valueLabelUpdate = true;
+ });
+ }
+
+ function applyChange() {
+ var val = self.element.val();
+ if (options.stateAlter) {
+ options.stateAlter(val, self);
+ }
+ if (doChange && options.hasOwnProperty("change")) {
+ options.change(val, self);
+ }
+ if (self.valueLabel) {
+ self.valueLabel.val(val);
+ }
+ }
+
+ this.element.change(function() {
+ applyChange();
+ });
+
+ this.element.on("input", function() {
+ if (options.changeOnInput) {
+ return applyChange();
+ }
+ if (options.input) {
+ options.input(self.element.val(), self);
+ }
+ if (self.valueLabel) {
+ self.valueLabel.val(self.element.val());
+ }
+ });
+
+ this.setValue = function(v, runChange) {
+ if (!runChange) {
+ doChange = false;
+ }
+ if (options.stateAlter) {
+ options.stateAlter(v, self);
+ }
+ self.element.val(v);
+ if (runChange && options.hasOwnProperty("change")) {
+ options.change(v, self);
+ }
+ if (self.valueLabel && valueLabelUpdate) {
+ self.valueLabel.val(v);
+ };
+ doChange = true;
+ };
+
+
+ if (options.hasOwnProperty("label")) {
+ label = $("<p />").addClass("knoblabel").text(options.label);
+ }
+
+ var tdl = $("<td />").appendTo(tr);
+ if (label) tdl.append(label);
+
+ self.element.appendTo($("<td />").appendTo(tr));
+
+ var tdv = $("<td />").appendTo(tr);
+ if (self.valueLabel) tdv.append(self.valueLabel);
+
+ if (options.tooltip) {
+ this.element.on("mouseover", function(e){
+ twirl.tooltip.show(e, options.tooltip);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ if (options.target) {
+ $("#" + options.target).append(tr);
+ }
+ }
+}
\ No newline at end of file diff --git a/site/app/twirl/theme.css b/site/app/twirl/theme.css new file mode 100644 index 0000000..6f36a90 --- /dev/null +++ b/site/app/twirl/theme.css @@ -0,0 +1,284 @@ +@font-face {
+ font-family: "Chicago";
+ src: url("font/Chicago.woff") format("woff");
+ font-weight: 500;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Nouveau IBM";
+ src: url("font/Nouveau_IBM.woff") format("woff");
+ font-style: normal;
+ font-display: swap;
+ font-size: 12pt;
+}
+
+@font-face {
+ font-family: "National Park";
+ src: url("font/NationalPark-Regular.woff") format("woff");
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Josefin Sans";
+ src: url("font/JosefinSans.ttf") format("truetype");
+ font-style: normal;
+ font-display: swap;
+}
+
+html {
+ --fontFace: Chicago, Arial, sans-serif;
+ --fontSizeSmall: 11px;
+ --fontSizeDefault: 12px;
+ --fontSizeMedium: 14px;
+ --fontSizeLarge: 16px;
+ --bgColor1: #706b3b;
+ --bgColor2: #614d2c;
+ --bgColor3: #61594e;
+ --bgColor4: #806b4b;
+ --fgColor1: #f0b041;
+ --fgColor2: #e0a031;
+ --fgColor3: #db931f;
+ --menuBarBottomBorder: 1px solid #565656;
+ --tabSelectedBgColor: #453c2e;
+ --tabUnselectedBgColor: #292621;
+ --tabSelectedFgColor: #db931f;
+ --tabUnselectedFgColor: #a67b37;
+ --rowOddBgColor: #383137;
+ --rowEvenBgColor: #525049;
+ --buttonBorder: 1px solid #dba418;
+ --codeBgColor: #524320;
+ --codeFgColor: #f7b314;
+ --codeFontSize: 12pt;
+ --codeFontFace: "Nouveau IBM", courier, monospace;
+ --promptBgColor: #615c53;
+ --promptFontSize: 16px;
+ --promptShadow: 3px 3px rgba(0,0,0,0.2);
+ --promptBorder: 1px solid black;
+ --waveformOverlayColor: #4c464e;
+ --scrollbarColor: #e8bb54 #57451b;
+ --waveformPlayheadColor: #FF2222;
+ --waveformCoverColor: #232323;
+ --waveformCoverOpacity: 0.5;
+ --waveformMarkerColor: #edea2b;
+ --waveformMarkerRunnerColor: #919171;
+ --waveformCrossfadeWidth: 1px;
+ --waveformCrossfadeLineColor: #bfbfbf;
+ --waveformSelectOpacity: 0.5;
+ --waveformSelectColor: #772222;
+ --waveformLocationColor: #e0c496;
+ --waveformBgColor: #332b1f;
+ --waveformFgColor: #d18502;
+ --waveformGridColor: rgb(227, 206, 154, 0.2);
+ --waveformGridTextColor: rgb(240, 190, 65, 0.5);
+ --waveformChannelLineColor: rgb(87, 87, 87, 0.7);
+ --waveformTimeBarBgColor: #806b4b;
+ --waveformTimeBarFgColor: #f0b041;
+ --iconFilter: brightness(0) saturate(100%) invert(78%) sepia(77%) saturate(923%) hue-rotate(334deg) brightness(89%) contrast(88%);
+}
+
+html.themeBasic {
+ --fontFace: "Josefin Sans", Arial, sans-serif;
+ --fontSizeSmall: 10pt;
+ --fontSizeDefault: 11pt;
+ --fontSizeMedium: 14pt;
+ --fontSizeLarge: 16pt;
+ --bgColor1: #a1a1cf;
+ --bgColor2: #9898a8;
+ --bgColor3: #8989b1;
+ --bgColor4: #bdbdff;
+ --fgColor1: #000000;
+ --fgColor2: #555555;
+ --fgColor3: #343445;
+ --menuBarBottomBorder: 1px solid #bdbdbd;
+ --tabSelectedBgColor: #7878bd;
+ --tabUnselectedBgColor: #5656aa;
+ --tabSelectedFgColor: #000000;
+ --tabUnselectedFgColor: #777777;
+ --rowOddBgColor: #a9a9c9;
+ --rowEvenBgColor: #9999a9;
+ --buttonBorder: 1px solid #343434;
+ --codeBgColor: #9999ff;
+ --codeFgColor: #121212;
+ --codeFontSize: 12pt;
+ --codeFontFace: courier, monospace;
+ --promptBgColor: #8585ff;
+ --promptFontSize: 12pt;
+ --promptShadow: 3px 3px rgba(0,0,50,0.3);
+ --promptBorder: 1px solid #5555aa;
+ --waveformOverlayColor: #4c464e;
+ --scrollbarColor: #ffffff #444499;
+ --waveformPlayheadColor: #FF2222;
+ --waveformCoverColor: #232323;
+ --waveformCoverOpacity: 0.5;
+ --waveformMarkerColor: #edea2b;
+ --waveformMarkerRunnerColor: #919171;
+ --waveformCrossfadeWidth: 1px;
+ --waveformCrossfadeLineColor: #3434ff;
+ --waveformSelectOpacity: 0.5;
+ --waveformSelectColor: #772222;
+ --waveformLocationColor: #000000;
+ --waveformBgColor: #fcf6de;
+ --waveformFgColor: #222266;
+ --waveformGridColor: rgb(0, 0, 0, 0.2);
+ --waveformGridTextColor: rgb(0, 0, 0, 0.5);
+ --waveformChannelLineColor: rgb(0, 0, 0, 0.7);
+ --waveformTimeBarBgColor: #ffffff;
+ --waveformTimeBarFgColor: #000000;
+ --iconFilter: invert(17%) sepia(31%) saturate(2452%) hue-rotate(208deg) brightness(96%) contrast(88%);
+}
+
+html.themeHacker {
+ --fontFace: "Nouveau IBM", monospace, Courier, sans-serif;
+ --fontSizeSmall: 10pt;
+ --fontSizeDefault: 12pt;
+ --fontSizeMedium: 14pt;
+ --fontSizeLarge: 16pt;
+ --bgColor1: #323232;
+ --bgColor2: #434343;
+ --bgColor3: #010101;
+ --bgColor4: #344534;
+ --fgColor1: #44ee44;
+ --fgColor2: #66cc66;
+ --fgColor3: #009900;
+ --menuBarBottomBorder: 1px solid #bbbbbb;
+ --tabSelectedBgColor: #454545;
+ --tabUnselectedBgColor: #232323;
+ --tabSelectedFgColor: #44bb44;
+ --tabUnselectedFgColor: #229922;
+ --rowOddBgColor: #232323;
+ --rowEvenBgColor: #343434;
+ --buttonBorder: none;
+ --codeBgColor: #000000;
+ --codeFgColor: #0000ab;
+ --codeFontSize: 12pt;
+ --codeFontFace: "Nouveau IBM", courier, monospace;
+ --promptBgColor: #000000;
+ --promptFontSize: 14pt;
+ --promptShadow: 5px 5px rgba(0,50,0,0.2);
+ --promptBorder: 1px solid white;
+ --waveformOverlayColor: #4c464e;
+ --scrollbarColor: #000000 #005500;
+ --waveformPlayheadColor: #FF2222;
+ --waveformCoverColor: #232323;
+ --waveformCoverOpacity: 0.5;
+ --waveformMarkerColor: #ed2bed;
+ --waveformMarkerRunnerColor: #545454;
+ --waveformCrossfadeWidth: 1px;
+ --waveformCrossfadeLineColor: #989898;
+ --waveformSelectOpacity: 0.5;
+ --waveformSelectColor: #227722;
+ --waveformLocationColor: #000000;
+ --waveformBgColor: #121212;
+ --waveformFgColor: #00aa00;
+ --waveformGridColor: rgb(90, 90, 90, 0.2);
+ --waveformGridTextColor: rgb(120, 170, 120, 0.5);
+ --waveformChannelLineColor: rgb(120, 120, 120, 0.7);
+ --waveformTimeBarBgColor: #446644;
+ --waveformTimeBarFgColor: #000000;
+ --iconFilter: invert(41%) sepia(18%) saturate(1917%) hue-rotate(62deg) brightness(104%) contrast(90%);
+}
+
+html.themeMonoclassic {
+ --fontFace: Chicago, Arial, sans-serif;
+ --fontSizeSmall: 11px;
+ --fontSizeDefault: 12px;
+ --fontSizeMedium: 14px;
+ --fontSizeLarge: 16px;
+ --bgColor1: #ffffff;
+ --bgColor2: #eeeeee;
+ --bgColor3: #dddddd;
+ --bgColor4: #cccccc;
+ --fgColor1: #000000;
+ --fgColor2: #111111;
+ --fgColor3: #222222;
+ --menuBarBottomBorder: 1px solid #000000;
+ --tabSelectedBgColor: #ffffff;
+ --tabUnselectedBgColor: #cccccc;
+ --tabSelectedFgColor: #000000;
+ --tabUnselectedFgColor: #555555;
+ --rowOddBgColor: #ffffff;
+ --rowEvenBgColor: #eeeeee;
+ --buttonBorder: 1px solid #000000;
+ --codeBgColor: #ffffff;
+ --codeFgColor: #000000;
+ --codeFontSize: 12pt;
+ --codeFontFace: "Nouveau IBM", courier, monospace;
+ --promptBgColor: #ffffff;
+ --promptFontSize: 16px;
+ --promptShadow: 3px 3px rgba(0,0,0,0.2);
+ --promptBorder: 1px solid black;
+ --waveformOverlayColor: #bbbbbb;
+ --scrollbarColor: #000000 #ffffff;
+ --waveformPlayheadColor: #000000;
+ --waveformCoverColor: #dddddd;
+ --waveformCoverOpacity: 0.5;
+ --waveformMarkerColor: #444444;
+ --waveformMarkerRunnerColor: #dddddd;
+ --waveformCrossfadeWidth: 1px;
+ --waveformCrossfadeLineColor: #333333;
+ --waveformSelectOpacity: 0.5;
+ --waveformSelectColor: #666666;
+ --waveformLocationColor: #111111;
+ --waveformBgColor: #ffffff;
+ --waveformFgColor: #000000;
+ --waveformGridColor: rgb(20, 20, 20, 0.2);
+ --waveformGridTextColor: rgb(20, 10, 20, 0.5);
+ --waveformChannelLineColor: rgb(10, 10, 10, 0.7);
+ --waveformTimeBarBgColor: #dddddd;
+ --waveformTimeBarFgColor: #444444;
+ --iconFilter: none;
+}
+
+html.themeDoze {
+ --fontFace: "Trebuchet MS", Arial, sans-serif;
+ --fontSizeSmall: 8pt;
+ --fontSizeDefault: 10pt;
+ --fontSizeMedium: 14pt;
+ --fontSizeLarge: 16pt;
+ --bgColor1: #8f8f8f;
+ --bgColor2: #7f7f7f;
+ --bgColor3: #6f6f6f;
+ --bgColor4: #5f5f5f;
+ --fgColor1: #000000;
+ --fgColor2: #171717;
+ --fgColor3: #212121;
+ --menuBarBottomBorder: 1px solid #000000;
+ --tabSelectedBgColor: #acacac;
+ --tabUnselectedBgColor: #6a6a6a;
+ --tabSelectedFgColor: #000000;
+ --tabUnselectedFgColor: #555555;
+ --rowOddBgColor: #acacac;
+ --rowEvenBgColor: #919191;
+ --buttonBorder: 1px solid #767676;
+ --codeBgColor: #000000;
+ --codeFgColor: #f5d151;
+ --codeFontSize: 12pt;
+ --codeFontFace: "Nouveau IBM", courier, monospace;
+ --promptBgColor: #8f8f8f;
+ --promptFontSize: 16pt;
+ --promptShadow: 6px 6px rgba(0,0,0,0.2);
+ --promptBorder: 1px solid #707070;
+ --waveformOverlayColor: #bbbbbb;
+ --scrollbarColor: #949494 #c2c2c2;
+ --waveformPlayheadColor: #bb0000;
+ --waveformCoverColor: #dddddd;
+ --waveformCoverOpacity: 0.5;
+ --waveformMarkerColor: #f0c93e;
+ --waveformMarkerRunnerColor: #9e8e55;
+ --waveformCrossfadeWidth: 1px;
+ --waveformCrossfadeLineColor: #79a1b0;
+ --waveformSelectOpacity: 0.5;
+ --waveformSelectColor: #eff77c;
+ --waveformLocationColor: #bb0000;
+ --waveformBgColor: #2c304d;
+ --waveformFgColor: #485ce0;
+ --waveformGridColor: rgb(20, 20, 20, 0.2);
+ --waveformGridTextColor: rgb(20, 10, 20, 0.5);
+ --waveformChannelLineColor: rgb(10, 10, 10, 0.7);
+ --waveformTimeBarBgColor: #949494;
+ --waveformTimeBarFgColor: #c2c2c2;
+ --iconFilter: none;
+}
\ No newline at end of file diff --git a/site/app/twirl/transform.js b/site/app/twirl/transform.js new file mode 100644 index 0000000..1feff51 --- /dev/null +++ b/site/app/twirl/transform.js @@ -0,0 +1,1212 @@ +twirl.transform = {};
+twirl.transform.Parameter = function(options) {
+ var self = this;
+ var instr = options.instrument;
+ var tDefinition = options.definition;
+ var parent = options.parent;
+ var transform = options.transform;
+ var host = options.host;
+ var onChange;
+ var refreshable = false;
+ var changeFunc;
+ var initval = true;
+ var definition = {};
+ var randomiseAllowed = true;
+ var visible = true;
+ var uniqueTransformID = (options.transform) ? options.transform.uniqueID : "";
+ if (parent) {
+ Object.assign(definition, tDefinition);
+ } else {
+ definition = tDefinition;
+ }
+
+ if (options.onChange || definition.onChange) {
+ onChange = function(val) {
+ if (options.onChange) options.onChange(val);
+ if (definition.onChange) definition.onChange(val);
+ }
+ }
+
+ if (definition.hasOwnProperty("preset")) {
+ var save = {};
+ for (var s of ["dfault", "name", "channel", "automatable", "description"]) {
+ if (definition.hasOwnProperty(s)) {
+ save[s] = definition[s];
+ }
+ }
+
+ if (definition.preset == "amp") {
+ Object.assign(definition, {name: "Amplitude", channel: "amp", description: "Amplitude", dfault: 1, min: 0, max: 1});
+ } else if (definition.preset == "pvslock") {
+ Object.assign(definition, {name: "Peak lock", channel: "pvslock", description: "Lock frequencies around peaks", min: 0, max: 1, step: 1, dfault: 0});
+ } else if (definition.preset == "fftsize") {
+ Object.assign(definition, {name: "FFT size", channel: "fftsize", description: "FFT size", options: [256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65535], dfault: 2, asvalue: true, automatable: false, lagHint: -1});
+ } else if (definition.preset == "wave") {
+ Object.assign(definition, {name: "Wave", description: "Wave shape to use", options: ["Sine", "Square", "Saw", "Pulse", "Triangle"], dfault: 0, channel: "wave"});
+ } else if (definition.preset == "wintype") {
+ Object.assign(definition, {name: "Window type", channel: "wintype", description: "Window shape", options: ["Hanning", "Hamming", "Half sine"], dfault: 0, automatable: false});
+
+ } else if (definition.preset == "instanceloop") {
+ Object.assign(definition, {name: "Cross instance loop type", channel: "otlooptype", description: "Loop type of other instance", options: ["None", "Forward", "Backward", "Ping-pong"], dfault: 0});
+
+ } else if (definition.preset == "note") {
+ var notes = {};
+ for (var i = 21; i < 128; i++) {
+ var v = twirl.noteData.data.notes[i];
+ notes[v[0]] = v[1];
+ }
+ Object.assign(definition, {name: "Note", channel: "note", description: "Note to use", options: notes, dfault: 69, automatable: true});
+ } else if (definition.preset == "instance") {
+ var c = (!definition.channel) ? "ot" : definition.channel;
+ initval = false;
+ if (transform) transform.refreshable = true;
+ refreshable = true;
+ Object.assign(definition, {
+ name: "Instance", description: "Other wave to use", channel: "instance",
+ options: options.otherInstanceNamesFunc(),
+ automatable: false
+ });
+ changeFunc = function(index) {
+ var oif = options.instancesFunc();
+ if (!oif[index]) return;
+ var s = oif[index].selected;
+ app.setControlChannel(instr + "_" + "inststart" + uniqueTransformID, s[0]);
+ app.setControlChannel(instr + "_" + "instend" + uniqueTransformID, s[1]);
+ app.setControlChannel(instr + "_" + "instchan" + uniqueTransformID, s[2]);
+ };
+ }
+ if (save) {
+ Object.assign(definition, save);
+ }
+ } // if preset
+
+ if (definition.channel == "applymode" || definition.noRandomisation) {
+ randomiseAllowed = false;
+ }
+
+ var type;
+
+ if (definition.hasOwnProperty("conditions") && !parent) {
+ refreshable = true;
+ if (transform) transform.refreshable = refreshable;
+ }
+
+ var channel = "";
+ if (!definition.hasOwnProperty("absolutechannel")) {
+ channel = (parent) ? parent.sendChannel : instr + "_";
+ }
+
+ if (definition.hasOwnProperty("channel")) {
+ channel += definition.channel;
+ } else {
+ channel += definition.name.toLowerCase();
+ }
+
+ this.sendChannel = channel;
+ if (!parent) {
+ this.sendChannel += uniqueTransformID;
+ }
+ var elContainer = $("<div />");
+ var elValueLabel = $("<div />");
+ var elValueInput;
+ var elModulations;
+ var elInput;
+ var elRow;
+ var elModSelect;
+ var automation = [];
+
+ this.definition = definition;
+ this.modulation = null;
+ this.automation = null;
+ this.channel = channel;
+ this.modulationParameters = null;
+
+ this.setPlaying = async function(state) {
+ if (definition.automatable || definition.hidden) return;
+
+ if (definition.disableOnPlay) {
+ if (elValueInput) {
+ elValueInput.prop("disabled", state);
+ elValueInput.css("opacity", (state) ? 0.8 : 1);
+ }
+
+ if (elInput) {
+ elInput.prop("disabled", state);
+ elInput.css("opacity", (state) ? 0.8 : 1);
+ }
+ } else {
+ if (state) {
+ var text = "Changes will be applied upon next run";
+ elContainer.on("mouseover", function(event){
+ twirl.tooltip.show(event, text);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ } else {
+ elContainer.off("mouseover").off("mouseout");
+ }
+ }
+ };
+
+
+ if (!definition.hasOwnProperty("hidden")) {
+ definition.hidden = false;
+ }
+
+ if (!definition.step) {
+ definition.step = 0.0000001;
+ }
+
+ if (definition.min == null) {
+ definition.min = 0;
+ }
+
+ if (definition.max == null) {
+ definition.max = 1;
+ }
+
+ if (!definition.hasOwnProperty("fireChanges")) {
+ definition.fireChanges = true;
+ }
+
+ if (definition.dfault == null) {
+ definition.dfault = 1;
+ }
+
+ if (parent) {
+ if (definition.hostrange) {
+ var items = ["step", "min", "max", "options", "conditions", "hostrange"];
+ if (definition.dfault == "hostrangemin") {
+ definition.dfault = parent.definition.min;
+ } else if (definition.dfault == "hostrangemax") {
+ definition.dfault = parent.definition.max;
+ } else {
+ items.push("dfault");
+ }
+ for (let o of items) {
+ if (parent.definition.hasOwnProperty(o)) {
+ definition[o] = parent.definition[o];
+ }
+ }
+ } else if (definition.preset == "hostrangemin") {
+ definition.min = definition.max = definition.dfault = parent.definition.min;
+ } else if (definition.preset == "hostrangemax") {
+ definition.min = definition.max = definition.dfault = parent.definition.max;
+ }
+ }
+
+ if (definition.options) {
+ type = "select";
+ definition.min = 0;
+ definition.max = definition.options.length - 1;
+ definition.step = 1;
+ } else if (definition.hasOwnProperty("type")) {
+ type = definition.type;
+ } else if (definition.min == 0 && definition.max == 1 && definition.step == 1) {
+ type = "checkbox";
+ } else {
+ type = "range";
+ }
+
+ if (!definition.hasOwnProperty("automatable")) {
+ definition.automatable = ((type == "range" || type == "checkbox") && !parent);
+ }
+
+ this.getLagHint = function() {
+ if (!definition.lagHint || !visible) return;
+ var lagHint;
+ if (typeof(definition.lagHint) == "object") {
+ lagHint = "setting <i>" + definition.name + "</i> to <i>"
+ + definition.options[definition.lagHint.option] + "</i>";
+ } else {
+ lagHint = ((definition.lagHint < 0) ? "reducing" : "increasing")
+ + " <i>" + definition.name + "</i>";
+ }
+ return lagHint;
+ };
+
+ this.setRawValue = function(val) {
+ if (type == "checkbox") {
+ elInput[0].checked = (val == 0) ? false : true;
+ } else {
+ elInput.val(val);
+ }
+ elInput.trigger("change");
+ }
+
+ this.getRawValue = function() {
+ return elInput.val();
+ }
+
+ this.getValue = function() {
+ var val;
+ if (type == "range" || type == "string") {
+ val = elInput.val();
+ } else if (type == "select") {
+ val = (definition.asvalue) ? elInput.find("option:selected").text() : elInput.val();
+ } else if (type == "checkbox") {
+ val = (elInput[0].checked) ? 1 : 0;
+ }
+ return val;
+ };
+
+ this.reset = function() {
+ self.setRawValue(definition.dfault);
+ if (!options.unmanagedAutomation) {
+ if (automationActive) disableAutomation();
+ if (self.automation) {
+ delete self.automation;
+ self.automation = null;
+ }
+ if (elSpline) {
+ elSpline.remove();
+ delete elSpline;
+ }
+ }
+ if (modulationShown && !options.unmanagedModulation) {
+ hideModulations();
+ }
+ };
+
+ this.randomise = function() {
+ if (!randomiseAllowed) return;
+ var val;
+ if (!options.unmanagedModulation && definition.automatable) {
+ if (Math.random() >= 0.5) {
+ modButton.el.click();
+ }
+ }
+
+ if (type == "select") {
+ val = Math.round(Math.random() * (definition.options.length - 1));
+ } else if (type == "range") {
+ val = (Math.random() * (definition.max - definition.min)) + definition.min;
+ if (definition.step == 1) {
+ val = Math.round(val);
+ } else {
+ val = Math.ceil((val - definition.min) / definition.step) * definition.step + definition.min;
+ }
+ } else if (type = "checkbox") {
+ val = (Math.round(Math.random()));
+ }
+ self.setRawValue(val);
+
+ if (self.modulationParameters && !options.unmanagedModulation) {
+ // 4 = just the non-crossadaptive ones
+ elModSelect.val(Math.round(Math.random() * 4)).trigger("change");
+ for (let mp in self.modulationParameters) {
+ self.modulationParameters[mp].randomise();
+ }
+ }
+ };
+
+
+ this.refresh = function() {
+ if (!refreshable || !transform) {
+ return;
+ }
+ if (definition.preset == "instance") {
+ createSelectOptions(elInput, options.otherInstanceNamesFunc(), true);
+ }
+ for (var k in definition.conditions) {
+ var c = definition.conditions[k];
+ var chan = (c.absolutechannel) ? c.channel : transform.instr + "_" + c.channel;
+ var val = transform.parameters[chan].getValue();
+ if (
+ (c.operator == "eq" && val != c.value) ||
+ (c.operator == "neq" && val == c.value) ||
+ (c.operator == "lt" && val >= c.value) ||
+ (c.operator == "gt" && val <= c.value) ||
+ (c.operator == "le" && val > c.value) ||
+ (c.operator == "ge" && val < c.value)
+ ) {
+ visible = false;
+ app.setControlChannel(self.sendChannel, definition.dfault);
+ return elRow.hide();
+ }
+ }
+ // app.setControlChannel(self.sendChannel, self.getValue());
+ visible = true;
+ elRow.show();
+ };
+
+ function createSelectOptions(elSelect, options, sendValue) {
+ var selected = elInput.val();
+ elSelect.empty();
+ var applied;
+ var firstOption;
+ for (var x in options) {
+ if (!firstOption) firstOption = x;
+ var opt = $("<option />").text(options[x]).val(x).appendTo(elSelect);
+ if (x == selected) {
+ opt.attr("selected", "1");
+ if (changeFunc) changeFunc(x);
+ applied = true;
+ }
+ }
+ if (!applied) {
+ elInput.val(firstOption);
+ if (changeFunc) changeFunc(firstOption);
+ }
+ if (sendValue) {
+ app.setControlChannel(self.sendChannel, self.getValue());
+ }
+ definition.min = 0;
+ definition.max = (Array.isArray(options)) ? options.length - 1 : Object.keys(options).length - 1;
+ }
+
+ function updateLabel() {
+ if (elValueInput) {
+ var val = self.getValue();
+ updateinput = false;
+ rounding = 10000;
+ val = Math.round(val * rounding) / rounding;
+ elValueInput.val(val);
+ updateinput = true;
+ }
+ }
+
+ if (type == "select") {
+ elInput = $("<select />");
+ elInput.change(function(){
+ var val = self.getValue();
+ if (transform) transform.refresh();
+ if (definition.fireChanges) {
+ if (changeFunc) changeFunc(val);
+ if (!host.offline) app.setControlChannel(self.sendChannel, val);
+ }
+ if (onChange) {
+ onChange(val);
+ }
+ });
+
+ var selectOptions = (definition.hostrange && parent) ? parent.definitions.options : definition.options;
+ createSelectOptions(elInput, selectOptions);
+
+ } else if (type == "string") {
+ elInput = $("<input />").change(function() {
+ if (transform) transform.refresh();
+ var val = self.getValue();
+ if (definition.fireChanges) {
+ app.setStringChannel(self.sendChannel, val);
+ }
+ if (onChange) {
+ onChange(val);
+ }
+ });
+
+ } else if (type == "checkbox") {
+ elInput = $("<input />").addClass("twirl_checkbox").attr("type", "checkbox").on("change", function() {
+ if (transform) transform.refresh();
+ var val = self.getValue();
+ if (definition.fireChanges) {
+ app.setControlChannel(self.sendChannel, val);
+ }
+ if (onChange) {
+ onChange(val);
+ }
+ });
+ } else if (type == "range") {
+ var updateinput = true;
+ var max = definition.max;
+ var min = definition.min;
+ var step = definition.step;
+ var dfault = definition.dfault;
+
+ elInput = $("<input />").addClass("twirl_slider").attr("type", "range").on("input", function() {
+ updateLabel();
+ if (definition.fireChanges) {
+ app.setControlChannel(self.sendChannel, self.getValue());
+ }
+ }).change(function() {
+ updateLabel();
+ if (transform) transform.refresh();
+ var val = self.getValue();
+ if (definition.fireChanges && !host.offline) {
+ app.setControlChannel(self.sendChannel, val);
+ }
+ if (onChange) {
+ onChange(val);
+ }
+ }).attr("min", min).attr("max", max).attr("step", step).val(dfault);
+
+ elValueInput = $("<input />").attr("type", "number").attr("min", min).attr("max", max).attr("step", step).addClass("twirl_transparentinput").appendTo(elValueLabel).change(function() {
+ if (updateinput) {
+ elInput.val($(this).val()).trigger("change").trigger("input");
+ }
+ });
+ }
+ /*
+ elInput.on("contextmenu", function(e){
+ var items = [{name: "Reset", click: function(){
+ self.reset();
+ }}];
+ if (definition.automatable) {
+ items.push({
+ name: "Automate",
+ click: function(){
+ if (!options.unmanagedAutomation) {
+ transform.showAutomation(definition.name, elSpline);
+ }
+ }
+ });
+ }
+
+ items.push({
+ name: "Randomise",
+ click: function(){
+ self.randomise();
+ }
+ });
+
+ items.push({
+ name: ((randomiseAllowed) ? "Exclude from" : "Include in") + " randomisation",
+ click: randomiseButton.click
+ });
+
+ twirl.contextMenu.show(e, items);
+ });*/
+
+ elContainer.append(elInput);
+ if (initval) {
+ self.setRawValue(definition.dfault);
+ if (definition.fireChanges) {
+ elInput.trigger("change");
+ }
+ }
+
+
+ this.setDefault = function() {
+ elInput.val(definition.dfault).trigger("change");
+ //app.setControlChannel(sendChannel, definition.dfault);
+ };
+
+ this.remove = function() {
+ disableAutomation();
+ elRow.remove();
+ if (elSpline) {
+ elSpline.remove();
+ }
+ if (self.modulation) {
+ self.modulation = null;
+ }
+
+ if (self.automation) {
+ self.automation = null;
+ }
+ };
+
+ this.getAutomationData = function(start, end) {
+ if (self.modulation) {
+ var m = twirl.appdata.modulations[self.modulation];
+ return {type: "modulation", data: [m.instr, self.sendChannel]};
+ } else if (automationActive && self.automation) {
+ return {type: "automation", channel: self.sendChannel, data: self.automation.getLinsegData(start, end, options.getRegionFunc())};
+ }
+ };
+
+ var resetButton = twirl.createIcon({
+ label: "Reset parameter",
+ icon: "reset",
+ click: function() {
+ self.reset();
+ }
+ });
+
+ var randomiseButton = twirl.createIcon({
+ label: "Include in randomisation",
+ icon: "randomise",
+ click: function(obj) {
+ randomiseAllowed = !randomiseAllowed;
+ var opacity = (randomiseAllowed) ? 1 : 0.4;
+ obj.el.css("opacity", opacity);
+ }
+ });
+ if (!randomiseAllowed) {
+ randomiseButton.el.css("opacity", 0.4);
+ }
+
+ var elSpline;
+ var editAutomationButton = twirl.createIcon({
+ label: "Select automation",
+ icon: "show",
+ click: function() {
+ if (!transform) return;
+ if (elSpline) {
+ automationShown = true;
+ transform.showAutomation(definition.name, elSpline);
+ }
+ }
+ });
+ editAutomationButton.el.hide();
+
+ var automationButton = twirl.createIcon({
+ label: "Automate",
+ label2: "Close automation",
+ icon: "automate",
+ icon2: "close",
+ click: function() {
+ if (elSpline && automationActive) {
+ disableAutomation();
+ if (options.onAutomationClick) options.onAutomationClick(false);
+ } else {
+ showAutomation();
+ if (options.onAutomationClick) options.onAutomationClick(true);
+ }
+ }
+ });
+
+ var automationActive = false;
+ var automationShown = false;
+
+ this.hideAutomation = function() {
+ if (!transform) return;
+ automationShown = false;
+ if (elSpline) {
+ transform.hideAutomation(definition.name);
+ }
+ }
+
+ function disableAutomation() {
+ if (!transform) return;
+ automationActive = false;
+ automationShown = false;
+ if (!host.offline) app.setControlChannel(self.sendChannel, self.getValue());
+ elValueLabel.show();
+ elInput.show();
+ modButton.el.show();
+ automationButton.setState(true);
+ editAutomationButton.el.hide();
+ self.hideAutomation();
+ }
+
+ this.redraw = function(region) {
+ if (self.automation && !options.unmanagedAutomation) {
+ if (region && region[0] != null && region[1] != null) {
+ self.automation.setRange(region[0], region[1]);
+ } else {
+ self.automation.redraw();
+ }
+ }
+ };
+
+ this.createAutomationSpline = function(elTarget, colour) {
+ if (!colour) colour = twirl.random.rgbColour();
+ if (!self.automation) {
+ self.automation = new SplineEdit(
+ elTarget, colour,
+ options.getDurationFunc,
+ [definition.min, definition.max, self.getValue(), definition.step],
+ definition.name
+ );
+ }
+ };
+
+ function showAutomation() {
+ if (!transform) return;
+ var colour = twirl.random.rgbColour();
+ automationShown = true;
+ automationActive = true;
+
+ if (!elSpline) {
+ elSpline = $("<div />").attr("id", "spl_" + channel).css({
+ position: "absolute", width: "100%", height: "100%", overflow: "hidden"
+ });
+ }
+
+ transform.showAutomation(definition.name, elSpline);
+ self.createAutomationSpline(elSpline, colour);
+
+
+ elValueLabel.hide();
+ elInput.hide();
+ modButton.el.hide();
+ elSpline.show();
+ editAutomationButton.el.show(); //.css("background-color", colour);
+ automationButton.setState(false);
+ }
+
+
+ elModulations = $("<div />").addClass("twirl_tf_container").hide().appendTo(elContainer);
+ var modulationShown = false;
+
+
+ var modButton = twirl.createIcon({
+ label: "Modulate",
+ label2: "Close modulation",
+ icon: "modulate",
+ icon2: "close",
+ click: function() {
+ if (elModulations && modulationShown) {
+ hideModulations();
+ } else {
+ showModulations();
+ }
+ }
+ });
+
+ function hideModulations() {
+ app.setControlChannel(self.sendChannel, self.getValue());
+ modulationShown = false;
+ elValueLabel.show();
+ elInput.show();
+ automationButton.el.show();
+ self.modulation = null;
+ modButton.setState(true);
+ if (elModulations) {
+ elModulations.hide();
+ }
+ }
+
+ function showModulations() {
+ if (!transform) return;
+ modulationShown = true;
+ elValueLabel.hide();
+ elInput.hide();
+ automationButton.el.hide();
+ elModulations.show();
+ modButton.setState(false);
+ if (elModulations.children().length != 0) {
+ elModSelect.val(0).trigger("change");
+ return;
+ }
+ var tb = $("<tbody />");
+ function buildModulation(i) {
+ tb.empty();
+ self.modulationParameters = {};
+ self.modulation = i;
+ let m = twirl.appdata.modulations[i];
+ for (let x of m.parameters) {
+ var tp = new twirl.transform.Parameter({
+ instrument: m.instr,
+ definition: x,
+ transform: transform,
+ parent: self,
+ onAutomationClick: options.onAutomationClick,
+ getDurationFunc: options.getDurationFunc,
+ getRegionFunc: options.getRegionFunc,
+ otherInstanceNamesFunc: options.otherInstanceNamesFunc,
+ instancesFunc: options.instancesFunc,
+ host: options.host
+ });
+ self.modulationParameters[tp.channel] = tp;
+ tb.append(tp.getElementRow(true)); // hmm modulate the modulation with false
+ }
+ }
+ var selecttb = $("<tbody />").appendTo($("<table />)").appendTo(elModulations));
+ var row = $("<tr />").append($("<td />").addClass("twirl_tf_cell_text").text("Modulation type")).appendTo(selecttb);
+ var elConditionalOptions = [];
+
+ if (host.onInstanceChangeds) {
+ host.onInstanceChangeds.push(function(){
+
+ for (let o of elConditionalOptions) {
+ if (options.instancesFunc().length == 1) {
+ o.prop("disabled", true);
+ } else {
+ o.prop("disabled", false);
+ }
+ }
+ });
+ }
+
+ elModSelect = $("<select />").change(function() {
+ self.modulation = $(this).val();
+ buildModulation(self.modulation);
+ }).appendTo($("<td />").appendTo(row));
+ $("<table />").append(tb).appendTo(elModulations);
+
+ for (let i in twirl.appdata.modulations) {
+ var m = twirl.appdata.modulations[i];
+ var o = $("<option />").text(m.name).val(i).appendTo(elModSelect);
+ if (m.inputs > 1) {
+ elConditionalOptions.push(o);
+ if (!options.instancesFunc || options.instancesFunc().length == 1) {
+ o.prop("disabled", true);
+ }
+ }
+ }
+ elModSelect.val(0).trigger("change");
+ }
+
+ this.getElementRow = function(nocontrols) {
+ if (definition.hidden) {
+ return null;
+ };
+ if (elRow) {
+ return elRow;
+ }
+ elRow = $("<tr />");
+ var name = $("<td />").addClass("twirl_tf_cell_text").text(definition.name).appendTo(elRow);
+ if (definition.description) {
+ name.on("mouseover", function(event){
+ twirl.tooltip.show(event, definition.description);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ $("<td />").addClass("twirl_tf_cell").append(elContainer).appendTo(elRow);
+ $("<td />").addClass("twirl_tf_cellfixed").append(elValueLabel).appendTo(elRow);
+ if (!nocontrols) {
+ for (let b of [resetButton, randomiseButton]) $("<td />").addClass("twirl_tf_cell_plainbg").append(b.el).appendTo(elRow);
+
+ if (definition.automatable) {
+ var items = [];
+ if (!options.unmanagedAutomation) {
+ items.push(automationButton);
+ items.push(editAutomationButton);
+ }
+ if (!options.unmanagedModulation) {
+ items.push(modButton);
+ }
+ for (let b of items) $("<td />").addClass("twirl_tf_cell_plainbg").append(b.el).appendTo(elRow);
+ }
+
+ }
+ return elRow;
+ };
+};
+
+
+
+twirl.transform.Transform = function(options) {
+ var self = this;
+ var elTarget = options.element;
+ var def = options.definition;
+ var host = options.host;
+ var elTb;
+ var pAddOdd = true;
+ this.path = (options.path) ? options.path : def.name;
+ this.instr = def.instr;
+ this.name = def.name;
+ this.refreshable = false;
+ var elSplineOverlay;
+ var hideAutomationButton;
+ this.parameters = {};
+ this.uniqueID = 0;
+
+ if (options.uniqueID) {
+ this.uniqueID = options.uniqueID;
+ }
+
+ var automationEls = {};
+ this.showAutomation = function(name, el) {
+ if (!elSplineOverlay) {
+ elSplineOverlay = $("<div />").addClass("twirl_spline_overlay").appendTo(options.splineElement);
+ }
+ for (var e in automationEls) {
+ automationEls[e].css({"z-index": 23, opacity: 0.4});
+ }
+ if (!el) {
+ el = automationEls[name];
+ } else {
+ automationEls[name] = el;
+ }
+ el.css({"z-index": 24, opacity: 1}).show();
+ hideAutomationButton.el.show();
+ elSplineOverlay.show();
+ if (el.parents(elSplineOverlay).length == 0) {
+ elSplineOverlay.append(el);
+ }
+ options.splineElement.show();
+ if (options.onShowAutomation) {
+ options.onShowAutomation();
+ }
+ };
+
+ this.getLagHints = function() {
+ var lagHints = [];
+ for (let i in self.parameters) {
+ var p = self.parameters[i];
+ var lagHint = p.getLagHint();
+ if (lagHint) lagHints.push(lagHint);
+ }
+ var lagHintHtml;
+ if (lagHints.length != 0) {
+ lagHintHtml = "Try ";
+ for (var i in lagHints) {
+ lagHintHtml += lagHints[i];
+ if (i != lagHints.length - 1) {
+ lagHintHtml += ((i == lagHints.length - 2) ? " or " : ", ");
+ }
+ }
+ }
+ return lagHintHtml;
+ };
+
+ this.hideAutomation = function(name) {
+ if (automationEls[name]) {
+ automationEls[name].hide();
+ delete automationEls[name];
+ if (Object.keys(automationEls).length == 0) {
+ elSplineOverlay.hide();
+ hideAutomationButton.el.hide();
+ options.splineElement.hide();
+ if (options.onHideAllAutomation) {
+ options.onHideAllAutomation();
+ }
+ }
+ }
+ }
+
+ this.hideAllAutomation = function(name) {
+ for (let p in self.parameters) {
+ self.parameters[p].hideAutomation();
+ }
+ };
+
+ this.redraw = function(region) {
+ for (let p in self.parameters) {
+ self.parameters[p].redraw(region);
+ }
+ };
+
+ this.refresh = function() {
+ if (!self.refreshable) {
+ return;
+ }
+ for (var k in self.parameters) {
+ self.parameters[k].refresh();
+ }
+ };
+
+ this.getAutomationData = function(start, end) {
+ var automations = [];
+ for (var k in self.parameters) {
+ var data = self.parameters[k].getAutomationData(start, end);
+ if (data) {
+ automations.push(data);
+ }
+ }
+ return automations;
+ };
+
+ this.getState = async function() {
+ var data = {instr: def.instr, name: self.path, channels: {}};
+ var value;
+ for (let chan in self.parameters) {
+ value = await app.getControlChannel(self.parameters[chan].sendChannel);
+ data.channels[chan] = value;
+ if (self.parameters[chan].modulationParameters) {
+ for (let modchan in self.parameters[chan].modulationParameters) {
+ value = await app.getControlChannel(self.parameters[chan].modulationParameters[modchan].sendChannel);
+ data.channels[modchan] = value;
+ }
+ }
+ }
+ return data;
+ };
+
+
+ this.reset = function() {
+ for (let p in self.parameters) {
+ self.parameters[p].reset();
+ }
+ };
+
+ this.randomise = function() {
+ for (let p in self.parameters) {
+ self.parameters[p].randomise();
+ if (!options.unmanagedModulation && self.parameters[p].modulationParameters) {
+ for (let mp in self.parameters[p].modulationParameters) {
+ self.parameters[p].modulationParameters[mp].randomise();
+ }
+ }
+ }
+ };
+
+ this.saveState = function() {
+ if (!options.useStorage || !host.storage) return;
+ var state = {};
+ for (let p in self.parameters) {
+ state[p] = self.parameters[p].getRawValue();
+ }
+ if (!host.storage.transforms) {
+ host.storage.transforms = {};
+ }
+ host.storage.transforms[def.instr] = state;
+ host.saveStorage();
+ };
+
+ this.remove = function() {
+ self.saveState();
+ for (let p in self.parameters) {
+ self.parameters[p].remove();
+ }
+ if (elSplineOverlay) {
+ elSplineOverlay.remove();
+ }
+ }
+
+ this.removeParameter = function(channel) {
+ if (self.parameters.hasOwnProperty(channel)) {
+ self.parameters[channel].remove();
+ delete self.parameters[channel]
+ }
+ };
+
+ function addParameter(pdef) {
+ var tp = new twirl.transform.Parameter({
+ instrument: def.instr,
+ definition: pdef,
+ transform: self,
+ getDurationFunc: options.getDurationFunc,
+ getRegionFunc: options.getRegionFunc,
+ otherInstanceNamesFunc: options.otherInstanceNamesFunc,
+ instancesFunc: options.instancesFunc,
+ unmanagedAutomation: options.unmanagedAutomation,
+ unmanagedModulation: options.unmanagedModulation,
+ host: host
+ });
+ self.parameters[tp.channel] = tp;
+ var er = tp.getElementRow();
+ if (er) {
+ elTb.append(er.addClass("twirl_tf_row_" + ((pAddOdd) ? "odd" : "even")));
+ pAddOdd = !pAddOdd;
+ };
+ };
+
+ this.setPlaying = function(state) {
+ for (let i in self.parameters) {
+ self.parameters[i].setPlaying(state);
+ }
+ };
+
+ function namePrepend(name, pdef) {
+ if (!pdef.hasOwnProperty("nameprepend")) return name;
+ name = pdef.nameprepend + " " + name;
+ return name[0] + name.substr(1).toLowerCase()
+ }
+
+ this.addParameter = function(pdef) {
+ if (!pdef.hasOwnProperty("presetgroup")) {
+ return addParameter(pdef);
+ }
+ var name;
+ var conditions;
+ var groupParameters = [];
+ var channelPrepend = (pdef.hasOwnProperty("channelprepend")) ? pdef.channelprepend : "";
+
+ if (pdef.presetgroup == "pvsynth") {
+ var dfaultMode = (pdef.hasOwnProperty("dfault")) ? pdef.dfault : 0;
+ conditions = [
+ {channel: channelPrepend + "pvresmode", operator: "eq", value: 1}
+ ];
+ groupParameters = [
+ {name: namePrepend("Resynth mode", pdef), channel: channelPrepend + "pvresmode", description: "Type of FFT resynthesis used", dfault: dfaultMode, options: ["Overlap-add", "Additive"], automatable: false},
+ {name: namePrepend("Oscillator spread", pdef), channel: channelPrepend + "pvaoscnum", description: "Number of oscillators used", automatable: false, conditions: conditions, lagHint: -1},
+ {name: namePrepend("Frequency modulation", pdef), channel: channelPrepend + "pvafreqmod", description: "Frequency modulation", dfault: 1, min: 0.01, max: 2, conditions: conditions},
+ {name: namePrepend("Oscillator offset", pdef), channel: channelPrepend + "pvabinoffset", description: "Oscillator bin offset", automatable: false, conditions: conditions, dfault: 0, lagHint: 1},
+ {name: namePrepend("Oscillator increment", pdef), channel: channelPrepend + "pvabinincr", description: "Oscillator bin increment", min: 1, max: 8, dfault: 1, step: 1, automatable: false, conditions: conditions, lagHint: -1}
+ ];
+
+ } else if (pdef.presetgroup == "applymode") {
+ var conditionsMix = [{channel: "applymode", operator: "eq", value: 1, absolutechannel: true}];
+ var conditionsFilter = [{channel: "applymode", operator: "eq", value: 4, absolutechannel: true}];
+ if (pdef.conditions) {
+ for (let c of pdef.conditions) {
+ conditionsMix.push(c);
+ conditionsFilter.push(c);
+ }
+ }
+ groupParameters = [
+ {name: "Apply mode", channel: "applymode", absolutechannel: true, description: "Apply mode", automatable: true, options: ["Replace", "Mix", "Modulate", "Demodulate", "Filter"], dfault: 0, conditions: pdef.conditions},
+ {name: "Dry mix", description: "Original signal amplitude", channel: "applymodedry", absolutechannel: true, conditions: conditionsMix, min: 0, max: 1, dfault: 1},
+ {name: "Wet mix", description: "Transformed signal amplitude", channel: "applymodewet", absolutechannel: true, conditions: conditionsMix, min: 0, max: 1, dfault: 1},
+ {name: "Minimum frequency", description: "Minimum frequency to transform", channel: "applymodedry", absolutechannel: true, conditions: conditionsFilter, min: 20, max: 44100, dfault: 500},
+ {name: "Maximum frequency", description: "Maximum frequency to transform", channel: "applymodedry", absolutechannel: true, conditions: conditionsFilter, min: 20, max: 44100, dfault: 2000}
+ ];
+
+ } else if (pdef.presetgroup == "pvanal") {
+ /* LPC unstable with WASM
+ groupParameters = [
+ {name: "Analysis type", channel: "pvstype", options: ["Overlap-add", "Linear prediction"], dfault: 0, automatable: false},
+ {preset: "fftsize"},
+ {name: "Overlap decimation", options: [2, 4, 8, 16], asvalue: true, dfault: 1, channel: "pvsdecimation", automatable: false, lagHint: -1},
+ {preset: "pvslock"},
+ {name: "Window size multiplier", min: 1, max: 4, dfault: 1, step :1, channel: "pvswinsizem", automatable: false, lagHint: -1, conditions: [{channel: "pvstype", operator: "eq", value: 0}]},
+ {name: "Order multiplier", description: "Linear predictor order (FFT size multiplier)", min: 0.001, max: 1, dfault: 0.25, channel: "pvsorderm", automatable: false, lagHint: -1, conditions: [{channel: "pvstype", operator: "eq", value: 1}]},
+ {name: "Window type", channel: "pvswintype", options: ["Hamming", "Von Hann", "Kaiser"], dfault: 1, automatable: false, conditions: [{channel: "pvstype", operator: "eq", value: 0}]},
+ {name: "Window type", channel: "pvswintypelpc", options: ["Hanning", "Hamming", "Half sine"], dfault: 0, automatable: false, conditions: [{channel: "pvstype", operator: "eq", value: 1}]}
+ ];
+ */
+ groupParameters = [
+ {preset: "fftsize"},
+ {name: "Overlap decimation", options: [2, 4, 8, 16], asvalue: true, dfault: 1, channel: "pvsdecimation", automatable: false, lagHint: -1},
+ {preset: "pvslock"},
+ {name: "Window size multiplier", min: 1, max: 4, dfault: 1, step :1, channel: "pvswinsizem", automatable: false, lagHint: -1},
+ {name: "Window type", channel: "pvswintype", options: ["Hamming", "Von Hann", "Kaiser"], dfault: 1, automatable: false}
+ ];
+ } else if (pdef.presetgroup == "pitchscale") {
+ groupParameters = [
+ {name: namePrepend("Pitch scale mode", pdef), channel: channelPrepend + "pitchscalemode", options: ["Ratio", "Semitone"], dfault: 0},
+ {name: namePrepend("Pitch scale", pdef), channel: channelPrepend + "pitchscale", description: "Pitch scaling", dfault: 1, min: 0.01, max: 10, conditions: [{channel: channelPrepend + "pitchscalemode", operator: "eq", value: 0}]},
+ {name: namePrepend("Semitones", pdef), channel: channelPrepend + "pitchsemitones", min: -24, max: 24, step: 1, dfault: 0, conditions: [{channel: channelPrepend + "pitchscalemode", operator: "eq", value: 1}]}
+ ];
+
+ } else if (pdef.presetgroup == "notefreq") {
+ var base = {name: namePrepend("Frequency mode", pdef), channel: channelPrepend + "freqmode", description: "Frequency mode", options: ["Frequency", "Note"], dfault: 0};
+ if (pdef.hasOwnProperty("conditions")) {
+ base["conditions"] = pdef.conditions;
+ }
+ groupParameters.push(base);
+
+ conditions = [{channel: channelPrepend + "freqmode", operator: "eq", value: 0}];
+ if (pdef.hasOwnProperty("conditions")) {
+ Array.prototype.push.apply(conditions, pdef.conditions);
+ }
+
+ var dfaultFreq = (pdef.hasOwnProperty("dfault")) ? pdef.dfault : 440;
+
+ var freq = {name: namePrepend("Frequency", pdef), channel: channelPrepend + "freq", description: "Frequency", dfault: dfaultFreq, min: 20, max: 22000, conditions: conditions}
+ if (pdef.hasOwnProperty("lagHint")) {
+ freq.lagHint = pdef.lagHint;
+ }
+ groupParameters.push(freq);
+
+ conditions = [{channel: channelPrepend + "freqmode", operator: "eq", value: 1}];
+ if (pdef.hasOwnProperty("conditions")) {
+ Array.prototype.push.apply(conditions, pdef.conditions);
+ }
+ var note = {preset: "note", name: namePrepend("Note", pdef), conditions: conditions, channel: channelPrepend + "note"};
+ if (pdef.hasOwnProperty("lagHint")) {
+ note.lagHint = pdef.lagHint;
+ }
+ groupParameters.push(note);
+
+ }
+ for (let gp of groupParameters) {
+ if (pdef.hasOwnProperty("automatable")) {
+ gp.automatable = pdef.automatable;
+ }
+ addParameter(gp);
+ }
+ }
+
+ function build() {
+ elTarget.empty();
+ var elContainer = $("<div />").addClass("twirl_tf_container").appendTo(elTarget);
+ hideAutomationButton = twirl.createIcon({label: "Hide automation", icon: "hide", click: function() {
+ self.hideAllAutomation();
+ }});
+ hideAutomationButton.el.hide();
+
+ if (!host.offline) {
+ app.setControlChannel("applymode" + self.uniqueID, 0); // not all transforms will set this
+ }
+
+ var header = $("<div />").addClass("twirl_tf_header");
+ if (def.unstable) {
+ $("<div />").css({
+ "background-color": "#aa0000",
+ color: "#ffffff",
+ "font-size": "var(--fontSizeSmall)"
+ }).text("Instabilities have been reported with this transform. It is recommended you save your work before using it.").appendTo(header);
+ }
+ $("<div />").text(def.name).appendTo(header);
+ if (options.onClose) {
+ header.append(twirl.createIcon({
+ label: "Close",
+ icon: "close",
+ click: function() { options.onClose()},
+ size: 20
+ }).el.css("float", "right"));
+ }
+ var el = $("<div />").addClass("twirl_tf_container").append(header).appendTo(elContainer);
+
+ if (def.description) {
+ header.on("mouseover", function(event){
+ twirl.tooltip.show(event, def.description);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+
+ $("<div />").css({"float": "right"}).append(
+ hideAutomationButton.el
+ ).append(
+ twirl.createIcon({
+ label: "Randomise parameters",
+ icon: "randomise",
+ click: function() {
+ self.randomise();
+ }
+ }).el
+ ).append(
+ twirl.createIcon({
+ label: "Reset parameters",
+ icon: "reset",
+ click: function() {
+ self.reset();
+ }
+ }).el
+ ).appendTo(el);
+
+
+
+ var tbl = $("<table />").appendTo(elContainer);
+ elTb = $("<tbody />").appendTo(tbl);
+ for (let p of def.parameters) {
+ self.addParameter(p);
+ }
+
+ if (options.useStorage && host.storage && host.storage.transforms && host.storage.transforms[def.instr]) {
+ var state = host.storage.transforms[def.instr];
+ for (var p in state) {
+ self.parameters[p].setRawValue(state[p]);
+ }
+ }
+ self.refresh();
+ }
+ build();
+};
+
+twirl.transform.TreeView = function(options) {
+ var self = this;
+
+ function recurse(items, descended, path) {
+ if (!path) path = "";
+ items = (items) ? items : options.items;
+ var ul = $("<ul />").addClass("twirl_treeview_treelist").addClass((descended) ? "twirl_treeview_nested" : "ttwirl_treeview_reelist");
+
+ for (let k in items) {
+ let name = items[k].name;
+ let thisPath = path + "> " + items[k].name;
+ var li = $("<li />");
+ if (items[k].description) {
+ li.on("mouseover", function(event){
+ twirl.tooltip.show(event, items[k].description);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ });
+ }
+ if (items[k].hasOwnProperty("contents")) {
+ $("<span />").addClass("twirl_treeview_caret").text(name).click(function() {
+ $(this).parent().children(".twirl_treeview_nested").toggleClass("twirl_treeview_active");
+ $(this).toggleClass("twirl_treeview_caret-down");
+ }).appendTo(li);
+ var subitems = recurse(items[k].contents, true, thisPath);
+ li.append(subitems);
+
+ } else {
+ var content = name;
+ if (items[k].hasOwnProperty("added")) {
+ var dp = items[k].added.split("-");
+ var added = new Date(dp[0], dp[1] - 1, dp[2]);
+ if (Math.round((new Date() - added) / (1000 * 60 * 60 * 24)) <= 14) {
+ x.html(name + " <p style=\"display:inline;color:#ff2222;\"> [new]</p>");
+ }
+ }
+ li.html(content).css("cursor", "pointer").click(function() {
+ options.click(items[k], thisPath);
+ });
+ }
+ ul.append(li);
+ }
+ options.element.append(ul);
+ return ul;
+ }
+
+ options.element.append(recurse());
+};
\ No newline at end of file diff --git a/site/app/twirl/twirl.css b/site/app/twirl/twirl.css new file mode 100644 index 0000000..f4163fa --- /dev/null +++ b/site/app/twirl/twirl.css @@ -0,0 +1,310 @@ +select {
+ background-color: var(--bgColor3);
+ color: var(--fgColor2);
+}
+
+input {
+ background-color: var(--bgColor3);
+ color: var(--fgColor2);
+}
+
+.twirl_contextmenu {
+ position: absolute;
+ background-color: var(--bgColor3);
+ border: var(--menuBarBottomBorder);
+ color: var(--fgColor1);
+ cursor: arrow;
+ display: none;
+ user-select: none;
+ z-index: 30;
+ opacity: 0.9;
+ padding: 2px;
+}
+
+.twirl_contextmenu_item {
+ font-size: var(--fontSizeDefault);
+}
+
+.twirl_contextmenu_item:hover {
+ color: var(--bgColor1);
+ background-color: var(--fgColor2);
+}
+
+.twirl_slider {
+ appearance: none;
+ outline: none;
+ background-color: var(--bgColor3);
+ background: var(--bgColor3);
+ accent-color: var(--fgColor2);
+}
+
+.twirl_transparentinput {
+ font-size: var(--fontSizeDefault);
+ background-color: var(--bgColor2);
+ color: var(--fgColor3);
+ border: none;
+}
+
+.twirl_tf_container {
+ font-size: var(--fontSizeSmall);
+ font-family: var(--fontFace);
+}
+
+.twirl_tf_row_odd {
+ background-color: var(--rowOddBgColor);
+}
+
+.twirl_tf_row_even {
+ background-color: var(--rowEvenBgColor);
+}
+
+.twirl_tf_cell_plainbg {
+ background-color: var(--bgColor4);
+}
+
+.twirl_tf_cell {
+ font-size: var(--fontSizeDefault);
+}
+
+.twirl_tf_cell_text {
+ font-size: var(--fontSizeDefault);
+ text-align: right;
+}
+
+.twirl_tf_cellfixed {
+ overflow: hidden;
+ width: 40px;
+}
+
+.twirl_tf_header {
+ background-color: var(--bgColor4);
+ font-size: var(--fontSizeDefault);
+ font-weight: bold;
+}
+
+.twirl_spline_overlay {
+ position: absolute;
+ width: 100%;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+}
+
+.twirl_tooltip {
+ position: absolute;
+ text-align: center;
+ border-radius: 5px;
+ pointer-events: none;
+ padding: 2px;
+ color: #000000;
+ opacity: 0;
+ font-family: var(--fontFace);
+ font-size: var(--fontSizeSmall);
+ text-shadow: 1px 1px #ffffff;
+ z-index: 210;
+}
+
+#twirl_prompt {
+ z-index: 201;
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+ display: none;
+}
+
+#twirl_prompt_background {
+ z-index: 202;
+ background-color: #ffffff;
+ opacity: 0.3;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+#twirl_prompt_inner {
+ z-index: 204;
+ margin: 0px;
+ padding: 10px;
+ position: absolute;
+ font-size: var(--promptFontSize);
+ background-color: var(--promptBgColor);
+ border: var(--promptBorder);
+ box-shadow: var(--promptShadow);
+ width: 40%;
+ min-height: 30%;
+ left: 30%;
+ top: 35%;
+ text-align: center;
+ overflow: auto;
+ scrollbar-color: var(--scrollbarColor);
+}
+
+#twirl_prompt_button_text {
+ font-size: var(--fontSizeMedium);
+ padding: 5px;
+}
+
+#twirl_loading {
+ position: fixed;
+ display: none;
+ z-index: 161;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+#twirl_loading_background {
+ position: absolute;
+ z-index: 162;
+ background-color: #ffffff;
+ opacity: 0.2;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+#twirl_loading_inner {
+ z-index: 163;
+ position: absolute;
+ font-size: var(--promptFontSize);
+ background-color: var(--promptBgColor);
+ border: var(--promptBorder);
+ box-shadow: var(--promptShadow);
+ text-align: center;
+ width: 50%;
+ height: 20%;
+ left: 25%;
+ top: 40%;
+}
+
+#twirl_loading_percent {
+ z-index: 163;
+ position: absolute;
+ top: 80%;
+ left: 10%;
+ width: 80%;
+ height: 10%;
+ background-color: #9e8c6d;
+}
+
+#twirl_loading_percent_inner {
+ z-index: 163;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 100%;
+ width: 1%;
+ background-color: #e0c494;
+}
+
+.twirl_icon {
+ cursor: pointer;
+ filter: var(--iconFilter);
+}
+
+.topmenu {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: var(--bgColor1);
+ border-bottom: var(--menuBarBottomBorder);
+ color: var(--fgColor2);
+ cursor: arrow;
+ user-select: none;
+ z-index: 40;
+}
+
+.topmenu_item {
+ float: left;
+ font-size: var(--fontSizeDefault);
+ text-align: center;
+ padding: 2px 5px;
+ z-index: 40;
+}
+
+.topmenu_item:hover {
+ color: var(--bgColor1);
+ background-color: var(--fgColor2);
+}
+
+.topmenu_dropdown {
+ display: none;
+ border: 1px solid black;
+ position: fixed;
+ margin: 2px -5px;
+ background-color: var(--bgColor1);
+ color: var(--fgColor2);
+ min-width: 160px;
+ padding-top: 0px;
+ padding-left: 2px;
+ padding-right: 2px;
+ padding-bottom: 2px;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ z-index: 40;
+}
+
+.topmenu_dropdown_item {
+ text-align: left;
+ z-index: 40;
+}
+
+.topmenu_dropdown_item_disabled {
+ text-align: left;
+ color: #666666;
+ font-style: italic;
+ z-index: 40;
+}
+
+.topmenu_dropdown .topmenu_dropdown_item:hover {
+ background-color: #000000;
+}
+
+.topmenu_dropdown_itemright {
+ float: right;
+}
+
+ul, .twirl_treeview_treelist {
+ list-style-type: none;
+ font-size: var(--fontSizeSmall);
+ border-bottom: 1px solid #878787;
+}
+
+.twirl_treeview_treelist {
+ margin: 0;
+ padding: 0;
+}
+
+.twirl_treeview_caret {
+ cursor: pointer;
+ font-size: var(--fontSizeDefault);
+ user-select: none;
+}
+
+.twirl_treeview_caret::before {
+ content: "\25B6";
+ color: black;
+ display: inline-block;
+ margin-right: 6px;
+}
+
+.twirl_treeview_caret-down::before {
+ transform: rotate(90deg);
+}
+
+.twirl_treeview_nested {
+ margin-left: 20px;
+ display: none;
+}
+.twirl_treeview_active {
+ display: block;
+}
diff --git a/site/app/twirl/twirl.js b/site/app/twirl/twirl.js new file mode 100644 index 0000000..5f201f6 --- /dev/null +++ b/site/app/twirl/twirl.js @@ -0,0 +1,479 @@ +window.twirl = {
+ debug: false, //window.location.href.startsWith("file://"),
+ themes: ["Default", "Basic", "Hacker", "Monoclassic", "Doze"],
+ audioTypes: ["audio/mpeg", "audio/mp4", "audio/ogg", "audio/vorbis", "audio/x-flac","audio/aiff","audio/x-aiff", "audio/vnd.wav", "audio/wave", "audio/x-wav", "audio/wav", "audio/flac"],
+ maxFileSize: 1e+8, // 100MB
+ latencyCorrection: 40,
+ storage: {},
+ errorState: null,
+ audioContext: null,
+ _booted: false,
+ _initialised: false,
+ _remote: {sessionID: null, sending: false},
+ _els: {
+ base: null,
+ toolTip: null,
+ prompt: {},
+ loading: {},
+ contextMenu: null
+ }
+};
+
+twirl.boot = function() {
+ if (twirl._booted) return;
+ twirl.audioContext = new AudioContext();
+ twirl._booted = true;
+};
+
+twirl.init = function() {
+ if (twirl._initialised) return;
+
+ var NoteData = function() {
+ var self = this;
+ this.data = null;
+ fetch("https://apps.csound.1bpm.net/app/twirl/notedata.json").then(function(r) {
+ r.json().then(function(j) {
+ self.data = j;
+ });
+ });
+ };
+ twirl.noteData = new NoteData();
+
+ // storage
+ twirl.storage.data = localStorage.getItem("twirl");
+ if (twirl.storage.data) {
+ twirl.storage.data = JSON.parse(twirl.storage.data);
+ } else {
+ twirl.storage.data = {};
+ }
+
+ // base
+ twirl._els.base = $("<div />").attr("id", "twirl").appendTo($("body"));
+
+ // tooltip
+ twirl._els.toolTip = $("<div />").addClass("twirl_tooltip").appendTo(twirl._els.base);
+
+ // context menu
+ twirl._els.contextMenu = $("<div />").addClass("twirl_contextmenu").appendTo(twirl._els.base);
+
+ // prompt
+ var p = twirl._els.prompt;
+ p.base = $("<div />").attr("id", "twirl_prompt").appendTo(twirl._els.base);
+ $("<div />").attr("id", "twirl_prompt_background").appendTo(p.base);
+ var promptInner = $("<div />").attr("id", "twirl_prompt_inner").appendTo(p.base);
+ p.text = $("<div />").attr("id", "twirl_prompt_text").appendTo(promptInner);
+ p.button = $("<button />").attr("id", "twirl_prompt_button_text").text("OK");
+ p.buttonContainer = $("<div />").attr("id", "twirl_prompt_button").append($("<hr />")).append(p.button).appendTo(promptInner);
+
+ // loading
+ var l = twirl._els.loading;
+ l.base = $("<div />").attr("id", "twirl_loading").appendTo(twirl._els.base);
+ $("<div />").attr("id", "twirl_loading_background").appendTo(l.base);
+ var loadingInner = $("<div />").attr("id", "twirl_loading_inner").appendTo(l.base);
+ l.text = $("<p />").attr("id", "twirl_loading_text").text("Processing").appendTo(loadingInner);
+ l.percentContainer = $("<div />").attr("id", "twirl_loading_percent").appendTo(loadingInner);
+ l.percent = $("<div />").attr("id", "twirl_loading_percent_inner").appendTo(l.percentContainer);
+
+ // theme
+ if (twirl.storage.data.theme) {
+ twirl.setTheme(twirl.storage.data.theme, true);
+ }
+ twirl._initialised = true;
+};
+
+twirl.storage.save = function() {
+ localStorage.setItem("twirl", JSON.stringify(twirl.storage.data));
+};
+
+twirl.random = {
+ rgbColour: function() {
+ return "rgb(" + (Math.round(Math.random() * 50) + 205) + ","
+ + (Math.round(Math.random() * 50) + 205) + ","
+ + (Math.round(Math.random() * 50) + 205) + ")";
+ }
+};
+
+twirl.createIcon = function(definition) {
+ var state = true;
+ var active = true;
+ function formatPath(i) {
+ return "../twirl/icon/" + i + ".svg";
+ }
+ var el = $("<img />");
+
+ if (definition.size) {
+ el.css("width", definition.size + "px");
+ }
+
+ var obj = {
+ el: el,
+ setState: function(tstate) {
+ if (!definition.icon2) return;
+ state = tstate;
+ if (state) {
+ el.attr("src", formatPath(definition.icon));
+ } else {
+ el.attr("src", formatPath(definition.icon2));
+ }
+
+ },
+ setActive: function(state) {
+ if (state) {
+ el.css("opacity", 1);
+ active = true;
+ } else {
+ el.css("opacity", 0.4);
+ active = false;
+ }
+ },
+ definition: definition
+ };
+
+ obj.click = function() {
+ definition.click(obj);
+ };
+
+ el.addClass("twirl_icon").css("opacity", 1).attr("src", formatPath(definition.icon)).on("mouseover", function(event){
+ var label = (!state && definition.label2) ? definition.label2 : definition.label;
+ twirl.tooltip.show(event, label);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ }).click(function(el) {
+ if (active || definition.clickOnInactive) definition.click(obj);
+ });
+ return obj;
+};
+
+twirl.setTheme = function(name, noSave) {
+ var html = $("html");
+ if (html.attr("class")) {
+ for (let c of html.attr("class").split(/\s+/)) {
+ if (c.startsWith("theme")) {
+ html.removeClass(c);
+ }
+ }
+ }
+ html.addClass("theme" + name[0].toUpperCase() + name.substr(1).toLowerCase());
+ if (!noSave) {
+ twirl.storage.data.theme = name;
+ twirl.storage.save();
+ }
+};
+
+twirl.prompt = {
+ hide: function() {
+ twirl._els.prompt.base.hide();
+ },
+ show: function(text, onComplete, noButton) {
+ var p = twirl._els.prompt;
+ twirl.loading.hide();
+ p.text.empty();
+ if (typeof(text) == "string") {
+ p.text.text(text);
+ } else {
+ p.text.append(text);
+ }
+ if (!noButton) {
+ p.buttonContainer.show();
+ p.button.unbind().click(function(){
+ twirl.prompt.hide();
+ if (onComplete) onComplete();
+ });
+ } else {
+ p.buttonContainer.hide();
+ }
+ p.base.show();
+ }
+};
+
+twirl.loading = {
+ hide: function() {
+ $("body").css("cursor", "default");
+ twirl._els.loading.base.hide();
+ },
+ show: function(text, showPercent) {
+ var l = twirl._els.loading;
+ $("body").css("cursor", "wait");
+ l.text.text((text) ? text : "Processing");
+ if (showPercent) {
+ l.percentContainer.show();
+ } else {
+ l.percentContainer.hide();
+ }
+ l.base.show();
+ },
+ setPercent: function(percent) {
+ twirl._els.loading.percent.width(percent + "%");
+ }
+};
+
+twirl._setContextPosition = function(event, el, augmentations) {
+ var margin = 100;
+ if (!augmentations) augmentations = [0, 0];
+ if (event.clientX >= window.innerWidth - margin) {
+ el.css({right: margin + "px", left: "auto"});
+ } else {
+ el.css({right: "auto", left: (event.clientX + augmentations[0]) + "px"});
+ }
+
+ if (event.clientY >= window.innerHeight - margin) {
+ el.css({bottom: margin + "px", top: "auto"});
+ } else {
+ el.css({bottom: "auto", top: (event.clientY + augmentations[1]) + "px"});
+ }
+};
+
+twirl.contextMenu = {
+ show: function(event, data) {
+ event.preventDefault();
+ twirl._els.contextMenu.empty().unbind().on("mouseout", function(){
+ twirl._els.contextMenu.hide().off("mouseout");
+ });
+ for (let i in data) {
+ let d = data[i];
+ $("<div />").addClass("twirl_contextmenu_item").text(d.name).click(function(){
+ twirl._els.contextMenu.hide().off("mouseout");
+ d.click();
+ }).appendTo(twirl._els.contextMenu).on("mouseout", function(e){
+ e.stopPropagation();
+ });
+ }
+ twirl._setContextPosition(event, twirl._els.contextMenu, [-10, -10]);
+ twirl._els.contextMenu.show();
+ return false;
+ }
+};
+
+twirl.tooltip = {
+ show: function(event, text, colour) {
+ if (!colour) colour = "#bbbbbb";
+ var el = twirl._els.toolTip;
+ el.html(text).css({opacity: 0.8, "background-color": colour});
+ twirl._setContextPosition(event, el, [20, -15]);
+
+ },
+ hide: function() {
+ twirl._els.toolTip.css("opacity", 0);
+ }
+};
+
+twirl.sendErrorState = async function(errorObj) {
+ if (twirl._remote.sending) return;
+ twirl._remote.sending = true;
+ if (typeof(errorObj) == "function") {
+ errorObj = errorObj();
+ }
+ errorObj.application = $("title").text();
+ var data = {
+ request_type: "LogError",
+ error: errorObj
+ };
+
+ if (twirl._remote.sessionID) {
+ data.session_id = twirl._remote.sessionID;
+ }
+ var resp = await fetch("https:///service/", {
+ method: "POST",
+ headers: {
+ "Content-type": "application/json"
+ },
+ body: JSON.stringify(data)
+ });
+ var json = await resp.json();
+ if (json.session_id && !twirl._remote.sessionID) {
+ twirl._remote.sessionID = json.session_id;
+ }
+ twirl._remote.sending = false;
+};
+
+twirl.errorHandler = function(text, onComplete, errorObj) {
+ var errorText = (!text) ? twirl.errorState : text;
+ if (!errorObj) errorObj = {};
+ errorObj.text = errorText;
+ //twirl.sendErrorState(errorObj);
+ twirl.prompt.show(errorText, onComplete);
+ twirl.errorState = null;
+};
+
+twirl.showSettings = function(host, settings, onThemeChange) {
+ var el = $("<div />").css("font-size", "var(--fontSizeDefault)").append($("<h3 />").text("Settings"));
+ var tb = $("<tbody />");
+ $("<table />").append(tb).appendTo(el);
+
+ var currentThemeIndex;
+ if (twirl.storage.data.theme) currentThemeIndex = twirl.themes.indexOf(twirl.storage.data.theme);
+ if (!currentThemeIndex) currentThemeIndex = 0;
+
+ var tpTheme = new twirl.transform.Parameter({
+ definition: {name: "Theme", options: twirl.themes, dfault: currentThemeIndex, fireChanges: false, automatable: false},
+ host: host,
+ onChange: function(val) {
+ twirl.setTheme(twirl.themes[val]);
+ if (onThemeChange) onThemeChange();
+ }
+ });
+ tb.append(tpTheme.getElementRow(true))
+
+ for (let s of settings) {
+ var value;
+ if (s.options && s.storageKey) {
+ if (host.storage[s.storageKey]) value = host.storage[s.storageKey];
+ if (value < 0) value = s.dfault;
+ } else if (s.storageKey) {
+ value = (host.storage[s.storageKey]) ? host.storage[s.storageKey] : s.dfault;
+ if (s.bool) {
+ s.min = 0;
+ s.max = 1;
+ s.step = 1;
+ }
+ } else {
+ value = s.dfault;
+ }
+
+ let param = new twirl.transform.Parameter({
+ definition: {
+ name: s.name,
+ description: s.description,
+ fireChanges: false, automatable: false,
+ min: s.min, max: s.max, step: s.step, dfault: value,
+ options: s.options, asvalue: s.asvalue
+
+ }, host: host, onChange: function(val) {
+ if (s.storageKey) {
+ host.storage[s.storageKey] = val;
+ host.saveStorage();
+ }
+ if (s.onChange) s.onChange(val);
+ }
+ });
+ tb.append(param.getElementRow(true));
+ }
+ twirl.prompt.show(el);
+};
+
+twirl.TopMenu = function(host, menuData, elTarget) {
+ var self = this;
+ var opened = false;
+ var keyHandlers = [];
+
+ function keyHandler(e) {
+ if (!host.visible) return;
+ var nodeType = e.target.nodeName.toLowerCase();
+ if (nodeType == "input" || nodeType == "textarea") return;
+ e.preventDefault();
+ for (let h of keyHandlers) {
+ if (
+ (h.key == e.key.toLowerCase()) &&
+ ((!h.hasOwnProperty("ctrlKey") && !e.ctrlKey) || (h.ctrlKey && e.ctrlKey)) &&
+ ((!h.hasOwnProperty("shiftKey") && !e.shiftKey) || (h.shiftKey && e.shiftKey)) &&
+ ((!h.hasOwnProperty("altKey") && !e.altKey) || (h.altKey && e.altKey))
+ ) {
+ if (h.hasOwnProperty("condition") && !h.condition(host)) {
+ return;
+ }
+ if (h.hasOwnProperty("keyCondition") && !h.keyCondition(host)) {
+ return;
+ }
+ return h.func(host);
+ }
+ }
+ }
+
+ function construct(data) {
+ if (!data) data = menuData;
+ let onPlayDisables = [];
+ elTarget.empty();
+ var elMenuBar = $("<div />").addClass("topmenu").appendTo(elTarget);
+ for (let d of data) {
+ let elTopItem = $("<div />").addClass("topmenu_item").text(d.name).appendTo(elMenuBar);
+ let elMenu = $("<div />").addClass("topmenu_dropdown").appendTo(elTopItem);
+ let showConditions = [];
+ let onShows = [];
+ for (let c of d.contents) {
+ let elItem;
+ if (c.preset) {
+ if (c.preset == "divider") {
+ elItem = $("<hr />").appendTo(elMenu);
+ }
+ } else {
+ elItem = $("<div />").addClass("topmenu_dropdown_item").appendTo(elMenu);
+ if (typeof(c.name) == "function") {
+ onShows.push(function(){
+ elItem.text(c.name(host));
+ });
+ } else {
+ elItem.text(c.name);
+ }
+ if (c.click) elItem.click(function(){
+ if (c.condition && !c.condition(host)) return;
+ elMenu.hide();
+ opened = false;
+ c.click(host);
+ });
+ if (c.shortcut) {
+ $("<div />").addClass("topmenu_dropdown_itemright").text(c.shortcut.name).appendTo(elItem);
+ var obj = {func: c.click};
+ Object.assign(obj, c.shortcut);
+ if (c.condition) obj.condition = c.condition;
+ if (c.keyCondition) obj.keyCondition = c.keyCondition;
+ delete obj.name;
+ keyHandlers.push(obj);
+ }
+ if (c.disableOnPlay) {
+ onPlayDisables.push(elItem);
+ }
+ }
+ if (c.condition) {
+ showConditions.push({el: elItem, func: c.condition});
+ }
+ }
+
+ function showMenu() {
+ for (let c of showConditions) {
+ if (c.func(host)) {
+ c.el.removeClass("topmenu_dropdown_item_disabled");
+ } else {
+ c.el.addClass("topmenu_dropdown_item_disabled");
+ }
+ }
+ for (let o of onShows) {
+ o(host);
+ }
+ elMenu.show();
+ }
+
+
+ elTopItem.on("mouseover", function(){
+ if (opened) {
+ setTimeout(function(){
+ opened = true;
+ }, 10);
+ showMenu();
+ }
+ }).on("click", function() {
+ opened = true;
+ showMenu();
+ }).on("mouseleave", function() {
+ elMenu.hide();
+ setTimeout(function(){
+ opened = false;
+ }, 5);
+ });
+
+ }
+ if (host.onPlays) {
+ host.onPlays.push(async function(playing){
+ for (let o of onPlayDisables) {
+ if (playing) {
+ o.addClass("topmenu_dropdown_item_disabled");
+ } else {
+ o.removeClass("topmenu_dropdown_item_disabled");
+ }
+ }
+ });
+ }
+ $("body").off("keydown", keyHandler).on("keydown", keyHandler);
+ }
+ construct();
+};
+
diff --git a/site/app/twirl/twirl_compiler.py b/site/app/twirl/twirl_compiler.py new file mode 100644 index 0000000..cc2ba3e --- /dev/null +++ b/site/app/twirl/twirl_compiler.py @@ -0,0 +1,92 @@ +import lxml.html as lh
+import lxml.etree as et
+import os
+import shutil
+
+base_path = "/mnt/hd/web/1bpm.net"
+top_path = os.path.join(base_path, "apps.csound")
+apps_top_path = os.path.join(top_path, "app")
+twirl_path = os.path.join(apps_top_path, "twirl")
+
+apps = ["twist", "twigs", "twine"]
+
+
+def compile_apps():
+ for app in apps:
+ compile(app)
+
+def compile(app):
+ print "Compiling {}".format(app)
+ app_path = os.path.join(apps_top_path, app)
+ target_dir = "{}/{}".format(base_path, app)
+
+ doc = lh.parse(os.path.join(app_path, "index.html"))
+ root = doc.getroot()
+ head = root.xpath("//head")[0]
+
+ script_data = ""
+ css_data = ""
+ post_scripts = []
+
+ for script in root.xpath("//script"):
+ src = script.attrib.get("src")
+ if src:
+ src = src.replace("https://apps.csound.1bpm.net/", "../../")
+ path = os.path.join(app_path, src)
+ with open(path, "r") as f:
+ script_data += f.read() + "\n"
+ else:
+ post_scripts.append(script)
+ script.getparent().remove(script)
+
+ new_script = et.fromstring("<script type=\"text/javascript\" src=\"{}.js\"></script>".format(app))
+ head.append(new_script)
+ for ps in post_scripts:
+ head.append(ps)
+
+ for css in root.xpath("//link"):
+ href = css.attrib.get("href")
+ if href:
+ href = href.replace("https://apps.csound.1bpm.net/", "../../")
+ path = os.path.join(app_path, href)
+ with open(path, "r") as f:
+ css_data += f.read() + "\n"
+ css.getparent().remove(css)
+
+ new_css = et.fromstring("<link rel=\"stylesheet\" href=\"{}.css\" />".format(app))
+ head.append(new_css)
+
+ doc.write(os.path.join(target_dir, "index.html"), method="html", encoding="UTF-8")
+
+ with open(os.path.join(target_dir, "{}.js".format(app)), "w") as f:
+ f.write(script_data)
+
+ with open(os.path.join(target_dir, "{}.css".format(app)), "w") as f:
+ f.write(css_data)
+
+ links = [
+ [twirl_path, "twirl"],
+ [os.path.join(twirl_path, "font"), "font"],
+ [os.path.join(top_path, "udo"), "udo"],
+ [os.path.join(top_path, "code"), "code"],
+ [os.path.join(app_path, "{}.csd".format(app)), "{}.csd".format(app)]
+ ]
+
+ for l in links:
+ target = os.path.join(target_dir, l[1])
+ if os.path.islink(target):
+ os.unlink(target)
+ os.symlink(l[0], target)
+
+ copies = ["documentation.html", "developer_documentation.html"]
+ for c in copies:
+ item = os.path.join(app_path, c)
+ if os.path.exists(item):
+ target = os.path.join(target_dir, c)
+ if os.path.exists(target):
+ os.unlink(target)
+ shutil.copy(item, target)
+
+if __name__ == "__main__":
+ compile_apps()
+
|