Skip to content

Instantly share code, notes, and snippets.

@snicol
Last active August 4, 2017 14:03
Show Gist options
  • Save snicol/07e296fc8280f10fa787255d7bb112a8 to your computer and use it in GitHub Desktop.
Save snicol/07e296fc8280f10fa787255d7bb112a8 to your computer and use it in GitHub Desktop.
Plays the Happy Birthday song via PCM using hajimehoshi's oto lib
package main
import (
"github.com/hajimehoshi/oto"
"log"
"math"
"strings"
)
const defaultNumChannels = 2
const defaultBytesPerSample = 2
const defaultSampleRate = 44100
const defaultBufferSize = 8192
var notePosition = 0
// Note: there is no release or envelope so two notes together sound pretty lame in this example
var happyBirthday = "G4 G4 A4 G4 C5 B4 | G4 G4 A4 G4 D5 C5 | G4 G4 G5 E5 C5 B4 A4 | F5 F5 E5 C5 D5 C5 | |"
func main() {
player, err := oto.NewPlayer(defaultSampleRate, defaultNumChannels, defaultBytesPerSample, defaultBufferSize)
if err != nil {
log.Fatal(err)
}
player.SetUnderrunCallback(func() {
log.Println("SLOW CODE!")
})
vals := []uint8{}
note := 392.00 // G4 default
pitches := map[string]float64{
"C4": 261.63, "D4": 293.66, "E4": 329.63, "F4": 349.23, // C4 - B5
"G4": 392.00, "A4": 440.00, "B4": 493.88,
"C5": 523.25, "D5": 587.33, "E5": 659.25, "F5": 698.46,
"G5": 783.99, "A5": 880.00, "B5": 987.77,
}
for sampleTick := 0; sampleTick <= defaultSampleRate+1; sampleTick++ {
if sampleTick >= defaultSampleRate {
sampleTick = 0
}
if len(vals) >= 8192 {
player.Write(vals)
vals = []uint8{} // clear buffer
continue
}
/*
44100 is sample bytes per one second, so sampleTick @ 22050 is 0.5s
120 bpm = 2 beats per second = 2 seconds per bar
1 beat per second = 0.5s
sampleTick == 22050 == 1 beat == 0.5s
sampleTick == 11025 == 2/4notes == 0.25s
*/
if sampleTick%22050 == 1 {
noteFreq := getNextNote()
if noteFreq != "|" {
note = pitches[noteFreq]
}
}
deltaTime := 1 / float64(defaultSampleRate)
frequency := note * deltaTime
at := float64(sampleTick) * frequency
sinePoint := deltaToSin(at)
val := Uint16ToUInt8(floatToInt(sinePoint))
vals = append(vals, val[1], val[0], val[1], val[0])
}
}
func getNextNote() string {
happyBirthdayNotes := strings.Split(happyBirthday, " ")
if notePosition == len(happyBirthdayNotes) {
notePosition = 0
}
note := happyBirthdayNotes[notePosition]
notePosition++
log.Println(note)
return note
}
func deltaToSin(at float64) float64 {
return math.Sin(at * math.Pi * 2.0)
}
func floatToInt(at float64) uint16 {
at = at + 1 // add 1 to remove negative
at = at / 2 // bring back into 0-1 range
at = at * 65536 // make it uint16 wide
return uint16(at)
}
func Uint16ToUInt8(at uint16) []uint8 {
var h, l uint8 = uint8(at >> 8), uint8(at & 0xff)
return []uint8{h, l}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment