From da97cc9a2f093f31f275c2177c4d7aef3cc268d5 Mon Sep 17 00:00:00 2001 From: TiagoLr Date: Fri, 29 Dec 2023 20:28:23 +0000 Subject: [PATCH] Release ADSR-1 v1.0 --- Modulation/tilr_ADSR-1.jsfx | 489 ++++++++++++++++++ Modulation/tilr_ADSR-1/adsr.array.jsfx-inc | 269 ++++++++++ Modulation/tilr_ADSR-1/adsr.curvelib.jsfx-inc | 217 ++++++++ Modulation/tilr_ADSR-1/adsr.mouselib.jsfx-inc | 42 ++ 4 files changed, 1017 insertions(+) create mode 100644 Modulation/tilr_ADSR-1.jsfx create mode 100644 Modulation/tilr_ADSR-1/adsr.array.jsfx-inc create mode 100644 Modulation/tilr_ADSR-1/adsr.curvelib.jsfx-inc create mode 100644 Modulation/tilr_ADSR-1/adsr.mouselib.jsfx-inc diff --git a/Modulation/tilr_ADSR-1.jsfx b/Modulation/tilr_ADSR-1.jsfx new file mode 100644 index 0000000..e41459d --- /dev/null +++ b/Modulation/tilr_ADSR-1.jsfx @@ -0,0 +1,489 @@ +desc: ADSR-1 +author: tilr +version: 1.0 +provides: + tilr_ADSR-1/adsr.array.jsfx-inc + tilr_ADSR-1/adsr.curvelib.jsfx-inc + tilr_ADSR-1/adsr.mouselib.jsfx-inc +screenshot: https://raw.githubusercontent.com/tiagolr/tilr_jsfx/master/doc/adsr1.png +about: + # ADSR-1 + + ADSR-1 is an ADSR envelope generator with: + + * Multi-segments / control points per stage + * Sustain looping + * Output smooth + +desc:ADSR-1 +tags:instrument + +slider1:att=150<1,5000,1:log>Attack +slider2:dec=200<1,5000,1:log>Decay +slider3:sus=80<0,100,1>Sustain +slider4:rel=500<1,5000,1:log>Release + +slider8:_lfomin=0<0,100,1>Min +slider9:_lfomax=100<0,100,1>Max +slider10:lfosmooth=0<0,100>Smooth + +slider20:value=0.5<0,1,0.01>Value + +import adsr.curvelib.jsfx-inc +import adsr.mouselib.jsfx-inc + +in_pin:none +out_pin:none + +options:gfx_hz=60 + +@serialize +file_var(0, curve.points.size); +file_var(0, curve.segments.size); +file_var(0, curve.points.num); +file_var(0, curve.segments.num); +file_mem(0, curve.points.buf, curve.points.num * curve.points.size); +file_mem(0, curve.segments.buf, curve.segments.num * curve.segments.size); +file_var(0, sus_loop); + +@init + +ext_noinit = 1; +snap = 0; +selected_point = -1; +selected_midpoint = -1; +midi_trigger = 0; +sus_loop = 0; + +freemem = curve.curve_init(0, 1000); +curve.insert_point(0,1,.25,0); // att +curve.insert_point(0.25,0,-.25,1); // dec +curve.insert_point(0.5,0,-.25,2); // sus +curve.insert_point(1,1,0,0); // rel +curve.build_segments(); + +// the sustain point moves up and down on key release +// use another curve to display the envelope without those moves +rcurve.curve_init(freemem, 1000); + +function rc_set(rc) + instance(a) ( + a = 1 / (rc * srate + 1); +); +function rc_lp(sample) + instance(lp, a) ( + lp += a * (sample - lp); +); +function smooth(sample) + instance (lp, smooth) ( + lp = smooth; + smooth = this.rc_lp(sample); +); + +function get_dec_point(curve*) +local (i, ptr, point) +( + loop(i=0; curve.points.size, + ptr = curve.points.array_get(i); + ptr[3] == 1 ? ( + point = ptr; + ); + i += 1; + ); + point; +); + +function get_sus_point(curve*) +local (i, ptr, point) +( + loop(i=0; curve.points.size, + ptr = curve.points.array_get(i); + ptr[3] == 2 ? ( + point = ptr; + ); + i += 1; + ); + point; +); + +// sets points x coordinate to match att, dec, sus and rel +function build_curve() +local (duration, i, point, attpoint, decpoint, suspoint, relpoint, dp0, sp0, dp1, sp1) +( + // for each point, store its relative position + duration = att + dec + rel; + + attpoint = curve.points.array_first(); + decpoint = get_dec_point(curve); + suspoint = get_sus_point(curve); + relpoint = curve.points.array_last(); + + dp0 = decpoint[0]; + sp0 = suspoint[0]; + + decpoint[0] = att / duration; + suspoint[0] = (att + dec) / duration; + + dp1 = decpoint[0]; + sp1 = suspoint[0]; + + // apply attack points range change + i = 0; + while((point = curve.points.array_get(i))[3] !== 1) ( + point[0] *= dp1 / dp0; + i += 1; + ); + // apply decay points range change + i += 1; + while((point = curve.points.array_get(i))[3] !== 2) ( + perc = (point[0] - dp0) / (sp0 - dp0); + point[0] = dp1 + perc * (sp1 - dp1); + i += 1; + ); + // apply release points range change + i += 1; + while((point = curve.points.array_get(i)) !== curve.points.array_last()) ( + perc = (point[0] - sp0) / (1 - sp0); + point[0] = sp1 + perc * (1 - sp1); + i += 1; + ); +); + +function get_curve_y(curve*, x) +local(val) +( + val = 1 - curve.get_y_at(x); + lfomin + (lfomax - lfomin) * val; +); + +@slider + +spoint = get_sus_point(curve); +spoint[1] = 1 - (sus / 100); +build_curve(); +curve.build_segments(); + +_lfomin > _lfomax ? _lfomin = _lfomax; +lfomin = _lfomin / 100; +lfomax = _lfomax / 100; + +value.rc_set(lfosmooth / 100 / 10); + +@block +suspoint = get_sus_point(curve); +decpoint = get_dec_point(curve); + +while (midirecv(offset, msg1, note, vel)) ( + event = msg1 & 0xF0; + // onnote + event == 0x90 && vel ? ( + midi_trigger = 1; + lmidi_trigger = 0; + release = 0; + xpos = 0; + ); + // offnote + event == 0x80 && midi_trigger ? ( + release = 1; + yval = curve.get_y_at(xpos); + rcurve.copy_from(curve); + rsuspoint = get_sus_point(rcurve); + rsuspoint[1] = yval; + xpos = rsuspoint[0]; + rcurve.build_segments(); + ); + midisend(offset, msg1, note, vel); +); + +@sample + +// when midi trigger starts reset env smooth +!lmidi_trigger && midi_trigger ? ( + value.smooth = 1 - curve.get_y_at(0); +); +lmidi_trigger = midi_trigger; + +midi_trigger ? ( + xpos += 1 / srate / ((att + dec + rel) * 0.001); + xpos >= 1 ? ( // finish + midi_trigger = 0; + xpos = 1; + ); + !release && xpos >= suspoint[0] ? ( + xpos = sus_loop + ? decpoint[0] + : suspoint[0]; // sustain + ); + value = release + ? value.smooth(get_curve_y(rcurve, xpos)) + : value.smooth(get_curve_y(curve, xpos)); + slider_automate(value); +); + +@gfx 600 350 + +color_active = 0x00FFFF; +color_bg = 0x141618; +hover_radius = 8; +hover = 0; +winx = 10; +winy = 30; +winw = gfx_w - 20; +winh = gfx_h - 40; +gridsegs = 16; +gridx = winw / gridsegs; +gridy = winh / gridsegs; + +function set_color(color) ( + gfx_r = (color & 0xFF0000) / 0xFF0000; + gfx_g = (color & 0x00FF00) / 0x00FF00; + gfx_b = (color & 0x0000FF) / 0x0000FF; +); + +function round(x) ( + floor(x + 0.5 * sign(x)); +); + +function draw_midpoint(seg, i) +local (x, y, xx, yy) +( + x = (seg[1] + seg[0]) * 0.5; + y = curve.get_y_at(x); + xx = x * winw + winx; + yy = y * winh + winy; + gfx_set(0,1,1); + gfx_circle(xx, yy, 3); + + selected_midpoint == -1 && selected_point == -1 && !hover + && mouse_in_rect(xx - hover_radius, yy - hover_radius, hover_radius * 2, hover_radius * 2) ? + ( + hover = 1; + gfx_set(0,1,1, 0.5); + gfx_circle(xx, yy, hover_radius, 1); + mouse.left_click ? ( + selected_midpoint = i; + ); + ); + + selected_midpoint == i ? ( + gfx_set(0,1,1, 1); + gfx_circle(xx, yy, 3, 1); + ); +); + +function on_midpoint_move(dy) +local(point, tension, next, rising) +( + point = curve.points.array_get(selected_midpoint); + next = curve.points.array_get(selected_midpoint + 1); + rising = point[1] < next[1]; + rising ? dy *= -1; + tension = point[2]; + tension += dy / 100; + tension > 1 ? tension = 1; + tension < -1 ? tension = -1; + point[2] = tension; + curve.build_segments(); +); + +function draw_point(point, i) +local (xx, yy) +( + point[3] ? ( + gfx_set(0, 1, 1); + ) : ( + gfx_set(1, 1, 1); + ); + xx = point[0] * winw + winx; + yy = point[1] * winh + winy; + gfx_circle(xx, yy, 4, 1); + + selected_point == -1 && selected_midpoint == -1 && !hover + && mouse_in_rect(xx - hover_radius, yy - hover_radius, hover_radius * 2, hover_radius * 2) ? + ( + hover = 1; + gfx_set(1,1,1,0.5); + gfx_circle(xx, yy, hover_radius, 1); + mouse.left_click ? ( + selected_point = i; + ); + ); + + selected_point == i ? ( + gfx_set(1,0,0,0.5); + gfx_circle(xx, yy, 5, 1); + ); +); + +function on_point_move() +local(point, prev, next, xx, yy) +( + point = curve.points.array_get(selected_point); + snap || mouse.control ? ( + xx = round((mouse.x - winx) / gridx) * gridx + winx; + yy = round((mouse.y - winy) / gridy) * gridy + winy; + ) : ( + xx = mouse.x; + yy = mouse.y; + ); + xx = (xx - winx) / winw; + yy = (yy - winy) / winh; + + selected_point == 0 + || selected_point == curve.points.size - 1 + || point[3] + ? ( + point[1] = yy; + point[1] < 0 ? point[1] = 0; + point[1] > 1 ? point[1] = 1; + point[3] == 2 ? ( // udate sustain from point y + sus = (1 - point[1]) * 100; + ); + ) : ( + point[0] = xx; + point[1] = yy; + point[0] < 0 ? point[0] = 0; + point[0] > 1 ? point[0] = 1; + point[1] < 0 ? point[1] = 0; + point[1] > 1 ? point[1] = 1; + prev = curve.points.array_get(selected_point - 1); + next = curve.points.array_get(selected_point + 1); + point[0] < prev[0] ? point[0] = prev[0]; + point[0] > next[0] ? point[0] = next[0]; + ); + curve.build_segments(); +); + + +function on_double_click () +local (found, seg, px, py, i, point, x, y, coolinear) +( + found = 0; + // if xy in point and point not edge delete point + loop(i=1; curve.points.size - 2, + point = curve.points.array_get(i); + px = winx + winw * point[0]; + py = winy + winh * point[1]; + !found && !point[3] && point_in_rect(mouse.x, mouse.y, px-hover_radius, py-hover_radius, hover_radius * 2, hover_radius * 2) ? ( + curve.remove_point(i); + found = 1; + ); + i += 1; + ); + // if xy in midpoint reset tension + loop(i=0; curve.segments.size, + seg = curve.segments.array_get(i); + x = (seg[1] + seg[0]) * 0.5; + y = curve.get_y_at(x); + px = x * winw + winx; + py = y * winh + winy; + !found && !is_collinear(seg) && point_in_rect(mouse.x, mouse.y, px-hover_radius, py-hover_radius, hover_radius * 2, hover_radius * 2) ? ( + point = curve.points.array_get(i); + point[2] = 0; // reset tension + found = 1; + ); + i += 1; + ); + + !found ? ( + px = mouse.x; + py = mouse.y; + snap || mouse.control ? ( + px = round((mouse.x - winx) / gridx) * gridx + winx; + py = round((mouse.y - winy) / gridy) * gridy + winy; + ); + x = (px - winx) / winw; + y = (py - winy) / winh; + x >= 0 && x <= 1 && y >= 0 && y <= 1 ? ( // point in env window + x == 1 ? x -= 0.000001; // special case avoid inserting point after last point + curve.insert_point(x, y, 0, 0); + ); + ); + curve.build_segments(); +); + +function draw_grid () +local(i, j) +( + gfx_set(1, 1, 1, 0.075); + loop(i=0; gridsegs + 1, + gfx_line(winx, winy + gridy * i, winx + winw, winy + gridy * i, 0); + gfx_line(winx + gridx * i, winy, winx + gridx * i, winy + winh, 0); + i += 1; + ); +); + +function draw_button (x, y, w, label, toggled) ( + gfx_a = 1; + set_color(color_active); + gfx_rect(x, y - 2, w, 10 + 2); + gfx_x = x; gfx_y = y; + !toggled ? ( + set_color(color_bg); + gfx_rect(x+1, y+1-2, w-2, 10); + ); + set_color(toggled ? color_bg : color_active); + gfx_drawstr(label, 1, x+w, y+10); +); + +mouse.update_mouse_state(); +gfx_clear = color_bg; + +draw_grid(); + +// draw seek +midi_trigger ? ( + gfx_set(1,0,0,0.5); + gfx_line(xpos * winw + winx, winy, xpos * winw + winx, winy + winh); + ypos = (1- value); + gfx_set(1,1,0,1); + gfx_circle(xpos * winw + winx, ypos * winh + winy, 5); +); + +gfx_set(1,1,1,1); +curve.draw_segments(winx, winy, winw, winh); + +// draw points +loop(i =0; curve.points.size, + point = curve.points.array_get(i); + draw_point(point, i); + i += 1; +); + +// draw midpoints +loop(i = 0; curve.segments.size, + seg = curve.segments.array_get(i); + !is_collinear(seg) ? ( + draw_midpoint(seg, i); + ); + i += 1; +); + + +// draw right buttons +drawx = gfx_w - 60; +draw_button(drawx, 10, 50, "Snap", snap || mouse.control); +mouse.left_click && mouse_in_rect(drawx, 10-2, 50, 10+2) ? ( + snap = !snap; +); + +drawx -= 60; +draw_button(drawx, 10, 50, "Loop", sus_loop); +mouse.left_click && mouse_in_rect(drawx, 10-2, 50, 10+2) ? ( + sus_loop = !sus_loop; +); + +mouse.double_click ? ( + on_double_click() +) +: selected_point > -1 && mouse.left && (mouse.dx != 0 || mouse.dy != 0) ? ( + on_point_move(); +) +: selected_midpoint > -1 && mouse.left && mouse.dy != 0 ? ( + on_midpoint_move(mouse.dy); +); + +!mouse.left ? ( + selected_midpoint = -1; + selected_point = -1; +); diff --git a/Modulation/tilr_ADSR-1/adsr.array.jsfx-inc b/Modulation/tilr_ADSR-1/adsr.array.jsfx-inc new file mode 100644 index 0000000..50876db --- /dev/null +++ b/Modulation/tilr_ADSR-1/adsr.array.jsfx-inc @@ -0,0 +1,269 @@ +desc:Simple two-dimensional array interface + +// Copyright (C) 2015-2019 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +/* Example + + desc:Last-note priority mono synth + + import Tale/array.jsfx-inc + import Tale/midi_queue.jsfx-inc + import Tale/poly_blep.jsfx-inc + + @init + + voice.array_init(0, 128, 2); + + @sample + + while(midi.midiq_recv()) ( + midi.msg1 &= 0xF0; + + // Note On + midi.msg1 == 0x90 && midi.msg3 ? ( + + // Remove note if somehow it is already playing. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? voice.array_remove(ptr); + + // Add note, and set pointer to it. + ptr = voice.array_add(); + ptr[0] = midi.msg2; + + // Set oscillator frequency. + ptr[1] = osc.poly_setf(midi.msg2, 440); + osc.a *= 0.5; + ) : + + // Note Off + midi.msg1 == 0x80 || midi.msg1 == 0x90 ? ( + + // Remove note. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? ( + voice.array_remove(ptr); + !voice.size ? osc.a = 0 : ( + + // Update pointer to new last note. + ptr = voice.array_get(voice.size - 1); + osc.poly_setdt(ptr[1]); + osc.a *= 0.5; + ); + ); + ) : + + // All Notes Off + midi.msg1 == 0xB0 && midi.msg2 == 123 ? ( + voice.array_clear(); + ); + ); + + spl0 = spl1 = osc.poly_saw(); + + Initialization Functions + + * array_init(index, max_rows[, cols]) + Example: array.array_init(0, 64, 2); + Sets the offset and size of the local memory buffer to store the array + in, and returns the next available memory index (i.e. + index+rows*cols). If cols is omitted, then it defaults to 1. + + * array_alloc(max_rows[, cols]) + * array_free() + Example: array.array_alloc(64, 2); + Allocates/deallocates a block of local memory to store the array in, + and returns its index. + + Note: Requires Tale/malloc.jsfx-inc. + + Array Functions + + * array_get(row) + Example: ptr = array.array_get(0); + Returns a pointer to the local memory index of the specified row. + + * array_add() + Example: ptr = array.array_add(); + Adds a row to the end of the array and returns its local memory index. + Note that the row is added but not initialized (i.e. it does not + contain any data yet, nor is it zeroed.). + + * array_insert(ptr) + Example: array.array_insert(array.array_get(0)); + Inserts a row into the array. Note that the row is inserted but not + initialized. + + * array_remove(ptr) + Example: array.array_remove(array.array_get(0)); + Removes a row from the array. + + * array_clear() + Example: array.array_clear(); + Removes all rows from the array. + + Miscellaneous Functions + + * array_first() + Example: ptr = array.array_first(); + Returns a pointer to the local memory index of the first row, or -1 if + there are no rows. + + * array_next(ptr) + Example: ptr = array.array_next(ptr); + Returns a pointer to the local memory index of the next row, or -1 if + there is no next row. + + * array_last() + Example: ptr = array.array_last(); + Returns a pointer to the local memory index of the last row, or -1 if + there are no rows. + + * array_find(value[, col[, ptr]]) + Example: ptr = array_find(123); + Finds a value in the array at the specified column (0 by default), + starting at the specified row pointer (first row by default), and + returns the local memory index of the entire row, or -1 if the value + was not found. + + Instance Variables + + * buf + Example: ptr = array.buf; + The local memory address of the buffer in which the array is stored. + + * size + Example: num_rows = array.size; + The current size of the array in rows. + + * num + Example: num_cols = array.num; + The number of columns in each row. + +*/ + +@init + +function array_init(index, max_rows, cols) + instance(buf, size, num) +( + buf = index; + size = 0; + num = cols; + + buf + max_rows * num; +); + +function array_init(index, max_rows) +( + this.array_init(index, max_rows, 1); +); + +function array_get(row) + instance(buf, num) +( + buf + row * num; +); + +function array_add() + instance(buf, size, num) +( + buf + ((size += 1) - 1) * num; +); + +function array_insert(ptr) + instance(buf, size, num) + local(end) +( + end = buf + size * num; + size += 1; + ptr < end ? memcpy(ptr + num, ptr, end - ptr); + + // Returning the pointer here might not be very useful, but it is + // consistent with array_add(). + ptr; +); + +function array_remove(ptr) + instance(buf, size, num) + local(end) +( + end = buf + (size -= 1) * num; + ptr < end ? memcpy(ptr, ptr + num, end - ptr); + + // Again, returning the pointer here is not very useful; meh. + ptr; +); + +function array_first() + instance(buf, size) +( + size ? buf : -1; +); + +function array_next(ptr) + instance(buf, size, num) +( + ptr += num; + ptr < buf + size * num ? ptr : -1; +); + +function array_last() + instance(buf, size, num) +( + size ? buf + (size - 1) * num : -1; +); + +function array_find(value, col, ptr) + instance(buf, size, num) + local(ret, end) +( + ret = -1; + end = buf + size * num; + while( + ptr < end ? ( + ptr[col] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_find(value, col) + instance(buf) +( + this.array_find(value, col, buf); +); + +function array_find(value) + instance(buf, size, num) + local(ret, ptr, end) +( + ret = -1; + end = (ptr = buf) + size * num; + while( + ptr < end ? ( + ptr[] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_clear() + instance(size) +( + size = 0; +); diff --git a/Modulation/tilr_ADSR-1/adsr.curvelib.jsfx-inc b/Modulation/tilr_ADSR-1/adsr.curvelib.jsfx-inc new file mode 100644 index 0000000..92729e3 --- /dev/null +++ b/Modulation/tilr_ADSR-1/adsr.curvelib.jsfx-inc @@ -0,0 +1,217 @@ +desc:curvelib.jsfx-inc + +/* +MIT License + +Copyright (c) 2024 TiagoLr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* + Curvelib is a set of helpers to work with exponential curves + + Example: + import curvelib.jsfx-inc + @init + + curve.curve_init(0, 1000); // init a curve with up to 1000 points + + curve.insert_point(0,0,1); // x, y, tension + curve.insert_point(0.25,0.25,-1); + curve.insert_point(0.75,0.75, 1); + curve.insert_point(1,1,0); + + curve.build_segments(); // build curve segments from points + + @gfx 300 300 + + x = 20; + y = 20; + width = 200; + height = 100; + + // draw curve segments + gfx_set(1, 0, 0); + curve.draw_segments(x, y, width, height); + + // draw a circle at midpoint + xx = 0.5; + yy = curve.get_y_at(x); + gfx_circle(xx * width + x, yy * height + y, 5); +*/ + +import adsr.array.jsfx-inc + +@init + +/* + Allocates memory for points and segments +*/ +function curve_init(buf, npoints) +instance(points, segments) +( + buf = points.array_init(buf, npoints, 4); // x,y,tension,type=1 attack, 2 decay, 3 sustain, 4 release + buf = segments.array_init(buf, npoints-1, 6); // x1,x2,y1,y2,tension,power + buf; +); + +/* + Inserts point ordered by x +*/ +function insert_point(x, y, tension, type) +instance(points) +local (ptr, p) +( + points.size == 0 ? ( + ptr = points.array_add(); + ptr[0] = x; + ptr[1] = y; + ptr[2] = tension; + ptr[3] = type; + ) : ( + ptr = points.array_last(); + while (ptr >= 0) ( + ptr[0] <= x ? ( + p = points.array_insert(ptr + points.num); + p[0] = x; + p[1] = y; + p[2] = tension; + p[3] = type; + ptr = -1; + ) : ( + ptr -= points.num; + ); + ); + ); +); + +function copy_from(curve*) +instance(points, segments) +( + points.size = curve.points.size; + memcpy(points.buf, curve.points.buf, curve.points.size * curve.points.num); + + segments.size = curve.segments.size; + memcpy(segments.buf, curve.segments.buf, curve.segments.size * curve.segments.num); +); + +function remove_point(x, y) +instance(points) +local (ptr) +( + ptr = points.array_first(); + while (ptr >= 0) ( + ptr[0] == x && ptr[1] == y ? ( + points.array_remove(ptr); + ptr = -1; + ) : ( + ptr = points.array_next(ptr); + ); + ); +); + +function remove_point(i) +instance(points) +( + points.array_remove(points.array_get(i)); +); + +function build_segments() +instance(points, segments) +local (seg, p1, p2) +( + segments.array_clear(); + p1 = points.array_first(); + p2 = points.array_next(p1); + while (p2 >= 0) ( + seg = segments.array_add(); + seg[0] = p1[0]; // x1 + seg[1] = p2[0]; // x2 + seg[2] = p1[1]; // y1 + seg[3] = p2[1]; // y2 + seg[4] = p1[2]; // tension + seg[5] = pow(1.1, abs(p1[2] * 50)); + p1 = p2; + p2 = points.array_next(p2); + ); +); + +/* + Based of https://github.com/KottV/SimpleSide/blob/main/Source/types/SSCurve.cpp +*/ +function get_y(seg, x) +local (ten, pwr) +( + ten = seg[4]; + pwr = seg[5]; + + seg[0] == seg[1] ? ( // x1 == x2 + seg[3]; // FIX glitch + ) : ten >= 0 ? ( + pow((x - seg[0]) / (seg[1] - seg[0]), pwr) * (seg[3] - seg[2]) + seg[2]; + ) : ( + -1 * (pow(1 - (x - seg[0]) / (seg[1] - seg[0]), pwr) - 1) * (seg[3] - seg[2]) + seg[2]; + ); +); + +function get_y_at(x) +instance(segments) +local (seg, val) +( + val = 0; + seg = segments.array_last(); // + while(seg >= 0) ( + seg[0] <= x && seg[1] >= x ? ( + val = get_y(seg, x); + seg = -1; + ) : ( + seg -= segments.num; + ); + ); + val; +); + +function is_collinear(seg) ( + abs(seg[0] - seg[1]) < 0.01 || abs(seg[2] - seg[3]) < 0.01 +); + +// GFX FUNCTIONS + +// GFX FUNCTIONS + +function draw_segments(winx, winy, winw, winh) +instance(points) +local (i, px, py) +( + gfx_x = winx; + gfx_y = points.array_first()[1] * winh + winy; + + loop(i = 0; winw + 1, + py = this.get_y_at(i / winw); + gfx_a = 0.0625; + gfx_triangle(gfx_x, gfx_y, gfx_x, winh + winy, i + winx, winh + winy, i + winx, py * winh + winy); + gfx_a = 1; + gfx_lineto(i + winx, py * winh + winy); + i += 1; + ); +); + + + diff --git a/Modulation/tilr_ADSR-1/adsr.mouselib.jsfx-inc b/Modulation/tilr_ADSR-1/adsr.mouselib.jsfx-inc new file mode 100644 index 0000000..3805457 --- /dev/null +++ b/Modulation/tilr_ADSR-1/adsr.mouselib.jsfx-inc @@ -0,0 +1,42 @@ +desc:mouselib.jsfx-inc + +@init + +function update_mouse_state() +instance(cap, x, y, lx, ly, dx, dy, right_click, left_click, lleft, lright, left, right, click_time, double_click, control, lwheel, wheel) +global(mouse_cap, mouse_x, mouse_y, mouse_wheel) +( + lleft = left; + lright = right; + lx = x; + ly = y; + cap = mouse_cap; + control = mouse_cap & 4; + x = mouse_x; + y = mouse_y; + + left = cap & 1 > 0; + right = cap & 2 > 0; + left_click = left && lleft == 0; + right_click = right && lright == 0; + dx = x - lx; + dy = y - ly; + + wheel = mouse_wheel > lwheel ? 1 : mouse_wheel < lwheel ? -1 : 0; + lwheel = mouse_wheel; + + left_click ? ( + time_precise() - click_time < .25 ? double_click = 1; + click_time = time_precise(); + ) : ( + double_click = 0; + ); +); + +function mouse_in_rect (x, y, w ,h) ( + mouse.x >= x && mouse.x <= x + w && mouse.y >= y && mouse.y <= y + h; +); + +function point_in_rect (x1, y1, x, y, w, h) ( + x1 >= x && x1 <= x + w && y1 >= y && y1 <= y + h; +)