diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..3439223 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/rete/default.tsx b/src/rete/default.tsx index b98cf3b..4bba7e5 100644 --- a/src/rete/default.tsx +++ b/src/rete/default.tsx @@ -37,6 +37,7 @@ import { EditorConstantNode } from './nodes/EditorConstantNode'; import { TimeDomainVisualizerNode, FrequencyDomainVisualizerNode } from './nodes/VisualizerNodes'; import { EditorBiquadNode } from './nodes/EditorBiquadNode'; import { ClipNode } from './nodes/ClipNode'; +import { NoteFrequencyNode } from './nodes/NoteFrequencyNode'; import { ModifierNodeStyle, OutputNodeStyle, SourceNodeStyle } from './styles/nodestyles'; import { CustomConnection } from './styles/connectionstyles'; @@ -49,19 +50,22 @@ import { CustomSocket } from './styles/socketstyles'; import brookExample from './examples/brook.json'; import amfmExample from './examples/amfm.json' import jetEngineExample from './examples/jetengine.json' +import chordExample from './examples/chord.json' const examples: { [key in string]: any } = { "Babbling Brook (HW3)": brookExample, "AM+FM Synthesis": amfmExample, "Jet Engine": jetEngineExample, + "Chord": chordExample } type SourceNode = | EditorConstantNode | EditorOscillatorNode | EditorNoiseNode + | NoteFrequencyNode -const sourceNodeTypes = [EditorConstantNode, EditorOscillatorNode, EditorNoiseNode] +const sourceNodeTypes = [EditorConstantNode, EditorOscillatorNode, EditorNoiseNode, NoteFrequencyNode] type ModifierNode = | EditorGainNode @@ -172,6 +176,7 @@ export async function createEditor(container: HTMLElement) { const contextMenu = new ContextMenuPlugin({ items: ContextMenuPresets.classic.setup([ ["Constant", () => new EditorConstantNode(process)], + ["Note Frequency", () => new NoteFrequencyNode(process)], ["Oscillator", () => new EditorOscillatorNode(process)], ["Gain", () => new EditorGainNode(process)], ["Biquad Filter", () => new EditorBiquadNode(process)], diff --git a/src/rete/examples/chord.json b/src/rete/examples/chord.json new file mode 100644 index 0000000..26e25b2 --- /dev/null +++ b/src/rete/examples/chord.json @@ -0,0 +1,138 @@ +{ + "nodes": [ + { + "id": "6c235b85e9207e2f", + "name": "Oscillator", + "data": { + "baseFreq": 440, + "waveform": "sine" + } + }, + { + "id": "2b6c4d44d4f69ef6", + "name": "Gain", + "data": { + "gain": 0.2 + } + }, + { + "id": "a742397e3e6cf5ad", + "name": "Universal Output", + "data": { + "gain": 1 + } + }, + { + "id": "9a3ce700284ae274", + "name": "Note Frequency", + "data": { + "octave": 3, + "note": "7" + } + }, + { + "id": "9158c035be4da8c9", + "name": "Note Frequency", + "data": { + "octave": 3, + "note": "3" + } + }, + { + "id": "825965ae7cf0e811", + "name": "Oscillator", + "data": { + "baseFreq": 440, + "waveform": "sine" + } + }, + { + "id": "051ec48cb7a4c245", + "name": "Note Frequency", + "data": { + "octave": 4, + "note": "2" + } + }, + { + "id": "1b521693124a65ac", + "name": "Oscillator", + "data": { + "baseFreq": 440, + "waveform": "sine" + } + }, + { + "id": "168f3351a007a05d", + "name": "Note Frequency", + "data": { + "octave": 3, + "note": "10" + } + }, + { + "id": "72da2ef00bdfcc04", + "name": "Oscillator", + "data": { + "baseFreq": 440, + "waveform": "sine" + } + } + ], + "connections": [ + { + "source": "2b6c4d44d4f69ef6", + "sourceOutput": "signal", + "target": "a742397e3e6cf5ad", + "targetInput": "signal" + }, + { + "source": "9a3ce700284ae274", + "sourceOutput": "value", + "target": "6c235b85e9207e2f", + "targetInput": "baseFrequency" + }, + { + "source": "9158c035be4da8c9", + "sourceOutput": "value", + "target": "825965ae7cf0e811", + "targetInput": "baseFrequency" + }, + { + "source": "825965ae7cf0e811", + "sourceOutput": "signal", + "target": "2b6c4d44d4f69ef6", + "targetInput": "signal" + }, + { + "source": "1b521693124a65ac", + "sourceOutput": "signal", + "target": "2b6c4d44d4f69ef6", + "targetInput": "signal" + }, + { + "source": "168f3351a007a05d", + "sourceOutput": "value", + "target": "72da2ef00bdfcc04", + "targetInput": "baseFrequency" + }, + { + "source": "72da2ef00bdfcc04", + "sourceOutput": "signal", + "target": "2b6c4d44d4f69ef6", + "targetInput": "signal" + }, + { + "source": "051ec48cb7a4c245", + "sourceOutput": "value", + "target": "1b521693124a65ac", + "targetInput": "baseFrequency" + }, + { + "source": "6c235b85e9207e2f", + "sourceOutput": "signal", + "target": "2b6c4d44d4f69ef6", + "targetInput": "signal" + } + ] +} \ No newline at end of file diff --git a/src/rete/examples/jetengine.json b/src/rete/examples/jetengine.json index 1bcb4bd..8511ee1 100644 --- a/src/rete/examples/jetengine.json +++ b/src/rete/examples/jetengine.json @@ -240,15 +240,29 @@ "data": { "gain": 1 } + }, + { + "id": "020ca3973b60564f", + "name": "Time Domain Visualizer", + "data": {} + }, + { + "id": "44b622038179e9f7", + "name": "Time Domain Visualizer", + "data": {} + }, + { + "id": "4bcbaf33a00c655d", + "name": "Frequency Domain Visualizer", + "data": {} + }, + { + "id": "ad0e6e24c7cdcbf7", + "name": "Frequency Domain Visualizer", + "data": {} } ], "connections": [ - { - "source": "13b244e074c36161", - "sourceOutput": "signal", - "target": "df1ea31217cd0d3f", - "targetInput": "signal" - }, { "source": "4e7567fdaecdc876", "sourceOutput": "signal", @@ -476,6 +490,36 @@ "sourceOutput": "signal", "target": "d663f381ecf84317", "targetInput": "signal" + }, + { + "source": "a549b26138870c74", + "sourceOutput": "signal", + "target": "020ca3973b60564f", + "targetInput": "signal" + }, + { + "source": "aeaa0eaf654203e8", + "sourceOutput": "signal", + "target": "44b622038179e9f7", + "targetInput": "signal" + }, + { + "source": "a549b26138870c74", + "sourceOutput": "signal", + "target": "4bcbaf33a00c655d", + "targetInput": "signal" + }, + { + "source": "aeaa0eaf654203e8", + "sourceOutput": "signal", + "target": "ad0e6e24c7cdcbf7", + "targetInput": "signal" + }, + { + "source": "13b244e074c36161", + "sourceOutput": "signal", + "target": "df1ea31217cd0d3f", + "targetInput": "signal" } ] } \ No newline at end of file diff --git a/src/rete/imports.ts b/src/rete/imports.ts index 814429b..aa251cc 100644 --- a/src/rete/imports.ts +++ b/src/rete/imports.ts @@ -7,6 +7,7 @@ import { EditorConstantNode } from './nodes/EditorConstantNode'; import { TimeDomainVisualizerNode, FrequencyDomainVisualizerNode } from './nodes/VisualizerNodes'; import { EditorBiquadNode } from './nodes/EditorBiquadNode'; import { ClipNode } from './nodes/ClipNode'; +import { NoteFrequencyNode } from "./nodes/NoteFrequencyNode"; export async function createNode( { editor, area, dataflow, process }: Context, @@ -30,6 +31,8 @@ export async function createNode( return new EditorNoiseNode(process, data); case "Oscillator": return new EditorOscillatorNode(process, data); + case "Note Frequency": + return new NoteFrequencyNode(process, data); case "Time Domain Visualizer": return new TimeDomainVisualizerNode(); case "Frequency Domain Visualizer": diff --git a/src/rete/nodes/NoteFrequencyNode.tsx b/src/rete/nodes/NoteFrequencyNode.tsx new file mode 100644 index 0000000..0d39e92 --- /dev/null +++ b/src/rete/nodes/NoteFrequencyNode.tsx @@ -0,0 +1,57 @@ +import { ClassicPreset as Classic } from "rete" +import { socket, audioCtx, audioSources, audioSourceStates } from "../default" +import { DropdownControl } from "../controls/DropdownControl"; + +export class NoteFrequencyNode extends Classic.Node<{}, { value: Classic.Socket }, { note: DropdownControl, octave: Classic.InputControl<"number", number> }> { + width = 180 + height = 160 + constructor(change: () => void, initial?: { octave: number, note: string }) { + super('Note Frequency'); + + this.addOutput('value', new Classic.Output(socket, 'Frequency')); + + const dropdownOptions = [ + { value: "0", label: "A" }, + { value: "1", label: "A#/B♭" }, + { value: "2", label: "B" }, + { value: "3", label: "C" }, + { value: "4", label: "C#/D♭" }, + { value: "5", label: "D" }, + { value: "6", label: "D#/E♭" }, + { value: "7", label: "E" }, + { value: "8", label: "F" }, + { value: "9", label: "F#/G♭" }, + { value: "10", label: "G" }, + { value: "11", label: "G#/A♭" }, + ] + this.addControl("note", new DropdownControl(change, dropdownOptions, initial ? initial.note : "0")) + + this.addControl( + 'octave', + new Classic.InputControl('number', { initial: initial ? initial.octave : 4, change }) + ); + } + + data(): { value: AudioNode } { + const constantNode = audioCtx.createConstantSource(); + const noteVal = Number(this.controls.note.value) + const octave = this.controls.octave.value || 0 + const val = 440 * Math.pow(2.0, (octave - 4) + (1.0 / 12) * noteVal); + + constantNode.offset.setValueAtTime(val, audioCtx.currentTime); + + audioSources.push(constantNode); + audioSourceStates.push(false); + + return { + value: constantNode + } + } + + serialize() { + return { + octave: this.controls.octave.value, + note: this.controls.note.value + } + } +} \ No newline at end of file