diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f11873b..3a628a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: run: | go build -v ./... # Compile without optimization to check potential stack overflow. - # The option '-gcflags=all=-N -l' is often used at Visual Studio Code. + # The option '-gcflags=all=-N -l' is often used in Visual Studio Code. # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch and the issue hajimehoshi/ebiten#2120. go build "-gcflags=all=-N -l" -v ./... diff --git a/README.md b/README.md index 6c1fe44..52d2cec 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Add them to "Linked Frameworks and Libraries" on your Xcode project. ### Linux, FreeBSD, OpenBSD Oto uses PulseAudio on Linux and BSD systems via the pure-Go package `github.com/jfreymuth/pulse`, -though BSD systems are not tested well. +though BSD systems are not well tested. If the PulseAudio server is not discoverable automatically, set `PULSE_SERVER`. @@ -68,7 +68,7 @@ is enabled by default, need nothing extra. ## Usage -The two main components of Oto are a `Context` and `Players`. The context handles interactions with +The two main components of Oto are `Context` and `Player`. The context handles interactions with the OS and audio drivers, and as such there can only be **one** context in your program. From a context you can create any number of different players, where each player is given an `io.Reader` that @@ -116,7 +116,7 @@ func main() { // Usually 44100 or 48000. Other values might cause distortions in Oto op.SampleRate = 44100 - // Number of channels (aka locations) to play sounds from. Either 1 or 2. + // Number of channels to play sounds from. Either 1 or 2. // 1 is mono sound, and 2 is stereo (most speakers are stereo). op.ChannelCount = 2 @@ -146,7 +146,7 @@ func main() { } // Now that the sound finished playing, we can restart from the beginning (or go to any location in the sound) using seek - // newPos, err := player.(io.Seeker).Seek(0, io.SeekStart) + // newPos, err := player.Seek(0, io.SeekStart) // if err != nil{ // panic("player.Seek failed: " + err.Error()) // } @@ -209,13 +209,13 @@ Data is moved from io.Reader->internal buffer->audio device, and when the intern is not guaranteed, so there might be a small delay. The amount of data in the buffer can be retrieved using `Player.BufferedSize()`. -The size of the underlying buffer of a player can also be set by type-asserting the player object: +The size of the underlying buffer of a player can also be set by calling the player's `SetBufferSize` function: ```go -myPlayer.(oto.BufferSizeSetter).SetBufferSize(newBufferSize) +myPlayer.SetBufferSize(newBufferSize) ``` -This works because players implement a `Player` interface and a `BufferSizeSetter` interface. +`NewPlayer` returns a `*oto.Player`, which has functions like `SetBufferSize` and `Seek`. ## Crosscompiling diff --git a/api_wasapi_windows.go b/api_wasapi_windows.go index 679a768..83b53d2 100644 --- a/api_wasapi_windows.go +++ b/api_wasapi_windows.go @@ -486,18 +486,18 @@ type _IMMDeviceEnumerator_Vtbl struct { UnregisterEndpointNotificationCallback uintptr } -func (i *_IMMDeviceEnumerator) GetDefaultAudioEndPoint(dataFlow _EDataFlow, role _ERole) (*_IMMDevice, error) { +func (i *_IMMDeviceEnumerator) GetDefaultAudioEndpoint(dataFlow _EDataFlow, role _ERole) (*_IMMDevice, error) { var endPoint *_IMMDevice r, _, _ := syscall.Syscall6(i.vtbl.GetDefaultAudioEndpoint, 4, uintptr(unsafe.Pointer(i)), uintptr(dataFlow), uintptr(role), uintptr(unsafe.Pointer(&endPoint)), 0, 0) if uint32(r) != uint32(windows.S_OK) { if isWin32Err(uint32(r)) { - return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndPoint failed: %w", _E_NOTFOUND) + return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: %w", _E_NOTFOUND) } if isRPCErr(uint32(r)) { - return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndPoint failed: %w", _RPC_ERR(r)) + return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: %w", _RPC_ERR(r)) } - return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndPoint failed: HRESULT(%d)", uint32(r)) + return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: HRESULT(%d)", uint32(r)) } return endPoint, nil } diff --git a/api_winmm_windows.go b/api_winmm_windows.go index 821299c..65c9905 100644 --- a/api_winmm_windows.go +++ b/api_winmm_windows.go @@ -101,7 +101,7 @@ func (m _MMRESULT) Error() string { case _WAVERR_SYNC: return "WAVERR_SYNC" } - return fmt.Sprintf("MMRESULT (%d)", m) + return fmt.Sprintf("MMRESULT(%d)", m) } func waveOutOpen(f *_WAVEFORMATEX, callback uintptr) (uintptr, error) { diff --git a/context.go b/context.go index 0169c9c..4c7833e 100644 --- a/context.go +++ b/context.go @@ -43,20 +43,20 @@ type Context struct { type Format int const ( - // FormatFloat32LE is the format of 32 bits floats little endian. + // FormatFloat32LE is the format of 32-bit floats in little endian. FormatFloat32LE Format = iota - // FormatUnsignedInt8 is the format of 8 bits integers. + // FormatUnsignedInt8 is the format of 8-bit integers. FormatUnsignedInt8 - // FormatSignedInt16LE is the format of 16 bits integers little endian. + // FormatSignedInt16LE is the format of 16-bit integers in little endian. FormatSignedInt16LE ) // NewContextOptions represents options for NewContext. type NewContextOptions struct { // SampleRate specifies the number of samples that should be played during one second. - // Usual numbers are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio + // Typical values are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio // sources with different sample rates at the same time. SampleRate int @@ -70,9 +70,9 @@ type NewContextOptions struct { // BufferSize specifies a buffer size in the underlying device. // // If 0 is specified, the driver's default buffer size is used. - // Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noises. - // Too big buffer size can increase the latency time. - // On the other hand, too small buffer size can cause glitch noises due to buffer shortage. + // Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noise. + // A buffer size that is too big increases the latency. + // On the other hand, a buffer size that is too small can cause glitch noises due to buffer shortage. BufferSize time.Duration // ApplicationName specifies the name of the client application. @@ -82,7 +82,7 @@ type NewContextOptions struct { // NewContext creates a new context with given options. // A context creates and holds ready-to-use Player objects. -// NewContext returns a context, a channel that closes when initialization finishes, and an error if it exists. +// NewContext returns a context, a channel that closes when initialization finishes, and an error, if any. // After the channel closes, call Context.Err to check whether initialization succeeded. // // Creating multiple contexts is NOT supported. @@ -97,7 +97,7 @@ func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) { var bufferSizeInBytes int if options.BufferSize != 0 { - // The underlying driver always uses 32bit floats. + // The underlying driver always uses 32-bit floats. bytesPerSample := options.ChannelCount * 4 bytesPerSecond := options.SampleRate * bytesPerSample bufferSizeInBytes = int(int64(options.BufferSize) * int64(bytesPerSecond) / int64(time.Second)) @@ -130,11 +130,11 @@ func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) { // Then, r's position and the current playing position don't necessarily match. // If you want to seek the position of r, call the player's Seek function, // which also clears the underlying buffer. -// If you want to stop using r e.g., you want to close r, call the player's PauseAndStopReading function. +// If you want to stop using r (e.g. you want to close r), call the player's PauseAndStopReading function. // // You cannot share r by multiple players. // -// The returned player implements Player, BufferSizeSetter, and io.Seeker. +// The returned player is a *Player, which has functions like SetBufferSize and Seek. // You can modify the buffer size of a player by the SetBufferSize function. // A small buffer size is useful if you want to play a real-time PCM for example. // Note that the audio quality might be affected if you modify the buffer size. diff --git a/driver_darwin.go b/driver_darwin.go index 18515ee..951f642 100644 --- a/driver_darwin.go +++ b/driver_darwin.go @@ -54,7 +54,7 @@ func newAudioQueue(sampleRate, channelCount int, oneBufferSizeInBytes int) (_Aud 0, //CFStringRef 0, &audioQueue); osstatus != noErr { - return 0, nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus) + return 0, nil, fmt.Errorf("oto: AudioQueueNewOutput failed: %d", osstatus) } bufs := make([]_AudioQueueBufferRef, 0, bufferCount) @@ -138,7 +138,7 @@ func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeI // defaultOneBufferSizeInBytes is the default buffer size in bytes. // // 12288 seems necessary at least on iPod touch (7th) and MacBook Pro 2020. - // With 48000[Hz] stereo, the maximum delay is (12288*4[buffers] / 4 / 2)[samples] / 48000 [Hz] = 100[ms]. + // With 48000[Hz] stereo, the maximum delay is (12288*4[buffers] / 4 / 2)[samples] / 48000 [Hz] = 128[ms]. // '4' is float32 size in bytes. '2' is a number of channels for stereo. const defaultOneBufferSizeInBytes = 12288 diff --git a/driver_wasapi_windows.go b/driver_wasapi_windows.go index f9da6fa..e59babf 100644 --- a/driver_wasapi_windows.go +++ b/driver_wasapi_windows.go @@ -125,8 +125,7 @@ type wasapiContext struct { } var ( - errDeviceSwitched = errors.New("oto: device switched") - errFormatNotSupported = errors.New("oto: the specified format is not supported (there is the closest format instead)") + errDeviceSwitched = errors.New("oto: device switched") ) const ( @@ -187,7 +186,7 @@ func (c *wasapiContext) isDeviceSwitched() (bool, error) { var switched bool var cerr error c.comThread.Run(func() { - device, err := c.enumerator.GetDefaultAudioEndPoint(eRender, eConsole) + device, err := c.enumerator.GetDefaultAudioEndpoint(eRender, eConsole) if err != nil { cerr = err return @@ -303,7 +302,7 @@ func (c *wasapiContext) startOnCOMThread() (ferr error) { } c.enumerator = (*_IMMDeviceEnumerator)(e) - device, err := c.enumerator.GetDefaultAudioEndPoint(eRender, eConsole) + device, err := c.enumerator.GetDefaultAudioEndpoint(eRender, eConsole) if err != nil { if errors.Is(err, _E_NOTFOUND) { return errDeviceNotFound @@ -333,7 +332,7 @@ func (c *wasapiContext) startOnCOMThread() (ferr error) { } // Check the format is supported by WASAPI. - // Stereo with 48000 [Hz] is likely supported, but mono and/or other sample rates are unlikely supported. + // Stereo with 48000 [Hz] is likely supported, but mono and/or other sample rates are unlikely to be supported. // Fallback to WinMM in this case anyway. const bitsPerSample = 32 nBlockAlign := c.channelCount * bitsPerSample / 8 @@ -531,8 +530,7 @@ func (c *wasapiContext) Err() error { // isWASAPIDeviceTransientError reports whether err from (re)starting the client // indicates a temporarily unusable device rather than a permanent failure. func isWASAPIDeviceTransientError(err error) bool { - return errors.Is(err, errFormatNotSupported) || - errors.Is(err, errDeviceNotFound) || + return errors.Is(err, errDeviceNotFound) || errors.Is(err, _AUDCLNT_E_DEVICE_INVALIDATED) || errors.Is(err, _AUDCLNT_E_RESOURCES_INVALIDATED) || errors.Is(err, _RPC_E_DISCONNECTED) diff --git a/driver_winmm_windows.go b/driver_winmm_windows.go index 63ff8ad..2384057 100644 --- a/driver_winmm_windows.go +++ b/driver_winmm_windows.go @@ -26,9 +26,6 @@ import ( "github.com/ebitengine/oto/v3/internal/mux" ) -// Avoid goroutines on Windows (hajimehoshi/ebiten#1768). -// Apparently, switching contexts might take longer than other platforms. - const defaultHeaderBufferSize = 4096 type header struct { @@ -201,7 +198,7 @@ func (c *winmmContext) isHeaderAvailable() bool { } var waveOutOpenCallback = windows.NewCallback(func(hwo, uMsg, dwInstance, dwParam1, dwParam2 uintptr) uintptr { - // Queuing a header in this callback might not work especially when a headset is connected or disconnected. + // Queueing a header in this callback might not work especially when a headset is connected or disconnected. // Just signal the condition variable and don't do other things. const womDone = 0x3bd if uMsg != womDone { diff --git a/example/main.go b/example/main.go index eff2503..7e52649 100644 --- a/example/main.go +++ b/example/main.go @@ -28,7 +28,7 @@ import ( var ( sampleRate = flag.Int("samplerate", 48000, "sample rate") - channelCount = flag.Int("channelcount", 2, "number of channel") + channelCount = flag.Int("channelcount", 2, "number of channels") format = flag.String("format", "s16le", "source format (u8, s16le, or f32le)") ) @@ -210,7 +210,7 @@ func run() error { wg.Wait() - // Pin the players not to GC the players. + // Keep the players alive so that they are not garbage-collected. runtime.KeepAlive(players) return nil diff --git a/internal/mux/mux.go b/internal/mux/mux.go index 1ae6165..3880a96 100644 --- a/internal/mux/mux.go +++ b/internal/mux/mux.go @@ -26,7 +26,7 @@ import ( "time" ) -// Format must sync with oto's Format. +// Format must be kept in sync with oto's Format. type Format int const ( @@ -108,7 +108,7 @@ func (m *Mux) loop() { } // Sleeping is necessary especially on browsers. - // Sometimes a player continues to read 0 bytes from the source and this loop can be a busy loop in such case. + // Sometimes a player continues to read 0 bytes from the source and this loop can be a busy loop in such a case. if allZero { time.Sleep(time.Millisecond) } @@ -630,7 +630,7 @@ func (p *playerImpl) returnBufferToPool() { // TODO: The term 'buffer' is confusing. Name each buffer with good terms. // defaultBufferSize returns the default size of the buffer for the audio source. -// This buffer is used when unreading on pausing the player. +// The mux loop reads the source into this buffer, and ReadFloat32s consumes it. func (m *Mux) defaultBufferSize() int { bytesPerSample := m.channelCount * m.format.ByteLength() s := m.sampleRate * bytesPerSample / 2 // 0.5[s] diff --git a/internal/mux/mux_test.go b/internal/mux/mux_test.go index e6f1ef8..63131a5 100644 --- a/internal/mux/mux_test.go +++ b/internal/mux/mux_test.go @@ -245,7 +245,7 @@ func TestPauseAndStopReadingKeepsOngoingReadResult(t *testing.T) { } // The player must be reusable after PauseAndStopReading. - // Consume the buffered data to make a room for a new read. + // Consume the buffered data to make room for a new read. p.Play() m.ReadFloat32s(make([]float32, 4096)) deadline := time.Now().Add(time.Second) @@ -360,7 +360,7 @@ func TestClosingSourceAfterPauseAndStopReadingIsSafe(t *testing.T) { _ = src.Close() reads := src.reads.Load() - // Consume the mux's buffer to tempt it to read the source again. + // Consume the mux's buffer to prompt it to read the source again. m.ReadFloat32s(make([]float32, 256)) time.Sleep(100 * time.Millisecond) @@ -409,7 +409,7 @@ func TestSeekDiscardsOngoingReadResult(t *testing.T) { // Wait until a read from the source is in flight. <-src.began - // Pause not to resume playing after seeking. + // Pause so that playing does not resume after seeking. p.Pause() done := make(chan struct{}) diff --git a/internal/oboe/binding_android.cpp b/internal/oboe/binding_android.cpp index 10136a8..05bc529 100644 --- a/internal/oboe/binding_android.cpp +++ b/internal/oboe/binding_android.cpp @@ -30,8 +30,6 @@ namespace { -class Player; - // Status is the outcome of an operation. msg is null on success, and describes // the failure otherwise. retryable, which is meaningful only for a failure, is // false for a configuration error, which would fail the same way however many @@ -65,9 +63,8 @@ Status StatusFromResult(oboe::Result result) { } // kStableRunDuration is how long a stream must keep playing for its start to -// count as a success. A stream that goes away sooner reached the device -// without being able to use it, so the delay before the next attempt keeps -// growing. +// count as a success. A stream that goes away sooner managed to start but not +// to keep playing, so the delay before the next attempt keeps growing. constexpr std::chrono::seconds kStableRunDuration{3}; // StartRetryDelay returns how long to wait before the next start attempt after @@ -98,7 +95,7 @@ enum class State { kStopped, // kStartDeferred means that a start attempt failed for a reason that can - // pass, and that LoopStartRetry attempts it again. + // be temporary, and that LoopStartRetry attempts it again. kStartDeferred, // kRunning means that the stream was started and has not been paused, closed @@ -123,8 +120,8 @@ class Stream : public oboe::AudioStreamDataCallback, public oboe::AudioStreamErrorCallback { public: // GetInstance returns the instance of Stream. Only one Stream object is used - // in one process. It is because multiple streams can be problematic in both - // AAudio and OpenSL (#1656, #1660). + // in one process, because multiple streams can be problematic in both AAudio + // and OpenSL (#1656, #1660). static Stream &GetInstance(); const char *Play(int sample_rate, int channel_num, int buffer_size_in_bytes); diff --git a/internal/oboe/binding_android.h b/internal/oboe/binding_android.h index 82c5c07..334ce5d 100644 --- a/internal/oboe/binding_android.h +++ b/internal/oboe/binding_android.h @@ -23,8 +23,6 @@ extern "C" { #endif -typedef uintptr_t PlayerID; - const char *oto_oboe_Play(int sample_rate, int channel_num, int buffer_size_in_bytes); const char *oto_oboe_Suspend(); diff --git a/player.go b/player.go index 8ec4e3d..4888937 100644 --- a/player.go +++ b/player.go @@ -67,14 +67,14 @@ func (p *Player) Volume() float64 { return p.player.Volume() } -// SetVolume sets the current volume, which must be in the range of [0, math.MaxFloat32]. +// SetVolume sets the current volume in the range of [0, math.MaxFloat32]. // A volume larger than 1 amplifies the sound and might cause clipping. // A value out of the range, including NaN, is treated as 0. func (p *Player) SetVolume(volume float64) { p.player.SetVolume(volume) } -// BufferedSize returns the byte size of the buffer data that is not sent to the audio hardware yet. +// BufferedSize returns the byte size of the buffered data that is not sent to the audio hardware yet. func (p *Player) BufferedSize() int { return p.player.BufferedSize() } @@ -109,7 +109,7 @@ func (p *Player) Seek(offset int64, whence int) (int64, error) { // // Close does nothing and always returns nil. // -// Deprecated: as of v3.4. you don't have to call Close. +// Deprecated: as of v3.4, you don't have to call Close. func (p *Player) Close() error { // (*mux.Player).Close() is called by the finalizer. Let's rely on it. return nil