Skip to content

Commit

Permalink
use notes in scale
Browse files Browse the repository at this point in the history
  • Loading branch information
patsimm committed Oct 15, 2024
1 parent a58a0c2 commit 470334e
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 23 deletions.
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@react-hook/resize-observer": "^2.0.2",
"@tonaljs/note": "^4.11.0",
"@tonaljs/scale": "^4.13.0",
"classnames": "^2.5.1",
"immer": "^10.1.1",
"react": "^18.3.1",
Expand Down
5 changes: 2 additions & 3 deletions src/synth/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ function startPlayer(context: AudioContext) {
const nodeStates = getSoundNodeStates();
for (const [id, nodeState] of Object.entries(nodeStates)) {
if (!Object.keys(soundNodes).includes(id)) {
soundNodes[id] = new SoundNode(id, context);
soundNodes[id] = new SoundNode(id, nodeState, context);
subscribeToNodeState(
id,
debounce((nodeState) => soundNodes[id].updateState(nodeState)),
);
soundNodes[id].updateState(nodeState);
}
soundNodes[id].nextState(nodeState);
soundNodes[id].loop();
}
});

Expand Down
37 changes: 25 additions & 12 deletions src/synth/SoundNode.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { SoundNodeState } from "./SoundNodeState.ts";
import { beatEnd, beatStart } from "./bpm.ts";
import * as Note from "@tonaljs/note";

export class SoundNode {
id: string;
private _state: SoundNodeState;
context: AudioContext;
oscillator: OscillatorNode;
gain: GainNode;

constructor(id: string, context: AudioContext) {
constructor(id: string, state: SoundNodeState, context: AudioContext) {
this.id = id;
this.context = context;
this.oscillator = context.createOscillator();
Expand All @@ -18,14 +20,24 @@ export class SoundNode {
this.oscillator.connect(this.gain);

this.gain.connect(context.destination);

this._state = state;
this.schedule(context.currentTime);
}

schedule(time: number, state: SoundNodeState) {
const startTime = time + state.time;
const endTime = startTime + state.length;
this.oscillator.frequency.setValueAtTime(state.freq, startTime);
this.gain.gain.setValueAtTime(0.4, startTime);
this.gain.gain.setValueAtTime(0, endTime);
schedule(time: number) {
const startTime = time + this._state.time;
const endTime = startTime + this._state.length;
const freq = Note.freq(this._state.note);
if (freq === null) {
console.error(`Cannot determine frequency for note ${this._state.note}`);
return;
}
this.oscillator.frequency.linearRampToValueAtTime(freq, startTime);
this.gain.gain.setValueAtTime(0, startTime - 0.001);
this.gain.gain.linearRampToValueAtTime(0.4, startTime + 0.001);
this.gain.gain.setValueAtTime(0.4, endTime - 0.001);
this.gain.gain.linearRampToValueAtTime(0, endTime);
}

cancelScheduled(time: number) {
Expand All @@ -34,16 +46,17 @@ export class SoundNode {
}

updateState(state: SoundNodeState) {
this._state = state;
const currentBeatStart = beatStart(this.context.currentTime);
const nextBeatStart = beatStart(this.context.currentTime);
const nextBeatStart = beatEnd(this.context.currentTime);
this.cancelScheduled(currentBeatStart);
this.schedule(currentBeatStart, state);
this.schedule(nextBeatStart, state);
this.schedule(currentBeatStart);
this.schedule(nextBeatStart);
}

nextState(state: SoundNodeState) {
loop() {
const nextBeatStart = beatEnd(this.context.currentTime);
this.cancelScheduled(nextBeatStart);
this.schedule(nextBeatStart, state);
this.schedule(nextBeatStart);
}
}
6 changes: 3 additions & 3 deletions src/synth/SoundNodeState.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Node, useAppStore } from "../App.store.ts";
import { posToTime } from "./bpm.ts";
import { mapObjectValues } from "../helpers.ts";
import { posToFreq } from "./note.ts";
import { posToNote } from "./note.ts";

export type SoundNodeState = {
time: number;
length: number;
freq: number;
note: string;
};

function computeSoundNodeState(node: Node): SoundNodeState {
return {
time: posToTime(node.x),
freq: posToFreq(node.y + node.height / 2),
note: posToNote(node.y + node.height / 2),
length: posToTime(node.width),
};
}
Expand Down
13 changes: 8 additions & 5 deletions src/synth/note.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as Note from "@tonaljs/note";
import * as Scale from "@tonaljs/scale";
import { useAppStore } from "../App.store.ts";

const availableNotes = 24;
const base = 60;
const base = 0;

export function posToFreq(pos: number) {
const c4major = Scale.steps("C4 mixolydian");

export function posToNote(pos: number) {
const { size } = useAppStore.getState();
const height = size[1];
const noteMidi =
Expand All @@ -15,9 +17,10 @@ export function posToFreq(pos: number) {
12 +
Math.ceil(((height - pos) / height) * (availableNotes + 1));

const freq = Note.freq(Note.fromMidi(noteMidi));
console.log(noteMidi);
const freq = c4major(noteMidi);
if (freq === null) {
throw new Error("could not determine note frequency!");
throw new Error(`could not determine note frequency! ${noteMidi}`);
}
return freq;
}

0 comments on commit 470334e

Please sign in to comment.