The piano plays sound
This commit is contained in:
parent
5c7e243566
commit
c5ee7c8cdb
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 {
|
||||
|
@ -1,12 +1,23 @@
|
||||
package main
|
||||
|
||||
import "github.com/faiface/beep"
|
||||
import "github.com/faiface/beep/speaker"
|
||||
import "github.com/faiface/beep/generators"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/fun"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music"
|
||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
||||
|
||||
const sampleRate = 44100
|
||||
const bufferSize = 256
|
||||
var tuning = music.EqualTemparment { A4: 440 }
|
||||
|
||||
var playing = map[music.Note] *beep.Ctrl { }
|
||||
|
||||
func main () {
|
||||
speaker.Init(sampleRate, bufferSize)
|
||||
tomo.Run(run)
|
||||
}
|
||||
|
||||
@ -20,7 +31,29 @@ func run () {
|
||||
container.Adopt(label, false)
|
||||
piano := fun.NewPiano(3, 5)
|
||||
container.Adopt(piano, true)
|
||||
piano.OnPress(playNote)
|
||||
piano.OnRelease(stopNote)
|
||||
|
||||
window.OnClose(tomo.Stop)
|
||||
window.Show()
|
||||
}
|
||||
|
||||
func stopNote (note music.Note) {
|
||||
if _, is := playing[note]; !is { return }
|
||||
|
||||
speaker.Lock()
|
||||
playing[note].Streamer = nil
|
||||
delete(playing, note)
|
||||
speaker.Unlock()
|
||||
}
|
||||
|
||||
func playNote (note music.Note) {
|
||||
streamer, err := generators.SinTone(sampleRate, int(tuning.Tune(note)))
|
||||
if err != nil { panic(err.Error()) }
|
||||
|
||||
stopNote(note)
|
||||
speaker.Lock()
|
||||
playing[note] = &beep.Ctrl { Streamer: streamer }
|
||||
speaker.Unlock()
|
||||
speaker.Play(playing[note])
|
||||
}
|
||||
|
9
go.mod
9
go.mod
@ -3,10 +3,19 @@ module git.tebibyte.media/sashakoshka/tomo
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66
|
||||
golang.org/x/image v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
|
||||
|
35
go.sum
35
go.sum
@ -2,25 +2,60 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJ
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 h1:+wPhoJD8EH0/bXipIq8Lc2z477jfox9zkXPCJdhvHj8=
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66/go.mod h1:KACeV+k6b+aoLTVrrurywEbu3UpqoQcQywj4qX8aQKM=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
||||
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
Reference in New Issue
Block a user