♪ A low-level library to play sound on multiple platforms ♪

Oto (音)

GoDoc

A low-level library to play sound. This package offers io.WriteCloser to play PCM sound.

Platforms

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • OpenBSD
  • Android
  • iOS
  • Web browsers (GopherJS and WebAssembly)

Prerequisite

macOS

Oto requies AudioToolbox.framework, but this is automatically linked.

iOS

Oto requies these frameworks:

  • AVFoundation.framework
  • AudioToolbox.framework

Add them to "Linked Frameworks and Libraries" on your Xcode project.

Linux

libasound2-dev is required. On Ubuntu or Debian, run this command:

apt install libasound2-dev

In most cases this command must be run by root user or through sudo command.

Crosscompiling

To crosscompile, make sure the libraries for the target architecture are installed, and set CGO_ENABLED=1 as Go disables Cgo on crosscompiles by default

FreeBSD

OpenAL is required. Install openal-soft:

pkg install openal-soft

OpenBSD

OpenAL is required. Install openal:

pkg_add -r openal
Owner
Hajime Hoshi
Software Engineer
Hajime Hoshi
Comments
  • oto: remove CGO on darwin

    oto: remove CGO on darwin

    This PR almost completely removes CGO for darwin (iOS included although I haven't tested that it works.) I'm creating this PR so that you can review it. The only thing left to do is remove the callbacks back into Go code. I'm a little stuck by this and the windows implementation is a LOT of code that we'd probably not want to maintain. During my testing I was able to get a custom assembly function to work properly by calling into crosscall2 in runtime/cgo. However, I have not figured out anyway to automatically generate this file. Perhaps I could write a go:generate program that does this? It would likely be pretty simple. Also, the objc package needs to be moved to another project but I made it here for ease of testing.

  • Ignore players that have no data in them to be read

    Ignore players that have no data in them to be read

    Players which have no data read to be read result in audio being blocked. This can result in choppy audio if a player is used like so:

    p := context.NewPlayer()
    defer p.Close()
    
    p.Write(coolSounds)
    
    doSomethingExpensive()
    

    This is counter-intuitive, as I wouldn't expect the time I call a Close() method to affect user experience in any way.

    This fixes that by not using io.Pipe. Since io.Pipe synchronizes reads and writes, it is not an ideal choice for data transfer here, as it is impossible to determine if there is a writer on the other side of a pipe. Instead, a buffer + mutex to allow concurrent access will suffice.

    An example main file which fails on master but works in this branch:

    package main
    
    import (
    	"math"
    	"time"
    
    	"github.com/hajimehoshi/oto"
    )
    
    func sin(x float64) float64 { return math.Sin(math.Pi*2*x) / 4 }
    
    func main() {
    	play := func(p *oto.Player, duration float64, freq float64) {
    		sl := make([]byte, int(duration*44100))
    		var f float64
    		for i := range sl {
    			f += freq * 1. / 44100
    			sl[i] = byte(128 + 127*sin(f))
    		}
    		p.Write(sl)
    	}
    
    	c, err := oto.NewContext(44100, 1, 1, 4096)
    	if err != nil {
    		panic(err)
    	}
    	p1 := c.NewPlayer()
    	p2 := c.NewPlayer()
    	p3 := c.NewPlayer()
    	go play(c.NewPlayer(), 120, 0)
    	for {
    		go play(p1, float64(1), 440)
    		time.Sleep(time.Second / 2)
    		go play(p2, float64(1), 660)
    		time.Sleep(time.Second / 2)
    		go play(p3, float64(1), 880)
    		time.Sleep(time.Second / 2)
    		time.Sleep(time.Second * 1)
    	}
    }
    
  • Player reads infinitely even after being paused/reset

    Player reads infinitely even after being paused/reset

    Hello (again ;))

    I found some weird behavior with the player (intentional?) that can cause issues and wanted to discuss them here (tested on Windows 11).

    Note that the following behaviors happen when the player is paused, but do not happen if the player has never played. They will start to happen even if the player was started then immediately paused, or if the sound played completely.

    1. Issuing a player.Reset() always causes one Read() call
    2. Reset followed by a seek (or vice versa) to io.SeekStart (or similar) will cause the player to call Read() infinitely! Once the infinite reading starts even seeking to io.SeekEnd doesn't stop it. The only thing that seems to stop that is to call Play() and let the sound finish.
    3. While point 2 is active (Read is being called by Oto), a user calling seek can cause the Read and Seek functions to be called concurrently which is many times not safe.
    4. Infinite calling of Read() will start with reset+seek, or by playing and pausing before a sound finishes playing.

    I had issues with sounds not re-playing properly because of this, although a mutex in read/seek seems to help in some cases.

    Here is simple code to reproduce:

    //This is a io.ReadSeeker wrapper that will log when Read/Seek is called, and will panic on concurrent use
    type ReadSeekerFileWrapper struct {
    	F *os.File
    	M sync.Mutex
    }
    
    //If mutex is already locked this will panic
    func (fw *ReadSeekerFileWrapper) Check(name string) {
    	locked := fw.M.TryLock()
    	if locked {
    		fw.M.Unlock()
    	} else {
    		panic("Concurrent use by: " + name)
    	}
    }
    
    var shouldPrint = false
    
    func (fw *ReadSeekerFileWrapper) Read(outBuf []byte) (bytesRead int, err error) {
    
    	//mp3 decoding calls read, so we don't want to log that
    	if shouldPrint {
    		println("Read called")
    	}
    
    	fw.Check("Read")
    	fw.M.Lock()
    	defer fw.M.Unlock()
    
    	n, err := fw.F.Read(outBuf)
    	if shouldPrint {
    		fmt.Printf("Read %d bytes\n", n)
    	}
    	return n, err
    }
    
    func (fw *ReadSeekerFileWrapper) Seek(offset int64, whence int) (int64, error) {
    
    	if shouldPrint {
    		fmt.Printf("Seek called: offset=%d, whence=%d\n", offset, whence)
    	}
    
    	fw.Check("Seek")
    	fw.M.Lock()
    	defer fw.M.Unlock()
    
    	return fw.F.Seek(offset, whence)
    }
    
    func main() {
    
    	//Load some mp3
    	file, _ := os.Open("./test_audio_files/camera.mp3")
    	fw := &ReadSeekerFileWrapper{F: file, M: sync.Mutex{}}
    	decodedMp3, _ := mp3.NewDecoder(fw)
    	shouldPrint = true
    
    	//Init Oto
    	otoCtx, readyChan, _ := oto.NewContext(44100, 2, 2)
    	<-readyChan
    	player := otoCtx.NewPlayer(decodedMp3)
    
            //Play once
           	player.Play()
    	for player.IsPlaying() {
    		time.Sleep(time.Millisecond)
    	}
    
    	//This will cause `Read` to be called infinitely (you will see many logs)
    	//Doing reset first then seek does the same thing
    	fw.Seek(0, io.SeekStart)
    	player.Reset()
    
    	//Instead of playing fully then reset+seek we could have done:
    	//player.Play(); player.Pause()
    
    	//Simulate a user thinking Oto isn't doing anything and wanting to seek.
    	//At some point this will panic because the wrapper detects concurrent use (seek/read called at the same time)
    	time.Sleep(100 * time.Millisecond)
    	for {
    
    		//Start/End both will panic
    
    		// fw.Seek(0, io.SeekStart)
    		fw.Seek(0, io.SeekEnd)
    	}
    }
    

    I don't think this is intended behavior. Not only can this cause concurrent access to resources which might not be safe, but will cause very high CPU usage.

  • Write uses 100% CPU when blocking

    Write uses 100% CPU when blocking

    I was just testing out the example in go-mp3 and the program is utilizing full 100% of CPU (well, 25% on a 4 core CPU). This is cause by the Write method's busy loop.

  • test fails with Go 1.16

    test fails with Go 1.16

    https://github.com/hajimehoshi/oto/runs/7974256368

    Run go test -tags=example  -v ./...
      go test -tags=example  -v ./...
      shell: /bin/bash --noprofile --norc -e -o pipefail {0}
    === RUN   TestEmptyPlayer
    --- PASS: TestEmptyPlayer (0.00s)
    PASS
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x2 pc=0x110fc94]
    
    goroutine 17 [running, locked to thread]:
    github.com/ebitengine/purego.callbackWrap(0x2)
    	/Users/runner/go/pkg/mod/github.com/ebitengine/[email protected]/syscall_darwin.go:102 +0x54
    FAIL	github.com/hajimehoshi/oto/v2	0.322s
    ?   	github.com/hajimehoshi/oto/v2/example	[no test files]
    ?   	github.com/hajimehoshi/oto/v2/internal/mux	[no test files]
    ?   	github.com/hajimehoshi/oto/v2/internal/oboe	[no test files]
    FAIL
    Error: Process completed with exit code 1.
    

    https://github.com/ebitengine/purego/blob/58aa9be54b86ebaa662778658ecf943a3d1976ac/syscall_darwin.go#L98-L106

    Is it possible that a *callbackArgs is nil? Could you take a look? @TotallyGamerJet

  • Stutter on Raspberry Pi 400 using example

    Stutter on Raspberry Pi 400 using example

    NOTE: I'm actively investigating this, but I thought I should put in an issue in case others are (or aren't) seeing the same problem.

    Raspberry Pi 400 (ARM v8) Default Pi OS that ships with Pi 400, but updated via apt to latest. HDMI audio. go 1.15.4

    Effects:

    • go run example/main.go using master at time of posting ( 7f7e156100089346d897740cd0eba0a00c488751 ). The notes are not clear, they stutter quickly and continuously at the same interval.
      • If I increase bufferSizeInBytes in the NewContext call the stutter becomes less frequent, when I reach 80KB (the example starts at 4KB) it is an occasional pop every few seconds.
      • CPU usage is <10% on all cores when testing.
    • When using Oto via Ebiten to play mp3 music it plays slowly and with stutter - not sure if that's indicative of buffer under-run (maybe specific to HDMI audio?)?

    aplay plays wavs on the Pi without issue.

  • `Seek` after `Reset` might not work as expected

    `Seek` after `Reset` might not work as expected

    https://github.com/hajimehoshi/oto/issues/171#issuecomment-1168107432

    Oto internally reads immediately after Reset, so Seek after Reset might be risky.

    /CC @bloeys

  • NewContext can be called only once

    NewContext can be called only once

    Hello FYI,

    image

    It's may be related to https://github.com/hajimehoshi/oto/commit/345927246826d1522bb3c191a01e93bca91ceabd

    And maybe it belongs to : https://github.com/faiface/beep

    I don't know I'm not sure

    Thanks

  • Crash on Windows 7 x86 in waveOutOpen

    Crash on Windows 7 x86 in waveOutOpen

    There seems a C-side NULL dereference in this line:

    https://github.com/hajimehoshi/oto/blob/main/winmm_windows.go#L136

    Details and backtrace are here: https://github.com/divVerent/aaaaxy/issues/17

    Could reproduce on a freshly installed Windows 7 VM. The exact same binary is fine in WINE and in Windows 10, though.

    Note that this error implies that the DLL function at least does exist - otherwise LazyDLL would have panicked. Something instead must be wrong with its arguments.

    Go itself still officially supports Windows 7 (even though Microsoft no longer does), so it would at least be interesting to figure out what this is.

  • Brand New APIs: Using io.Reader and using a native mixer instead of internal/mux

    Brand New APIs: Using io.Reader and using a native mixer instead of internal/mux

    I sometimes find clicking noises on some platforms especially mobiles and browsers. My understanding is that buffer underflows are happening. Now Oto has its own multiplexer at internal/mux, and I suspect this is the culprit, I'm not 100% sure though. Go's scheduler might not be efficient enough to avoid the underflows. The current multiplexer utilizes io.Pipe and channels.

    So I think we should switch the multiplexer to native ones. Oto will just give data to each audio buffers, and multiplexing will happen internally. This doesn't require Oto's API changes fortunately [1].

    As this is a pretty big change, I'll try to implement one by one, starting with the critical ones like mobiles and browsers. Then Oto will have two types of implementations in the migration period.

    Any thoughts? @Noofbiz @faiface

    CC @sinisterstuf

    [1] Though we don't have to change the API in theory, I think we can have a better API set on second thought. See the below comments.

    EDIT: a mixer sounds a better word than a multiplexer (Disclaimer: I am not an English native speaker)

  • Allow only one instance of Player or multiple instances

    Allow only one instance of Player or multiple instances

    I realized that multiple Player instances might causes problems in the current implementation (e.g. #46). I'm still investigating this, but at least the fact is I've not considered multiple instances.

    To solve this problem, there are 2 suggestions:

    1. Allow only one Player instance. This is simple, but users would need to mixing if they want to play multiple sources (actually I do this in Ebiten).

    2. Allow multiple Player instances. Oto does multiplexing sources.

    I'm leaning toward 2. since it wouldn't break existing usages. Any thoughts? CC @faiface @Noofbiz

  • Pre-read loads undefined number of bytes and doesn't fill buffer

    Pre-read loads undefined number of bytes and doesn't fill buffer

    Opening this based on: https://github.com/hajimehoshi/oto/issues/172#issuecomment-1168198827

    Pre-read reads random number of bytes before stopping, and doesn't fill its buffer even when there is a lot of free space remaining.

  • How to properly implement retry

    How to properly implement retry

    Hi there! Thanks for such an awesome library. :D

    I have an application that was just bitten by this panic. I'd like to implement the missing retry logic, but I'm not really sure that I understand the approach that I should take in doing so. Do you have any guidance about how I should approach adding this feature?

  • request: `(*Context).Close()`

    request: `(*Context).Close()`

    I'm trying to switch to a new version (v2) of the library but seems there is no way to recreate context now. But I see this option documented here https://github.com/hajimehoshi/oto/blob/main/driver.go#L26? What I missed? Context has Suspend and Resume methods, are they do the job?

Mini audio library

malgo Go bindings for miniaudio library. Requires cgo but does not require linking to anything on the Windows/macOS and it links only -ldl on Linux/BS

Dec 31, 2022
Go bindings for the PortAudio audio I/O library

portaudio This package provides an interface to the PortAudio audio I/O library. See the package documentation for details. To build this package you

Jan 1, 2023
EasyMidi is a simple and reliable library for working with standard midi file (SMF)

EasyMidi EasyMidi is a simple and reliable library for working with standard midi file (SMF). Installing A step by step series of examples that tell y

Sep 21, 2022
Go library for searching on YouTube Music.

ytmusic Go library for searching on YouTube Music and getting other useful information. Installing go get github.com/raitonoberu/ytmusic Usage Search

Oct 15, 2022
Small application to convert my music library folder structure to 'crates' in the open-source DJ software Mixxx

Small application to convert my music library folder structure to 'crates' in the open-source DJ software Mixxx

Nov 18, 2022
A project outputs Bluetooth Low Energy (BLE) sensors data in InfluxDB line protocol formatA project outputs Bluetooth Low Energy (BLE) sensors data in InfluxDB line protocol format

Intro This project outputs Bluetooth Low Energy (BLE) sensors data in InfluxDB line protocol format. It integrates nicely with the Telegraf execd inpu

Apr 15, 2022
Smart.go is a pure Golang library to access disk low-level S.M.A.R.T. information

Smart.go is a pure Golang library to access disk low-level S.M.A.R.T. information. Smart.go tries to match functionality provided by smartctl but with golang API.

Dec 27, 2022
Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Feb 8, 2022
Open Sound Control (OSC) library for Golang. Implemented in pure Go.

GoOSC Open Sound Control (OSC) library for Golang. Implemented in pure Go. This repository is a heavily modified fork of the original go-osc. Please c

Dec 22, 2021
API-HTTP service for wav-file synthesis based on sound library (morphemes)

Сервис для генерации аудио-файлов по заданной последовательности звуков из библиотеки. Предоставляет HTTP-API для передачи последовательности для гене

Jan 9, 2022
Low-level key/value store in pure Go.
Low-level key/value store in pure Go.

Description Package slowpoke is a simple key/value store written using Go's standard library only. Keys are stored in memory (with persistence), value

Jan 2, 2023
The X Go Binding is a low-level API to communicate with the X server. It is modeled on XCB and supports many X extensions.

Note that this project is largely unmaintained as I don't have the time to do or support more development. Please consider using this fork instead: ht

Dec 29, 2022
Package socket provides a low-level network connection type which integrates with Go's runtime network poller to provide asynchronous I/O and deadline support. MIT Licensed.

socket Package socket provides a low-level network connection type which integrates with Go's runtime network poller to provide asynchronous I/O and d

Dec 14, 2022
Low-level Go interface to SQLite 3

zombiezen.com/go/sqlite This package provides a low-level Go interface to SQLite 3. It is a fork of crawshaw.io/sqlite that uses modernc.org/sqlite, a

Dec 21, 2022
low level data type and utils in Golang.

low low level data type and utils in Golang. A stable low level function set is the basis of a robust architecture. It focuses on stability and requir

Dec 24, 2022
DiscordGo: a Go package that provides low level bindings to the Discord chat client API
DiscordGo: a Go package that provides low level bindings to the Discord chat client API

DiscordGo DiscordGo is a Go package that provides low level bindings to the Discord chat client API. DiscordGo has nearly complete support for all of

Dec 14, 2021
Packer is a tool for creating identical machine images for multiple platforms from a single source configuration.

Packer Website: https://www.packer.io IRC: #packer-tool on Freenode Mailing list: Google Groups Packer is a tool for building identical machine images

Jan 8, 2023
PingMe is a CLI tool which provides the ability to send messages or alerts to multiple messaging platforms & email.
PingMe is a CLI tool which provides the ability to send messages or alerts to multiple messaging platforms & email.

PingMe is a personal project to satisfy my needs of having alerts, most major platforms have integration to send alerts but its not always useful, either you are stuck with one particular platform, or you have to do alot of integrations. I needed a small app which i can just call from my backup scripts, cron jobs, CI/CD pipelines or from anywhere to send a message with particular information. And i can ship it everywhere with ease. Hence, the birth of PingMe.

Dec 28, 2022
A tool for building identical machine images for multiple platforms from a single source configuration
A tool for building identical machine images for multiple platforms from a single source configuration

Packer Packer is a tool for building identical machine images for multiple platforms from a single source configuration. Packer is lightweight, runs o

Oct 3, 2021
Tool for creating identical machine images for multiple platforms from a single source configuration.

Packer Packer is a tool for building identical machine images for multiple platforms from a single source configuration. Packer is lightweight, runs o

Jan 18, 2022