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.
This commit is contained in:
2025-06-07 13:12:03 +02:00
parent 9c9962d7db
commit 1d02a6ee16

View File

@@ -42,21 +42,27 @@ class KeyboardComponent(
val onNoteUp: (Int) -> Unit = {} val onNoteUp: (Int) -> Unit = {}
) : Komponent() { ) : Komponent() {
// Define a data class for keyboard state
data class KeyboardState(
val octave: Int,
val pressedNotes: Set<Int> = emptySet()
)
// Use immutable state
private var state = KeyboardState(initialOctave)
// Current octave with range validation // Current octave with range validation
private var _octave: Int = initialOctave
var octave: Int var octave: Int
get() = _octave get() = state.octave
set(value) { set(value) {
_octave = when { updateOctave(value)
value < MIN_OCTAVE -> MIN_OCTAVE
value > MAX_OCTAVE -> MAX_OCTAVE
else -> value
}
requestUpdate()
} }
// Set to track which notes are currently pressed // Update state with functions that return new state
private val pressedNotes = mutableSetOf<Int>() private fun updateOctave(newOctave: Int) {
state = state.copy(octave = newOctave.coerceIn(MIN_OCTAVE, MAX_OCTAVE))
requestUpdate()
}
// MIDI note numbers for one octave // MIDI note numbers for one octave
private val whiteKeys = BASE_WHITE_KEYS private val whiteKeys = BASE_WHITE_KEYS
@@ -77,25 +83,25 @@ class KeyboardComponent(
) )
fun noteDown(midiNote: Int) { fun noteDown(midiNote: Int) {
pressedNotes.add(midiNote) state = state.copy(pressedNotes = state.pressedNotes + midiNote)
onNoteDown(midiNote) onNoteDown(midiNote)
requestUpdate() requestUpdate()
} }
fun noteUp(midiNote: Int) { fun noteUp(midiNote: Int) {
pressedNotes.remove(midiNote) state = state.copy(pressedNotes = state.pressedNotes - midiNote)
onNoteUp(midiNote) onNoteUp(midiNote)
requestUpdate() requestUpdate()
} }
private fun releaseAllNotes() { private fun releaseAllNotes() {
// Create a copy of the set to avoid concurrent modification // Create a copy of the set to avoid concurrent modification
val notesToRelease = pressedNotes.toSet() val notesToRelease = state.pressedNotes.toSet()
for (note in notesToRelease) { for (note in notesToRelease) {
noteUp(note) noteUp(note)
} }
// Clear the set just to be safe // Just to be safe, clear all pressed notes
pressedNotes.clear() state = state.copy(pressedNotes = emptySet())
} }
private fun getOctaveOffset(): Int = (octave - OCTAVE_BASE) * NOTES_PER_OCTAVE private fun getOctaveOffset(): Int = (octave - OCTAVE_BASE) * NOTES_PER_OCTAVE
@@ -142,7 +148,7 @@ class KeyboardComponent(
+"<" +"<"
onMouseDownFunction = { event -> onMouseDownFunction = { event ->
if (event is MouseEvent) { if (event is MouseEvent) {
octave-- updateOctave(octave - 1)
event.preventDefault() event.preventDefault()
} }
} }
@@ -167,7 +173,7 @@ class KeyboardComponent(
+">" +">"
onMouseDownFunction = { event -> onMouseDownFunction = { event ->
if (event is MouseEvent) { if (event is MouseEvent) {
octave++ updateOctave(octave + 1)
event.preventDefault() event.preventDefault()
} }
} }
@@ -210,7 +216,7 @@ class KeyboardComponent(
private fun SVG.renderWhiteKeys() { private fun SVG.renderWhiteKeys() {
for (i in 0 until WHITE_KEYS_PER_OCTAVE) { for (i in 0 until WHITE_KEYS_PER_OCTAVE) {
val midiNote = whiteKeys[i] + getOctaveOffset() val midiNote = whiteKeys[i] + getOctaveOffset()
val isPressed = pressedNotes.contains(midiNote) val isPressed = state.pressedNotes.contains(midiNote)
rect( rect(
i * whiteKeyWidth, i * whiteKeyWidth,
0, 0,
@@ -226,7 +232,7 @@ class KeyboardComponent(
private fun SVG.renderBlackKeys() { private fun SVG.renderBlackKeys() {
for (i in 0 until BLACK_KEYS_PER_OCTAVE) { for (i in 0 until BLACK_KEYS_PER_OCTAVE) {
val midiNote = blackKeys[i] + getOctaveOffset() val midiNote = blackKeys[i] + getOctaveOffset()
val isPressed = pressedNotes.contains(midiNote) val isPressed = state.pressedNotes.contains(midiNote)
rect( rect(
blackKeyPositions[i], blackKeyPositions[i],
0, 0,