Add KeyboardComponent
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
package nl.astraeus.vst.ui.components
|
||||
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.js.onMouseDownFunction
|
||||
import kotlinx.html.js.onMouseLeaveFunction
|
||||
import kotlinx.html.js.onMouseUpFunction
|
||||
import kotlinx.html.style
|
||||
import kotlinx.html.svg
|
||||
import nl.astraeus.css.properties.Position
|
||||
import nl.astraeus.css.properties.TextAlign
|
||||
import nl.astraeus.css.properties.px
|
||||
import nl.astraeus.css.properties.rem
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.ui.css.Css
|
||||
import nl.astraeus.vst.ui.css.Css.defineCss
|
||||
import nl.astraeus.vst.ui.css.CssId
|
||||
import nl.astraeus.vst.ui.css.CssName
|
||||
import nl.astraeus.vst.ui.util.height
|
||||
import nl.astraeus.vst.ui.util.rect
|
||||
import nl.astraeus.vst.ui.util.width
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
|
||||
/**
|
||||
* The keyboard component shows 1 octabe of a (piano) keyboard and
|
||||
* calls the noteDown and noteUp methods when the keys are clicked
|
||||
*/
|
||||
class KeyboardComponent(
|
||||
val title: String = "Keyboard",
|
||||
val octave: Int = 4,
|
||||
val onNoteDown: (Int) -> Unit = {},
|
||||
val onNoteUp: (Int) -> Unit = {}
|
||||
) : Komponent() {
|
||||
|
||||
// Set to track which notes are currently pressed
|
||||
private val pressedNotes = mutableSetOf<Int>()
|
||||
|
||||
// MIDI note numbers for C4 to B4 (one octave)
|
||||
private val whiteKeys = listOf(60, 62, 64, 65, 67, 69, 71)
|
||||
private val blackKeys = listOf(61, 63, 66, 68, 70)
|
||||
|
||||
// Key dimensions
|
||||
private val keyboardWidth = 210
|
||||
private val keyboardHeight = 100
|
||||
private val whiteKeyWidth = 30
|
||||
private val whiteKeyHeight = 100
|
||||
private val blackKeyWidth = 20
|
||||
private val blackKeyHeight = 60
|
||||
|
||||
// Calculate positions for black keys
|
||||
private val blackKeyPositions = listOf(
|
||||
whiteKeyWidth - blackKeyWidth/2,
|
||||
whiteKeyWidth*2 - blackKeyWidth/2,
|
||||
whiteKeyWidth*4 - blackKeyWidth/2,
|
||||
whiteKeyWidth*5 - blackKeyWidth/2,
|
||||
whiteKeyWidth*6 - blackKeyWidth/2
|
||||
)
|
||||
|
||||
fun noteDown(midiNote: Int) {
|
||||
println("noteDown $midiNote")
|
||||
pressedNotes.add(midiNote)
|
||||
onNoteDown(midiNote)
|
||||
}
|
||||
|
||||
fun noteUp(midiNote: Int) {
|
||||
println("noteUp $midiNote")
|
||||
pressedNotes.remove(midiNote)
|
||||
onNoteUp(midiNote)
|
||||
}
|
||||
|
||||
private fun releaseAllNotes() {
|
||||
// Create a copy of the set to avoid concurrent modification
|
||||
val notesToRelease = pressedNotes.toSet()
|
||||
for (note in notesToRelease) {
|
||||
noteUp(note)
|
||||
}
|
||||
// Clear the set just to be safe
|
||||
pressedNotes.clear()
|
||||
}
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
div(KeyboardCls.name) {
|
||||
style = "width: ${keyboardWidth}px; height: ${keyboardHeight + 60}px"
|
||||
|
||||
onMouseLeaveFunction = { event ->
|
||||
if (event is MouseEvent) {
|
||||
releaseAllNotes()
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
div(KeyboardTitleCls.name) {
|
||||
// Show title of the keyboard
|
||||
+title
|
||||
}
|
||||
|
||||
div(KeyboardOctaveCls.name) {
|
||||
// Show current octave the piano is being played at
|
||||
+"Octave: $octave"
|
||||
}
|
||||
|
||||
div(KeyboardKeysCls.name) {
|
||||
// Draw the keyboard with SVG, and mousedown and mouseup methods
|
||||
// that call noteDown and noteUp
|
||||
svg {
|
||||
width(keyboardWidth)
|
||||
height(keyboardHeight)
|
||||
|
||||
// Define mouse event handlers at the SVG level
|
||||
onMouseDownFunction = { event ->
|
||||
if (event is MouseEvent) {
|
||||
val x = event.offsetX
|
||||
val y = event.offsetY
|
||||
|
||||
// Check if click is on a black key (black keys are on top of white keys)
|
||||
if (y <= blackKeyHeight) {
|
||||
var blackKeyPressed = false
|
||||
for (j in 0 until 5) {
|
||||
if (x >= blackKeyPositions[j] && x <= blackKeyPositions[j] + blackKeyWidth) {
|
||||
noteDown(blackKeys[j] + (octave - 4) * 12)
|
||||
blackKeyPressed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If no black key was pressed, check for white key
|
||||
if (!blackKeyPressed) {
|
||||
// Check if click is on a white key
|
||||
val keyIndex = (x / whiteKeyWidth).toInt()
|
||||
if (keyIndex in 0..6) {
|
||||
noteDown(whiteKeys[keyIndex] + (octave - 4) * 12)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If y > blackKeyHeight, it's definitely a white key
|
||||
val keyIndex = (x / whiteKeyWidth).toInt()
|
||||
if (keyIndex in 0..6) {
|
||||
noteDown(whiteKeys[keyIndex] + (octave - 4) * 12)
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUpFunction = { event ->
|
||||
if (event is MouseEvent) {
|
||||
val x = event.offsetX
|
||||
val y = event.offsetY
|
||||
|
||||
// Check if release is on a black key (black keys are on top of white keys)
|
||||
if (y <= blackKeyHeight) {
|
||||
var blackKeyReleased = false
|
||||
for (j in 0 until 5) {
|
||||
if (x >= blackKeyPositions[j] && x <= blackKeyPositions[j] + blackKeyWidth) {
|
||||
noteUp(blackKeys[j] + (octave - 4) * 12)
|
||||
blackKeyReleased = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If no black key was released, check for white key
|
||||
if (!blackKeyReleased) {
|
||||
// Check if release is on a white key
|
||||
val keyIndex = (x / whiteKeyWidth).toInt()
|
||||
if (keyIndex in 0..6) {
|
||||
noteUp(whiteKeys[keyIndex] + (octave - 4) * 12)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If y > blackKeyHeight, it's definitely a white key
|
||||
val keyIndex = (x / whiteKeyWidth).toInt()
|
||||
if (keyIndex in 0..6) {
|
||||
noteUp(whiteKeys[keyIndex] + (octave - 4) * 12)
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
// Draw white keys
|
||||
for (i in 0 until 7) {
|
||||
val midiNote = whiteKeys[i] + (octave - 4) * 12
|
||||
rect(
|
||||
i * whiteKeyWidth,
|
||||
0,
|
||||
whiteKeyWidth,
|
||||
whiteKeyHeight,
|
||||
0,
|
||||
WhiteKeyCls.name
|
||||
)
|
||||
}
|
||||
|
||||
// Draw black keys
|
||||
for (i in 0 until 5) {
|
||||
val midiNote = blackKeys[i] + (octave - 4) * 12
|
||||
rect(
|
||||
blackKeyPositions[i],
|
||||
0,
|
||||
blackKeyWidth,
|
||||
blackKeyHeight,
|
||||
0,
|
||||
BlackKeyCls.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : CssId("keyboard") {
|
||||
object KeyboardCls : CssName()
|
||||
object KeyboardTitleCls : CssName()
|
||||
object KeyboardOctaveCls : CssName()
|
||||
object KeyboardKeysCls : CssName()
|
||||
object WhiteKeyCls : CssName()
|
||||
object BlackKeyCls : CssName()
|
||||
|
||||
init {
|
||||
defineCss {
|
||||
select(cls(KeyboardCls)) {
|
||||
position(Position.relative)
|
||||
margin(5.px)
|
||||
|
||||
select(cls(KeyboardTitleCls)) {
|
||||
position(Position.absolute)
|
||||
width(100.px)
|
||||
textAlign(TextAlign.center)
|
||||
fontSize(1.2.rem)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
top(5.px)
|
||||
left(0.px)
|
||||
right(0.px)
|
||||
}
|
||||
|
||||
select(cls(KeyboardOctaveCls)) {
|
||||
position(Position.absolute)
|
||||
width(100.px)
|
||||
textAlign(TextAlign.center)
|
||||
fontSize(1.0.rem)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
top(30.px)
|
||||
left(0.px)
|
||||
right(0.px)
|
||||
}
|
||||
|
||||
select(cls(KeyboardKeysCls)) {
|
||||
position(Position.absolute)
|
||||
top(60.px)
|
||||
}
|
||||
}
|
||||
|
||||
select(cls(WhiteKeyCls)) {
|
||||
plain("fill", "#FFFFFF")
|
||||
plain("stroke", "#000000")
|
||||
plain("stroke-width", "1")
|
||||
}
|
||||
|
||||
select(cls(BlackKeyCls)) {
|
||||
plain("fill", "#000000")
|
||||
plain("stroke", "#000000")
|
||||
plain("stroke-width", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.ui.components.ExpKnobComponent
|
||||
import nl.astraeus.vst.ui.components.KeyboardComponent
|
||||
import nl.astraeus.vst.ui.components.KnobComponent
|
||||
import nl.astraeus.vst.ui.css.Css
|
||||
import nl.astraeus.vst.ui.css.Css.defineCss
|
||||
@@ -110,6 +111,10 @@ class MainView : Komponent() {
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
include(KeyboardComponent())
|
||||
}
|
||||
/*
|
||||
div {
|
||||
span(ButtonBarCss.name) {
|
||||
@@ -176,7 +181,6 @@ class MainView : Komponent() {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object MainViewCss : CssName() {
|
||||
|
||||
Reference in New Issue
Block a user