From 8d9485af7eace68f2b6b769098c041f0ae668087 Mon Sep 17 00:00:00 2001 From: faiface Date: Thu, 6 Jul 2017 21:44:34 +0200 Subject: [PATCH] reimplement speaker --- audio/playback/speaker.go | 139 -------------------------------------- audio/speaker/speaker.go | 110 ++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 139 deletions(-) delete mode 100644 audio/playback/speaker.go create mode 100644 audio/speaker/speaker.go diff --git a/audio/playback/speaker.go b/audio/playback/speaker.go deleted file mode 100644 index 465c810..0000000 --- a/audio/playback/speaker.go +++ /dev/null @@ -1,139 +0,0 @@ -package playback - -import ( - "errors" - - "io" - - "github.com/faiface/pixel/audio" - "github.com/hajimehoshi/oto" -) - -// Speaker is the interface used for playing back audio.Streamers. -type Speaker interface { - // Play tells the Speaker that it is ready for playback and handles preparing the Streamer. - Play(audio.Streamer) - // Update is called once per game loop and handles pulling samples from the Streamer and writing them to Speaker's - // player. - Update() error -} - -// DefaultSpeaker is a default implementation of speaker capable of playing back samples to the default output device. -type DefaultSpeaker struct { - // audio.Streamer is the Streamer to pull samples from. It is passed in and set with Speaker.Play(audio.Streamer) - audio.Streamer - // isPlaying informs the update loop about whether or not this Speaker is playing - isPlaying bool - // samples is the internal buffer of samples that read() and readSample() fill and drain, respectively - // samples' length is the total buffer size / 2 - samples [][2]float64 - // player is the underlying *oto.Player, which uses os specific APIs for audio playback - player *oto.Player - // buf is the buffer of samples converted to bytes that is written to player - buf []uint8 - // bufferSize is the size in bytes of the total buffer in bytes. bufferSize must be a power of 2. - bufferSize int -} - -// NewDefaultSpeaker returns a *DefaultSpeaker ready to read samples and write to the underlying player for playback -func NewDefaultSpeaker(bufferSize int) (*DefaultSpeaker, error) { - p, err := oto.NewPlayer(int(audio.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 -} - -var ( - // ErrBufferMustBePowerOf2 should be returned when the buffer passed in to read is not a power of 2, as a sample is - // 2 bytes, the buffer must have the capacity to handle all samples. - ErrBufferMustBePowerOf2 = errors.New("Buffer passed to Read must be a power of 2") -) - -// read reads up to len(dst) / 2 samples into dst -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 -} - -// eof returns whether or not we have read all samples currently in the samples buffer -func (s *DefaultSpeaker) eof() bool { - return len(s.samples) == 0 -} - -// Sample is a single sample stored as an array of [2]float64, with Sample[0] being the left channel and Sample[1] being the right channel -type Sample [2]float64 - -// readSample reads a single sample from s.samples and truncates it from the buffer -func (s *DefaultSpeaker) readSample() Sample { - sample := s.samples[0] - s.samples = s.samples[1:] - return sample -} - -// Play initializes the Streamer and sets s.isPlaying to true -func (ds *DefaultSpeaker) Play(s audio.Streamer) { - ds.isPlaying = true - ds.Streamer = s -} - -// streamToPlayer Streams up to len(s.samples) into s.samples, converts those into bytes for s.buf, and writes s.buf -// to the underlying player -func (s *DefaultSpeaker) streamToPlayer() error { - n, ok := s.Stream(s.samples) - if (n == len(s.samples) || 0 < n && n < len(s.samples)) && ok { - r, err := s.read(s.buf) - if err != nil { - return err - } - s.buf = s.buf[:r] - _, err = s.player.Write(s.buf) - if err != nil { - return err - } - // we drained the streamer while while reading, - if n < len(s.samples) { - s.isPlaying = false - return nil - } - s.buf = make([]byte, s.bufferSize) - } - // this stream is already drained, set isPlaying to false - if n == 0 && !ok { - s.isPlaying = false - } - return nil -} - -// Update should be called during the main update loop in order to handle synchronization -// If s.isPlaying, Update will stream all available samples to the underlying player once per update. -func (s *DefaultSpeaker) Update() error { - if s.isPlaying { - return s.streamToPlayer() - } - return nil -} diff --git a/audio/speaker/speaker.go b/audio/speaker/speaker.go new file mode 100644 index 0000000..c5ab1e9 --- /dev/null +++ b/audio/speaker/speaker.go @@ -0,0 +1,110 @@ +package speaker + +import ( + "math" + "sync" + "time" + + "github.com/faiface/pixel/audio" + "github.com/hajimehoshi/oto" + "github.com/pkg/errors" +) + +var ( + mu sync.Mutex + streamer audio.Streamer + samples [][2]float64 + buf []byte + player *oto.Player +) + +// Init initializes audio playback through speaker. Must be called before using this package. The +// value of audio.SampleRate must be set (or left to the default) before calling this function. +// +// The bufferSize argument specifies the length of the speaker's buffer. On calling Update, speaker +// pulls this amount of data from the playing Streamers and starts playing this data. Bigger +// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better +// responsiveness and less delay. +func Init(bufferSize time.Duration) error { + mu.Lock() + defer mu.Unlock() + + if player != nil { + player.Close() + player = nil + } + + numSamples := int(math.Ceil(bufferSize.Seconds() * audio.SampleRate)) + numBytes := numSamples * 4 + + var err error + player, err = oto.NewPlayer(int(audio.SampleRate), 2, 2, numBytes) + if err != nil { + return errors.Wrap(err, "failed to initialize speaker") + } + + samples = make([][2]float64, numSamples) + buf = make([]byte, numBytes) + + return nil +} + +// Play starts playing the provided Streamer through the speaker. +func Play(s audio.Streamer) { + mu.Lock() + defer mu.Unlock() + + streamer = s +} + +// Update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the +// data is sent and started playing. +// +// This function should be called at least once the duration of bufferSize given in Init, but it's +// recommended to call it more frequently to avoid glitches. +func Update() error { + mu.Lock() + defer mu.Unlock() + + if player == nil { + panic("didn't call speaker.Init") + } + + // pull data from the streamer, if any + n := 0 + if streamer != nil { + var ok bool + n, ok = streamer.Stream(samples) + if !ok { + streamer = nil + return nil + } + } + + // convert samples to bytes + for i := range samples[:n] { + for c := range samples[i] { + val := samples[i][c] + if val < -1 { + val = -1 + } + if val > +1 { + val = +1 + } + valInt16 := int16(val * (1 << 15)) + low := byte(valInt16 % (1 << 8)) + high := byte(valInt16 / (1 << 8)) + buf[i*4+c*2+0] = low + buf[i*4+c*2+1] = high + } + } + + // fill the rest with silence + for i := n * 4; i < len(buf); i++ { + buf[i] = 0 + } + + player.Write(buf) + + return nil +}