From 1d02a6ee16ba7de146de568b86419a7c3dede52a Mon Sep 17 00:00:00 2001 From: rnentjes Date: Sat, 7 Jun 2025 13:12:03 +0200 Subject: [PATCH] Refactor `KeyboardComponent` to use immutable state for octave and pressed notes Replaced mutable state with an immutable `KeyboardState` data class to track octave and pressed notes. Updated state management logic with functional updates for improved consistency and immutability. Simplified note handling and rendering to reference the unified state object. --- .../vst/ui/components/KeyboardComponent.kt | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt b/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt index db787fe..a0f6c50 100644 --- a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt @@ -42,21 +42,27 @@ class KeyboardComponent( val onNoteUp: (Int) -> Unit = {} ) : Komponent() { + // Define a data class for keyboard state + data class KeyboardState( + val octave: Int, + val pressedNotes: Set = emptySet() + ) + + // Use immutable state + private var state = KeyboardState(initialOctave) + // Current octave with range validation - private var _octave: Int = initialOctave var octave: Int - get() = _octave + get() = state.octave set(value) { - _octave = when { - value < MIN_OCTAVE -> MIN_OCTAVE - value > MAX_OCTAVE -> MAX_OCTAVE - else -> value - } - requestUpdate() + updateOctave(value) } - // Set to track which notes are currently pressed - private val pressedNotes = mutableSetOf() + // Update state with functions that return new state + private fun updateOctave(newOctave: Int) { + state = state.copy(octave = newOctave.coerceIn(MIN_OCTAVE, MAX_OCTAVE)) + requestUpdate() + } // MIDI note numbers for one octave private val whiteKeys = BASE_WHITE_KEYS @@ -77,25 +83,25 @@ class KeyboardComponent( ) fun noteDown(midiNote: Int) { - pressedNotes.add(midiNote) + state = state.copy(pressedNotes = state.pressedNotes + midiNote) onNoteDown(midiNote) requestUpdate() } fun noteUp(midiNote: Int) { - pressedNotes.remove(midiNote) + state = state.copy(pressedNotes = state.pressedNotes - midiNote) onNoteUp(midiNote) requestUpdate() } private fun releaseAllNotes() { // Create a copy of the set to avoid concurrent modification - val notesToRelease = pressedNotes.toSet() + val notesToRelease = state.pressedNotes.toSet() for (note in notesToRelease) { noteUp(note) } - // Clear the set just to be safe - pressedNotes.clear() + // Just to be safe, clear all pressed notes + state = state.copy(pressedNotes = emptySet()) } private fun getOctaveOffset(): Int = (octave - OCTAVE_BASE) * NOTES_PER_OCTAVE @@ -142,7 +148,7 @@ class KeyboardComponent( +"<" onMouseDownFunction = { event -> if (event is MouseEvent) { - octave-- + updateOctave(octave - 1) event.preventDefault() } } @@ -167,7 +173,7 @@ class KeyboardComponent( +">" onMouseDownFunction = { event -> if (event is MouseEvent) { - octave++ + updateOctave(octave + 1) event.preventDefault() } } @@ -210,7 +216,7 @@ class KeyboardComponent( private fun SVG.renderWhiteKeys() { for (i in 0 until WHITE_KEYS_PER_OCTAVE) { val midiNote = whiteKeys[i] + getOctaveOffset() - val isPressed = pressedNotes.contains(midiNote) + val isPressed = state.pressedNotes.contains(midiNote) rect( i * whiteKeyWidth, 0, @@ -226,7 +232,7 @@ class KeyboardComponent( private fun SVG.renderBlackKeys() { for (i in 0 until BLACK_KEYS_PER_OCTAVE) { val midiNote = blackKeys[i] + getOctaveOffset() - val isPressed = pressedNotes.contains(midiNote) + val isPressed = state.pressedNotes.contains(midiNote) rect( blackKeyPositions[i], 0,