From 84a8e0c99227e102fa24a67e02ba5fab6de26d05 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Sun, 11 May 2025 18:03:49 +0100 Subject: [PATCH 1/8] Basic songComposer drawing --- app.ts | 2 +- assets.ts | 20 +++ channel.ts | 7 +- pattern.ts | 21 ++- patternComposer.ts | 38 +---- sampleSelectionScreen.ts | 1 + song.ts | 32 ++++- songComposer.ts | 302 ++++++++++++++++++++++++++++++++++++++- 8 files changed, 381 insertions(+), 42 deletions(-) diff --git a/app.ts b/app.ts index 395c361..476ad6c 100644 --- a/app.ts +++ b/app.ts @@ -30,7 +30,7 @@ namespace micromusic { datalogger.includeTimestamp(FlashLogTimeStampFormat.None) // if (shieldhelpers.shieldPresent()) - this.pushScene(new Home(this)) + this.pushScene(SongComposerScreen.getInstance(this)) } public save(slot: string, buffer: Buffer): boolean { diff --git a/assets.ts b/assets.ts index 6a586a9..94f466b 100644 --- a/assets.ts +++ b/assets.ts @@ -12,6 +12,8 @@ namespace micromusic { if (name == "largeDisk") return icondb.largeDisk if (name == "volume") return icondb.volumeLogo + if (name == "invisiblePatternButton") + return icondb.invisiblePatternButton if (name == "green_tick_2") return icondb.green_tick_transparent @@ -151,6 +153,24 @@ namespace icondb { //------------------------ // "ICONLESS" BUTTON ICONS //------------------------ + export const invisiblePatternButton = bmp` + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + ` + export const invisibleButtonPlaceholder = bmp` . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . diff --git a/channel.ts b/channel.ts index 5757f34..98cc81d 100644 --- a/channel.ts +++ b/channel.ts @@ -5,8 +5,9 @@ namespace micromusic { private _notes: string[] private _octaves: number[] private _sample: Sample + private _id: number - constructor() { + constructor(id: number) { this._notes = [] this._octaves = [] @@ -17,6 +18,10 @@ namespace micromusic { this._sample = new Sample("ResBass") } + public get id() { + return this._id + } + public get notes() { return this._notes } diff --git a/pattern.ts b/pattern.ts index 5195d5b..c30f089 100644 --- a/pattern.ts +++ b/pattern.ts @@ -3,14 +3,21 @@ namespace micromusic { export class Pattern { private _channels: Channel[] + private _id: number - constructor() { + constructor(id: number) { this._channels = [ - new Channel(), - new Channel(), - new Channel(), - new Channel(), + new Channel(0), + new Channel(1), + new Channel(2), + new Channel(3), ] + + this._id = id + } + + public get id() { + return this._id } public setChannel(channel: Channel, index: number) { @@ -24,5 +31,9 @@ namespace micromusic { public getChannel(index: number) { return this._channels[index] } + + public playAsync() { + // starts playing on all channels, each one has their own control.inbackground? + } } } diff --git a/patternComposer.ts b/patternComposer.ts index 76cc2fe..2476ba3 100644 --- a/patternComposer.ts +++ b/patternComposer.ts @@ -11,7 +11,6 @@ namespace micromusic { const NUM_TRACKS = 4 const NUM_VISIBLE_STEPS = 8 - const TOTAL_BANKS = 4 const LEFT_TRACK_INDEX = 0 const RIGHT_TRACK_INDEX = 1 const NOTES = [ @@ -48,7 +47,6 @@ namespace micromusic { private static instance: PatternScreen | null = null private currentStep: number private currentTrack: number - // private trackData: Note[][][] private controlBtns: Button[] private sampleSelectBtn: Button private noteSelectBtn: Button @@ -66,14 +64,13 @@ namespace micromusic { private cursorVisible: boolean private playedNote: number private hasClickedBack: boolean - private currentBank: number private pattern: Pattern private channels: Channel[] private constructor( app: AppInterface, - pattern?: Pattern, + pattern: Pattern, volume?: Setting, bpm?: Setting ) { @@ -97,11 +94,7 @@ namespace micromusic { this.bpm = new Setting(120) } - if (pattern) { - this.pattern = pattern - } else { - this.pattern = new Pattern() - } + this.pattern = pattern this.selectedTrackPos = 0 this.currentStep = 0 @@ -115,7 +108,6 @@ namespace micromusic { this.isSelectingSample = false this.playedNote = 0 this.hasClickedBack = false - this.currentBank = 0 } public static getInstance( @@ -123,13 +115,16 @@ namespace micromusic { volume?: Setting, bpm?: Setting ) { + // TODO: replace temp value + let pattern = new Pattern(0) + if (!PatternScreen.instance) { if (app === undefined) { console.error( "SoundTrackerScreen singleton not initialized. Call with parameters first." ) } - PatternScreen.instance = new PatternScreen(app) + PatternScreen.instance = new PatternScreen(app, pattern) } return PatternScreen.instance @@ -534,27 +529,6 @@ namespace micromusic { } } - private switchToBank(bankIndex: number) { - if (bankIndex >= 0 && bankIndex < TOTAL_BANKS) { - // If playing, stop before switching banks - if (this.isPlaying) { - this.pause() - } - - this.currentBank = bankIndex - - // Reset current step when switching banks - this.currentStep = 0 - - // Redraw the screen - this.draw() - } - } - - private nextBank() { - this.switchToBank((this.currentBank + 1) % TOTAL_BANKS) - } - private drawText( x: number, y: number, diff --git a/sampleSelectionScreen.ts b/sampleSelectionScreen.ts index c7b7671..4f5e1a2 100644 --- a/sampleSelectionScreen.ts +++ b/sampleSelectionScreen.ts @@ -213,6 +213,7 @@ namespace micromusic { Screen.print("Select Sample", -40, -50, 0x1) this.cursor.draw() + super.draw() } } } diff --git a/song.ts b/song.ts index 316c43c..8d87f4f 100644 --- a/song.ts +++ b/song.ts @@ -1,6 +1,34 @@ namespace micromusic { export class Song { - private patternCount: number - private patterns: Pattern[] + private _patterns: Pattern[] + private _patternSequence: Pattern[] + + constructor() { + this._patternSequence = [] + this._patterns = [] + + this._patternSequence[0] = new Pattern(0) + this._patternSequence[1] = new Pattern(1) + this._patternSequence[2] = new Pattern(2) + this._patternSequence[3] = new Pattern(3) + this._patternSequence[4] = new Pattern(4) + this._patternSequence[5] = new Pattern(5) + } + + get patterns(): Pattern[] { + return this._patterns + } + + get patternSequence(): Pattern[] { + return this._patternSequence + } + + public addPattern(id: number) { + this.patterns[this.patterns.length] = new Pattern(id) + } + + public removePattern(pattern: Pattern) { + this.patterns.removeElement(pattern) + } } } diff --git a/songComposer.ts b/songComposer.ts index 8d15f56..e5a93e6 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -24,5 +24,305 @@ namespace micromusic { * */ - export class SongComposerScreen extends CursorSceneWithPriorPage {} + export class SongComposerScreen extends CursorSceneWithPriorPage { + private static instance: SongComposerScreen | null = null + private song: Song + private controlBtns: Button[] + private bpm: Setting + private volume: Setting + private hasClickedBack: boolean + private isPlaying: boolean + + private constructor(app: AppInterface) { + super( + app, + function () { + this.app.popScene() + this.app.pushScene(new Home(this.app)) + }, + new GridNavigator() + ) + + this.volume = new Setting(100) + this.bpm = new Setting(120) + this.hasClickedBack = false + } + + public static getInstance(app?: AppInterface, song?: Song) { + if (!SongComposerScreen.instance) { + if (app === undefined) { + console.error( + "SongComposerScreen singleton not initialized. Call with parameters first." + ) + } + SongComposerScreen.instance = new SongComposerScreen(app) + } + + if (song) { + SongComposerScreen.instance.setSong(song) + } else { + SongComposerScreen.instance.setSong(new Song()) + } + + return SongComposerScreen.instance + } + + private setSong(song: Song) { + this.song = song + } + + /*override*/ startup() { + super.startup() + + this.cursor.setBorderThickness(1) + + this.controlBtns = [ + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "pause", + x: -25, + y: -54, + onClick: () => { + this.pause() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "stop", + x: -13, + y: -54, + onClick: () => { + this.stop() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "fast_forward", + x: 1, + y: -54, + onClick: () => { + this.fastForward() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "settings_cog_small", + x: 70, + y: -52, + onClick: () => { + this.isPlaying = false + this.app.popScene() + this.app.pushScene( + SettingsScreen.getInstance( + this.app, + this, + this.volume, + this.bpm + ) + ) + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "back_button", + x: -68, + y: -52, + onClick: () => { + this.backConfirmation() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "rewind", + x: -47, + y: -54, + onClick: () => { + this.rewind() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "play", + x: -37, + y: -54, + onClick: () => { + this.play() + }, + }), + ] + + this.navigator.setBtns([ + this.controlBtns, + [ + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "invisiblePatternButton", + x: -53, + y: 6, + onClick: () => { + this.play() + }, + }), + ], + ]) + } + + draw() { + Screen.fillRect( + Screen.LEFT_EDGE, + Screen.TOP_EDGE, + Screen.WIDTH, + Screen.HEIGHT, + 0xc + ) + + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + + for (let i = 0; i < this.song.patternSequence.length; i++) { + // const y = startY + i * cellHeight + const x = startX + i * cellWidth + const digitCount = i.toString().length + + Screen.print(i.toString(), x, startY, 0, bitmaps.font12) + } + // TODO: plus sign in the ones without? or perhaps not show anything past 6, + // 6 to have a plus sign then that adds the seventh as a possibility as well? + let count = 0 + + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 6; i++) { + const y = startY + j * cellHeight + const x = startX + i * cellWidth + + if (count < this.song.patternSequence.length) { + const x = startX + i * cellWidth + + Screen.print(i.toString(), x, y, 0, bitmaps.font12) + } else if (count == this.song.patternSequence.length) { + const x = startX + i * cellWidth + Screen.print("+", x + 1, y + 2, 0, bitmaps.font8) + } + + const digitCount = count.toString().length + const rightX = x - (digitCount - 1) * 2 + + Screen.drawRect(x - 5, y - 2, 17, 17, 0) + Screen.print( + count.toString(), + rightX, + y + 16, + 0, + bitmaps.font5 + ) + + count += 1 + if (count > this.song.patternSequence.length) { + break + } + } + if (count > this.song.patternSequence.length) { + break + } + } + + this.navigator.drawComponents() + this.cursor.draw() + super.draw() + } + + private play() {} + + private pause() {} + + private stop() {} + + private fastForward() {} + + private rewind() {} + + private backConfirmation() { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } + + this.hasClickedBack = true + const ic = icons.get("placeholder") + this.navigator.setBtns([ + [ + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic, + x: -22, + y: 18, + onClick: () => { + this.app.popScene() + this.app.pushScene(new Home(this.app)) + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic, + x: 21, + y: 18, + onClick: () => { + this.hasClickedBack = false + this.moveCursor(CursorDir.Up) + this.moveCursor(CursorDir.Down) + }, + }), + ], + ]) + this.moveCursor(CursorDir.Down) + } + + private resetControllerEvents() { + this.cursor.visible = true + control.onEvent( + ControllerButtonEvent.Pressed, + controller.up.id, + () => { + if (!this.isPlaying) this.moveCursor(CursorDir.Up) + } + ) + control.onEvent( + ControllerButtonEvent.Pressed, + controller.down.id, + () => { + if (!this.isPlaying) this.moveCursor(CursorDir.Down) + } + ) + control.onEvent( + ControllerButtonEvent.Pressed, + controller.right.id, + () => this.moveCursor(CursorDir.Right) + ) + control.onEvent( + ControllerButtonEvent.Pressed, + controller.left.id, + () => this.moveCursor(CursorDir.Left) + ) + control.onEvent( + ControllerButtonEvent.Pressed, + controller.B.id, + () => { + this.backConfirmation() + this.moveCursor(CursorDir.Right) + } + ) + } + } } From 3345ea486769cd1be376b3c95770b8a0476b7481 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Mon, 12 May 2025 12:30:21 +0100 Subject: [PATCH 2/8] Added buttons for patterns --- songComposer.ts | 78 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/songComposer.ts b/songComposer.ts index e5a93e6..f54fdfa 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -28,6 +28,7 @@ namespace micromusic { private static instance: SongComposerScreen | null = null private song: Song private controlBtns: Button[] + private patternBtns: Button[] private bpm: Setting private volume: Setting private hasClickedBack: boolean @@ -158,21 +159,8 @@ namespace micromusic { }), ] - this.navigator.setBtns([ - this.controlBtns, - [ - new Button({ - parent: null, - style: ButtonStyles.Transparent, - icon: "invisiblePatternButton", - x: -53, - y: 6, - onClick: () => { - this.play() - }, - }), - ], - ]) + this.fillPatternBtns() + this.resetNavigator() } draw() { @@ -289,6 +277,66 @@ namespace micromusic { this.moveCursor(CursorDir.Down) } + private patternClicked() {} + + private plusClicked() {} + + private fillPatternBtns() { + this.patternBtns = [] + + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + + let count = 0 + + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 6; i++) { + const y = startY + j * cellHeight + const x = startX + i * cellWidth + + if (count < this.song.patternSequence.length) { + this.patternBtns[count] = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "invisiblePatternButton", + x: x + 3, + y: y + 6, + onClick: () => { + this.patternClicked() + }, + }) + } else if (count == this.song.patternSequence.length) { + this.patternBtns[count] = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "invisiblePatternButton", + x: x + 3, + y: y + 6, + onClick: () => { + this.plusClicked() + }, + }) + } + + count += 1 + if (count > this.song.patternSequence.length) { + break + } + } + if (count > this.song.patternSequence.length) { + break + } + } + + this.resetNavigator() + } + + private resetNavigator() { + this.navigator.setBtns([this.controlBtns, this.patternBtns]) + } + private resetControllerEvents() { this.cursor.visible = true control.onEvent( From 08bb8eb15b68984e43c77630f46a7a7297425a40 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Mon, 12 May 2025 16:38:22 +0100 Subject: [PATCH 3/8] Created song composer screen and working patterns --- TODO.md | 2 + home.ts | 3 +- patternComposer.ts | 20 ++-- song.ts | 21 +++-- songComposer.ts | 223 +++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 234 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 74958ac..5966e5a 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,8 @@ Console.log over serial - [ ] More than 1 bank of track data important one, can have them playing after eachother - [ ] Want a nice strong evaluation, start thinking about that now - [ ] Intuitiveness, when selecting a note, A to come out of it and B to reset the note to "-"? Or A to reset, B to come out of it +- [ ] Pattern at the top of the screen so you can swap straight in the patterns editor +- [ ] Change size of invisible buttons on confirmation pages ## Bugs diff --git a/home.ts b/home.ts index 7d4f8e2..371bfb0 100644 --- a/home.ts +++ b/home.ts @@ -36,7 +36,8 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene( - PatternScreen.getInstance(this.app) + // PatternScreen.getInstance(this.app) + SongComposerScreen.getInstance(this.app) ) }, })), diff --git a/patternComposer.ts b/patternComposer.ts index 2476ba3..42335ae 100644 --- a/patternComposer.ts +++ b/patternComposer.ts @@ -111,22 +111,25 @@ namespace micromusic { } public static getInstance( + pattern: Pattern, app?: AppInterface, volume?: Setting, bpm?: Setting ) { - // TODO: replace temp value - let pattern = new Pattern(0) - if (!PatternScreen.instance) { - if (app === undefined) { + if (app === undefined || pattern === undefined) { console.error( - "SoundTrackerScreen singleton not initialized. Call with parameters first." + "PatternScreen singleton not initialized. Call with parameters first." ) } PatternScreen.instance = new PatternScreen(app, pattern) } + if (!pattern) { + console.error("Pattern required") + } + PatternScreen.instance.setPattern(pattern) + return PatternScreen.instance } @@ -228,6 +231,10 @@ namespace micromusic { this.resetControllerEvents() } + private setPattern(pattern: Pattern) { + this.pattern = pattern + } + private backConfirmation() { if (this.isPlaying) { this.resetControllerEvents() @@ -244,8 +251,9 @@ namespace micromusic { x: -22, y: 18, onClick: () => { + this.hasClickedBack = false this.app.popScene() - this.app.pushScene(new Home(this.app)) + this.app.pushScene(SongComposerScreen.getInstance()) }, }), new Button({ diff --git a/song.ts b/song.ts index 8d87f4f..6ce890f 100644 --- a/song.ts +++ b/song.ts @@ -2,17 +2,18 @@ namespace micromusic { export class Song { private _patterns: Pattern[] private _patternSequence: Pattern[] + public name: string constructor() { this._patternSequence = [] this._patterns = [] - this._patternSequence[0] = new Pattern(0) - this._patternSequence[1] = new Pattern(1) - this._patternSequence[2] = new Pattern(2) - this._patternSequence[3] = new Pattern(3) - this._patternSequence[4] = new Pattern(4) - this._patternSequence[5] = new Pattern(5) + // this._patternSequence[0] = new Pattern(0) + // this._patternSequence[1] = new Pattern(1) + // this._patternSequence[2] = new Pattern(2) + // this._patternSequence[3] = new Pattern(3) + // this._patternSequence[4] = new Pattern(4) + // this._patternSequence[5] = new Pattern(5) } get patterns(): Pattern[] { @@ -23,8 +24,12 @@ namespace micromusic { return this._patternSequence } - public addPattern(id: number) { - this.patterns[this.patterns.length] = new Pattern(id) + public newPattern(): Pattern { + this.patterns[this.patterns.length] = new Pattern( + this.patterns.length + ) + console.log(this.patterns.length) + return this.patterns[this.patterns.length - 1] } public removePattern(pattern: Pattern) { diff --git a/songComposer.ts b/songComposer.ts index f54fdfa..d54e5ba 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -32,7 +32,10 @@ namespace micromusic { private bpm: Setting private volume: Setting private hasClickedBack: boolean + private hasClickedNumber: boolean + private hasClickedPlus: boolean private isPlaying: boolean + private clickedPattern: number // TODO: Get pattern for click private constructor(app: AppInterface) { super( @@ -57,12 +60,11 @@ namespace micromusic { ) } SongComposerScreen.instance = new SongComposerScreen(app) + SongComposerScreen.instance.setSong(new Song()) } if (song) { SongComposerScreen.instance.setSong(song) - } else { - SongComposerScreen.instance.setSong(new Song()) } return SongComposerScreen.instance @@ -74,6 +76,7 @@ namespace micromusic { /*override*/ startup() { super.startup() + console.log(this.song.name) this.cursor.setBorderThickness(1) @@ -161,6 +164,7 @@ namespace micromusic { this.fillPatternBtns() this.resetNavigator() + this.resetControllerEvents() } draw() { @@ -172,20 +176,83 @@ namespace micromusic { 0xc ) + if (this.hasClickedBack) { + this.drawBackConfirmation() + this.navigator.drawComponents() + return + } + + if (this.hasClickedNumber) { + this.drawPatternConfirmation() + this.navigator.drawComponents() + return + } + + if (this.hasClickedPlus) { + this.drawPlusConfirmation() + this.navigator.drawComponents() + return + } + + this.drawBoxes() + + this.navigator.drawComponents() + this.cursor.draw() + super.draw() + } + + private drawPatternConfirmation() { + Screen.fillRect(-57, -37, 120, 80, 0) + Screen.fillRect(-60, -40, 120, 80, 0x6) + this.drawText(-51, -30, "Would you like to") + Screen.print("edit or replace", -45, -20, 0) + Screen.print("this pattern?", -34, -10, 0) + this.drawText(-40, 15, "Edit") + this.drawText(2, 15, "Replace") + this.cursor.draw() + this.cursor.setOutlineColour(0x2) + } + + private drawPlusConfirmation() { + Screen.fillRect(-57, -37, 120, 80, 0) + Screen.fillRect(-60, -40, 120, 80, 0x6) + this.drawText(-46, -30, "Create new pattern") + Screen.print("or use an", -38, -20, 0) + Screen.print("existing one?", -38, -10, 0) + this.drawText(-40, 15, "new") + this.drawText(2, 15, "existing") + this.cursor.draw() + } + + private drawBackConfirmation() { + Screen.fillRect(-57, -37, 120, 80, 0) + Screen.fillRect(-60, -40, 120, 80, 0x6) + this.drawText(-36, -30, "Return Home?") + Screen.print("Any unsaved work", -48, -20, 0x2) + Screen.print("will be lost", -38, -10, 0x2) + this.drawText(-30, 15, "Yes") + this.drawText(15, 15, "No") + this.cursor.draw() + } + + private drawText( + x: number, + y: number, + text: string, + colour?: number, + _font?: bitmaps.Font + ) { + if (!colour) colour = 0 + if (!_font) _font = font + Screen.print(text, x, y, colour, _font) + } + + private drawBoxes() { const startX = -56 const startY = 0 const cellWidth = 21 const cellHeight = 31 - for (let i = 0; i < this.song.patternSequence.length; i++) { - // const y = startY + i * cellHeight - const x = startX + i * cellWidth - const digitCount = i.toString().length - - Screen.print(i.toString(), x, startY, 0, bitmaps.font12) - } - // TODO: plus sign in the ones without? or perhaps not show anything past 6, - // 6 to have a plus sign then that adds the seventh as a possibility as well? let count = 0 for (let j = 0; j < 2; j++) { @@ -207,7 +274,7 @@ namespace micromusic { Screen.drawRect(x - 5, y - 2, 17, 17, 0) Screen.print( - count.toString(), + (count + 1).toString(), rightX, y + 16, 0, @@ -223,10 +290,6 @@ namespace micromusic { break } } - - this.navigator.drawComponents() - this.cursor.draw() - super.draw() } private play() {} @@ -246,6 +309,7 @@ namespace micromusic { } this.hasClickedBack = true + this.unbindBackButton() const ic = icons.get("placeholder") this.navigator.setBtns([ [ @@ -258,6 +322,7 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene(new Home(this.app)) + this.hasClickedBack = false }, }), new Button({ @@ -274,12 +339,109 @@ namespace micromusic { }), ], ]) + this.unbindBackButton() this.moveCursor(CursorDir.Down) } - private patternClicked() {} + private patternClicked(clickedPatternIndex: number) { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } - private plusClicked() {} + this.unbindBackButton() + + this.hasClickedNumber = true + const ic = icons.get("placeholder") + this.navigator.setBtns([ + [ + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic, + x: -22, + y: 18, + onClick: () => { + console.log( + this.song.patternSequence[clickedPatternIndex] + .id + ) + this.editPattern( + this.song.patternSequence[clickedPatternIndex] + ) + this.resetBooleans() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic.doubledX(), + x: 21, + y: 18, + onClick: () => { + this.newPattern(clickedPatternIndex) + this.resetBooleans() + }, + }), + ], + ]) + this.moveCursor(CursorDir.Down) + } + + private plusClicked(clickedPatternIndex: number) { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } + this.hasClickedPlus = true + this.unbindBackButton() + const ic = icons.get("placeholder") + this.navigator.setBtns([ + [ + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic, + x: -30, + y: 18, + onClick: () => { + console.log("pick") + this.newPattern(clickedPatternIndex) + this.resetBooleans() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic.doubledX(), + x: 21, + y: 18, + onClick: () => { + this.pickPattern() + this.resetBooleans() + }, + }), + ], + ]) + this.cursor.setOutlineColour(0x2) + this.moveCursor(CursorDir.Down) + } + + private pickPattern() {} + + private editPattern(clickedPattern: Pattern) { + this.app.popScene() + this.app.pushScene( + PatternScreen.getInstance(clickedPattern, this.app) + ) + } + + private newPattern(clickedPatternIndex: number) { + let pattern = this.song.newPattern() + this.song.patternSequence[clickedPatternIndex] = pattern + this.app.popScene() + this.app.pushScene(PatternScreen.getInstance(pattern, this.app)) + } private fillPatternBtns() { this.patternBtns = [] @@ -295,6 +457,7 @@ namespace micromusic { for (let i = 0; i < 6; i++) { const y = startY + j * cellHeight const x = startX + i * cellWidth + const index = count if (count < this.song.patternSequence.length) { this.patternBtns[count] = new Button({ @@ -304,7 +467,7 @@ namespace micromusic { x: x + 3, y: y + 6, onClick: () => { - this.patternClicked() + this.patternClicked(index) }, }) } else if (count == this.song.patternSequence.length) { @@ -315,7 +478,7 @@ namespace micromusic { x: x + 3, y: y + 6, onClick: () => { - this.plusClicked() + this.plusClicked(index) }, }) } @@ -337,6 +500,26 @@ namespace micromusic { this.navigator.setBtns([this.controlBtns, this.patternBtns]) } + private resetBooleans() { + this.hasClickedBack = false + this.hasClickedNumber = false + this.hasClickedPlus = false + } + + private unbindBackButton() { + control.onEvent( + ControllerButtonEvent.Pressed, + controller.B.id, + () => { + this.resetBooleans() + this.resetControllerEvents() + this.resetNavigator() + this.moveCursor(CursorDir.Left) + this.moveCursor(CursorDir.Right) + } + ) + } + private resetControllerEvents() { this.cursor.visible = true control.onEvent( From e311ddb119491a066fbd8cd6fe9fdc87cb6c3093 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Mon, 12 May 2025 18:30:31 +0100 Subject: [PATCH 4/8] fixed outline colour in one spot --- songComposer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/songComposer.ts b/songComposer.ts index d54e5ba..eb31c31 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -379,6 +379,7 @@ namespace micromusic { x: 21, y: 18, onClick: () => { + // TODO: Replace, this should let you swap it out or create a new one ideally this.newPattern(clickedPatternIndex) this.resetBooleans() }, @@ -405,7 +406,6 @@ namespace micromusic { x: -30, y: 18, onClick: () => { - console.log("pick") this.newPattern(clickedPatternIndex) this.resetBooleans() }, @@ -514,6 +514,7 @@ namespace micromusic { this.resetBooleans() this.resetControllerEvents() this.resetNavigator() + this.cursor.setOutlineColour(0x9) this.moveCursor(CursorDir.Left) this.moveCursor(CursorDir.Right) } From 170a298e229ee07785eddb780737b2a91d23c753 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Mon, 12 May 2025 21:39:24 +0100 Subject: [PATCH 5/8] Started replace implementation --- song.ts | 2 +- songComposer.ts | 187 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 184 insertions(+), 5 deletions(-) diff --git a/song.ts b/song.ts index 6ce890f..f8f1702 100644 --- a/song.ts +++ b/song.ts @@ -28,7 +28,7 @@ namespace micromusic { this.patterns[this.patterns.length] = new Pattern( this.patterns.length ) - console.log(this.patterns.length) + return this.patterns[this.patterns.length - 1] } diff --git a/songComposer.ts b/songComposer.ts index eb31c31..b7dcacf 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -32,8 +32,10 @@ namespace micromusic { private bpm: Setting private volume: Setting private hasClickedBack: boolean + private isTemp: boolean private hasClickedNumber: boolean private hasClickedPlus: boolean + private hasClickedReplace: boolean private isPlaying: boolean private clickedPattern: number // TODO: Get pattern for click @@ -194,6 +196,12 @@ namespace micromusic { return } + if (this.isTemp) { + this.drawTemp() + this.navigator.drawComponents() + return + } + this.drawBoxes() this.navigator.drawComponents() @@ -201,6 +209,49 @@ namespace micromusic { super.draw() } + private drawTemp() { + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + + let count = 0 + + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 6; i++) { + const y = startY + j * cellHeight + const x = startX + i * cellWidth + + if (count == this.song.patternSequence.length) { + break + } + + if (count < this.song.patterns.length) { + const x = startX + i * cellWidth + + Screen.print(i.toString(), x, y, 0, bitmaps.font12) + } + + const digitCount = count.toString().length + const rightX = x - (digitCount - 1) * 2 + + Screen.drawRect(x - 5, y - 2, 17, 17, 0) + Screen.print( + (count + 1).toString(), + rightX, + y + 16, + 0, + bitmaps.font5 + ) + + count += 1 + } + if (count > this.song.patternSequence.length) { + break + } + } + } + private drawPatternConfirmation() { Screen.fillRect(-57, -37, 120, 80, 0) Screen.fillRect(-60, -40, 120, 80, 0x6) @@ -333,6 +384,8 @@ namespace micromusic { y: 18, onClick: () => { this.hasClickedBack = false + this.resetNavigator() + this.resetControllerEvents() this.moveCursor(CursorDir.Up) this.moveCursor(CursorDir.Down) }, @@ -380,8 +433,7 @@ namespace micromusic { y: 18, onClick: () => { // TODO: Replace, this should let you swap it out or create a new one ideally - this.newPattern(clickedPatternIndex) - this.resetBooleans() + this.replaceClicked(clickedPatternIndex) }, }), ], @@ -389,6 +441,17 @@ namespace micromusic { this.moveCursor(CursorDir.Down) } + private replaceClicked(clickedPatternIndex: number) { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } + + this.unbindBackButton() + + this.pickPattern(clickedPatternIndex) + } + private plusClicked(clickedPatternIndex: number) { if (this.isPlaying) { this.resetControllerEvents() @@ -417,7 +480,7 @@ namespace micromusic { x: 21, y: 18, onClick: () => { - this.pickPattern() + this.pickPattern(clickedPatternIndex) this.resetBooleans() }, }), @@ -427,7 +490,121 @@ namespace micromusic { this.moveCursor(CursorDir.Down) } - private pickPattern() {} + private temp(clickedPatternIndex: number) { + this.resetBooleans() + this.isTemp = true + } + + private pickPattern(clickedPatternIndex: number) { + this.resetBooleans() + // Create a UI to display all available patterns + Screen.fillRect(-57, -37, 120, 80, 0) + Screen.fillRect(-60, -40, 120, 80, 0x6) + this.drawText(-49, -30, "Select a pattern:") + + const patterns = this.song.patterns + const navButtons = [] + const buttonRow = [] + + // Display pattern options based on available patterns + const startX = -40 + const startY = -10 + const spacing = 25 + const maxPerRow = 3 + + for (let i = 0; i < patterns.length; i++) { + const rowIndex = Math.floor(i / maxPerRow) + const colIndex = i % maxPerRow + const x = startX + colIndex * spacing + const y = startY + rowIndex * 20 + + const patternButton = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "green_tick_2", + x: x, + y: y, + onClick: () => { + // Replace the pattern in the sequence + if ( + this.hasClickedReplace && + clickedPatternIndex !== undefined + ) { + this.song.patternSequence[clickedPatternIndex] = + patterns[i] + this.resetBooleans() + this.fillPatternBtns() + } + }, + }) + + buttonRow.push(patternButton) + + // Display pattern ID or other identifier + Screen.print( + `ID: ${patterns[i].id}`, + x - 8, + y, + 0, + bitmaps.font5 + ) + } + + // Add a button to create a new pattern + const newPatternButton = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "invisiblePatternButton", + x: startX, + y: startY + Math.ceil(patterns.length / maxPerRow) * 20, + onClick: () => { + const newPattern = this.song.newPattern() + if ( + this.hasClickedReplace && + this.clickedPattern !== undefined + ) { + this.song.patternSequence[this.clickedPattern] = + newPattern + } + this.resetBooleans() + this.fillPatternBtns() + }, + }) + + buttonRow.push(newPatternButton) + Screen.print( + "New Pattern", + startX - 12, + startY + Math.ceil(patterns.length / maxPerRow) * 20, + 0, + bitmaps.font5 + ) + + // Add a cancel button + const cancelButton = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "back_arrow", + x: 20, + y: startY + Math.ceil(patterns.length / maxPerRow) * 20, + onClick: () => { + this.resetBooleans() + }, + }) + + buttonRow.push(cancelButton) + Screen.print( + "Cancel", + 10, + startY + Math.ceil(patterns.length / maxPerRow) * 20, + 0, + bitmaps.font5 + ) + + navButtons.push(buttonRow) + this.navigator.setBtns(navButtons) + this.cursor.setOutlineColour(0x2) + } private editPattern(clickedPattern: Pattern) { this.app.popScene() @@ -504,6 +681,8 @@ namespace micromusic { this.hasClickedBack = false this.hasClickedNumber = false this.hasClickedPlus = false + this.hasClickedReplace = false + this.cursor.setOutlineColour(0x9) } private unbindBackButton() { From 8d1909fc420779127a0ff80e537f0dca729f3c41 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Tue, 13 May 2025 01:05:08 +0100 Subject: [PATCH 6/8] Replace functionality works, needs cleaning up --- patternComposer.ts | 12 ++++--- song.ts | 8 +++-- songComposer.ts | 80 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/patternComposer.ts b/patternComposer.ts index 42335ae..9f8a5b9 100644 --- a/patternComposer.ts +++ b/patternComposer.ts @@ -265,8 +265,8 @@ namespace micromusic { onClick: () => { this.hasClickedBack = false this.resetNavigator() - this.moveCursor(CursorDir.Up) - this.moveCursor(CursorDir.Down) + this.moveCursor(CursorDir.Left) + this.moveCursor(CursorDir.Right) }, }), ], @@ -433,9 +433,10 @@ namespace micromusic { if (this.hasClickedBack) { Screen.fillRect(-57, -37, 120, 80, 0) Screen.fillRect(-60, -40, 120, 80, 0x6) - this.drawText(-36, -30, "Return Home?") - Screen.print("Any unsaved work", -48, -20, 0x2) - Screen.print("will be lost", -38, -10, 0x2) + this.drawText(-36, -15, "Return Home?") + this.cursor.setOutlineColour(0x2) + // Screen.print("Any unsaved work", -48, -20, 0x2) + // Screen.print("will be lost", -38, -10, 0x2) this.drawText(-30, 15, "Yes") this.drawText(15, 15, "No") this.cursor.draw() @@ -449,6 +450,7 @@ namespace micromusic { this.drawSamples() this.drawGrid() this.drawBankSelector() + this.cursor.setOutlineColour(0x9) super.draw() } diff --git a/song.ts b/song.ts index f8f1702..a04ad8e 100644 --- a/song.ts +++ b/song.ts @@ -2,11 +2,13 @@ namespace micromusic { export class Song { private _patterns: Pattern[] private _patternSequence: Pattern[] + private patternsMade: number public name: string constructor() { this._patternSequence = [] this._patterns = [] + this.patternsMade = 0 // this._patternSequence[0] = new Pattern(0) // this._patternSequence[1] = new Pattern(1) @@ -25,9 +27,9 @@ namespace micromusic { } public newPattern(): Pattern { - this.patterns[this.patterns.length] = new Pattern( - this.patterns.length - ) + this.patterns[this.patterns.length] = new Pattern(this.patternsMade) + + this.patternsMade += 1 return this.patterns[this.patterns.length - 1] } diff --git a/songComposer.ts b/songComposer.ts index b7dcacf..a2efd32 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -199,6 +199,8 @@ namespace micromusic { if (this.isTemp) { this.drawTemp() this.navigator.drawComponents() + this.cursor.draw() + this.moveCursor(CursorDir.Down) return } @@ -217,19 +219,28 @@ namespace micromusic { let count = 0 + this.drawText(-50, -55, "Select a pattern") + this.drawText(-60, -45, "Or press B to cancel") + for (let j = 0; j < 2; j++) { for (let i = 0; i < 6; i++) { const y = startY + j * cellHeight const x = startX + i * cellWidth - if (count == this.song.patternSequence.length) { + if (count == this.song.patterns.length) { break } if (count < this.song.patterns.length) { const x = startX + i * cellWidth - Screen.print(i.toString(), x, y, 0, bitmaps.font12) + Screen.print( + this.song.patterns[count].id.toString(), + x, + y, + 0, + bitmaps.font12 + ) } const digitCount = count.toString().length @@ -314,7 +325,13 @@ namespace micromusic { if (count < this.song.patternSequence.length) { const x = startX + i * cellWidth - Screen.print(i.toString(), x, y, 0, bitmaps.font12) + Screen.print( + this.song.patternSequence[count].id.toString(), + x, + y, + 0, + bitmaps.font12 + ) } else if (count == this.song.patternSequence.length) { const x = startX + i * cellWidth Screen.print("+", x + 1, y + 2, 0, bitmaps.font8) @@ -428,12 +445,13 @@ namespace micromusic { new Button({ parent: null, style: ButtonStyles.Transparent, - icon: ic.doubledX(), + icon: ic.doubledX().doubledY(), x: 21, y: 18, onClick: () => { // TODO: Replace, this should let you swap it out or create a new one ideally - this.replaceClicked(clickedPatternIndex) + this.temp(clickedPatternIndex) + // this.replaceClicked(clickedPatternIndex) }, }), ], @@ -493,6 +511,57 @@ namespace micromusic { private temp(clickedPatternIndex: number) { this.resetBooleans() this.isTemp = true + let patternSelections = [] + + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + let count = 0 + + let patternBtns = [] + + for (let j = 0; j < 2; j++) { + // TODO: patterns won't necessarily be in order due to their id, i can index normally but then display via their id? Potentially won't work, need to know what happens to an array when i remove the middle one - if i Array.splice(count, index) it works and removes the middle one, nice! + for (let i = 0; i < 6; i++) { + const y = startY + j * cellHeight + const x = startX + i * cellWidth + const index = count + + if (count < this.song.patterns.length) { + console.log("testing here") + patternBtns[count] = new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: "invisiblePatternButton", + x: x + 3, + y: y + 6, + onClick: () => { + this.song.patternSequence[clickedPatternIndex] = + this.song.patterns[index] + this.resetBooleans() + this.resetNavigator() + this.moveCursor(CursorDir.Left) + this.moveCursor(CursorDir.Right) + }, + }) + } + + count += 1 + if (count > this.song.patternSequence.length) { + break + } + } + if (count > this.song.patternSequence.length) { + break + } + } + + this.navigator.setBtns([ + // TODO: Make this add enough buttons for the number of patterns available + patternBtns, + ]) + // Way of making isTemp false, also buttons for actually selecting and replacing } private pickPattern(clickedPatternIndex: number) { @@ -682,6 +751,7 @@ namespace micromusic { this.hasClickedNumber = false this.hasClickedPlus = false this.hasClickedReplace = false + this.isTemp = false this.cursor.setOutlineColour(0x9) } From 76e28bf9ca527b9295b84b4f5ec8aed45ace40f5 Mon Sep 17 00:00:00 2001 From: RThom6 Date: Tue, 13 May 2025 14:43:49 +0100 Subject: [PATCH 7/8] New static settings --- settings.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 settings.ts diff --git a/settings.ts b/settings.ts new file mode 100644 index 0000000..0308782 --- /dev/null +++ b/settings.ts @@ -0,0 +1,22 @@ +namespace micromusic { + export class Settings { + private static _volume: Setting = new Setting(100) + private static _bpm: Setting = new Setting(120) + + public static get volume(): Setting { + return this._volume + } + + public static setVolume(vol: number) { + this._volume.value = vol + } + + public static get bpm(): Setting { + return this._bpm + } + + public static setBpm(bpm: number) { + this._bpm.value = bpm + } + } +} From a2c7a471f0c0041e5ba1893016d84c032021aaea Mon Sep 17 00:00:00 2001 From: RThom6 Date: Tue, 13 May 2025 14:43:59 +0100 Subject: [PATCH 8/8] Changed how settings are changed --- TODO.md | 26 ++++++ app.ts | 2 +- home.ts | 11 +-- patternComposer.ts | 7 +- pxt.json | 3 +- sampleSelectionScreen.ts | 1 - settingsScreen.ts | 30 ++----- song.ts | 24 +++++- songComposer.ts | 172 +++------------------------------------ 9 files changed, 74 insertions(+), 202 deletions(-) diff --git a/TODO.md b/TODO.md index 5966e5a..28bb32e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,31 @@ ### TODO +## Things today (13/05/2025) + +- [ ] Fix all dialogue boxes +- [x] Rename temp to pick +- [ ] Pattern on top of screen to swap out in the pattern viewer - May include passing in song and pattern index instead of just passing in a pattern +- [ ] Implement copy-paste +- [ ] Arrow showing which pattern is being played currently +- [ ] Ensure playing full song works on main, aka, test on microbit +- [ ] Fix the way settings work on main + +## Specs of song screen + +- [ ] Should be able to open up with a song +- [ ] Song has a number of patterns, patterns are shown +- [ ] Click on a pattern and should either be able to replace it or edit it +- [ ] Replace gives the option to pick a pattern from all our patterns or to let us make a new one +- [ ] Edit brings up patternComposer screen letting us edit it +- [ ] Click on an empty pattern, brings up choice to use existing or new, existing brings up same page as replace for selection +- [ ] Need a way to naturally scroll through if we have a lot of patterns +- [ ] Limit song size to 12, that would be 3 minutes at 220bpm if all were full +- [ ] Consider limit on number of patterns, max 12 we can swap out? Or maybe 24, would then need a natural way to scroll through +- [ ] Copy-paste, select copying a channel or copying an entire pattern into a new one, since channels are now 64 rows i don't think I need further copying, maybe add a way to copy a certain amount but would take more [If I have the man hours I'll do it] +- [ ] Need to add options to option buttons for removing patterns or sequence, on the boxes for seq but elsewhere on the screen for pattern..? + +## Things by this week + Console.log over serial - [ ] More than 1 bank of track data important one, can have them playing after eachother diff --git a/app.ts b/app.ts index 476ad6c..395c361 100644 --- a/app.ts +++ b/app.ts @@ -30,7 +30,7 @@ namespace micromusic { datalogger.includeTimestamp(FlashLogTimeStampFormat.None) // if (shieldhelpers.shieldPresent()) - this.pushScene(SongComposerScreen.getInstance(this)) + this.pushScene(new Home(this)) } public save(slot: string, buffer: Buffer): boolean { diff --git a/home.ts b/home.ts index 371bfb0..992a071 100644 --- a/home.ts +++ b/home.ts @@ -12,12 +12,9 @@ namespace micromusic { private liveDataBtn: Button private recordDataBtn: Button private distributedLoggingBtn: Button - private volume: Setting - // private viewBtn: Button constructor(app: AppInterface) { super(app) - this.volume = new Setting(100) } /* override */ startup() { @@ -36,7 +33,6 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene( - // PatternScreen.getInstance(this.app) SongComposerScreen.getInstance(this.app) ) }, @@ -60,11 +56,7 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene( - SettingsScreen.getInstance( - this.app, - this, - this.volume - ) + SettingsScreen.getInstance(this, this.app) ) }, })), @@ -134,7 +126,6 @@ namespace micromusic { this.liveDataBtn.draw() this.recordDataBtn.draw() this.distributedLoggingBtn.draw() - // this.viewBtn.draw() this.drawVersion() super.draw() diff --git a/patternComposer.ts b/patternComposer.ts index 9f8a5b9..02a748e 100644 --- a/patternComposer.ts +++ b/patternComposer.ts @@ -186,12 +186,7 @@ namespace micromusic { this.isPlaying = false this.app.popScene() this.app.pushScene( - SettingsScreen.getInstance( - this.app, - this, - this.volume, - this.bpm - ) + SettingsScreen.getInstance(this, this.app) ) }, }), diff --git a/pxt.json b/pxt.json index 3eaf5f6..5519e5e 100644 --- a/pxt.json +++ b/pxt.json @@ -24,7 +24,8 @@ "patternComposer.ts", "song.ts", "pattern.ts", - "channel.ts" + "channel.ts", + "settings.ts" ], "testFiles": [], "targetVersions": { diff --git a/sampleSelectionScreen.ts b/sampleSelectionScreen.ts index 4f5e1a2..3242eea 100644 --- a/sampleSelectionScreen.ts +++ b/sampleSelectionScreen.ts @@ -25,7 +25,6 @@ namespace micromusic { app, function () { this.app.popScene() - this.previousScene.navigator = new GridNavigator() this.app.pushScene(this.previousScene) }, new GridNavigator() diff --git a/settingsScreen.ts b/settingsScreen.ts index 4364f40..e63e86a 100644 --- a/settingsScreen.ts +++ b/settingsScreen.ts @@ -19,28 +19,17 @@ namespace micromusic { private settings: Setting[] private static instance: SettingsScreen | null = null - private constructor( - app: AppInterface, - previousScene: CursorScene, - volume?: Setting, - bpm?: Setting, - other?: Setting - ) { + private constructor(app: AppInterface, previousScene: CursorScene) { super( app, function () { this.app.popScene() - this.previousScene.navigator = new GridNavigator() this.app.pushScene(this.previousScene) }, new GridNavigator() ) - if (!volume) volume = new Setting(100) - if (!bpm) bpm = new Setting(100) - if (!other) other = new Setting(100) - - this.settings = [volume, bpm, other] + this.settings = [Settings.volume, Settings.bpm] this.previousScene = previousScene } @@ -80,13 +69,11 @@ namespace micromusic { new Button({ parent: null, style: ButtonStyles.Transparent, - icon: "back_arrow", + icon: "back_button", x: -68, y: -52, onClick: () => { this.app.popScene() - this.previousScene.navigator = - new GridNavigator() // Has to be given a new GridNavigator, old one stops working this.app.pushScene(this.previousScene) }, }), @@ -96,11 +83,8 @@ namespace micromusic { } public static getInstance( - app?: AppInterface, - previousScene?: CursorScene, - volume?: Setting, - bpm?: Setting, - other?: Setting + previousScene: CursorScene, + app?: AppInterface ) { if (!SettingsScreen.instance) { if (app === undefined) { @@ -111,6 +95,10 @@ namespace micromusic { SettingsScreen.instance = new SettingsScreen(app, previousScene) } + if (previousScene) { + SettingsScreen.instance.previousScene = previousScene + } + return SettingsScreen.instance } diff --git a/song.ts b/song.ts index a04ad8e..a098e7f 100644 --- a/song.ts +++ b/song.ts @@ -3,7 +3,6 @@ namespace micromusic { private _patterns: Pattern[] private _patternSequence: Pattern[] private patternsMade: number - public name: string constructor() { this._patternSequence = [] @@ -34,8 +33,29 @@ namespace micromusic { return this.patterns[this.patterns.length - 1] } + /** + * Remove a pattern from the sequence, + * does not delete the pattern + * */ + public removeSequenceItem(index: number) { + this.patternSequence.splice(index, 1) + } + + /** + * Fully deletes a pattern and removes parts of sequence that include it + * @param pattern the pattern that should be removed entirely + */ public removePattern(pattern: Pattern) { - this.patterns.removeElement(pattern) + this.patterns.splice(this.patterns.indexOf(pattern), 1) + + for (let p of this.patternSequence) { + if (p === pattern) { + this.patternSequence.splice( + this.patterns.indexOf(pattern), + 1 + ) + } + } } } } diff --git a/songComposer.ts b/songComposer.ts index a2efd32..f4b753f 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -9,21 +9,6 @@ namespace micromusic { import ButtonStyles = user_interface_base.ButtonStyles import font = user_interface_base.font - /** - * PLAN FOR SONG PATTERNS - * - * Screen will open up for song - * By default, one block and a plus sign somewhere called "add block" or such - * Clicking add block visually adds a block and sets it up - * Perhaps some class to hold all our info - * Can click onto blocks - * - * For copy paste: - * Click copy, asks whether you want to copy a single channel from a pattern or a number of rows - * Paste into existing or new block, then which channel it should be pasted onto - * - */ - export class SongComposerScreen extends CursorSceneWithPriorPage { private static instance: SongComposerScreen | null = null private song: Song @@ -32,10 +17,9 @@ namespace micromusic { private bpm: Setting private volume: Setting private hasClickedBack: boolean - private isTemp: boolean + private hasClickedPick: boolean private hasClickedNumber: boolean private hasClickedPlus: boolean - private hasClickedReplace: boolean private isPlaying: boolean private clickedPattern: number // TODO: Get pattern for click @@ -78,7 +62,6 @@ namespace micromusic { /*override*/ startup() { super.startup() - console.log(this.song.name) this.cursor.setBorderThickness(1) @@ -123,12 +106,7 @@ namespace micromusic { this.isPlaying = false this.app.popScene() this.app.pushScene( - SettingsScreen.getInstance( - this.app, - this, - this.volume, - this.bpm - ) + SettingsScreen.getInstance(this, this.app) ) }, }), @@ -196,8 +174,8 @@ namespace micromusic { return } - if (this.isTemp) { - this.drawTemp() + if (this.hasClickedPick) { + this.drawPick() this.navigator.drawComponents() this.cursor.draw() this.moveCursor(CursorDir.Down) @@ -211,7 +189,7 @@ namespace micromusic { super.draw() } - private drawTemp() { + private drawPick() { const startX = -56 const startY = 0 const cellWidth = 21 @@ -449,8 +427,8 @@ namespace micromusic { x: 21, y: 18, onClick: () => { - // TODO: Replace, this should let you swap it out or create a new one ideally - this.temp(clickedPatternIndex) + // TODO: Replace, this should let you swap it out or create a new one ideally. New dialogue for this + this.pickClicked(clickedPatternIndex) // this.replaceClicked(clickedPatternIndex) }, }), @@ -459,17 +437,6 @@ namespace micromusic { this.moveCursor(CursorDir.Down) } - private replaceClicked(clickedPatternIndex: number) { - if (this.isPlaying) { - this.resetControllerEvents() - this.isPlaying = false - } - - this.unbindBackButton() - - this.pickPattern(clickedPatternIndex) - } - private plusClicked(clickedPatternIndex: number) { if (this.isPlaying) { this.resetControllerEvents() @@ -498,7 +465,7 @@ namespace micromusic { x: 21, y: 18, onClick: () => { - this.pickPattern(clickedPatternIndex) + this.pickClicked(clickedPatternIndex) this.resetBooleans() }, }), @@ -508,10 +475,9 @@ namespace micromusic { this.moveCursor(CursorDir.Down) } - private temp(clickedPatternIndex: number) { + private pickClicked(clickedPatternIndex: number) { this.resetBooleans() - this.isTemp = true - let patternSelections = [] + this.hasClickedPick = true const startX = -56 const startY = 0 @@ -522,14 +488,12 @@ namespace micromusic { let patternBtns = [] for (let j = 0; j < 2; j++) { - // TODO: patterns won't necessarily be in order due to their id, i can index normally but then display via their id? Potentially won't work, need to know what happens to an array when i remove the middle one - if i Array.splice(count, index) it works and removes the middle one, nice! for (let i = 0; i < 6; i++) { const y = startY + j * cellHeight const x = startX + i * cellWidth const index = count if (count < this.song.patterns.length) { - console.log("testing here") patternBtns[count] = new Button({ parent: null, style: ButtonStyles.Transparent, @@ -561,118 +525,7 @@ namespace micromusic { // TODO: Make this add enough buttons for the number of patterns available patternBtns, ]) - // Way of making isTemp false, also buttons for actually selecting and replacing - } - - private pickPattern(clickedPatternIndex: number) { - this.resetBooleans() - // Create a UI to display all available patterns - Screen.fillRect(-57, -37, 120, 80, 0) - Screen.fillRect(-60, -40, 120, 80, 0x6) - this.drawText(-49, -30, "Select a pattern:") - - const patterns = this.song.patterns - const navButtons = [] - const buttonRow = [] - - // Display pattern options based on available patterns - const startX = -40 - const startY = -10 - const spacing = 25 - const maxPerRow = 3 - - for (let i = 0; i < patterns.length; i++) { - const rowIndex = Math.floor(i / maxPerRow) - const colIndex = i % maxPerRow - const x = startX + colIndex * spacing - const y = startY + rowIndex * 20 - - const patternButton = new Button({ - parent: null, - style: ButtonStyles.Transparent, - icon: "green_tick_2", - x: x, - y: y, - onClick: () => { - // Replace the pattern in the sequence - if ( - this.hasClickedReplace && - clickedPatternIndex !== undefined - ) { - this.song.patternSequence[clickedPatternIndex] = - patterns[i] - this.resetBooleans() - this.fillPatternBtns() - } - }, - }) - - buttonRow.push(patternButton) - - // Display pattern ID or other identifier - Screen.print( - `ID: ${patterns[i].id}`, - x - 8, - y, - 0, - bitmaps.font5 - ) - } - - // Add a button to create a new pattern - const newPatternButton = new Button({ - parent: null, - style: ButtonStyles.Transparent, - icon: "invisiblePatternButton", - x: startX, - y: startY + Math.ceil(patterns.length / maxPerRow) * 20, - onClick: () => { - const newPattern = this.song.newPattern() - if ( - this.hasClickedReplace && - this.clickedPattern !== undefined - ) { - this.song.patternSequence[this.clickedPattern] = - newPattern - } - this.resetBooleans() - this.fillPatternBtns() - }, - }) - - buttonRow.push(newPatternButton) - Screen.print( - "New Pattern", - startX - 12, - startY + Math.ceil(patterns.length / maxPerRow) * 20, - 0, - bitmaps.font5 - ) - - // Add a cancel button - const cancelButton = new Button({ - parent: null, - style: ButtonStyles.Transparent, - icon: "back_arrow", - x: 20, - y: startY + Math.ceil(patterns.length / maxPerRow) * 20, - onClick: () => { - this.resetBooleans() - }, - }) - - buttonRow.push(cancelButton) - Screen.print( - "Cancel", - 10, - startY + Math.ceil(patterns.length / maxPerRow) * 20, - 0, - bitmaps.font5 - ) - - navButtons.push(buttonRow) - this.navigator.setBtns(navButtons) - this.cursor.setOutlineColour(0x2) + // Way of making hasClickedPick false, also buttons for actually selecting and replacing } private editPattern(clickedPattern: Pattern) { @@ -750,8 +603,7 @@ namespace micromusic { this.hasClickedBack = false this.hasClickedNumber = false this.hasClickedPlus = false - this.hasClickedReplace = false - this.isTemp = false + this.hasClickedPick = false this.cursor.setOutlineColour(0x9) }