// Copyright 2015 Hajime Hoshi // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build example jsgo package main import ( "log" "math" "strings" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/ebitenutil" ) const ( screenWidth = 320 screenHeight = 240 sampleRate = 44100 ) var audioContext *audio.Context func init() { var err error audioContext, err = audio.NewContext(sampleRate) if err != nil { log.Fatal(err) } } const ( freqA = 440.0 freqAS = 466.2 freqB = 493.9 freqC = 523.3 freqCS = 554.4 freqD = 587.3 freqDS = 622.3 freqE = 659.3 freqF = 698.5 freqFS = 740.0 freqG = 784.0 freqGS = 830.6 ) // Twinkle, Twinkle, Little Star var score = strings.Replace( `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`, " ", "", -1) // square fills out with square wave values with the specified volume, frequency and sequence. func square(out []int16, volume float64, freq float64, sequence float64) { if freq == 0 { for i := 0; i < len(out); i++ { out[i] = 0 } return } length := int(float64(sampleRate) / freq) if length == 0 { panic("invalid freq") } for i := 0; i < len(out); i++ { a := int16(volume * math.MaxInt16) if i%length < int(float64(length)*sequence) { a = -a } out[i] = a } } // toBytes returns the 2ch little endian 16bit byte sequence with the given left/right sequence. func toBytes(l, r []int16) []byte { if len(l) != len(r) { panic("len(l) must equal to len(r)") } b := make([]byte, len(l)*4) for i := range l { b[4*i] = byte(l[i]) b[4*i+1] = byte(l[i] >> 8) b[4*i+2] = byte(r[i]) b[4*i+3] = byte(r[i] >> 8) } return b } // playNote plays the note at scoreIndex of the score. func playNote(scoreIndex int) rune { note := score[scoreIndex] // If the note is 'rest', play nothing. if note == 'R' { return rune(note) } freqs := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2} freq := 0.0 switch { case 'A' <= note && note <= 'B': freq = freqs[int(note)+len(freqs)-int('C')] case 'C' <= note && note <= 'G': freq = freqs[note-'C'] default: panic("note out of range") } const vol = 1.0 / 16.0 size := 30 * sampleRate / ebiten.TPS() l := make([]int16, size) r := make([]int16, size) square(l, vol, freq, 0.25) square(r, vol, freq, 0.25) p, _ := audio.NewPlayerFromBytes(audioContext, toBytes(l, r)) p.Play() return rune(note) } var ( scoreIndex = 0 frames = 0 currentNote rune ) func update(screen *ebiten.Image) error { // Play notes for each half second. if frames%30 == 0 { currentNote = playNote(scoreIndex) scoreIndex++ scoreIndex %= len(score) } frames++ if ebiten.IsDrawingSkipped() { return nil } msg := "Note: " if currentNote == 'R' { msg += "-" } else { msg += string(currentNote) } ebitenutil.DebugPrint(screen, msg) return nil } func main() { if err := ebiten.Run(update, screenWidth, screenHeight, 2, "PCM (Ebiten Demo)"); err != nil { log.Fatal(err) } }