diff --git a/audio/example/sin/main.go b/audio/example/sin/main.go new file mode 100644 index 0000000..c53a1a7 --- /dev/null +++ b/audio/example/sin/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "math" + + "fmt" + + "os" + + "github.com/faiface/pixel/audio" +) + +type sine struct { + freq float64 + rate float64 + time float64 +} + +func (s *sine) Stream(samples [][2]float64) (n int, ok bool) { + if len(samples) == 0 { + os.Exit(-1) + } + fmt.Println(len(samples)) + for i := 0; i < len(samples)-2; i += 2 { + val := math.Sin(math.Pi*s.time*s.freq) / 1.1 + s.time += 1 / s.rate + valI := int16((1 << 15) * val) + low := float64(valI % (1 << 8)) + high := float64(valI / (1 << 8)) + samples[i][0] = low + samples[i][1] = high + samples[i+1][0] = low + samples[i+1][1] = high + } + fmt.Println(samples) + return len(samples), true +} + +func main() { + err := run() + if err != nil { + fmt.Println(err) + } +} + +func run() error { + audio.SampleRate = 44100 + const bufSize = 1 << 13 + + speaker, err := audio.NewDefaultSpeaker(bufSize) + if err != nil { + return err + } + + s := &sine{freq: 440, rate: audio.SampleRate, time: 0} + + speaker.Play(s) + + for { + err := speaker.Update() + if err != nil { + return err + } + } + + return nil +} diff --git a/audio/speaker.go b/audio/speaker.go new file mode 100644 index 0000000..628d6a4 --- /dev/null +++ b/audio/speaker.go @@ -0,0 +1,118 @@ +package audio + +import ( + "errors" + + "io" + + "github.com/hajimehoshi/oto" +) + +type Speaker interface { + Play(Streamer) + Update() error +} + +type DefaultSpeaker struct { + Streamer + isPlaying bool + samples [][2]float64 + player *oto.Player + buf []uint8 + bufferSize int +} + +var ( + ErrBufferMustBePowerOf2 = errors.New("Buffer passed to Read must be a power of 2") +) + +func (s *DefaultSpeaker) Read(dst []byte) (n int, err error) { + if !s.isPlaying || s.eof() { + return 0, io.EOF + } + // we need dst to be a power of two in order for us to write samples cleanly + if len(dst)%2 != 0 { + return 0, ErrBufferMustBePowerOf2 + } + + if l := len(dst); l > 1 { + for n < l-1 { + sample := s.readSample() + dst[n] = byte(sample[0]) + dst[n+1] = byte(sample[1]) + if s.eof() { + s.samples = make([][2]float64, s.bufferSize/2) + break + } + n += 2 + } + } + return n, nil +} + +func (s *DefaultSpeaker) eof() bool { + return len(s.samples) == 0 +} + +type Sample [2]float64 + +func (s *DefaultSpeaker) readSample() Sample { + sample := s.samples[0] + s.samples = s.samples[1:] + return sample +} + +func NewDefaultSpeaker(bufferSize int) (*DefaultSpeaker, error) { + p, err := oto.NewPlayer(int(SampleRate), 2, 2, bufferSize) + if err != nil { + return nil, err + } + return &DefaultSpeaker{ + player: p, + samples: make([][2]float64, bufferSize/2), + buf: make([]uint8, bufferSize), + bufferSize: bufferSize, + }, nil +} + +func (bs *DefaultSpeaker) Play(s Streamer) { + bs.isPlaying = true + bs.Streamer = s +} + +func (b *DefaultSpeaker) Update() error { + + if b.isPlaying { + n, ok := b.Stream(b.samples) + if n == len(b.samples) && ok { + r, err := b.Read(b.buf) + if err != nil { + return err + } + b.buf = b.buf[:r] + _, err = b.player.Write(b.buf) + if err != nil { + return err + } + b.buf = make([]byte, b.bufferSize) + } + // we're read bytes but drained the streamer, so copy data and stop playing + if n > 0 && n < len(b.samples) && ok { + r, err := b.Read(b.buf) + if err != nil { + return err + } + b.buf = b.buf[:r] + _, err = b.player.Write(b.buf) + if err != nil { + return err + } + b.isPlaying = false + } + // this stream is already drained, set isPlaying to false + if n == 0 && !ok { + b.isPlaying = false + } + } + return nil +}