The piano plays sound
This commit is contained in:
69
elements/fun/music/music.go
Normal file
69
elements/fun/music/music.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package music
|
||||
|
||||
import "math"
|
||||
|
||||
var semitone = math.Pow(2, 1.0 / 12.0)
|
||||
|
||||
// Tuning is an interface representing a tuning.
|
||||
type Tuning interface {
|
||||
// Tune returns the frequency of a given note in Hz.
|
||||
Tune (Note) float64
|
||||
}
|
||||
|
||||
// EqualTemparment implements twelve-tone equal temparment.
|
||||
type EqualTemparment struct { A4 float64 }
|
||||
|
||||
// Tune returns the EqualTemparment frequency of a given note in Hz.
|
||||
func (tuning EqualTemparment) Tune (note Note) float64 {
|
||||
return tuning.A4 * math.Pow(semitone, float64(note - NoteA4))
|
||||
}
|
||||
|
||||
// Octave represents a MIDI octave.
|
||||
type Octave int
|
||||
|
||||
// Note returns the note at the specified scale degree in the chromatic scale.
|
||||
func (octave Octave) Note (degree int) Note {
|
||||
return Note(int(octave + 1) * 12 + degree)
|
||||
}
|
||||
|
||||
// Note represents a MIDI note.
|
||||
type Note int
|
||||
|
||||
const (
|
||||
NoteC0 Note = iota
|
||||
NoteDb0
|
||||
NoteD0
|
||||
NoteEb0
|
||||
NoteE0
|
||||
NoteF0
|
||||
NoteGb0
|
||||
NoteG0
|
||||
NoteAb0
|
||||
NoteA0
|
||||
NoteBb0
|
||||
NoteB0
|
||||
|
||||
NoteA4 Note = 69
|
||||
)
|
||||
|
||||
// Octave returns the octave of the note
|
||||
func (note Note) Octave () int {
|
||||
return int(note / 12 - 1)
|
||||
}
|
||||
|
||||
// Degree returns the scale degree of the note in the chromatic scale.
|
||||
func (note Note) Degree () int {
|
||||
mod := note % 12
|
||||
if mod < 0 { mod += 12 }
|
||||
return int(mod)
|
||||
}
|
||||
|
||||
// IsSharp returns whether or not the note is a sharp.
|
||||
func (note Note) IsSharp () bool {
|
||||
degree := note.Degree()
|
||||
return degree == 1 ||
|
||||
degree == 3 ||
|
||||
degree == 6 ||
|
||||
degree == 8 ||
|
||||
degree == 10
|
||||
}
|
||||
@@ -6,51 +6,19 @@ import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
|
||||
// Octave represents a MIDI octave.
|
||||
type Octave int
|
||||
|
||||
// Note returns the note at the specified scale degree in the chromatic scale.
|
||||
func (octave Octave) Note (degree int) Note {
|
||||
return Note(int(octave + 1) * 12 + degree)
|
||||
}
|
||||
|
||||
// Note represents a MIDI note.
|
||||
type Note int
|
||||
|
||||
// Octave returns the octave of the note
|
||||
func (note Note) Octave () int {
|
||||
return int(note / 12 - 1)
|
||||
}
|
||||
|
||||
// Degree returns the scale degree of the note in the chromatic scale.
|
||||
func (note Note) Degree () int {
|
||||
mod := note % 12
|
||||
if mod < 0 { mod += 12 }
|
||||
return int(mod)
|
||||
}
|
||||
|
||||
// IsSharp returns whether or not the note is a sharp.
|
||||
func (note Note) IsSharp () bool {
|
||||
degree := note.Degree()
|
||||
return degree == 1 ||
|
||||
degree == 3 ||
|
||||
degree == 6 ||
|
||||
degree == 8 ||
|
||||
degree == 10
|
||||
}
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music"
|
||||
|
||||
const pianoKeyWidth = 18
|
||||
|
||||
type pianoKey struct {
|
||||
image.Rectangle
|
||||
Note
|
||||
music.Note
|
||||
}
|
||||
|
||||
type Piano struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
low, high Octave
|
||||
low, high music.Octave
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
@@ -60,11 +28,11 @@ type Piano struct {
|
||||
|
||||
pressed *pianoKey
|
||||
|
||||
onPress func (Note)
|
||||
onRelease func (Note)
|
||||
onPress func (music.Note)
|
||||
onRelease func (music.Note)
|
||||
}
|
||||
|
||||
func NewPiano (low, high Octave) (element *Piano) {
|
||||
func NewPiano (low, high music.Octave) (element *Piano) {
|
||||
element = &Piano {
|
||||
low: low,
|
||||
high: high,
|
||||
@@ -79,12 +47,12 @@ func NewPiano (low, high Octave) (element *Piano) {
|
||||
}
|
||||
|
||||
// OnPress sets a function to be called when a key is pressed.
|
||||
func (element *Piano) OnPress (callback func (note Note)) {
|
||||
func (element *Piano) OnPress (callback func (note music.Note)) {
|
||||
element.onPress = callback
|
||||
}
|
||||
|
||||
// OnRelease sets a function to be called when a key is released.
|
||||
func (element *Piano) OnRelease (callback func (note Note)) {
|
||||
func (element *Piano) OnRelease (callback func (note music.Note)) {
|
||||
element.onRelease = callback
|
||||
}
|
||||
|
||||
@@ -110,11 +78,6 @@ func (element *Piano) HandleMouseMove (x, y int) {
|
||||
func (element *Piano) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
|
||||
|
||||
func (element *Piano) pressUnderMouseCursor (point image.Point) {
|
||||
// release previous note
|
||||
if element.pressed != nil && element.onRelease != nil {
|
||||
element.onRelease((*element.pressed).Note)
|
||||
}
|
||||
|
||||
// find out which note is being pressed
|
||||
newKey := (*pianoKey)(nil)
|
||||
for index, key := range element.flatKeys {
|
||||
@@ -132,6 +95,11 @@ func (element *Piano) pressUnderMouseCursor (point image.Point) {
|
||||
if newKey == nil { return }
|
||||
|
||||
if newKey != element.pressed {
|
||||
// release previous note
|
||||
if element.pressed != nil && element.onRelease != nil {
|
||||
element.onRelease((*element.pressed).Note)
|
||||
}
|
||||
|
||||
// press new note
|
||||
element.pressed = newKey
|
||||
if element.onPress != nil {
|
||||
@@ -198,7 +166,7 @@ func (element *Piano) recalculate () {
|
||||
element.sharpKeys[sharpIndex].Rectangle = image.Rect (
|
||||
-(pianoKeyWidth * 3) / 7, 0,
|
||||
(pianoKeyWidth * 3) / 7,
|
||||
bounds.Dy() / 2).Add(dot)
|
||||
(bounds.Dy() * 5) / 8).Add(dot)
|
||||
element.sharpKeys[sharpIndex].Note = note
|
||||
sharpIndex ++
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user