h a r m o n y
This commit is contained in:
parent
06e97461fa
commit
5446ffe40b
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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") {
|
||||||
|
@ -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 },
|
||||||
|
Reference in New Issue
Block a user