Skip to content

Commit

Permalink
feat: introduce useBoundState
Browse files Browse the repository at this point in the history
  • Loading branch information
ascpixi committed Dec 24, 2024
1 parent 6b88cc2 commit 67b03fc
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 119 deletions.
20 changes: 20 additions & 0 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useState } from "react";

/**
* Returns a stateful value, with a value to change it, bound to a
* property `key` of the given `parent` object.
*/
export function useBoundState<TParent, K extends keyof TParent>(
parent: TParent,
key: K
): [TParent[K], (x: TParent[K]) => void] {
const [getState, setState] = useState<TParent[K]>(parent[key]);

return [
getState,
(x: TParent[K]) => {
setState(x);
parent[key] = x;
}
];
}
23 changes: 7 additions & 16 deletions src/nodes/ArpeggiatorNode.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as flow from "@xyflow/react";
import { memo, useEffect, useState } from "react";
import { RiDice3Fill, RiDropperFill } from "@remixicon/react";
import { RiDice3Fill } from "@remixicon/react";

import type { NodeTypeDescriptor } from ".";
import { makeNodeFactory } from "./basis";
import { NOTE_INPUT_HID_MAIN, NOTE_OUTPUT_HID, NoteGeneratorNodeData, ParametricNoteGenerator } from "../graph";
import { allEqualUnordered, assert, match } from "../util";
import { FlatNodeDataSerializer } from "../serializer";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -76,30 +77,20 @@ export const ARPEGGIATOR_NOTE_DESCRIPTOR = {
export const ArpeggiatorNodeRenderer = memo(function ArpeggiatorNodeRenderer(
{ id, data }: flow.NodeProps<flow.Node<ArpeggiatorNodeData>>
) {
const generator = data.generator;

const [style, setStyle] = useState(generator.style);
const [speed, setSpeed] = useState(generator.speed);
const [style, setStyle] = useBoundState(data.generator, "style");
const [speed, setSpeed] = useBoundState(data.generator, "speed");

const [notes, setNotes] = useState<number[]>([]);

useEffect(() => {
generator.style = style;
}, [generator, style]);

useEffect(() => {
generator.speed = speed;
}, [generator, speed]);

useEffect(() => {
const id = setInterval(() => {
if (!allEqualUnordered(notes, generator.lastNotes)) {
setNotes(generator.lastNotes);
if (!allEqualUnordered(notes, data.generator.lastNotes)) {
setNotes(data.generator.lastNotes);
}
}, 100);

return () => clearInterval(id);
}, [generator, notes]);
}, [data.generator, notes]);

return (
<VestigeNodeBase
Expand Down
7 changes: 2 additions & 5 deletions src/nodes/FilterNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Automatable } from "../parameters";
import { assert, invLogLerp, logLerp, match } from "../util";
import { toneFreq } from "../audioUtil";
import { FlatNodeDataSerializer, FlatSerializerSpec } from "../serializer";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -114,7 +115,7 @@ export const FilterNodeRenderer = memo(function FilterNodeRenderer(
) {
const filter = data.effect.filter;

const [type, setType] = useState<FilterType>(filter.type as FilterType);
const [type, setType] = useBoundState(filter, "type");

const [cutoff, setCutoff] = useState(hzToCutoffScalar(tone.Frequency(filter.frequency.value).toFrequency()) * 100);
const [resonance, setResonance] = useState(resonanceToScalar(filter.Q.value) * 100);
Expand All @@ -135,10 +136,6 @@ export const FilterNodeRenderer = memo(function FilterNodeRenderer(
3: -96
});

useEffect(() => {
filter.type = type;
}, [filter, type]);

useEffect(() => {
filter.rolloff = rolloffDbPerOct;
}, [filter, rolloffDbPerOct]);
Expand Down
8 changes: 2 additions & 6 deletions src/nodes/LfoNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,12 @@ export const LfoNodeRenderer = memo(function LfoNodeRenderer(
const [max, setMax] = useState(lfo.max * 100);

function onMinChange(newMin: number) {
if (newMin > max)
return;

if (newMin > max) return;
setMin(newMin);
}

function onMaxChange(newMax: number) {
if (newMax < min)
return;

if (newMax < min) return;
setMax(newMax);
}

Expand Down
43 changes: 12 additions & 31 deletions src/nodes/PentatonicChordsNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NOTE_OUTPUT_HID, NoteGeneratorNodeData, PlainNoteGenerator } from "../g
import { FlatNodeDataSerializer } from "../serializer";
import { allEqualUnordered, pickRandom, randInt, seedRng } from "../util";
import { getHarmony, MAJOR_PENTATONIC, MIDI_NOTES, MINOR_PENTATONIC, ScaleMode } from "../audioUtil";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -96,14 +97,14 @@ export const PentatonicChordsNodeRenderer = memo(function PentatonicChordsNodeRe
) {
const gen = data.generator;

const [rootNote, setRootNote] = useState<number>(gen.rootNote);
const [mode, setMode] = useState<ScaleMode>(gen.mode);
const [rootNote, setRootNote] = useBoundState(gen, "rootNote");
const [mode, setMode] = useBoundState(gen, "mode");

const [chordLength, setChordLength] = useState(gen.chordLength);
const [minNotes, setMinNotes] = useState(gen.minNotes);
const [maxNotes, setMaxNotes] = useState(gen.maxNotes);
const [octave, setOctave] = useState(gen.octave);
const [pitchRange, setPitchRange] = useState(gen.pitchRange);
const [chordLength, setChordLength] = useBoundState(gen, "chordLength");
const [minNotes, setMinNotes] = useBoundState(gen, "minNotes");
const [maxNotes, setMaxNotes] = useBoundState(gen, "maxNotes");
const [octave, setOctave] = useBoundState(gen, "octave");
const [pitchRange, setPitchRange] = useBoundState(gen, "pitchRange");

const [notes, setNotes] = useState<number[]>([]);

Expand All @@ -122,39 +123,19 @@ export const PentatonicChordsNodeRenderer = memo(function PentatonicChordsNodeRe
return () => clearInterval(id);
}, [gen, notes]);

useEffect(() => {
gen.chordLength = chordLength;
gen.minNotes = minNotes;
gen.maxNotes = maxNotes;
gen.rootNote = rootNote;
gen.mode = mode;
gen.octave = octave;
gen.pitchRange = pitchRange;
}, [gen, rootNote, mode, chordLength, minNotes, maxNotes, octave, pitchRange]);

function onMaxNotesChange(x: number) {
if (x < minNotes)
return;

if (x < minNotes) return;
setMaxNotes(x);
}

function onMinNotesChange(x: number) {
if (x > maxNotes)
return;

if (x > maxNotes) return;
setMinNotes(x);
}

function onPitchRangeChange(x: number) {
if (maxNotes > x) {
setMaxNotes(x);
}

if (minNotes > x) {
setMinNotes(x);
}

if (maxNotes > x) { setMaxNotes(x); }
if (minNotes > x) { setMinNotes(x); }
setPitchRange(x);
}

Expand Down
32 changes: 12 additions & 20 deletions src/nodes/PentatonicMelodyNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NOTE_OUTPUT_HID, NoteGeneratorNodeData, PlainNoteGenerator } from "../g
import { allEqualUnordered, hashify } from "../util";
import { getHarmony, MAJOR_PENTATONIC, MIDI_NOTES, MINOR_PENTATONIC, ScaleMode } from "../audioUtil";
import { FlatNodeDataSerializer } from "../serializer";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -105,13 +106,15 @@ export const PENTATONIC_MELODY_NODE_DESCRIPTOR = {
export const PentatonicMelodyNodeRenderer = memo(function PentatonicMelodyNodeRenderer(
{ id, data }: flow.NodeProps<flow.Node<PentatonicMelodyNodeData>>
) {
const [rootNote, setRootNote] = useState<number>(data.generator.rootNote);
const [mode, setMode] = useState<ScaleMode>(data.generator.mode);
const gen = data.generator;

const [rootNote, setRootNote] = useBoundState(gen, "rootNote");
const [mode, setMode] = useBoundState(gen, "mode");

const [density, setDensity] = useState(data.generator.density);
const [octave, setOctave] = useState(data.generator.octave);
const [pitchRange, setPitchRange] = useState(data.generator.pitchRange);
const [polyphony, setPolyphony] = useState(data.generator.polyphony);
const [density, setDensity] = useBoundState(gen, "density");
const [octave, setOctave] = useBoundState(gen, "octave");
const [pitchRange, setPitchRange] = useBoundState(gen, "pitchRange");
const [polyphony, setPolyphony] = useBoundState(gen, "polyphony");

const [notes, setNotes] = useState<number[]>([]);

Expand All @@ -120,26 +123,15 @@ export const PentatonicMelodyNodeRenderer = memo(function PentatonicMelodyNodeRe
MINOR_PENTATONIC[pitchRange % 5]
) + (12 * Math.floor(pitchRange / 5));

useEffect(() => {
const gen = data.generator;

gen.rootNote = rootNote;
gen.mode = mode;
gen.density = density;
gen.octave = octave;
gen.pitchRange = pitchRange;
gen.polyphony = polyphony;
}, [data.generator, rootNote, mode, density, octave, pitchRange, polyphony]);

useEffect(() => {
const id = setInterval(() => {
if (!allEqualUnordered(notes, data.generator.lastNotes)) {
setNotes(data.generator.lastNotes);
if (!allEqualUnordered(notes, gen.lastNotes)) {
setNotes(gen.lastNotes);
}
}, 100);

return () => clearInterval(id);
}, [data, notes]);
}, [gen, notes]);

return (
<VestigeNodeBase
Expand Down
9 changes: 3 additions & 6 deletions src/nodes/PickNoteNode.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as flow from "@xyflow/react";
import { memo, useEffect, useState } from "react";
import { memo } from "react";
import { RiDropperFill } from "@remixicon/react";

import type { NodeTypeDescriptor } from ".";
import { makeNodeFactory } from "./basis";
import { NOTE_INPUT_HID_MAIN, NOTE_OUTPUT_HID, NoteGeneratorNodeData, ParametricNoteGenerator } from "../graph";
import { assert } from "../util";
import { FlatNodeDataSerializer } from "../serializer";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -63,11 +64,7 @@ export const PICK_NOTE_DESCRIPTOR = {
export const PickNoteNodeRenderer = memo(function PickNoteNodeRenderer(
{ id, data }: flow.NodeProps<flow.Node<PickNoteNodeData>>
) {
const [mode, setMode] = useState<Mode>(data.generator.mode);

useEffect(() => {
data.generator.mode = mode;
}, [data.generator, mode])
const [mode, setMode] = useBoundState(data.generator, "mode");

return (
<VestigeNodeBase
Expand Down
16 changes: 4 additions & 12 deletions src/nodes/SamplerNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { makeNodeFactory } from "./basis";
import { AudioGenerator, NoteEvent, InstrumentNodeData, NOTE_INPUT_HID_MAIN, SIGNAL_OUTPUT_HID, AudioDestination } from "../graph";
import { Automatable } from "../parameters";
import { FlatNodeDataSerializer } from "../serializer";
import { useBoundState } from "../hooks";

import { NodePort } from "../components/NodePort";
import { PlainField } from "../components/PlainField";
Expand Down Expand Up @@ -161,21 +162,12 @@ export const SamplerNodeRenderer = memo(function SamplerNodeRenderer(
) {
const gen = data.generator;

const [sampleSet, setSampleSet] = useState<KnownSampleSet>(gen.set);
const [sampleSet, setSampleSet] = useBoundState(gen, "set");
const [attack, setAttack] = useState<number>(tone.Time(gen.sampler.attack).toSeconds());
const [release, setRelease] = useState<number>(tone.Time(gen.sampler.release).toSeconds());

useEffect(() => {
gen.set = sampleSet;
}, [gen, sampleSet]);

useEffect(() => {
gen.sampler.attack = attack;
}, [gen, attack]);

useEffect(() => {
gen.sampler.release = release;
}, [gen, release]);
useEffect(() => { gen.sampler.attack = attack }, [gen, attack]);
useEffect(() => { gen.sampler.release = release }, [gen, release]);

return (
<VestigeNodeBase
Expand Down
Loading

0 comments on commit 67b03fc

Please sign in to comment.