Skip to content

Commit

Permalink
Add menu to change ADSR curve ramp type
Browse files Browse the repository at this point in the history
  • Loading branch information
Ameobea committed Jan 1, 2025
1 parent 894f1f6 commit e412423
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 21 deletions.
66 changes: 66 additions & 0 deletions src/controls/adsr2/ConfigureRampControlPanel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import SvelteControlPanel, {
type ControlPanelSetting,
} from 'src/controls/SvelteControlPanel/SvelteControlPanel.svelte';
import type { RampFn } from 'src/graphEditor/nodes/CustomAudio/FMSynth';
import { get, writable, type Writable } from 'svelte/store';
export let top: number;
export let left: number;
export let onCancel: () => void;
export let onSubmit: (newRampFnType: RampFn['type']) => void;
export let initialRampFnType: RampFn['type'];
interface LocalState {
'ramp fn': RampFn['type'];
}
let state: Writable<LocalState> = writable({ 'ramp fn': initialRampFnType });
const handleSubmit = () => void onSubmit(get(state)['ramp fn']);
const settings: ControlPanelSetting[] = [
{
type: 'select',
label: 'ramp fn',
options: ['linear', 'exponential', 'bezier', 'instant'],
},
{ type: 'button', label: 'cancel', action: onCancel },
{
type: 'button',
label: 'submit',
action: () => handleSubmit(),
},
];
const handleChange = (key: string, val: any) => state.update(s => ({ ...s, [key]: val }));
</script>

<div class="adsr2-configure-ramp-control-panel" style="top:{top}px; left:{left}px;">
<SvelteControlPanel
{settings}
state={$state}
onChange={handleChange}
theme={{ background1: '#141414' }}
/>
</div>

<style lang="css">
.adsr2-configure-ramp-control-panel {
display: flex;
flex-direction: column;
transform: scale(0.7);
transform-origin: top left;
position: fixed;
border: 1px solid #888;
box-sizing: border-box;
margin-bottom: -1px;
}
/* :global(.adsr2-configure-ramp-control-panel .control-panel > .container > :first-child) {
width: 10% !important;
}
:global(.adsr2-configure-ramp-control-panel .control-panel > .container > :nth-child(3)) {
width: 37% !important;
} */
</style>
137 changes: 116 additions & 21 deletions src/controls/adsr2/adsr2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
unregisterVcHideCb,
} from 'src/ViewContextManager/VcHideStatusRegistry';
import ConfigureStepControlPanel from './ConfigureStepControlPanel.svelte';
import ConfigureRampControlPanel from './ConfigureRampControlPanel.svelte';
import type { FederatedPointerEvent } from '@pixi/events';

const dpr = window.devicePixelRatio ?? 1;
Expand Down Expand Up @@ -83,7 +84,8 @@ const computeCubicBezierControlPoints = (
handlePos: Point,
endPoint: Point
): { clampedHandlePos: Point; controlPoints: [Point, Point] } => {
// TODO: Add link to note deriving this
// This was derived using the method detailed here:
// https://cprimozic.net/notes/posts/creating-constrained-bezier-curves-for-an-envelope-generator/
const controlPoint = {
x: (4 / 3) * handlePos.x - (1 / 6) * startPoint.x - (1 / 6) * endPoint.x,
y: (4 / 3) * handlePos.y - (1 / 6) * startPoint.y - (1 / 6) * endPoint.y,
Expand Down Expand Up @@ -120,6 +122,8 @@ class RampHandle {
private parentRamp: RampCurve;
private graphics!: PIXI.Graphics;
private renderedRegion: RenderedRegion;
private lastClickTime = 0;
configurator: { inst: ConfigureRampControlPanel } | null = null;

private computeInitialPos(): PIXI.Point {
const rampStartPx = computeTransformedXPosition(
Expand Down Expand Up @@ -161,11 +165,21 @@ class RampHandle {
(1 - y) * this.inst.height
);
}
default: {
throw new UnreachableError(
'Ramp type does not support modifying curve: ' + this.endStep.ramper.type
case 'linear': {
return new PIXI.Point(
rampStartPx + 0.5 * rampWidthPx,
this.inst.height - (this.startStep.y * this.inst.height + 0.5 * rampHeightPx)
);
}
case 'instant': {
return new PIXI.Point(
rampStartPx + rampWidthPx,
this.inst.height - this.startStep.y * this.inst.height
);
}
default: {
throw new Error(`Unimplemented ramp type: ${(this.endStep.ramper as any).type}`);
}
}
}

Expand Down Expand Up @@ -216,10 +230,27 @@ class RampHandle {
);
break;
}
default: {
throw new UnreachableError(
'Ramp type does not support modifying curve: ' + this.endStep.ramper.type
case 'linear': {
const a = (this.endStep.y - this.startStep.y) / (this.endStep.x - this.startStep.x);
const b = this.startStep.y - a * this.startStep.x;

const x = computeReverseTransformedXPosition(this.renderedRegion, this.inst.width, pos.x);
const yOnLine = a * x + b;
const yPx = (1 - yOnLine) * this.inst.height;
this.graphics.position.set(pos.x, yPx);
break;
}
case 'instant': {
const transformedX = computeTransformedXPosition(
this.renderedRegion,
this.inst.width,
this.endStep.x
);
this.graphics.position.set(transformedX, (1 - this.startStep.y) * this.inst.height);
break;
}
default: {
throw new UnreachableError(`Unhandled ramp type: ${(this.endStep.ramper as any).type}`);
}
}
}
Expand Down Expand Up @@ -257,6 +288,68 @@ class RampHandle {
this.inst.onUpdated();
}

private openConfigurator(x: number, y: number) {
const parent = this.inst.app
? (this.inst.app.renderer.view as HTMLCanvasElement).parentElement
: null;
if (!parent) {
console.error('Could not find parent element of renderer');
return;
}

this.configurator = {
inst: new ConfigureRampControlPanel({
props: {
top: y - 50,
left: x,
onCancel: () => this.closeConfigurator(),
initialRampFnType: this.endStep.ramper.type,
onSubmit: newRampFnType => {
switch (newRampFnType) {
case 'bezier': {
this.endStep.ramper.type = 'bezier';
(this.endStep.ramper as any).controlPoints = [
{ x: 0, y: 0 },
{ x: 0, y: 0 },
];
break;
}
case 'exponential': {
this.endStep.ramper.type = 'exponential';
(this.endStep.ramper as any).exponent = 1;
break;
}
default: {
this.endStep.ramper.type = newRampFnType;
}
}

this.handleDrag(this.graphics.position);
this.computeNewEndPoint(this.graphics.position);
this.parentRamp.reRenderRampCurve(this.startStep, this.endStep);
this.inst.onUpdated();

this.closeConfigurator();
},
},
target: parent,
}),
};
}

private closeConfigurator() {
this.configurator?.inst.$destroy();
this.configurator = null;
}

private toggleConfigurator(x: number, y: number) {
if (this.configurator) {
this.closeConfigurator();
} else {
this.openConfigurator(x, y);
}
}

private render() {
const g = new PIXI.Graphics();
g.lineStyle(0);
Expand All @@ -278,6 +371,14 @@ class RampHandle {
};

g.on('pointerdown', (evt: FederatedPointerEvent) => {
const clickTime = Date.now();
const isDoubleClick = clickTime - this.lastClickTime < DOUBLE_CLICK_TIME_RANGE_MS;
this.lastClickTime = clickTime;

if (evt.nativeEvent.button !== 0 || isDoubleClick) {
this.toggleConfigurator(evt.x, evt.y);
}

this.dragData = evt;
document.addEventListener('pointermove', handlePointerMove);
})
Expand Down Expand Up @@ -341,19 +442,7 @@ class RampCurve {
this.inst = inst;
this.renderedRegion = renderedRegion;
this.curve = this.renderRampCurve(startStep, endStep);
this.handle = this.buildRampHandle(startStep, endStep);
}

private buildRampHandle(startStep: AdsrStep, endStep: AdsrStep) {
switch (endStep.ramper.type) {
case 'exponential':
case 'bezier': {
return new RampHandle(this.inst, this, startStep, endStep, this.renderedRegion);
}
default: {
return null;
}
}
this.handle = new RampHandle(this.inst, this, startStep, endStep, this.renderedRegion);
}

private computeRampCurve(step1: AdsrStep, step2: AdsrStep): Point[] {
Expand Down Expand Up @@ -455,7 +544,13 @@ class RampCurve {
}

if (!this.handle) {
this.handle = this.buildRampHandle(this.steps[0], this.steps[1]);
this.handle = new RampHandle(
this.inst,
this,
this.steps[0],
this.steps[1],
this.renderedRegion
);
}

const oldRenderedRegionRange = this.renderedRegion.end - this.renderedRegion.start;
Expand Down

0 comments on commit e412423

Please sign in to comment.