diff --git a/package-lock.json b/package-lock.json index 8ed89b1..cb4ef39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "larsen", "version": "0.0.0", "dependencies": { + "@tweakpane/plugin-essentials": "^0.2.1", "phaser": "^3.85.2", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2660,6 +2661,14 @@ "integrity": "sha512-0P3xcmvjBr8AmqMOEDNYIbkiaPwvQPkj8VeJX+8ZYkpRgWWbNp1HLbld0MDI0WJHdom89osH3MmCDLnWEXKI2w==", "dev": true }, + "node_modules/@tweakpane/plugin-essentials": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tweakpane/plugin-essentials/-/plugin-essentials-0.2.1.tgz", + "integrity": "sha512-VbFU1/uD+CJNFQdfLXUOLjeG5HyUZH97Ox9CxmyVetg1hqjVun3C83HAGFULyhKzl8tSgii8jr304r8QpdHwzQ==", + "peerDependencies": { + "tweakpane": "^4.0.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index cb60534..6ab6da2 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "preview": "vite preview" }, "dependencies": { + "@tweakpane/plugin-essentials": "^0.2.1", "phaser": "^3.85.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/scenes/PadsScene.ts b/src/scenes/PadsScene.ts index 603aaa4..3b9ef50 100644 --- a/src/scenes/PadsScene.ts +++ b/src/scenes/PadsScene.ts @@ -4,6 +4,7 @@ import {rotateArray} from '../utils/math.ts'; import {hexToColor} from '../utils/colors.ts'; import {FontColor, FontFamily, FontSize} from '../utils/fonts.ts'; import {EVENTS} from '../events.ts'; +import {logger} from '../utils/logger.ts'; type Pad = { instrument: number, @@ -11,19 +12,27 @@ type Pad = { text?: Phaser.GameObjects.Text, } +export type Setting = Partial>; + export type PadsSceneSettings = { volume: number; noteDuration?: number; + octaveRange?: { + min: number; + max: number; + }; + onChange?: (setting: Setting) => void; } export abstract class PadsScene extends Phaser.Scene { private pads: Pad[] = []; readonly settings: PadsSceneSettings = { - volume: 50 + volume: 50, + onChange: (setting) => this.onSettingChange(setting), } - protected constructor(private cols: number, private rows: number) { + protected constructor(protected cols: number, protected rows: number) { super(); } @@ -34,6 +43,12 @@ export abstract class PadsScene extends Phaser.Scene { this.game.events.emit(EVENTS.sceneChange, this.settings); } + protected changePadNumber(cols: number, rows: number) { + this.cols = cols; + this.rows = rows; + this.scene.restart(); + } + protected createPads() { const numberOfPads = this.cols * this.rows; this.pads = new Array(numberOfPads).fill(0).map((_, index) => { @@ -130,4 +145,9 @@ export abstract class PadsScene extends Phaser.Scene { protected getPadText(_index: number): string | undefined { return undefined; } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onSettingChange(_setting: Setting) { + logger.log('Setting changed', _setting); + } } diff --git a/src/scenes/SimpleSynthScene.ts b/src/scenes/SimpleSynthScene.ts index df14665..2f407aa 100644 --- a/src/scenes/SimpleSynthScene.ts +++ b/src/scenes/SimpleSynthScene.ts @@ -1,14 +1,22 @@ -import {PadsScene, PadsSceneSettings} from './PadsScene.ts'; +import {PadsScene, PadsSceneSettings, Setting} from './PadsScene.ts'; import {allFrequencies} from '../samples/synth-frequencies.ts'; import {createAudioContext} from '../samples/sample-utils.ts'; import Phaser from 'phaser'; import {logger} from '../utils/logger.ts'; +const numberOfNotes = 12; +const maxNumberOfOctaves = 6; +const maxNumberOfPads = numberOfNotes * maxNumberOfOctaves; + export class SimpleSynthScene extends PadsScene { constructor() { - super(6, 12); + super(maxNumberOfOctaves, numberOfNotes); this.settings.noteDuration = 1.5; + this.settings.octaveRange = { + min: 1, + max: maxNumberOfOctaves, + }; } getPadText(index: number) { @@ -16,8 +24,12 @@ export class SimpleSynthScene extends PadsScene { return note?.key; } - getPadColor(numberOfPads: number, index: number): Phaser.Display.Color { - const color = super.getPadColor(numberOfPads, index); + getPadColor(_numberOfPads: number, index: number): Phaser.Display.Color { + index += this.getNoteIndexOffset(); + const color = super.getPadColor( + maxNumberOfPads, + index + ); const key = allFrequencies[index]?.key; if (key?.includes('#')) { return color.darken(70); @@ -25,7 +37,19 @@ export class SimpleSynthScene extends PadsScene { return color.darken(50); } + /** + * when changing the range of the notes, we need to offset the index to get the correct note + */ + private getNoteIndexOffset() { + return this.getLowerRangeIndex() * numberOfNotes; + } + + private getLowerRangeIndex() { + return this.settings.octaveRange!.min - 1; + } + playSound(index: number): void { + index += this.getNoteIndexOffset(); const note = allFrequencies[index].freq; logger.log('Playing note', note); // play the sound @@ -34,6 +58,13 @@ export class SimpleSynthScene extends PadsScene { ...this.settings }); } + + onSettingChange(setting: Setting) { + super.onSettingChange(setting); + if (setting.octaveRange) { + super.changePadNumber(setting.octaveRange.max + 1 - setting.octaveRange.min, 12); + } + } } const playPianoTone = ({ frequency, volume = 50, noteDuration = 1.5 }: { frequency: number } & PadsSceneSettings) => { diff --git a/src/settings/TweakPane.ts b/src/settings/TweakPane.ts index 85063b9..f5025ba 100644 --- a/src/settings/TweakPane.ts +++ b/src/settings/TweakPane.ts @@ -1,22 +1,28 @@ import Phaser from 'phaser'; import {ButtonApi, Pane} from 'tweakpane'; +import * as EssentialsPlugin from '@tweakpane/plugin-essentials'; import {LoopTracksScene} from '../scenes/LoopTracksScene.ts'; import {logger} from '../utils/logger.ts'; import {BindingApi} from '@tweakpane/core'; import {EVENTS} from '../events.ts'; import {PadsSceneSettings} from '../scenes/PadsScene.ts'; +type MinMax = { min: number; max: number; }; + export class TweakPane { private static logsParams= { logs: '', } private pane: Pane; + private settings?: PadsSceneSettings; + private sceneControls: { deleteInstrument?: ButtonApi; deleteLoop?: ButtonApi; - volume?: BindingApi, - noteDuration?: BindingApi + volume?: BindingApi; + noteDuration?: BindingApi; + octaveRange?: BindingApi; } = {}; public static log(message: string) { @@ -33,8 +39,16 @@ export class TweakPane { expanded: false, container, }); + this.pane.registerPlugin(EssentialsPlugin); + this.pane.on('change', (event) => { + const bindingKey = (event.target as any).key as string; + if (this.settings && event.last && bindingKey in this.settings) { + this.settings.onChange?.({[bindingKey]: event.value}); + } + }); game.events.on(EVENTS.sceneChange, (settings?: PadsSceneSettings) => { this.deleteSettings(); + this.settings = settings; if (settings) { this.addSettings(settings); } @@ -85,6 +99,15 @@ export class TweakPane { index }); } + if (settings.octaveRange) { + this.sceneControls.octaveRange = this.pane.addBinding(settings, 'octaveRange', { + label: 'Octave range', + min: 1, + max: 6, + step: 1, + index + }); + } this.sceneControls.volume = this.pane.addBinding(settings, 'volume', { min: 0, max: 100,