diff --git a/instruments/Bastl_Drum/model.yml b/instruments/Bastl_Drum/model.yml new file mode 100644 index 0000000..4114647 --- /dev/null +++ b/instruments/Bastl_Drum/model.yml @@ -0,0 +1,48 @@ +name: Bastl Drum +slug: Bastl_Drum +description: The drum without buttons, not that drums usually have button but +params: +- drum_mod +- drum +- pitch +- decay +- pitch_mod +- tempo_mod +- tempo +sockets: +- decay_3 +- decay_2 +- decay_1 +- noise_3 +- noises_2 +- noises_1 +- clk_3 +- clk_2 +- clk_1 +- lfo_3 +- lfo_2 +- lfo_1 +- drums_2 +- drums_1 +- pattern_3 +- pattern_2 +- pattern_1 +- i/o_right +- i/o_left +- trig_tempo_mod_3 +- trig_tempo_mod_2 +- trig_tempo_mod_1 +- trig_pitch_mod_3 +- trig_pitch_mod_2 +- trig_pitch_mod_1 +- trig_drum_mod_3 +- trig_drum_mod_2 +- trig_drum_mod_1 +- mode_minus +- mode_plus +- clk_in_2 +- clk_in_1 +- trig_in_2 +- trig_in_1 +- feed_2 +- feed_1 diff --git a/instruments/Bastl_Drum/panel.svg b/instruments/Bastl_Drum/panel.svg new file mode 100644 index 0000000..2c048ed --- /dev/null +++ b/instruments/Bastl_Drum/panel.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/instruments/Bastl_Drum/patches/Looo.yml b/instruments/Bastl_Drum/patches/Looo.yml new file mode 100644 index 0000000..2de9b12 --- /dev/null +++ b/instruments/Bastl_Drum/patches/Looo.yml @@ -0,0 +1,25 @@ +cables: '' +date: '2022-06-26' +decay: '50' +description: test +drum: '50' +drum_mod: '82' +name: Looo +pitch: '50' +pitch_mod: '50' +slug: Looo +tempo: '50' +tempo_mod: '50' diff --git a/instruments/Bastl_Drum/patches/test.yml b/instruments/Bastl_Drum/patches/test.yml new file mode 100644 index 0000000..af8458f --- /dev/null +++ b/instruments/Bastl_Drum/patches/test.yml @@ -0,0 +1,49 @@ +date: "2022-06-28" +decay: "50" +description: toast +drum: "50" +drum_mod: "50" +name: test +pitch: "50" +pitch_mod: "50" +slug: test +tempo: "50" +tempo_mod: "50" +cables: '' diff --git a/instruments/Bastl_Kastle_v1.5/model.yml b/instruments/Bastl_Kastle_v1.5/model.yml new file mode 100644 index 0000000..e53efd8 --- /dev/null +++ b/instruments/Bastl_Kastle_v1.5/model.yml @@ -0,0 +1,48 @@ +name: Bastl Kastle v1.5 +slug: Bastl_Kastle_v1.5 +description: The one we know +params: + - pitch_mod + - osc_pitch + - timbre_mod + - waveshape + - osc_timbre + - rate_mod + - lfo_rate +sockets: + - rate_mod_1 + - rate_mod_2 + - rate_mod_3 + - triangle_lfo_1 + - triangle_lfo_2 + - triangle_lfo_3 + - step_generator_1 + - step_generator_2 + - step_generator_3 + - square_lfo_1 + - square_lfo_2 + - square_lfo_3 + - osc_out_1 + - osc_out_2 + - osc_out_3 + - timbre_mod_1 + - timbre_mod_2 + - timbre_mod_3 + - mode_static_minus + - mode_static_plus + - lfo_reset_1 + - lfo_reset_2 + - bit_in_1 + - bit_in_2 + - left_input + - right_input + - mode_minus + - mode_plus + - secondary_osc_out_1 + - secondary_osc_out_2 + - pitch_mode_1 + - pitch_mode_2 + - pitch_mode_3 + - waveshape_1 + - waveshape_2 + - waveshape_3 diff --git a/instruments/Bastl_Kastle_v1.5/panel.svg b/instruments/Bastl_Kastle_v1.5/panel.svg new file mode 100644 index 0000000..61fbd4f --- /dev/null +++ b/instruments/Bastl_Kastle_v1.5/panel.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/instruments/Kastle_Drum/model.yml b/instruments/Kastle_Drum/model.yml deleted file mode 100644 index 2372dcf..0000000 --- a/instruments/Kastle_Drum/model.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: ye the drum -name: Kastle Drum -slug: Kastle_Drum - -params: -- drum -- noise -- tempo \ No newline at end of file diff --git a/instruments/Pic_nic_synthetyzer/patches/daily pic nic.yml b/instruments/Pic_nic_synthetyzer/patches/daily pic nic.yml deleted file mode 100644 index 3a65515..0000000 --- a/instruments/Pic_nic_synthetyzer/patches/daily pic nic.yml +++ /dev/null @@ -1,13 +0,0 @@ -celery: 'no' -'cherry tomatos': '3' -mayo: a lot -salad: sure -bread: 'yes' -date: '2022-06-13' -description: 'is a picnicco ' -ginger: 'yes' -input: Hunger -name: daily pic nic -noddles: sweet pot -output: Our stomachs -routing: new york hotl diff --git a/instruments/Sample_Demo_Dinner/model.yml b/instruments/Sample_Demo_Dinner/model.yml new file mode 100644 index 0000000..2c3a96d --- /dev/null +++ b/instruments/Sample_Demo_Dinner/model.yml @@ -0,0 +1,23 @@ +name: Sample Demo Dinner +slug: Sample_Demo_Dinner +description: A test panel just for fun +params: +- soup +- goat_cheese +- pasta +- tequila +- rice_rolls +- lemon +- salt +- shot_rate +- thai_curry +- linzen +- kip +- leek +- stuffed_peppers +- pizza +- kikkerverten +sockets: +- lunch_3 +- breakfast +- dinner_2 diff --git a/instruments/Sample_Demo_Dinner/panel.svg b/instruments/Sample_Demo_Dinner/panel.svg new file mode 100644 index 0000000..4370563 --- /dev/null +++ b/instruments/Sample_Demo_Dinner/panel.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/instruments/Simple_sample_synth/model.yml b/instruments/Simple_sample_synth/model.yml deleted file mode 100644 index 921ce47..0000000 --- a/instruments/Simple_sample_synth/model.yml +++ /dev/null @@ -1,6 +0,0 @@ -description: SSS snake synth for demo -name: Simple sample synth -params: -- sample -- hold -slug: Simple_sample_synth diff --git a/instruments/Simple_sample_synth/panel.svg b/instruments/Simple_sample_synth/panel.svg deleted file mode 100644 index adf981b..0000000 --- a/instruments/Simple_sample_synth/panel.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/instruments/Simple_sample_synth/patches/4.yml b/instruments/Simple_sample_synth/patches/4.yml deleted file mode 100644 index 82f00b3..0000000 --- a/instruments/Simple_sample_synth/patches/4.yml +++ /dev/null @@ -1,8 +0,0 @@ -date: '2022-06-08' -description: gg -hold: g -input: d -name: '4' -output: s -routing: g -sample: r diff --git a/instruments/Simple_sample_synth/patches/A test patch just the first one.yml b/instruments/Simple_sample_synth/patches/A test patch just the first one.yml deleted file mode 100644 index 76d9fa2..0000000 --- a/instruments/Simple_sample_synth/patches/A test patch just the first one.yml +++ /dev/null @@ -1,8 +0,0 @@ -date: '2022-06-13' -description: A nice patch to see if itsworki -hold: eeeeee -input: us -name: A test patch just the first one -output: them -routing: us----->them -sample: quak diff --git a/instruments/Simple_sample_synth/patches/The_second_patch.yml b/instruments/Simple_sample_synth/patches/The_second_patch.yml deleted file mode 100644 index fd9949e..0000000 --- a/instruments/Simple_sample_synth/patches/The_second_patch.yml +++ /dev/null @@ -1,8 +0,0 @@ -date: '2022-06-13' -description: ahah eheh ahah -hold: ggg -input: v -name: The second patch -output: j -routing: v2 ---> j -sample: efff diff --git a/instruments/Simple_sample_synth/patches/third.yml b/instruments/Simple_sample_synth/patches/third.yml deleted file mode 100644 index 289925a..0000000 --- a/instruments/Simple_sample_synth/patches/third.yml +++ /dev/null @@ -1,8 +0,0 @@ -date: '2022-06-13' -description: finally picnic -hold: wine -input: buffeto -name: third -output: us -routing: us -----> buffettp -sample: bread diff --git a/instruments/moog/model.yml b/instruments/moog/model.yml deleted file mode 100644 index f429919..0000000 --- a/instruments/moog/model.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: the classy moog -name: moog -params: -- VCO -- LFO -- VCF -- VCA -slug: moog diff --git a/instruments/moog/panel.svg b/instruments/moog/panel.svg deleted file mode 100644 index adf981b..0000000 --- a/instruments/moog/panel.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/instruments/moog/patches/my_first_analog_patch.yml b/instruments/moog/patches/my_first_analog_patch.yml deleted file mode 100644 index e343673..0000000 --- a/instruments/moog/patches/my_first_analog_patch.yml +++ /dev/null @@ -1,10 +0,0 @@ -LFO: '321' -VCA: '234' -VCF: '43' -VCO: '12' -date: '2022-07-01' -description: wow i spent 3000 in this cannot believie it -input: '43' -name: my first analog patch -output: '2' -routing: '432' diff --git a/instruments/perepepepepe/model.yml b/instruments/perepepepepe/model.yml deleted file mode 100644 index a31ce3d..0000000 --- a/instruments/perepepepepe/model.yml +++ /dev/null @@ -1 +0,0 @@ -name: Watersand synth amazing 2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ff8b2d2..aa60c37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ click==8.1.3 +colorama==0.4.5 Flask==2.1.2 importlib-metadata==4.11.4 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.1 +PyYAML==6.0 Werkzeug==2.1.2 zipp==3.8.0 diff --git a/static/css/home.css b/static/css/home.css index 9e907d7..42e7b64 100644 --- a/static/css/home.css +++ b/static/css/home.css @@ -29,7 +29,7 @@ svg { position: absolute; top: 0; left: 0; - border: 1px solid currentColor; + /* border: 1px solid currentColor; */ width: 100%; height: 100%; } diff --git a/static/css/panel.css b/static/css/panel.css new file mode 100644 index 0000000..c6c3eb1 --- /dev/null +++ b/static/css/panel.css @@ -0,0 +1,5 @@ +#panel-container { + position: relative; + width: auto; + height: auto; +} diff --git a/static/css/patch.css b/static/css/patch.css index 6a1d7f6..f3e9580 100644 --- a/static/css/patch.css +++ b/static/css/patch.css @@ -1,61 +1,60 @@ .instrument { - max-height: 100vh; - max-width: 100vw; - object-fit: contain; - margin: 0 auto; - display: block; + max-height: 100vh; + max-width: 100vw; + object-fit: contain; + margin: 0 auto; + display: block; + position: relative; } .samples { - width: 100%; - padding: 0; + width: 100%; + padding: 0; } .samples li { - display: flex; - width: 100%; - font-size: 32px; - padding: 8px; + display: flex; + width: 100%; + font-size: 32px; + padding: 8px; } .samples li > * { - flex-shrink: 0; + flex-shrink: 0; } .samples li:nth-child(even) { - background:#efefef; - } + background: #efefef; +} -.samples li button{ - background-color: white; - border: 1px solid currentColor; - padding: 4px; - width: 32px; - height: 32px; - display: inline-block; +.samples li button { + background-color: white; + border: 1px solid currentColor; + padding: 4px; + width: 32px; + height: 32px; + display: inline-block; } -.samples li button svg{ - width: 100%; - height: 100%; +.samples li button svg { + width: 100%; + height: 100%; } .download { - margin-left: auto; - margin-right: 16px; + margin-left: auto; + margin-right: 16px; } .samples .description { - margin: 0 8px; - flex-shrink: 1; - + margin: 0 8px; + flex-shrink: 1; } -.print svg{ - background-color: white; - border: 1px solid currentColor; - padding: 4px; - width: 32px; - height: 32px; - display: inline-block; +.print svg { + background-color: white; + border: 1px solid currentColor; + padding: 4px; + width: 32px; + height: 32px; + display: inline-block; } - diff --git a/static/js/input-knobs.js b/static/js/input-knobs.js new file mode 100644 index 0000000..e4486f9 --- /dev/null +++ b/static/js/input-knobs.js @@ -0,0 +1,375 @@ +window.addEventListener("load", () => { + let op = window.inputKnobsOptions || {}; + op.knobWidth = op.knobWidth || op.knobDiameter || 64; + op.knobHeight = op.knobHeight || op.knobDiameter || 64; + op.sliderWidth = op.sliderWidth || op.sliderDiameter || 128; + op.sliderHeight = op.sliderHeight || op.sliderDiameter || 20; + op.switchWidth = op.switchWidth || op.switchDiameter || 24; + op.switchHeight = op.switchHeight || op.switchDiameter || 24; + op.fgcolor = op.fgcolor || "#fff"; + op.bgcolor = op.bgcolor || "#000"; + op.knobMode = op.knobMode || "linear"; + op.sliderMode = op.sliderMode || "relative"; + let styles = document.createElement("style"); + styles.innerHTML = `input[type=range].input-knob,input[type=range].input-slider{ + -webkit-appearance:none; + -moz-appearance:none; + border:none; + box-sizing:border-box; + overflow:hidden; + background-repeat:no-repeat; + background-size:100% 100%; + background-position:0px 0%; + background-color:transparent; + touch-action:none; +} +input[type=range].input-knob{ + width:${op.knobWidth}px; height:${op.knobHeight}px; +} +input[type=range].input-slider{ + width:${op.sliderWidth}px; height:${op.sliderHeight}px; +} +input[type=range].input-knob::-webkit-slider-thumb,input[type=range].input-slider::-webkit-slider-thumb{ + -webkit-appearance:none; + opacity:0; +} +input[type=range].input-knob::-moz-range-thumb,input[type=range].input-slider::-moz-range-thumb{ + -moz-appearance:none; + height:0; + border:none; +} +input[type=range].input-knob::-moz-range-track,input[type=range].input-slider::-moz-range-track{ + -moz-appearance:none; + height:0; + border:none; +} +input[type=checkbox].input-switch,input[type=radio].input-switch { + width:${op.switchWidth}px; + height:${op.switchHeight}px; + -webkit-appearance:none; + -moz-appearance:none; + background-size:100% 200%; + background-position:0% 0%; + background-repeat:no-repeat; + border:none; + border-radius:0; + background-color:transparent; +} +input[type=checkbox].input-switch:checked,input[type=radio].input-switch:checked { + background-position:0% 100%; +}`; + document.head.appendChild(styles); + let makeKnobFrames = (fr, fg, bg) => { + let r = ` + + +`; + for (let i = 1; i < fr; ++i) + r += ``; + return r + ""; + }; + let makeHSliderFrames = (fr, fg, bg, w, h) => { + let r = ` + +`; + for (let i = 0; i < fr; ++i) { + r += ``; + r += ``; + } + return r + ""; + }; + let makeVSliderFrames = (fr, fg, bg, w, h) => { + let r = ` + +`; + for (let i = 0; i < fr; ++i) { + r += ``; + r += ``; + } + return r + ""; + }; + let initSwitches = (el) => { + let w, h, d, fg, bg; + if (el.inputKnobs) return; + el.inputKnobs = {}; + el.refresh = () => { + let src = el.getAttribute("data-src"); + d = +el.getAttribute("data-diameter"); + let st = document.defaultView.getComputedStyle(el, null); + w = parseFloat(el.getAttribute("data-width") || d || st.width); + h = parseFloat(el.getAttribute("data-height") || d || st.height); + bg = el.getAttribute("data-bgcolor") || op.bgcolor; + fg = el.getAttribute("data-fgcolor") || op.fgcolor; + el.style.width = w + "px"; + el.style.height = h + "px"; + if (src) el.style.backgroundImage = "url(" + src + ")"; + else { + let minwh = Math.min(w, h); + let svg = ` + + +`; + el.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svg) + ")"; + } + }; + el.refresh(); + }; + let initKnobs = (el) => { + let w, h, d, fg, bg; + if (el.inputKnobs) { + el.redraw(); + return; + } + let ik = (el.inputKnobs = {}); + el.refresh = () => { + d = +el.getAttribute("data-diameter"); + let st = document.defaultView.getComputedStyle(el, null); + w = parseFloat(el.getAttribute("data-width") || d || st.width); + h = parseFloat(el.getAttribute("data-height") || d || st.height); + bg = el.getAttribute("data-bgcolor") || op.bgcolor; + fg = el.getAttribute("data-fgcolor") || op.fgcolor; + ik.sensex = ik.sensey = 200; + if (el.className.indexOf("input-knob") >= 0) ik.itype = "k"; + else { + if (w >= h) { + ik.itype = "h"; + ik.sensex = w - h; + ik.sensey = Infinity; + el.style.backgroundSize = "auto 100%"; + } else { + ik.itype = "v"; + ik.sensex = Infinity; + ik.sensey = h - w; + el.style.backgroundSize = "100% auto"; + } + } + el.style.width = w + "px"; + el.style.height = h + "px"; + ik.frameheight = h; + let src = el.getAttribute("data-src"); + if (src) { + el.style.backgroundImage = `url(${src})`; + let sp = +el.getAttribute("data-sprites"); + if (sp) ik.sprites = sp; + else ik.sprites = 0; + if (ik.sprites >= 1) el.style.backgroundSize = `100% ${(ik.sprites + 1) * 100}%`; + else if (ik.itype != "k") { + el.style.backgroundColor = bg; + el.style.borderRadius = Math.min(w, h) * 0.25 + "px"; + } + } else { + let svg; + switch (ik.itype) { + case "k": + svg = makeKnobFrames(101, fg, bg); + break; + case "h": + svg = makeHSliderFrames(101, fg, bg, w, h); + break; + case "v": + svg = makeVSliderFrames(101, fg, bg, w, h); + break; + } + ik.sprites = 100; + el.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svg) + ")"; + el.style.backgroundSize = `100% ${(ik.sprites + 1) * 100}%`; + } + ik.valrange = { + min: +el.min, + max: el.max == "" ? 100 : +el.max, + step: el.step == "" ? 1 : +el.step, + }; + el.redraw(true); + }; + el.setValue = (v) => { + v = + Math.round((v - ik.valrange.min) / ik.valrange.step) * ik.valrange.step + + ik.valrange.min; + if (v < ik.valrange.min) v = ik.valrange.min; + if (v > ik.valrange.max) v = ik.valrange.max; + el.value = v; + if (el.value != ik.oldvalue) { + el.setAttribute("value", el.value); + el.redraw(); + let event = document.createEvent("HTMLEvents"); + event.initEvent("input", false, true); + el.dispatchEvent(event); + ik.oldvalue = el.value; + } + }; + ik.pointerdown = (ev) => { + el.focus(); + const evorg = ev; + if (ev.touches) ev = ev.touches[0]; + let rc = el.getBoundingClientRect(); + let cx = (rc.left + rc.right) * 0.5, + cy = (rc.top + rc.bottom) * 0.5; + let dx = ev.clientX, + dy = ev.clientY; + let da = Math.atan2(ev.clientX - cx, cy - ev.clientY); + if (ik.itype == "k" && op.knobMode == "circularabs") { + dv = + ik.valrange.min + + ((da / Math.PI) * 0.75 + 0.5) * (ik.valrange.max - ik.valrange.min); + el.setValue(dv); + } + if (ik.itype != "k" && op.sliderMode == "abs") { + dv = + (ik.valrange.min + ik.valrange.max) * 0.5 + + ((dx - cx) / ik.sensex - (dy - cy) / ik.sensey) * + (ik.valrange.max - ik.valrange.min); + el.setValue(dv); + } + ik.dragfrom = { + x: ev.clientX, + y: ev.clientY, + a: Math.atan2(ev.clientX - cx, cy - ev.clientY), + v: +el.value, + }; + document.addEventListener("mousemove", ik.pointermove); + document.addEventListener("mouseup", ik.pointerup); + document.addEventListener("touchmove", ik.pointermove); + document.addEventListener("touchend", ik.pointerup); + document.addEventListener("touchcancel", ik.pointerup); + document.addEventListener("touchstart", ik.preventScroll); + evorg.preventDefault(); + evorg.stopPropagation(); + }; + ik.pointermove = (ev) => { + let dv; + let rc = el.getBoundingClientRect(); + let cx = (rc.left + rc.right) * 0.5, + cy = (rc.top + rc.bottom) * 0.5; + if (ev.touches) ev = ev.touches[0]; + let dx = ev.clientX - ik.dragfrom.x, + dy = ev.clientY - ik.dragfrom.y; + let da = Math.atan2(ev.clientX - cx, cy - ev.clientY); + switch (ik.itype) { + case "k": + switch (op.knobMode) { + case "linear": + dv = + (dx / ik.sensex - dy / ik.sensey) * + (ik.valrange.max - ik.valrange.min); + if (ev.shiftKey) dv *= 0.2; + el.setValue(ik.dragfrom.v + dv); + break; + case "circularabs": + if (!ev.shiftKey) { + dv = + ik.valrange.min + + ((da / Math.PI) * 0.75 + 0.5) * + (ik.valrange.max - ik.valrange.min); + el.setValue(dv); + break; + } + case "circularrel": + if (da > ik.dragfrom.a + Math.PI) da -= Math.PI * 2; + if (da < ik.dragfrom.a - Math.PI) da += Math.PI * 2; + da -= ik.dragfrom.a; + dv = (da / Math.PI / 1.5) * (ik.valrange.max - ik.valrange.min); + if (ev.shiftKey) dv *= 0.2; + el.setValue(ik.dragfrom.v + dv); + } + break; + case "h": + case "v": + dv = (dx / ik.sensex - dy / ik.sensey) * (ik.valrange.max - ik.valrange.min); + if (ev.shiftKey) dv *= 0.2; + el.setValue(ik.dragfrom.v + dv); + break; + } + }; + ik.pointerup = () => { + document.removeEventListener("mousemove", ik.pointermove); + document.removeEventListener("touchmove", ik.pointermove); + document.removeEventListener("mouseup", ik.pointerup); + document.removeEventListener("touchend", ik.pointerup); + document.removeEventListener("touchcancel", ik.pointerup); + document.removeEventListener("touchstart", ik.preventScroll); + let event = document.createEvent("HTMLEvents"); + event.initEvent("change", false, true); + el.dispatchEvent(event); + }; + ik.preventScroll = (ev) => { + ev.preventDefault(); + }; + ik.keydown = () => { + el.redraw(); + }; + ik.wheel = (ev) => { + let delta = ev.deltaY > 0 ? -ik.valrange.step : ik.valrange.step; + if (!ev.shiftKey) delta *= 5; + el.setValue(+el.value + delta); + ev.preventDefault(); + ev.stopPropagation(); + }; + el.redraw = (f) => { + if (f || ik.valueold != el.value) { + let v = (el.value - ik.valrange.min) / (ik.valrange.max - ik.valrange.min); + if (ik.sprites >= 1) + el.style.backgroundPosition = + "0px " + -((v * ik.sprites) | 0) * ik.frameheight + "px"; + else { + switch (ik.itype) { + case "k": + el.style.transform = "rotate(" + (270 * v - 135) + "deg)"; + break; + case "h": + el.style.backgroundPosition = (w - h) * v + "px 0px"; + break; + case "v": + el.style.backgroundPosition = "0px " + (h - w) * (1 - v) + "px"; + break; + } + } + ik.valueold = el.value; + } + }; + el.refresh(); + el.redraw(true); + el.addEventListener("keydown", ik.keydown); + el.addEventListener("mousedown", ik.pointerdown); + el.addEventListener("touchstart", ik.pointerdown); + el.addEventListener("wheel", ik.wheel); + }; + let refreshque = () => { + let elem = document.querySelectorAll("input.input-knob,input.input-slider"); + for (let i = 0; i < elem.length; ++i) procque.push([initKnobs, elem[i]]); + elem = document.querySelectorAll( + "input[type=checkbox].input-switch,input[type=radio].input-switch" + ); + for (let i = 0; i < elem.length; ++i) { + procque.push([initSwitches, elem[i]]); + } + }; + let procque = []; + refreshque(); + setInterval(() => { + for (let i = 0; procque.length > 0 && i < 8; ++i) { + let q = procque.shift(); + q[0](q[1]); + } + if (procque.length <= 0) refreshque(); + }, 50); +}); diff --git a/static/js/new-panel.js b/static/js/new-panel.js new file mode 100644 index 0000000..70ea355 --- /dev/null +++ b/static/js/new-panel.js @@ -0,0 +1,29 @@ +const container = document.querySelector("#panel-container"); +const file = document.querySelector("input[name=panel]"); +const form = document.querySelector("form"); + +const submit = document.querySelector("input[type=submit]"); +let panel; + +form.addEventListener("submit", (e) => { + e.preventDefault(); + form.querySelector("[name=params]").value = JSON.stringify(panel.model.params); + form.querySelector("[name=sockets]").value = JSON.stringify(panel.model.sockets); + form.submit(); +}); + +file.addEventListener("change", (e) => { + let file = e.target.files[0]; + + if (file) { + var reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onload = function (evt) { + let svg = evt.target.result; + panel = new Panel(svg, container); + }; + reader.onerror = function (evt) { + console.log("error reading file"); + }; + } +}); diff --git a/static/js/new-patch.js b/static/js/new-patch.js new file mode 100644 index 0000000..ce25e5d --- /dev/null +++ b/static/js/new-patch.js @@ -0,0 +1,12 @@ +const form = document.querySelector("form"); + +const container = document.querySelector("#panel-container"); +const svg = container.querySelector("svg").outerHTML; + +let panel = new Panel(svg, container); + +form.addEventListener("submit", (e) => { + e.preventDefault(); + form.querySelector("[name=cables]").value = container.querySelector(".cables").outerHTML; + form.submit(); +}); diff --git a/static/js/panel.js b/static/js/panel.js new file mode 100644 index 0000000..766a299 --- /dev/null +++ b/static/js/panel.js @@ -0,0 +1,298 @@ +class Cable { + start = ""; + end = ""; + strPath = ""; + color = ""; + strokeWidth = 10; + bufferSize = 20; + buffer = []; + path = null; + + constructor(start) { + this.start = start; + this.randomColor(); + } + + randomColor() { + const hue = Math.floor(Math.random() * 360); + const saturation = Math.floor(50 + Math.random() * (50 + 1)) + "%"; + const lightness = "75%"; + this.color = "hsl(" + hue + ", " + saturation + ", " + lightness + ")"; + } + + createCable(position) { + this.path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + + this.path.setAttribute("fill", "none"); + + this.path.setAttribute("stroke", this.color); + this.path.setAttribute("stroke-width", this.strokeWidth); + this.path.setAttribute("stroke-linecap", "round"); + + this.buffer = []; + this.appendToBuffer(position); + this.strPath = "M" + position.x + " " + position.y; + this.path.setAttribute("d", this.strPath); + } + + appendToBuffer(position) { + this.buffer.push(position); + while (this.buffer.length > this.bufferSize) { + this.buffer.shift(); + } + } + + getAveragePoint(offset) { + // Calculate the average point, starting at offset in the buffer + let len = this.buffer.length; + if (len % 2 === 1 || len >= this.bufferSize) { + let totalX = 0; + let totalY = 0; + let position, i; + let count = 0; + for (i = offset; i < len; i++) { + count++; + position = this.buffer[i]; + totalX += position.x; + totalY += position.y; + } + return { + x: totalX / count, + y: totalY / count, + }; + } + return null; + } + + updatePath() { + let position = this.getAveragePoint(0); + if (position) { + // Get the smoothed part of the path that will not change + this.strPath += " L" + position.x + " " + position.y; + + // Get the last part of the path (close to the current mouse position) + // This part will change if the mouse moves again + var tmpPath = ""; + for (var offset = 2; offset < this.buffer.length; offset += 2) { + position = this.getAveragePoint(offset); + tmpPath += " L" + position.x + " " + position.y; + } + this.path.setAttribute("d", this.strPath + tmpPath); + } + } +} + +class Panel { + width = 0; + height = 0; + + svg = null; + container = null; + containerBoundingClient = null; + + model = {}; + params = []; + sockets = []; + + cable = null; + cables = null; + + constructor(svg, container, debug = false) { + this.svg = this.htmlToElement(svg); + this.container = container; + this.container.style.position = "relative"; + this.containerRect = container.getBoundingClientRect(); + this.debug = debug; + + while (container.firstChild) { + container.removeChild(container.lastChild); + } + container.appendChild(this.svg); + + this.setSize(); + this.createParams(); + this.createSockets(); + this.createCables(); + + this.svg.addEventListener("mousedown", (e) => this.startCable(e)); + this.svg.addEventListener("mousemove", (e) => this.drawCable(e)); + this.svg.addEventListener("mouseup", (e) => this.endCable(e)); + // this.svg.addEventListener("mouseleave", (e) => this.endCable(e)); + } + + htmlToElement(string) { + var template = document.createElement("template"); + string = string.trim(); // Never return a text node of whitespace as the result + template.innerHTML = string; + return template.content.firstChild; + } + + setSize() { + this.width = this.svg.getAttribute("width"); + this.height = this.svg.getAttribute("height"); + this.container.style.width = this.width + "px"; + this.container.style.height = this.height + "px"; + } + + createParams() { + this.params = this.svg.querySelectorAll("#params [fill='#FF0000']"); + this.model.params = []; + + for (const param of this.params) { + let rect = param.getBoundingClientRect(); + + let control = document.createElement("input"); + control.setAttribute("type", "range"); + control.setAttribute("data-width", rect.width); + control.setAttribute("data-height", rect.height); + + control.setAttribute("data-fgcolor", "white"); + control.classList.add("input-knob"); + control.style.position = "absolute"; + control.style.left = rect.left - this.containerRect.left + "px"; + control.style.top = rect.top - this.containerRect.top + "px"; + + control.setAttribute("name", param.id); + + this.container.appendChild(control); + + this.model.params.push(param.id); + + if (this.debug) { + let label = document.createElement("label"); + label.setAttribute("for", param.id); + label.innerHTML = param.id; + + label.style.position = "absolute"; + label.style.left = rect.left - this.containerRect.left + "px"; + label.style.top = rect.top + rect.height + "px"; + label.style.fontSize = "1rem"; + label.style.backgroundColor = "red"; + label.style.color = "white"; + + this.container.appendChild(label); + } + } + let group = this.svg.querySelector("#params"); + if (group) group.style.display = "none"; + } + + createSockets() { + this.sockets = this.svg.querySelectorAll("#sockets [fill='#00FF00']"); + this.model.sockets = []; + for (const socket of this.sockets) { + let rect = socket.getBoundingClientRect(); + + let input = document.createElement("input"); + input.setAttribute("name", socket.id); + input.classList.add("socket"); + + input.style.position = "absolute"; + input.style.left = rect.left - this.containerRect.left + "px"; + input.style.top = rect.top - this.containerRect.top + "px"; + input.style.width = rect.width + "px"; + input.style.height = rect.height + "px"; + + input.style.border = "none"; + input.style.background = "none"; + input.style.opacity = 0; + + if (this.debug) { + input.style.border = "1px solid #00FF00"; + input.style.opacity = 1; + } + + input.style.cursor = "alias"; + + input.addEventListener("mouseup", (e) => this.endCable(e)); + + input.addEventListener("mousedown", (e) => { + e.preventDefault(); + this.startCable(e); + return false; + }); + + this.container.appendChild(input); + + this.model.sockets.push(socket.id); + } + + let group = this.svg.querySelector("#sockets"); + if (group) group.style.display = "none"; + } + + createCables() { + let cables = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + cables.classList.add("cables"); + cables.setAttribute("width", this.width); + cables.setAttribute("height", this.height); + cables.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`); + cables.setAttribute("fill", "none"); + cables.style.pointerEvents = "none"; + cables.style.position = "absolute"; + cables.style.left = 0; + cables.style.top = 0; + cables.style.width = this.width; + cables.style.height = this.height; + + this.cables = cables; + this.container.appendChild(this.cables); + } + + startCable(event) { + let position = this.getMousePosition(event); + let socket = event.target; + + if (socket && socket.classList.contains("socket")) { + this.cable = new Cable(socket.getAttribute("name")); + this.cable.createCable(position); + let cable = this.cable.path; + cable.style.pointerEvents = "stroke"; + + cable.addEventListener("click", (e) => { + console.log(e); + cable.remove(); + }); + + this.cables.appendChild(this.cable.path); + + for (const input of this.container.querySelectorAll(".input-knob")) { + input.style.pointerEvents = "none"; + } + } + } + + drawCable(event) { + if (this.cable) { + this.cable.appendToBuffer(this.getMousePosition(event)); + this.cable.updatePath(); + } + } + + endCable(event) { + if (this.cable) { + let socket = event.target; + + if (socket && socket.classList.contains("socket")) { + this.cable.end = socket.getAttribute("name"); + + // TODO: log cable + } else { + this.cable.path.remove(); + } + + for (const input of this.container.querySelectorAll(".input-knob")) { + input.style.pointerEvents = "all"; + } + + this.cable = null; + } + } + + getMousePosition(e) { + return { + x: e.pageX - this.containerRect.left, + y: e.pageY - this.containerRect.top, + }; + } +} diff --git a/templates/add_instrument.html b/templates/add_instrument.html index a8daa71..ec45200 100644 --- a/templates/add_instrument.html +++ b/templates/add_instrument.html @@ -2,20 +2,31 @@ - + - + + + + + + + New instrument -
- + + + + + - - - - - -
+ + + +
diff --git a/templates/add_patch.html b/templates/add_patch.html index fe51483..f32c638 100644 --- a/templates/add_patch.html +++ b/templates/add_patch.html @@ -2,33 +2,38 @@ - - Add new patch for {{instrument['name']}} - - -
- + + + + + Add new patch for {{instrument['name']}} + + + + + + - - - - + - {% for param in instrument['params']%} - - {% endfor %} - - - - - + + + + - -
+ +
{{panel|safe}}
+ diff --git a/templates/patch.html b/templates/patch.html index a30f1c7..464fbbc 100644 --- a/templates/patch.html +++ b/templates/patch.html @@ -3,7 +3,11 @@ {{name}} - + @@ -12,23 +16,46 @@ / Workbook / - {{instrument}} + {{instrument['name']}} / -

{{name}}

+

{{patch['name']}}

- Patch 1 +
+ {{panel|safe}} {% if patch['cables'] %} {{patch['cables']|safe}} {% endif %} +
+