h a r m o n y

This commit is contained in:
Sasha Koshka 2023-02-09 16:15:02 -05:00
parent 06e97461fa
commit 5446ffe40b
4 changed files with 110 additions and 17 deletions

View File

@ -18,7 +18,9 @@ type pianoKey struct {
// Piano is an element that can be used to input midi notes. // Piano is an element that can be used to input midi notes.
type Piano struct { type Piano struct {
*core.Core *core.Core
*core.FocusableCore
core core.CoreControl core core.CoreControl
focusableControl core.FocusableCoreControl
low, high music.Octave low, high music.Octave
config config.Wrapped config config.Wrapped
@ -28,6 +30,7 @@ type Piano struct {
sharpKeys []pianoKey sharpKeys []pianoKey
pressed *pianoKey pressed *pianoKey
keynavPressed map[music.Note] bool
onPress func (music.Note) onPress func (music.Note)
onRelease func (music.Note) onRelease func (music.Note)
@ -45,12 +48,16 @@ func NewPiano (low, high music.Octave) (element *Piano) {
element = &Piano { element = &Piano {
low: low, low: low,
high: high, high: high,
keynavPressed: make(map[music.Note] bool),
} }
element.theme.Case = theme.C("fun", "piano") element.theme.Case = theme.C("fun", "piano")
element.Core, element.core = core.NewCore (func () { element.Core, element.core = core.NewCore (func () {
element.recalculate() element.recalculate()
element.draw() element.draw()
}) })
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo)
element.updateMinimumSize() element.updateMinimumSize()
return return
} }
@ -66,13 +73,14 @@ func (element *Piano) OnRelease (callback func (note music.Note)) {
} }
func (element *Piano) HandleMouseDown (x, y int, button input.Button) { func (element *Piano) HandleMouseDown (x, y int, button input.Button) {
element.Focus()
if button != input.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressUnderMouseCursor(image.Pt(x, y)) element.pressUnderMouseCursor(image.Pt(x, y))
} }
func (element *Piano) HandleMouseUp (x, y int, button input.Button) { func (element *Piano) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return } if button != input.ButtonLeft { return }
if element.onRelease != nil { if element.onRelease != nil && element.pressed != nil {
element.onRelease((*element.pressed).Note) element.onRelease((*element.pressed).Note)
} }
element.pressed = nil element.pressed = nil
@ -118,6 +126,52 @@ func (element *Piano) pressUnderMouseCursor (point image.Point) {
} }
} }
var noteForKey = map[input.Key] music.Note {
'a': 60,
'w': 61,
's': 62,
'e': 63,
'd': 64,
'f': 65,
't': 66,
'g': 67,
'y': 68,
'h': 69,
'u': 70,
'j': 71,
'k': 72,
'o': 73,
'l': 74,
'p': 75,
';': 76,
'\'': 77,
}
func (element *Piano) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if !element.Enabled() { return }
note, exists := noteForKey[key]
if !exists { return }
if !element.keynavPressed[note] {
element.keynavPressed[note] = true
if element.onPress != nil {
element.onPress(note)
}
element.redo()
}
}
func (element *Piano) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
note, exists := noteForKey[key]
if !exists { return }
_, pressed := element.keynavPressed[note]
if !pressed { return }
delete(element.keynavPressed, note)
if element.onRelease != nil {
element.onRelease(note)
}
element.redo()
}
// SetTheme sets the element's theme. // SetTheme sets the element's theme.
func (element *Piano) SetTheme (new theme.Theme) { func (element *Piano) SetTheme (new theme.Theme) {
if new == element.theme.Theme { return } if new == element.theme.Theme { return }
@ -194,38 +248,49 @@ func (element *Piano) recalculate () {
} }
func (element *Piano) draw () { func (element *Piano) draw () {
state := theme.PatternState { } state := theme.PatternState {
Focused: element.Focused(),
Disabled: !element.Enabled(),
}
pattern := element.theme.Pattern(theme.PatternSunken, state) pattern := element.theme.Pattern(theme.PatternSunken, state)
// inset := element.theme.Inset(theme.PatternSunken) // inset := element.theme.Inset(theme.PatternSunken)
artist.FillRectangle(element, pattern, element.Bounds()) artist.FillRectangle(element, pattern, element.Bounds())
for _, key := range element.flatKeys { for _, key := range element.flatKeys {
_, keynavPressed := element.keynavPressed[key.Note]
element.drawFlat ( element.drawFlat (
key.Rectangle, key.Rectangle,
element.pressed != nil && element.pressed != nil &&
(*element.pressed).Note == key.Note) (*element.pressed).Note == key.Note || keynavPressed,
state)
} }
for _, key := range element.sharpKeys { for _, key := range element.sharpKeys {
_, keynavPressed := element.keynavPressed[key.Note]
element.drawSharp ( element.drawSharp (
key.Rectangle, key.Rectangle,
element.pressed != nil && element.pressed != nil &&
(*element.pressed).Note == key.Note) (*element.pressed).Note == key.Note || keynavPressed,
state)
} }
} }
func (element *Piano) drawFlat (bounds image.Rectangle, pressed bool) { func (element *Piano) drawFlat (
state := theme.PatternState { bounds image.Rectangle,
Pressed: pressed, pressed bool,
} state theme.PatternState,
) {
state.Pressed = pressed
pattern := element.theme.Theme.Pattern ( pattern := element.theme.Theme.Pattern (
theme.PatternButton, theme.C("fun", "flatKey"), state) theme.PatternButton, theme.C("fun", "flatKey"), state)
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
} }
func (element *Piano) drawSharp (bounds image.Rectangle, pressed bool) { func (element *Piano) drawSharp (
state := theme.PatternState { bounds image.Rectangle,
Pressed: pressed, pressed bool,
} state theme.PatternState,
) {
state.Pressed = pressed
pattern := element.theme.Theme.Pattern ( pattern := element.theme.Theme.Pattern (
theme.PatternButton, theme.C("fun", "sharpKey"), state) theme.PatternButton, theme.C("fun", "sharpKey"), state)
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)

View File

@ -64,7 +64,7 @@ func stopNote (note music.Note) {
} }
func playNote (note music.Note) { func playNote (note music.Note) {
streamer, _ := Tone(sampleRate, int(tuning.Tune(note)), waveform) streamer, _ := Tone(sampleRate, int(tuning.Tune(note)), waveform, 0.3)
stopNote(note) stopNote(note)
speaker.Lock() speaker.Lock()
@ -80,12 +80,21 @@ type toneStreamer struct {
position float64 position float64
delta float64 delta float64
waveform int waveform int
gain float64
} }
func Tone (sr beep.SampleRate, freq int, waveform int) (beep.Streamer, error) { func Tone (
sr beep.SampleRate,
freq int,
waveform int,
gain float64,
) (
beep.Streamer,
error,
) {
if int(sr) / freq < 2 { if int(sr) / freq < 2 {
return nil, errors.New ( return nil, errors.New (
"square tone generator: samplerate must be at least " + "tone generator: samplerate must be at least " +
"2 times greater then frequency") "2 times greater then frequency")
} }
tone := new(toneStreamer) tone := new(toneStreamer)
@ -93,6 +102,7 @@ func Tone (sr beep.SampleRate, freq int, waveform int) (beep.Streamer, error) {
tone.waveform = waveform tone.waveform = waveform
steps := float64(sr) / float64(freq) steps := float64(sr) / float64(freq)
tone.delta = 1.0 / steps tone.delta = 1.0 / steps
tone.gain = gain
return tone, nil return tone, nil
} }
@ -122,7 +132,7 @@ func (tone *toneStreamer) nextSample () (sample float64) {
func (tone *toneStreamer) Stream (buf [][2]float64) (int, bool) { func (tone *toneStreamer) Stream (buf [][2]float64) (int, bool) {
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
sample := tone.nextSample() sample := tone.nextSample() * tone.gain
buf[i] = [2]float64{sample, sample} buf[i] = [2]float64{sample, sample}
} }
return len(buf), true return len(buf), true

View File

@ -88,7 +88,11 @@ func (Default) Pattern (
} }
} }
} else { } else {
return sunkenPattern if state.Focused {
return focusedSunkenPattern
} else {
return sunkenPattern
}
} }
case PatternPinboard: case PatternPinboard:
return texturedSunkenPattern return texturedSunkenPattern
@ -101,6 +105,12 @@ func (Default) Pattern (
return pressedDarkButtonPattern return pressedDarkButtonPattern
} else { } else {
return darkButtonPattern return darkButtonPattern
}
} else if c == C("fun", "flatKey") {
if state.Pressed {
return pressedButtonPattern
} else {
return buttonPattern
} }
} else { } else {
if state.Pressed || state.On && c == C("basic", "checkbox") { if state.Pressed || state.On && c == C("basic", "checkbox") {

View File

@ -19,6 +19,14 @@ var sunkenPattern = artist.NewMultiBordered (
}, },
}, },
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
var focusedSunkenPattern = artist.NewMultiBordered (
artist.Stroke { Weight: 1, Pattern: strokePattern },
artist.Stroke {
Weight: 1,
Pattern: accentPattern,
},
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
var texturedSunkenPattern = artist.NewMultiBordered ( var texturedSunkenPattern = artist.NewMultiBordered (
artist.Stroke { Weight: 1, Pattern: strokePattern }, artist.Stroke { Weight: 1, Pattern: strokePattern },