diff --git a/src/scenes/EmptyScene.ts b/src/scenes/EmptyScene.ts index cd51a6e..03b64d1 100644 --- a/src/scenes/EmptyScene.ts +++ b/src/scenes/EmptyScene.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import {LoopTracksScene} from './LoopTracksScene.ts'; -import {Colors, HexaColor, hexToColor} from '../utils/colors.ts'; +import {Colors, colorToHex, PhaserColor, PhaserColors} from '../utils/colors.ts'; import {FontFamily, FontSize} from '../utils/fonts.ts'; import {DrumsScene} from './DrumsScene.ts'; import {GibberishScene} from './GiberishScene.ts'; @@ -9,13 +9,6 @@ import {SimpleSynthScene} from './SimpleSynthScene.ts'; import {EVENTS} from '../events.ts'; import {DaftSynthScene} from './DaftSynthScene.ts'; -const colors = { - bg: Colors.black, - active: '#2b2d30', - text: Colors.white, - border: Colors.black, -} satisfies Record - export class EmptyScene extends Phaser.Scene { static key = 'EmptyScene'; private readonly rowNumber = 5; @@ -23,24 +16,28 @@ export class EmptyScene extends Phaser.Scene { private instrumentButtons!: Phaser.GameObjects.Rectangle[][]; private trackIndex!: number; + public static sceneText = '+'; + public static sceneTextColor: PhaserColor = PhaserColors.white; + constructor() { super(EmptyScene.key); } - activateButton({row, col, text, scene}: { + activateButton( row: number, col: number, text: string, - scene: typeof Phaser.Scene | typeof SimpleSynthScene - }) { + sceneClass: typeof Phaser.Scene | typeof SimpleSynthScene, + color: PhaserColor + ) { const button = this.instrumentButtons[col][row]; const trackSceneKey = LoopTracksScene.getTrackSceneKey(this.trackIndex); - button.setData('text', this.addText(button, text)) - .setFillStyle(hexToColor(colors.active).color, 0.5) + button.setData('text', this.addText(button, text, color)) + .setStrokeStyle(1, PhaserColors.grey.color) .setInteractive() .on(Phaser.Input.Events.POINTER_DOWN, () => { this.scene.setVisible(false); - this.scene.add(trackSceneKey, scene, true); + this.scene.add(trackSceneKey, sceneClass, true, {color, text}); }) } @@ -51,16 +48,16 @@ export class EmptyScene extends Phaser.Scene { this.cameras.main .setOrigin(0, 0) - .setBackgroundColor(colors.bg); + .setBackgroundColor(Colors.black); this.createButtonsTable(); - this.activateButton({row: 0, col: 0, text: 'Synth', scene: SimpleSynthScene}); - this.activateButton({row: 1, col: 0, text: 'Daft synth', scene: DaftSynthScene}); + this.activateButton(0, 0, 'Synth', SimpleSynthScene, PhaserColors.blue); + this.activateButton(1, 0, 'Daft synth', DaftSynthScene, PhaserColors.purple); - this.activateButton({row: 0, col: 1, text: 'Drums', scene: DrumsScene}); + this.activateButton(0, 1, 'Drums', DrumsScene, PhaserColors.red); - this.activateButton({row: 0, col: 2, text: 'Gibberish', scene: GibberishScene}); + this.activateButton(0, 2, 'Gibberish', GibberishScene, PhaserColors.green); window.addEventListener('resize', () => this.resizeScene()); this.resizeScene(); @@ -74,17 +71,17 @@ export class EmptyScene extends Phaser.Scene { this.instrumentButtons[i].push( this.add.rectangle() .setOrigin(0, 0) - .setStrokeStyle(2, hexToColor(colors.border).color, 0.1) .setInteractive() ); } } } - private addText(button: Phaser.GameObjects.Rectangle, text: string) { + private addText(button: Phaser.GameObjects.Rectangle, text: string, color: PhaserColor) { return this.add.text(button.x + button.width / 2, button.y + button.height / 2, text, { fontSize: FontSize.tiny, - color: colors.text, + color: colorToHex(color.darken(20)), + resolution: 2, fontFamily: FontFamily.Text, }).setOrigin(0.5); } diff --git a/src/scenes/GiberishScene.ts b/src/scenes/GiberishScene.ts index 726511f..148fbb4 100644 --- a/src/scenes/GiberishScene.ts +++ b/src/scenes/GiberishScene.ts @@ -1,14 +1,7 @@ +import {logger} from '../utils/logger.ts'; +import {PadsScene} from './PadsScene.ts'; import Phaser from 'phaser'; import {PhaserColors} from '../utils/colors.ts'; -import {LoopTracksScene} from './LoopTracksScene.ts'; -import {rotateArray} from '../utils/math.ts'; -import {logger} from '../utils/logger.ts'; -import {EVENTS} from '../events.ts'; - -type Pad = { - instrument: number, - button: Phaser.GameObjects.Rectangle, -} declare const Freeverb: any, Bus2: any, Gibberish: any, Synth: any, Add: any, Sine: any, Sequencer: any; @@ -59,82 +52,36 @@ const testNote = () => { noteSeq.start(); } -export class GibberishScene extends Phaser.Scene { +export class GibberishScene extends PadsScene { + canRecord = false; + canPlay = false; isGibberishLoaded = false; - constructor() { - super(); + playSound(_index: number): void { + if (!this.isGibberishLoaded) { + this.isGibberishLoaded = true; + Gibberish.workletPath = './worklet.js'; + logger.log('loading Gibberish 2...'); + Gibberish.init().then(() => { + logger.log('Gibberish is ready!') + Gibberish.export(window) + testNote(); + }).catch((e: unknown) => logger.error('oops', e)); + } else { + testNote(); + } } - create() { - this.createPads(8); - this.game.events.emit(EVENTS.sceneChange) + constructor() { + super(1 , 1); } - private createPads(numberOfPads: number) { - // change the order of the pads if needed, top to bottom and left to right - const pads: Pad[] = new Array(numberOfPads).fill(0).map((_, index) => { - return this.createPad(index); - }); - - const resizePads = () => { - // FIXME duplicates what's in PadsScene. Moreover they all play the same music - const isPortrait = window.innerWidth < window.innerHeight; - const colNumber = isPortrait ? 2 : 4;// FIXME should depend on numberOfPads - const rowNumber = isPortrait ? 4 : 2; - const width = isPortrait ? window.innerWidth / colNumber : (window.innerWidth - LoopTracksScene.sceneWidthHeight) / colNumber; - const height = isPortrait ? (window.innerHeight - LoopTracksScene.sceneWidthHeight) / rowNumber : window.innerHeight / rowNumber; - - const currentPads = isPortrait ? rotateArray(pads, rowNumber, colNumber) : pads; - - currentPads.forEach(({button}, index) => { - const x = (index % colNumber) * width; - const y = Math.floor(index / colNumber) * height; - const offsetX = isPortrait ? 0 : LoopTracksScene.sceneWidthHeight; - const offsetY = isPortrait ? LoopTracksScene.sceneWidthHeight : 0; - button.setSize(width, height).setPosition(offsetX + x, offsetY + y); - }); - }; - - // Attach the event listener and initial call - window.addEventListener('resize', resizePads); - resizePads(); + protected getPadColor(_numberOfPads: number, _index: number): Phaser.Display.Color { + return PhaserColors.black; } - private createPad(index: number): Pad { - const padColor = Phaser.Display.Color.HSLToColor(index / 16, 1, 0.5) - const inactiveColor = padColor.darken(40).color; - const hitColor = padColor.brighten(4).color; - const button = this.add.rectangle() - .setFillStyle(inactiveColor) - .setStrokeStyle(2, PhaserColors.white.color, 0.8) - .setInteractive() - .setOrigin(0, 0); - button.on('pointerdown', (e: Phaser.Input.Pointer) => { - if (e.downElement?.tagName?.toLowerCase() !== 'canvas') { - return; - } - if (!this.isGibberishLoaded) { - this.isGibberishLoaded = true; - Gibberish.workletPath = './worklet.js'; - logger.log('loading Gibberish 2...'); - Gibberish.init().then(() => { - logger.log('Gibberish is ready!') - Gibberish.export(window) - testNote(); - }).catch((e: unknown) => logger.error('oops', e)); - } else { - testNote(); - } - button.setFillStyle(hitColor); - // TODO send a call to the loop scene to play the instrument ? or an object - // this.scene.get(LoopTracksScene.key).events.emit(EVENTS.instrumentPlayed, {instrument, scene: this}); - }).on('pointerup', () => button.setFillStyle(inactiveColor)) - .on('pointerout', () => button.setFillStyle(inactiveColor)); - return { - instrument: index, - button - }; + protected getPadText(_index: number): { text: string; color: Phaser.Display.Color } { + return { text: 'Play Gibberish', color: PhaserColors.green }; } } diff --git a/src/scenes/LoopTracksScene.ts b/src/scenes/LoopTracksScene.ts index 38175b5..451d637 100644 --- a/src/scenes/LoopTracksScene.ts +++ b/src/scenes/LoopTracksScene.ts @@ -1,5 +1,5 @@ import Phaser from 'phaser'; -import {Colors, PhaserColors} from '../utils/colors.ts'; +import {Colors, colorToHex, PhaserColors} from '../utils/colors.ts'; import {FontColor, FontFamily, FontSize} from '../utils/fonts.ts'; import {EmptyScene} from './EmptyScene.ts'; import {Loop} from '../Loop.ts'; @@ -22,10 +22,12 @@ const controlColors = { type Track = { button: Phaser.GameObjects.Rectangle; buttonText: Phaser.GameObjects.Text; + // TODO remove? buttonSelectedCircle: Phaser.GameObjects.Ellipse; selected: boolean, loop: Loop; controlIcon: Phaser.GameObjects.Text; + // FIXME re-implement loopProgressArc: Phaser.GameObjects.Graphics; }; @@ -72,11 +74,6 @@ export class LoopTracksScene extends Phaser.Scene { } create() { - this.cameras.main - .setOrigin(0, 0) - .setPosition(0, 0) - .setViewport(0, 0, LoopTracksScene.sceneWidthHeight, window.innerHeight) - .setBackgroundColor('#963');// ugly color that should never be seen this.createTracks(); this.game.events.on(EVENTS.sceneChange, () => { this.updateControlsState(); @@ -94,8 +91,8 @@ export class LoopTracksScene extends Phaser.Scene { } } - public getTrackScene(index: number) { - return this.scene.get(LoopTracksScene.getTrackSceneKey(index)); + public getTrackScene(index: number): PadsScene | undefined { + return this.scene.get(LoopTracksScene.getTrackSceneKey(index)) as PadsScene; } updateProgressArc(track: Track) { @@ -127,12 +124,12 @@ export class LoopTracksScene extends Phaser.Scene { loop: new Loop(index), button: this.add.rectangle() .setOrigin(0, 0) - .setStrokeStyle(2, PhaserColors.white.color, 0.8) + .setStrokeStyle(1, PhaserColors.darkGrey.color) .setInteractive() .on(Phaser.Input.Events.POINTER_DOWN, () => this.selectTrack(index)), selected: false, - buttonSelectedCircle: this.add.ellipse(0, 0, 0, 0, PhaserColors.white.color), - buttonText: this.add.text(0, 0, `${index + 1}`, { + buttonSelectedCircle: this.add.ellipse(0, 0, 0, 0, PhaserColors.white.color, 0).setVisible(false), + buttonText: this.add.text(0, 0, '+', { fontFamily: FontFamily.Text, fontSize: FontSize.medium, color: FontColor.white, @@ -171,11 +168,11 @@ export class LoopTracksScene extends Phaser.Scene { { button, buttonText, - buttonSelectedCircle, + // buttonSelectedCircle, controlIcon, }, index) => { const x = isPortrait ? buttonWidth * index : 0; - const y = isPortrait ? -1 : buttonHeight * index; + const y = isPortrait ? 0 : buttonHeight * index; button.setSize(buttonWidth, buttonHeight).setPosition(x, y); const textX = isPortrait ? button.getCenter().x : button.getCenter().x - buttonWidth / 4; @@ -186,10 +183,13 @@ export class LoopTracksScene extends Phaser.Scene { .setSize(buttonWidth, buttonHeight) .setFontSize(minWidthHeight / 4) .setPosition(textX, textY) - - buttonSelectedCircle - .setSize(minWidthHeight / 3, minWidthHeight / 3) - .setPosition(textX, textY) + .setWordWrapWidth(buttonWidth - 10, true) + .setRotation(isPortrait ? 0 : - Math.PI / 2); + // + // buttonSelectedCircle + // .setSize(minWidthHeight / 3, minWidthHeight / 3) + // .setPosition(textX, textY) + // .setVisible(false); controlIcon .setOrigin(isPortrait ? 0.5 : 1, isPortrait ? 1 : 0.5) @@ -217,7 +217,8 @@ export class LoopTracksScene extends Phaser.Scene { const trackScene = this.getTrackScene(index); if (trackScene) { trackScene.scene.bringToTop(); - const settings = (trackScene as PadsScene).settings; + const settings = trackScene.settings; + track.buttonText.setColor(colorToHex(trackScene.sceneTextColor)).setText(trackScene.sceneText); this.game.events.emit(EVENTS.sceneChange, settings); } else { this.game.scene.start(EmptyScene.key, {index}); @@ -226,24 +227,32 @@ export class LoopTracksScene extends Phaser.Scene { track.loop.handleClick(); this.updateControlsState(); } + this.scene.bringToTop(); } private updateControlsState() { LoopTracksScene.tracks.forEach((track, index) => { - track.button.setFillStyle(PhaserColors.black.color); + const trackScene = this.getTrackScene(index); + const sceneTextColor = trackScene?.sceneTextColor.color ?? EmptyScene.sceneTextColor.color; + track.button + .setDepth(track.selected ? 0 : -1) + .setFillStyle(PhaserColors.black.color) + .setStrokeStyle(1, track.selected ? sceneTextColor : PhaserColors.darkGrey.color); track.buttonText - .setColor(track.selected ? Colors.black : Colors.white); + .setText(trackScene?.sceneText ?? EmptyScene.sceneText) + .setColor(colorToHex(trackScene?.sceneTextColor ?? EmptyScene.sceneTextColor)) + .setAlpha(track.selected ? 1 : 0.5); + track.buttonSelectedCircle - .setVisible(true) + .setVisible(false) .setFillStyle(track.selected ? PhaserColors.white.color : PhaserColors.black.color); - const trackScene = this.getTrackScene(index); - const hasLoopControls = trackScene && trackScene instanceof PadsScene; + const hasLoopControls = trackScene?.canRecord || trackScene?.canPlay; if (hasLoopControls) { - if (track.selected && (track.loop.isRecording() || track.loop.isReadyToRecord())) { + if (trackScene?.canRecord && track.selected && (track.loop.isRecording() || track.loop.isReadyToRecord())) { track.controlIcon.setText(controlIcons.record) .setColor(track.loop.isRecording() ? controlColors.recording : controlColors.idle) - } else if (track.loop.isPlaying() || track.loop.isReadyToPlay()) { + } else if (trackScene?.canPlay && (track.loop.isPlaying() || track.loop.isReadyToPlay())) { track.controlIcon.setText(controlIcons.play) .setColor(track.loop.isPlaying() ? controlColors.playing : controlColors.idle); } else { diff --git a/src/scenes/PadsScene.ts b/src/scenes/PadsScene.ts index 6026d59..f030552 100644 --- a/src/scenes/PadsScene.ts +++ b/src/scenes/PadsScene.ts @@ -40,13 +40,21 @@ export abstract class PadsScene extends Phaser.Scene { onChange: (setting) => this.onSettingChange(setting), } + public canRecord = true; + public canPlay = true; + + public sceneText = ''; + public sceneTextColor: PhaserColor = PhaserColors.white; + protected constructor(protected cols: number, protected rows: number) { super(); } abstract playSound(index: number): void; - create() { + create({color, text}: { color: PhaserColor, text: string }) { + this.sceneText = text; + this.sceneTextColor = color; this.createPads(); this.game.events.emit(EVENTS.sceneChange, this.settings); } @@ -93,9 +101,10 @@ export abstract class PadsScene extends Phaser.Scene { const padColor = this.getPadColor(numberOfPads, index); const hitColor = this.getHitColor(numberOfPads, index); const padText = this.getPadText(index); + const borderColor = this.getBorderColor(index); const button = this.add.rectangle() .setFillStyle(padColor.color) - .setStrokeStyle(2, PhaserColors.white.color) + .setStrokeStyle(1, borderColor.color, 0.8) .setInteractive() .setOrigin(0, 0); let buttonText: Pad['text'] = undefined; @@ -160,4 +169,8 @@ export abstract class PadsScene extends Phaser.Scene { protected getHitColor(numberOfPads: number, index: number): Phaser.Display.Color { return this.getPadColor(numberOfPads, index).brighten(40); } + + protected getBorderColor(_index: number): Phaser.Display.Color { + return PhaserColors.darkGrey; + } } diff --git a/src/utils/colors.ts b/src/utils/colors.ts index 5246e58..24a1e12 100644 --- a/src/utils/colors.ts +++ b/src/utils/colors.ts @@ -8,6 +8,8 @@ export const Colors = { white: '#D3D3D3', black: '#1F2023FF', // black: '#1a1a1ac', + grey: '#2C2C2C', + darkGrey: '#1F2023', orange: '#FDA341', red: '#F24E1E', blue: '#4A90E2', @@ -27,6 +29,12 @@ export const PhaserColors = { get black() { return hexToColor(Colors.black); }, + get grey() { + return hexToColor(Colors.grey); + }, + get darkGrey() { + return hexToColor(Colors.darkGrey); + }, get orange() { return hexToColor(Colors.orange); }, @@ -51,7 +59,7 @@ export const PhaserColors = { get purple() { return hexToColor(Colors.purple); }, -} as const; +} as const satisfies Record; // @Deprecated export const hexToColor = (hexColor: HexaColor): PhaserColor => {