diff --git a/TODO.md b/TODO.md index 74958ac..28bb32e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,38 @@ ### 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 - [ ] 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/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/home.ts b/home.ts index 7d4f8e2..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,7 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene( - PatternScreen.getInstance(this.app) + SongComposerScreen.getInstance(this.app) ) }, })), @@ -59,11 +56,7 @@ namespace micromusic { onClick: () => { this.app.popScene() this.app.pushScene( - SettingsScreen.getInstance( - this.app, - this, - this.volume - ) + SettingsScreen.getInstance(this, this.app) ) }, })), @@ -133,7 +126,6 @@ namespace micromusic { this.liveDataBtn.draw() this.recordDataBtn.draw() this.distributedLoggingBtn.draw() - // this.viewBtn.draw() this.drawVersion() super.draw() 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..02a748e 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,23 +108,28 @@ namespace micromusic { this.isSelectingSample = false this.playedNote = 0 this.hasClickedBack = false - this.currentBank = 0 } public static getInstance( + pattern: Pattern, app?: AppInterface, volume?: Setting, bpm?: Setting ) { 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) + PatternScreen.instance = new PatternScreen(app, pattern) } + if (!pattern) { + console.error("Pattern required") + } + PatternScreen.instance.setPattern(pattern) + return PatternScreen.instance } @@ -188,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) ) }, }), @@ -233,6 +226,10 @@ namespace micromusic { this.resetControllerEvents() } + private setPattern(pattern: Pattern) { + this.pattern = pattern + } + private backConfirmation() { if (this.isPlaying) { this.resetControllerEvents() @@ -249,8 +246,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({ @@ -262,8 +260,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) }, }), ], @@ -430,9 +428,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() @@ -446,6 +445,7 @@ namespace micromusic { this.drawSamples() this.drawGrid() this.drawBankSelector() + this.cursor.setOutlineColour(0x9) super.draw() } @@ -534,27 +534,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/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 c7b7671..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() @@ -213,6 +212,7 @@ namespace micromusic { Screen.print("Select Sample", -40, -50, 0x1) this.cursor.draw() + super.draw() } } } 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 + } + } +} 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 316c43c..a098e7f 100644 --- a/song.ts +++ b/song.ts @@ -1,6 +1,61 @@ namespace micromusic { export class Song { - private patternCount: number - private patterns: Pattern[] + private _patterns: Pattern[] + private _patternSequence: Pattern[] + private patternsMade: number + + constructor() { + this._patternSequence = [] + this._patterns = [] + this.patternsMade = 0 + + // 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 newPattern(): Pattern { + this.patterns[this.patterns.length] = new Pattern(this.patternsMade) + + this.patternsMade += 1 + + 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.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 8d15f56..f4b753f 100644 --- a/songComposer.ts +++ b/songComposer.ts @@ -9,20 +9,653 @@ 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 {} + export class SongComposerScreen extends CursorSceneWithPriorPage { + private static instance: SongComposerScreen | null = null + private song: Song + private controlBtns: Button[] + private patternBtns: Button[] + private bpm: Setting + private volume: Setting + private hasClickedBack: boolean + private hasClickedPick: boolean + private hasClickedNumber: boolean + private hasClickedPlus: boolean + private isPlaying: boolean + private clickedPattern: number // TODO: Get pattern for click + + 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) + SongComposerScreen.instance.setSong(new Song()) + } + + if (song) { + SongComposerScreen.instance.setSong(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, this.app) + ) + }, + }), + 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.fillPatternBtns() + this.resetNavigator() + this.resetControllerEvents() + } + + draw() { + Screen.fillRect( + Screen.LEFT_EDGE, + Screen.TOP_EDGE, + Screen.WIDTH, + Screen.HEIGHT, + 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 + } + + if (this.hasClickedPick) { + this.drawPick() + this.navigator.drawComponents() + this.cursor.draw() + this.moveCursor(CursorDir.Down) + return + } + + this.drawBoxes() + + this.navigator.drawComponents() + this.cursor.draw() + super.draw() + } + + private drawPick() { + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + + 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.patterns.length) { + break + } + + if (count < this.song.patterns.length) { + const x = startX + i * cellWidth + + Screen.print( + this.song.patterns[count].id.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) + 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 + + 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( + 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) + } + + 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 + } + } + if (count > this.song.patternSequence.length) { + break + } + } + } + + private play() {} + + private pause() {} + + private stop() {} + + private fastForward() {} + + private rewind() {} + + private backConfirmation() { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } + + this.hasClickedBack = true + this.unbindBackButton() + 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)) + this.hasClickedBack = false + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic, + x: 21, + y: 18, + onClick: () => { + this.hasClickedBack = false + this.resetNavigator() + this.resetControllerEvents() + this.moveCursor(CursorDir.Up) + this.moveCursor(CursorDir.Down) + }, + }), + ], + ]) + this.unbindBackButton() + this.moveCursor(CursorDir.Down) + } + + private patternClicked(clickedPatternIndex: number) { + if (this.isPlaying) { + this.resetControllerEvents() + this.isPlaying = false + } + + 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().doubledY(), + x: 21, + y: 18, + onClick: () => { + // 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) + }, + }), + ], + ]) + 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: () => { + this.newPattern(clickedPatternIndex) + this.resetBooleans() + }, + }), + new Button({ + parent: null, + style: ButtonStyles.Transparent, + icon: ic.doubledX(), + x: 21, + y: 18, + onClick: () => { + this.pickClicked(clickedPatternIndex) + this.resetBooleans() + }, + }), + ], + ]) + this.cursor.setOutlineColour(0x2) + this.moveCursor(CursorDir.Down) + } + + private pickClicked(clickedPatternIndex: number) { + this.resetBooleans() + this.hasClickedPick = true + + const startX = -56 + const startY = 0 + const cellWidth = 21 + const cellHeight = 31 + let count = 0 + + let patternBtns = [] + + for (let j = 0; j < 2; j++) { + 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) { + 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 hasClickedPick false, also buttons for actually selecting and replacing + } + + 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 = [] + + 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 + const index = count + + 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(index) + }, + }) + } 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(index) + }, + }) + } + + 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 resetBooleans() { + this.hasClickedBack = false + this.hasClickedNumber = false + this.hasClickedPlus = false + this.hasClickedPick = false + this.cursor.setOutlineColour(0x9) + } + + private unbindBackButton() { + control.onEvent( + ControllerButtonEvent.Pressed, + controller.B.id, + () => { + this.resetBooleans() + this.resetControllerEvents() + this.resetNavigator() + this.cursor.setOutlineColour(0x9) + this.moveCursor(CursorDir.Left) + this.moveCursor(CursorDir.Right) + } + ) + } + + 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) + } + ) + } + } }