summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorjulian laplace <julescarbon@gmail.com>2025-07-07 19:54:02 +0200
committerjulian laplace <julescarbon@gmail.com>2025-07-07 19:54:02 +0200
commitdfbd36be4341f633cb51d187d3245efbc9d500a8 (patch)
tree15a1006ba51a9c4b2773161bae888cc0522fcdff /client
parent95a494a5570ba7933943cfe2093f1357c5f087f4 (diff)
transitions, fix colors, add help
Diffstat (limited to 'client')
-rw-r--r--client/index.js194
-rw-r--r--client/lib/color.js2
-rw-r--r--client/lib/kalimba.js49
-rw-r--r--client/lib/organ.js43
-rw-r--r--client/lib/sampler.js23
-rw-r--r--client/lib/scales.js8
-rw-r--r--client/lib/util.js15
-rw-r--r--client/vendor/oktransition.js182
8 files changed, 397 insertions, 119 deletions
diff --git a/client/index.js b/client/index.js
index 2c986dd..df127e4 100644
--- a/client/index.js
+++ b/client/index.js
@@ -11,20 +11,23 @@ import kalimba from "./lib/kalimba";
import sampler from "./lib/sampler";
import organ from "./lib/organ";
import midi from "./lib/midi";
+import oktransition from "./vendor/oktransition";
import { getOutput } from "./lib/output";
import {
browser,
requestAudioContext,
+ clamp,
choice,
- roundFreq,
- frequencyInRange,
+ roundInterval,
+ intervalInRange,
mod,
} from "./lib/util";
import { scales } from "./lib/scales";
let instrument = kalimba;
-const root = 440;
+let grid = document.createElement("grid");
+let root = 440;
const s = 50;
let w, h, ws, hs;
@@ -35,16 +38,17 @@ const mul_off = 0.9;
let dragging = false;
let erasing = false;
-let lastFreq = 0;
+let lastNote = 0;
let notes = [];
let base_x = 0;
let base_y = 0;
let scaleMode = 0;
let is_split = false;
-let frequencies;
+let intervals;
requestAudioContext(() => {
const output = getOutput();
+ document.body.appendChild(grid);
kalimba.load(output);
organ.load(output);
sampler.load(output, function ready() {
@@ -80,28 +84,28 @@ function log() {
// console.log(notes);
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
- const frequency = notes[i][j].frequency;
- const rounded = roundFreq(frequency);
- if (!seen[rounded] && frequencyInRange(frequency)) {
- seen[rounded] = notes[i][j].frequency;
+ const interval = notes[i][j].interval;
+ const rounded = roundInterval(interval);
+ if (!seen[rounded] && intervalInRange(interval, root)) {
+ seen[rounded] = notes[i][j].interval;
}
}
}
- frequencies = Object.values(seen).sort((a, b) => a - b);
- // console.log(frequencies);
- console.log(frequencies.length, "unique frequencies in 8x8");
+ intervals = Object.values(seen).sort((a, b) => a - b);
+ // console.log(intervals);
+ console.log(intervals.length, "unique intervals in 8x8");
}
-function play(freq) {
- if (!organ.isPlaying(freq.frequency)) {
- let frequency = freq.frequency;
- // while (frequency < root) {
- // frequency *= 2;
+function play(note) {
+ if (!organ.isPlaying(note.interval)) {
+ let interval = note.interval;
+ // while (interval < root) {
+ // interval *= 2;
// }
- // while (frequency > root) {
- // frequency /= 2;
+ // while (interval > root) {
+ // interval /= 2;
// }
- const rounded = roundFreq(freq.frequency);
- organ.play(frequency);
+ const rounded = roundInterval(note.interval);
+ organ.play(interval);
notes.forEach((row) =>
row.forEach(
(note) => note.rounded === rounded && note.div.classList.add("playing"),
@@ -109,20 +113,20 @@ function play(freq) {
);
}
}
-function trigger(freq) {
- if (frequencyInRange(freq.frequency)) {
- instrument.play(freq.frequency);
+function trigger(note) {
+ if (intervalInRange(note.interval, root)) {
+ instrument.play(note.interval, root);
}
}
function trigger_index(index) {
- const frequency = frequencies[index];
- if (frequency) {
- instrument.play(frequency);
+ const interval = intervals[index];
+ if (interval) {
+ instrument.play(interval, root);
}
}
-function pause(freq) {
- organ.pause(freq.frequency);
- const rounded = roundFreq(freq.frequency);
+function pause(note) {
+ organ.pause(note.interval);
+ const rounded = roundInterval(note.interval);
notes.forEach((row) =>
row.forEach(
(note) =>
@@ -130,11 +134,11 @@ function pause(freq) {
),
);
}
-function toggle(freq) {
- if (organ.isPlaying(freq.rounded) || freq.div.classList.contains("playing")) {
- pause(freq);
+function toggle(note) {
+ if (organ.isPlaying(note.rounded) || note.div.classList.contains("playing")) {
+ pause(note);
} else {
- play(freq);
+ play(note);
}
}
@@ -146,16 +150,16 @@ function add(i, j) {
const [a, b] = scale.get(ii, jj, i, j, base_x, base_y);
const div = document.createElement("div");
- const frequency = (root * a) / b;
- // const frequency = root * Math.pow(2, ((b / a) % 1) + 1);
+ const interval = a / b;
+ // const interval = root * Math.pow(2, ((b / a) % 1) + 1);
let add = 0;
let frac;
div.style.left = i * s + "px";
div.style.top = j * s + "px";
- const freq = {
- frequency,
- rounded: roundFreq(frequency),
+ const note = {
+ interval,
+ rounded: roundInterval(interval, root),
div,
i,
j,
@@ -192,19 +196,19 @@ function add(i, j) {
}
div.innerHTML = `<div>${a_disp}</div><div>/</div><div>${b_disp}</div>`;
- if (freq.playing) {
+ if (note.playing) {
div.style.backgroundColor = color(frac, add + add_on, mul_on);
} else {
div.style.backgroundColor = color(frac, add + add_off, mul_off);
}
- if (organ.isPlaying(frequency)) {
+ if (organ.isPlaying(interval)) {
div.classList.add("playing");
}
},
};
- freq.recolor(1, 1);
+ note.recolor(1, 1);
if (browser.isDesktop) {
div.addEventListener("mousedown", function (event) {
@@ -213,17 +217,17 @@ function add(i, j) {
event.preventDefault();
// notes.forEach((row) => row.forEach((note) => note.recolor(a, b)));
is_split = [a, b];
- toggle(freq);
+ toggle(note);
return;
}
div.style.backgroundColor = color(frac, add + add_on, mul_on);
dragging = true;
- trigger(freq);
+ trigger(note);
});
div.addEventListener("mouseenter", function () {
div.style.backgroundColor = color(frac, add + add_on, mul_on);
if (dragging) {
- trigger(freq);
+ trigger(note);
}
});
div.addEventListener("mouseleave", function () {
@@ -237,17 +241,16 @@ function add(i, j) {
} else {
div.addEventListener("touchstart", function (e) {
e.preventDefault();
- toggle(freq);
- erasing = !freq.playing;
- lastFreq = freq;
+ toggle(note);
+ erasing = !note.playing;
+ lastNote = note;
});
}
- document.body.appendChild(div);
- return freq;
+ grid.appendChild(div);
+ return note;
}
function bind() {
- window.addEventListener("resize", build);
if (browser.isDesktop) {
document.addEventListener("mousedown", (event) => {
if (event.button !== 2) {
@@ -267,61 +270,124 @@ function bind() {
const x = Math.floor(e.touches[0].pageX / s);
const y = Math.floor(e.touches[0].pageY / s);
if (!(x in notes) || !(y in notes[x])) return;
- const freq = notes[x][y];
- if (freq !== lastFreq) {
+ const note = notes[x][y];
+ if (note !== lastNote) {
if (dragging) {
if (erasing) {
- pause(freq);
+ pause(note);
} else {
- toggle(freq);
+ toggle(note);
}
}
- lastFreq = freq;
+ lastNote = note;
}
});
document.addEventListener("touchend", () => {
dragging = false;
});
}
+ window.addEventListener("resize", build);
+ window.addEventListener("keydown", keydown, true);
+ keys.listen(trigger_index);
+ document
+ .querySelector("#help .close")
+ .addEventListener("click", () =>
+ document.querySelector("#help").classList.remove("visible"),
+ );
+ document
+ .querySelector("#help-button")
+ .addEventListener("click", () =>
+ document.querySelector("#help").classList.toggle("visible"),
+ );
}
+let isReset = false;
function keydown(e) {
- if (e.altKey || e.ctrlKey || e.metaKey) return;
let step = 1;
if (e.shiftKey) {
step += 4;
}
- // console.log(e.keyCode);
+ console.log(e.keyCode);
switch (e.keyCode) {
+ case 27: // esc - PANIC
+ if (isReset) {
+ base_x = 0;
+ base_y = 0;
+ showMessage(`reset!`);
+ }
+ organ.stop();
+ sampler.stop();
+ rebuild();
+ isReset = true;
+ setTimeout(() => (isReset = false), 500);
+ break;
case 37: // left
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
base_x = Math.max(0, base_x - step);
rebuild();
break;
case 38: // up
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
base_y = Math.max(0, base_y - step);
rebuild();
break;
case 39: // right
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
base_x += step;
rebuild();
break;
case 40: // down
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
base_y += step;
rebuild();
break;
case 220: // \
midi.enable(trigger_index);
break;
+ case 191: // ?
+ document.querySelector("#help").classList.toggle("visible");
+ break;
case 189: // -
- scaleMode = mod(scaleMode - 1, scales.length);
- rebuild();
+ e.preventDefault();
+ if (e.altKey || e.metaKey) {
+ root = clamp(root - (e.shiftKey ? 10 : 1), 1, 200000);
+ organ.setRoot(root);
+ showMessage(`Root: ${root} hz`);
+ } else {
+ scaleMode = mod(scaleMode - 1, scales.length);
+ rebuild();
+ showMessage(scales[scaleMode].name);
+ }
break;
case 187: // =
- scaleMode = mod(scaleMode + 1, scales.length);
- rebuild();
+ e.preventDefault();
+ if (e.altKey || e.metaKey) {
+ root = clamp(root + (e.shiftKey ? 10 : 1), 1, 200000);
+ organ.setRoot(root);
+ showMessage(`Root: ${root} hz`);
+ } else {
+ scaleMode = mod(scaleMode + 1, scales.length);
+ rebuild();
+ showMessage(scales[scaleMode].name);
+ }
break;
}
}
-window.addEventListener("keydown", keydown, true);
-keys.listen(trigger_index);
+let messageTransition;
+function showMessage(message) {
+ const el = document.getElementById("message");
+ el.innerHTML = message;
+ el.style.opacity = 1;
+ if (messageTransition) {
+ messageTransition.cancel();
+ }
+ messageTransition = oktransition.add({
+ obj: el.style,
+ from: { opacity: 1 },
+ to: { opacity: 0 },
+ delay: 1500,
+ duration: 2000,
+ easing: oktransition.easing.circ_out,
+ });
+}
diff --git a/client/lib/color.js b/client/lib/color.js
index bea0330..bbc137e 100644
--- a/client/lib/color.js
+++ b/client/lib/color.js
@@ -62,7 +62,7 @@ function color(t, add, mul) {
b = palette[1][i];
c = palette[2][i];
d = palette[3][i];
- rgb[i] = Math.round(channel(t, a, b, c, d, add, mul) * 255);
+ rgb[i] = Math.round(channel(-t, a, b, c, d, add, mul) * 255);
}
return "rgb(" + rgb + ")";
}
diff --git a/client/lib/kalimba.js b/client/lib/kalimba.js
index 64ace00..53fcb99 100644
--- a/client/lib/kalimba.js
+++ b/client/lib/kalimba.js
@@ -18,7 +18,7 @@ const samples = [
// { root: 671, fn: 'samples/380732__cabled-mess__sansula-09-e-raw.wav', },
];
-function load(output) {
+function load({ output }) {
samples.forEach((sample) => {
sample.players = [];
sample.index = -1;
@@ -45,15 +45,12 @@ function load(output) {
);
}
-let last = 440;
-
-function play(freq) {
- last = freq;
+function play(interval, root) {
const sample = choice(samples);
sample.index = (sample.index + 1) % sample.players.length;
const player = sample.players[sample.index];
- player.playbackRate = freq / sample.root;
+ player.playbackRate = (interval * root) / sample.root;
player.start();
}
@@ -63,24 +60,24 @@ function pause() {
export default { load, play, pause };
-// for help tuning
-function keydown(e) {
- // console.log(e.keyCode)
- if (e.metaKey && last) {
- let step = e.shiftKey ? (e.ctrlKey ? 0.01 : 0.1) : 1;
- switch (e.keyCode) {
- case 38: // up
- e.preventDefault();
- samples[0].root -= step;
- play(last);
- break;
- case 40: // down
- e.preventDefault();
- samples[0].root += step;
- play(last);
- break;
- }
- console.log(samples[0].root);
- }
-}
+// for help tuning the kalimba samples
+// function keydown(e) {
+// // console.log(e.keyCode)
+// if (e.metaKey && last) {
+// let step = e.shiftKey ? (e.ctrlKey ? 0.01 : 0.1) : 1;
+// switch (e.keyCode) {
+// case 38: // up
+// e.preventDefault();
+// samples[0].root -= step;
+// play(last);
+// break;
+// case 40: // down
+// e.preventDefault();
+// samples[0].root += step;
+// play(last);
+// break;
+// }
+// console.log(samples[0].root);
+// }
+// }
// window.addEventListener("keydown", keydown, true);
diff --git a/client/lib/organ.js b/client/lib/organ.js
index 652351e..e66f89d 100644
--- a/client/lib/organ.js
+++ b/client/lib/organ.js
@@ -4,30 +4,31 @@
*/
import Tone from "tone";
-import { roundFreq } from "./util";
+import { roundInterval } from "./util";
-const oscillators = {};
+let root = 440;
+let oscillators = {};
let output;
let lastPlayed;
function load(out) {
output = out;
}
-
-function isPlaying(freq) {
- const rounded = roundFreq(freq);
+function isPlaying(interval) {
+ const rounded = roundInterval(interval);
const osc = oscillators[rounded];
return osc && osc.playing;
}
-function play(freq) {
+function play(interval) {
if (!output) {
return;
}
- const rounded = roundFreq(freq);
+ const rounded = roundInterval(interval);
const osc = (oscillators[rounded] = oscillators[rounded] || {});
if (!osc.el) {
- osc.el = new Tone.Oscillator(freq, "sine");
+ osc.interval = interval;
+ osc.el = new Tone.Oscillator(interval * root, "sine");
osc.el.connect(output);
}
osc.el.start();
@@ -36,13 +37,31 @@ function play(freq) {
return osc;
}
-function pause(freq) {
- const rounded = roundFreq(freq);
+function pause(interval) {
+ const rounded = roundInterval(interval);
if (!oscillators[rounded]) return;
const osc = (oscillators[rounded] = oscillators[rounded] || {});
- if (osc.el) osc.el.stop();
+ if (osc.el) {
+ osc.el.stop();
+ }
osc.playing = false;
return osc;
}
-export default { load, isPlaying, play, pause, oscillators };
+function setRoot(newRoot) {
+ root = newRoot;
+ for (const osc of Object.values(oscillators)) {
+ osc.el.frequency.value = osc.interval * newRoot;
+ }
+}
+function stop() {
+ for (const osc of Object.values(oscillators)) {
+ osc.el.stop();
+ osc.el.disconnect();
+ osc.playing = false;
+ delete osc.el;
+ }
+ oscillators = {};
+}
+
+export default { load, isPlaying, play, pause, stop, setRoot };
diff --git a/client/lib/sampler.js b/client/lib/sampler.js
index 08f253d..69da86e 100644
--- a/client/lib/sampler.js
+++ b/client/lib/sampler.js
@@ -5,6 +5,8 @@
import Tone from "tone";
+let root = 440;
+
let output;
let ready;
let current = "";
@@ -72,14 +74,19 @@ export function loadSampleFromFile(file, url) {
/**
* Player
*/
-let last = 440;
+let last = [1, 440];
-function play(freq) {
- last = freq;
+function stop() {
+ for (const sample of Object.values(samples)) {
+ sample.players.forEach((player) => player.stop());
+ }
+}
+function play(interval, root) {
+ last = [interval, root];
const sample = samples[current];
sample.index = (sample.index + 1) % sample.players.length;
const player = sample.players[sample.index];
- player.playbackRate = freq / sample.root;
+ player.playbackRate = (interval * root) / sample.root;
player.start();
}
@@ -87,7 +94,7 @@ function pause() {
// no-op
}
-export default { load, play, pause };
+export default { load, play, pause, stop };
// for help tuning
function keydown(e) {
@@ -99,12 +106,14 @@ function keydown(e) {
case 38: // up
e.preventDefault();
sample.root -= step;
- play(last);
+ stop();
+ play(last[0], last[1]);
break;
case 40: // down
e.preventDefault();
sample.root += step;
- play(last);
+ stop();
+ play(last[0], last[1]);
break;
}
}
diff --git a/client/lib/scales.js b/client/lib/scales.js
index 87dcb0e..1e5afd6 100644
--- a/client/lib/scales.js
+++ b/client/lib/scales.js
@@ -19,11 +19,11 @@ import {
let a, b;
export const scales = [
- { name: "integer", get: (i, j) => [i + 1, j + 1] },
- { name: "subharmonic", get: (i, j) => [i + 1, i + j + 2] },
- { name: "harmonic", get: (i, j) => [i + j + 2, j + 1] },
+ { name: "natural", get: (i, j) => [i + 1, j + 1] },
+ { name: "undertone", get: (i, j) => [i + 1, i + j + 2] },
+ { name: "overtone", get: (i, j) => [i + j + 2, j + 1] },
{
- name: "prime",
+ name: "primes",
reset: (x, y, w, h) => {
a = Prime().skip(x).take(w).toJS();
b = Prime().skip(y).take(h).toJS();
diff --git a/client/lib/util.js b/client/lib/util.js
index 5bf93dc..d0a3914 100644
--- a/client/lib/util.js
+++ b/client/lib/util.js
@@ -23,10 +23,13 @@ function choice(a) {
function mod(n, m) {
return n - m * Math.floor(n / m);
}
-function roundFreq(freq) {
- return Math.round(freq * 100);
+function roundInterval(interval) {
+ return Math.round(interval * 10000000);
}
-const frequencyInRange = (freq) => 20 < freq && freq < 15000;
+const intervalInRange = (interval, root) =>
+ 20 < interval * root && interval * root < 15000;
+const lerp = (n, a, b) => (b - a) * n + a;
+const clamp = (n, a = 0, b = 1) => (n < a ? a : n < b ? n : b);
function requestAudioContext(fn) {
if (window.location.protocol !== "https:") {
@@ -75,7 +78,9 @@ export {
choice,
mod,
browser,
- roundFreq,
- frequencyInRange,
+ lerp,
+ clamp,
+ roundInterval,
+ intervalInRange,
requestAudioContext,
};
diff --git a/client/vendor/oktransition.js b/client/vendor/oktransition.js
new file mode 100644
index 0000000..816ffb0
--- /dev/null
+++ b/client/vendor/oktransition.js
@@ -0,0 +1,182 @@
+/*
+ oktransition.add({
+ obj: el.style,
+ units: "px",
+ from: { left: 0 },
+ to: { left: 100 },
+ duration: 1000,
+ easing: oktransition.easing.circ_out,
+ update: function(obj){
+ console.log(obj.left)
+ }
+ finished: function(){
+ console.log("done")
+ }
+ })
+*/
+
+const oktransition = {};
+let transitions = [];
+
+let last_t = 0;
+let id = 0;
+
+const lerp = (n, a, b) => (b - a) * n + a;
+
+oktransition.speed = 1;
+oktransition.add = (transition) => {
+ transition.id = id++;
+ transition.obj = transition.obj || {};
+ if (transition.easing) {
+ if (typeof transition.easing === "string") {
+ transition.easing = oktransition.easing[transition.easing];
+ }
+ } else {
+ transition.easing = oktransition.easing.linear;
+ }
+ if (!("from" in transition) && !("to" in transition)) {
+ transition.keys = [];
+ } else if (!("from" in transition)) {
+ transition.from = {};
+ transition.keys = Object.keys(transition.to);
+ transition.keys.forEach(function (prop) {
+ transition.from[prop] = parseFloat(transition.obj[prop]);
+ });
+ } else {
+ transition.keys = Object.keys(transition.from);
+ }
+ transition.delay = transition.delay || 0;
+ transition.start = last_t + transition.delay;
+ transition.done = false;
+ transition.after = transition.after || [];
+ transition.then = (fn) => {
+ transition.after.push(fn);
+ return transition;
+ };
+ transition.tick = 0;
+ transition.skip = transition.skip || 1;
+ transition.dt = 0;
+ transition.cancel = () =>
+ (transitions = transitions.filter((item) => item !== transition));
+ transitions.push(transition);
+ return transition;
+};
+oktransition.update = (t) => {
+ let done = false;
+ requestAnimationFrame(oktransition.update);
+ last_t = t * oktransition.speed;
+ if (transitions.length === 0) return;
+ transitions.forEach((transition, i) => {
+ const dt = Math.min(1.0, (t - transition.start) / transition.duration);
+ transition.tick++;
+ if (
+ dt < 0 ||
+ (dt < 1 && transition.tick % transition.skip != 0) ||
+ transition.done
+ )
+ return;
+ const ddt = transition.easing(dt);
+ transition.dt = ddt;
+ transition.keys.forEach((prop) => {
+ let val = lerp(ddt, transition.from[prop], transition.to[prop]);
+ if (transition.round) val = Math.round(val);
+ if (transition.units) val = Math.round(val) + transition.units;
+ transition.obj[prop] = val;
+ });
+ if (transition.update) {
+ transition.update(transition.obj, dt);
+ }
+ if (dt === 1) {
+ if (transition.finished) {
+ transition.finished(transition);
+ }
+ if (transition.after.length) {
+ const twn = transition.after.shift();
+ twn.obj = twn.obj || transition.obj;
+ twn.after = transition.after;
+ oktransition.add(twn);
+ }
+ if (transition.loop) {
+ transition.start = t + transition.delay;
+ } else {
+ done = true;
+ transition.done = true;
+ }
+ }
+ });
+ if (done) {
+ transitions = transitions.filter((transition) => !transition.done);
+ }
+};
+
+requestAnimationFrame(oktransition.update);
+
+oktransition.easing = {
+ linear: (t) => {
+ return t;
+ },
+ circ_out: (t) => {
+ return Math.sqrt(1 - (t = t - 1) * t);
+ },
+ circ_in: (t) => {
+ return -(Math.sqrt(1 - t * t) - 1);
+ },
+ circ_in_out: (t) => {
+ return (t *= 2) < 1
+ ? -0.5 * (Math.sqrt(1 - t * t) - 1)
+ : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ quad_in: (n) => {
+ return Math.pow(n, 2);
+ },
+ quad_out: (n) => {
+ return n * (n - 2) * -1;
+ },
+ quad_in_out: (n) => {
+ n = n * 2;
+ if (n < 1) {
+ return Math.pow(n, 2) / 2;
+ }
+ return (-1 * (--n * (n - 2) - 1)) / 2;
+ },
+ cubic_bezier: (mX1, mY1, mX2, mY2) => {
+ function A(aA1, aA2) {
+ return 1.0 - 3.0 * aA2 + 3.0 * aA1;
+ }
+ function B(aA1, aA2) {
+ return 3.0 * aA2 - 6.0 * aA1;
+ }
+ function C(aA1) {
+ return 3.0 * aA1;
+ }
+
+ // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
+ function CalcBezier(aT, aA1, aA2) {
+ return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
+ }
+
+ // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
+ function GetSlope(aT, aA1, aA2) {
+ return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
+ }
+
+ function GetTForX(aX) {
+ // Newton raphson iteration
+ let aGuessT = aX;
+ for (let i = 0; i < 10; ++i) {
+ const currentSlope = GetSlope(aGuessT, mX1, mX2);
+ if (currentSlope == 0.0) return aGuessT;
+ const currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
+ aGuessT -= currentX / currentSlope;
+ }
+ return aGuessT;
+ }
+
+ return function (aX) {
+ if (mX1 == mY1 && mX2 == mY2) return aX; // linear
+ return CalcBezier(aX, mY1, mY2);
+ };
+ },
+};
+
+export default oktransition;