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 = {}
) : 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
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<Int>()
// 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,