diff --git a/Contributing.md b/Contributing.md new file mode 100644 index 0000000..1c4ad9a --- /dev/null +++ b/Contributing.md @@ -0,0 +1,17 @@ +# Contributing to zikichombo.org/sio + +1. If you are filing an issue for a bug report, please specify the system, + version, environment and to the extent possible a small reproducer. + +1. For porting, see [the porting guide](Porting.md). [ZikiChombo](http://zikichombo.org) + supports 3rd party independently licensed ports as well ports distributed + with this sio module. To contribute to the zikichombo.org/sio distribution, + we need an out-of-github band confirmation, for example by [email](contrib@zikichombo.org), + to participate as an author in our shared author BSD License. + +1. General discussion relating to ports, entry points, sio project design are + welcome on the issue tracker, please label any new issues with "discussion: " + to help distinguish these from bugs. + + + diff --git a/Porting.md b/Porting.md new file mode 100644 index 0000000..37eb5c1 --- /dev/null +++ b/Porting.md @@ -0,0 +1,144 @@ +# Porting Guide + +## Status of Ports +The status of ports is kept in the main module [README](README.md). + +## Overview +ZikiChombo sio code has a porting process that is unconventional due to the +nature of sound support on host systems. Usually, to port a system, one takes some +desired cross-host functionality, such as a filesystem, and implements it with +the OS or OS/hardware combination of interest and that's that (to make a long +story short). + +For sound, the problem is that hosts usually have multiple different software +components as entry points to playing and capturing sound, and moreover these +entry points have some mutually exclusive properties, such as lower bounds on +latency, availability of duplex synchronised by hardware audio clock, ability +to be shared accross many users or applications, etc. + +Although the main CPU hardware architectures can certainly be a concern, it has +not been so far in our experience. So we focus on organizing around host, +which is taken to be the value of runtime.GOOS in a Go program. + +# Hosts +A host often has a hardware abstraction layer, hardware drivers, and some +levels of interacting with these layers to coordinate and make secure demands +related to playing and capturing sound on a device. + +To simplify the end use case, we want to make all this transparent to the +consumer of sio, and let them simply work with sound.{Source,Sink,Duplex}. + +For each Host, ZikiChombo defines a default entry point to accomplish this. + +All entry points are available both to the consumer of ZikiChombo and either +with ZikiChombo or as 3rd party implementations. So if you're working with +VoIP and have precise duplex echo cancellation needs, or a music app that +listens and plays in real time with feedback, or writing a PulseAudio +replacement, you're more likely to be interested in using a specialized entry +point than the default. + +There is a directory ZikiChombo/sio/ports/{runtime.GOOS} for each host. + +# Entry Points +An entry point defines a subset of the functionality listed in the main +[README](README.md). ZikiChombo defines for each host (runtime.GOOS) a list of +entry points which refer to the software layer with which the go program will +communicate. These are named after the respective entry points in the main +[README](README.md). + +Each entry point defines a base set of functionality related to what the entry +point supports: device scanning, device change notifications, playback, +capture, duplex. Note the README excludes some functionality from some +entry points and entry points may be incomplete. + +The functionality is slightly more rich than implementing +sio.{Capture,Play,Player,Duplex} to allow callers to achieve latency or other +requirements. Package sio automatically uses the Entry point to apply defaults +so that the caller may simply call sio.{Play,Player,Capture,Duplex}. Package +sio also enforces that only one entry point may be in use at a time in one Go +program. + +Entry points can then be registered by a package implementing them in +their init() function. Consumers of ZikiChombo may optionaly control which +package implements a given entry point. The default is chosen by +package initialisation order. + +See [host](http://godoc.org/zikichombo.org/sio/host) for details. + +The list of entry names for each host is defined in host/entry_{host}.go +under the function host.Names(). + + +# Supporting concepts Devices, Inputs, Outputs, Duplex, Packets +To implement an Entry Point, ZikiChombo provides some support code in +[libsio](http://godoc.org/zikichombo.org/sio/libsio). The only required part +of this code to reference is libsio/Dev to implement an Entry. + +Other parts of this code, libsio.{Input,Output,Duplex,Packet,DuplexPacket} +provide interfaces for synchronising with the host via Go channels and +implementations to adapt these structures to sound.{Source,Sink,Duplex}. + +## Import directions +Package zikichombo.org/sio imports the package implementing entry points +for registration side effects + +``` +import _ "zikichombo.org/sio/ports/{runtime.GOOS}" +``` + +Packages implementing the entries should import "zikichombo.org/sio/host" and +call +``` +host.RegisterEntry(mySuperReliableEntry) +``` + +These packages may also import zikichombo.org/libsio. + + + +# Duplex + +Duplex support is intended for synchronized input/output. Systems which simply +buffer underlying independent I+O and loosely synchronize with the slack that +results from the buffering should consider not implementing Duplex and just +letting the caller use sound.{Source,Sink} synchronously. Ideally, duplex +should be audio hardware clock synchronized as in Apple's Aggregate devices. +Duplexing I+O ringbuffers which are independently clocked to the same sample +rate are acceptable but less reliable especially for long sessions. PortAudio +actually supports duplex connections which have different sample rates. We +recommend that this not be done to preserve the reliability of latency in +Duplex implementations. + +# Build tags + +Submitted ports and tests which produce or capture sound should use the build +tag "listen" so that they do not run by default but are easily invokable with + +``` +go test zikichombo.org/sio/... -tags listen +``` + + +# 3rd Party Ports +To have an independently distributed port listed here, please file an issue. +We only list the most recent zc version/port version pairs here. + +| Port | zc version | Port version | +-------|------------|--------------| +| - | v0.0.1-alpha.2 | vX.Y.Z | + + +# References +The following may be useful references for those considering sio ports. +* [Plan9 Audio](http://man.cat-v.org/plan_9/3/audio) +* [PortAudio](http://portaudio.com) +* [FreeBSD](https://www.freebsd.org/doc/handbook/sound-setup.html) +* [ALSA](https://www.alsa-project.org/main/index.php/Main_Page) +* [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio/) +* [Web Audio](https://www.w3.org/TR/webaudio/) +* [Audio Units](https://developer.apple.com/documentation/audiounit?language=objc) +* [VST](https://www.steinberg.net/en/company/technologies/vst3.html) +* [Pulse Audio](https://en.wikipedia.org/wiki/PulseAudio) +* [Android Audio](https://source.android.com/devices/audio/terminology) +* [AudioFlinger](https://android.googlesource.com/platform/frameworks/av/+/109347d421413303eb1678dd9e2aa9d40acf89d2/services/audioflinger/AudioFlinger.cpp) + diff --git a/README.md b/README.md index a98ffc5..b8b921b 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,113 @@ [![Build Status](https://travis-ci.com/zikichombo/sio.svg?branch=master)](https://travis-ci.com/zikichombo/sio) +# Usage +If you are using sio for sound capture and playback, only the [sio](http://godoc.org/zikichombo.org/sio) +package is needed. For device scanning and APIs, the [host](http://godoc.org/zikichombo.org/sio/host) +package provides the necessary support. + + +# Ports +For porting, see the [porting guide](Porting.md) and [contributing](Contributing.md). + +## Status + +Below is the status of sio ports. Items marked with an "X" in plain text (checked off +in html rendered markdown) are incorporated into sio, potentially with alpha status. +Items marked with a "?" indicates we do not yet have sufficient knowledge to judge +whether or not the item is a TODO. Related discussion on the issue tracker is welcome. +Items marked with "-" are those for which we think the functionality is not relevant or +not sufficiently supported by the external software interface to add to sio. + +In the event there are opinions about the content of the list itself, such as whether +to support JACK, whether to interface with Android HAL, the issue tracker is our best +means of coordinating the discussion. + + +* Linux + 1. ALSA (cgo) + 1. [X] Playback + 1. [X] Capture + 1. [ ] Duplex + 1. [ ] Device Scanning + 1. [ ] Device Notification + 1. TinyALSA (cgo) + 1. [ ] Playback + 1. [ ] Capture + 1. [ ] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification + 1. ALSA (no cgo) + 1. [?] Playback + 1. [?] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification + 1. Pulse Audio + 1. [ ] Playback + 1. [ ] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification +* Darwin/iOS + 1. Audio Queue Services + 1. [X] Playback + 1. [X] Capture + 1. [-] Duplex + 1. [-] Device Scanning + 1. [ ] Test for iOS + 1. AUHAL + 1. [ ] Playback + 1. [ ] Capture + 1. [ ] Duplex + 1. [X] Device Scanning + 1. [ ] Test for iOS via RemoteIO replacing AUHAL. +* Android + 1. TinyALSA + 1. [ ] Playback + 1. [ ] Capture + 1. [ ] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification + 1. Android Audio HAL [?] + 1. AudioFlinger + 1. [ ] Playback + 1. [ ] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification +* Windows + 1. ASIO + 1. [?] Playback + 1. [?] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification + 1. Direct Sound + 1. [?] Playback + 1. [?] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification + 1. WASAPI + 1. [?] Playback + 1. [?] Capture + 1. [?] Duplex + 1. [?] Device Scanning + 1. [?] Device Notification +* js + 1. Web Audio + 1. [ ] Playback + 1. [ ] Capture + 1. [-] Duplex + 1. [ ] Device Scanning + 1. [?] Device Notification + +* plan9 [?] +* netbsd [?] +* freebsd [?] +* openbsd [?] +* dragonfly [?] + + diff --git a/aqin_darwin_test.go b/aqin_darwin_test.go deleted file mode 100644 index 0617b6c..0000000 --- a/aqin_darwin_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build darwin -// +build listen - -package sio_test - -import ( - "testing" - "time" - - "zikichombo.org/codec/wav" - "zikichombo.org/sio" - "zikichombo.org/sound" - "zikichombo.org/sound/ops" - "zikichombo.org/sound/sample" -) - -func TestDarwinIn(t *testing.T) { - v := sound.StereoCd() - q, e := sio.DefaultInputDev.Input(v, sample.SFloat32L, 128) - if e != nil { - t.Fatal(e) - } - defer q.Close() - src := ops.LimitDur(sio.InputSource(q), time.Second) - if e := wav.Save(src, "darwin-in.wav"); e != nil { - t.Fatal(e) - } -} diff --git a/aqo_darwin_test.go b/aqo_darwin_test.go deleted file mode 100644 index df24dc7..0000000 --- a/aqo_darwin_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build darwin -// +build cgo -// +build listen - -package sio_test - -import ( - "fmt" - "testing" - "time" - - "zikichombo.org/sio" - "zikichombo.org/sound" - "zikichombo.org/sound/freq" - "zikichombo.org/sound/gen" - "zikichombo.org/sound/sample" -) - -func TestDarwinOut(t *testing.T) { - key := 440 * freq.Hertz - //z := gen.Notes(key, (key*3)/2, (key*6)/5, 2*key) - z := gen.Sin(key) - q, e := sio.DefaultOutputDev.Output(sound.StereoCd(), sample.SFloat32L, 256) - if e != nil { - t.Fatal(e) - } - defer q.Close() - fmt.Printf("started q\n") - ttl := 0 - buf := make([]float64, 1) - start := time.Now() - - for ttl < 44000 { - pkt, ok := <-q.FillC() - if !ok { - break - } - pkt.D = pkt.D[:cap(pkt.D)] - j := 0 - - for j < len(pkt.D) { - n, e := z.Receive(buf) - if e != nil { - t.Fatal(e) - } - if n != 1 { - t.Fatalf("expected %d got %d\n", 1, n) - } - pkt.D[j], pkt.D[j+1] = buf[0], buf[0] - j += 2 - } - ttl += j / 2 - pkt.D = pkt.D[:j] - q.PlayC() <- pkt - } - fmt.Printf("starting delayed.\n") - off := 44000 // second delay scheduled. - lim := ttl + 44000 - for ttl < lim { - pkt, ok := <-q.FillC() - if !ok { - break - } - pkt.D = pkt.D[:cap(pkt.D)] - j := 0 - for j < len(pkt.D) { - n, e := z.Receive(buf) - if e != nil { - t.Fatal(e) - } - if n != 1 { - t.Fatalf("expected %d got %d\n", 1, n) - } - pkt.D[j], pkt.D[j+1] = buf[0], buf[0] - j += 2 - } - if off != 0 { - pkt.N += off - off = 0 - } - ttl += j / 2 - pkt.D = pkt.D[:j] - - q.PlayC() <- pkt - } - fmt.Printf("sent %d in %s\n", ttl, time.Since(start)) - time.Sleep(time.Second) -} diff --git a/dev.go b/dev.go deleted file mode 100644 index ec96b53..0000000 --- a/dev.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build !darwin !cgo -// +build !linux !cgo - -package sio - -import ( - "fmt" - - "zikichombo.org/sound" - "zikichombo.org/sound/sample" -) - -func Devices() []*Dev { - return nil -} - -func (d *Dev) Input(v sound.Form, co sample.Codec, n int) (Input, error) { - return nil, fmt.Errorf("unsupported\n") -} - -func (d *Dev) Output(v sound.Form, co sample.Codec, n int) (Output, error) { - return nil, fmt.Errorf("unsupported\n") -} diff --git a/dev_alsa.go b/dev_alsa.go deleted file mode 100644 index 7e69dc5..0000000 --- a/dev_alsa.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build linux -// +build cgo - -package sio - -import ( - "fmt" - "unsafe" - - "zikichombo.org/sound" - "zikichombo.org/sound/sample" -) - -// #cgo LDFLAGS: -lasound -// #include "alsa/asoundlib.h" -// -import "C" - -//TBD(wsc) figure out how to do this right. -var devices = []*Dev{ - &Dev{Name: "default"}, - &Dev{Name: "plughw:0,0"}, - &Dev{Name: "hw:0,0"}} - -func Devices() []*Dev { - return devices -} - -// Input attempts to create and start an Input. -func (d *Dev) Input(v sound.Form, co sample.Codec, n int) (Input, error) { - pcm := newAlsaPcmIn(d.Name, v, co, n) - if err := pcm.open(); err != nil { - return nil, err - } - return pcm, nil -} - -// Output attempts to create and start an Output, such as to a speaker. -func (d *Dev) Output(v sound.Form, co sample.Codec, n int) (Output, error) { - pcm := newAlsaPcmOut(d.Name, v, co, n) - if err := pcm.open(); err != nil { - return nil, err - } - return pcm, nil -} - -func init() { - devs := Devices() - if len(devs) > 0 { - DefaultInputDev = devs[0] - DefaultOutputDev = devs[0] - } - j := 0 - for _, d := range devs { - var pcm *C.snd_pcm_t - var name = C.CString(d.Name) - var hwp *C.snd_pcm_hw_params_t - C.snd_pcm_hw_params_malloc(&hwp) - defer C.snd_pcm_hw_params_free(hwp) - defer C.free(unsafe.Pointer(name)) - ret := C.snd_pcm_open(&pcm, name, C.SND_PCM_STREAM_CAPTURE, 0) - if ret < 0 { - continue - } - C.snd_pcm_hw_params_any(pcm, hwp) - for _, codec := range sample.Codecs { - ret = C.snd_pcm_hw_params_test_format(pcm, hwp, - scodec2Alsa[codec]) - if ret < 0 { - continue - } - d.SampleCodecs = append(d.SampleCodecs, codec) - } - fmt.Printf("%s: %v\n", d, d.SampleCodecs) - devs[j] = d - j++ - C.snd_pcm_drain(pcm) - C.snd_pcm_close(pcm) - } - devices = devs[:j] - DefaultForm = sound.StereoCd() - DefaultCodec = sample.SFloat32L - DefaultOutputBufferSize = 256 - DefaultInputBufferSize = 256 -} diff --git a/dev_test.go b/dev_test.go deleted file mode 100644 index 7d4c2d7..0000000 --- a/dev_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build cgo - -package sio_test - -import ( - "fmt" - "testing" - "time" - - "zikichombo.org/sio" - "zikichombo.org/sound" -) - -func TestDevices(t *testing.T) { - devs := sio.Devices() - nIns := 0 - nOuts := 0 - for _, d := range devs { - fmt.Printf("testing %v\n", d) - for _, sc := range d.SampleCodecs { - for _, v := range []sound.Form{sound.MonoCd(), sound.StereoCd()} { - i, ie := d.Input(v, sc, 256) - if ie != nil { - t.Errorf("device %s can't make input for %s, %s: %s\n", d, v, sc, ie) - } else { - testInput(i, t) - nIns++ - } - o, oe := d.Output(v, sc, 256) - if oe != nil { - t.Errorf("device %s can't make output for %s, %s: %s\n", d, v, sc, oe) - } else { - testOutput(o, t) - nOuts++ - } - } - } - } - if nIns == 0 { - t.Errorf("no inputs found on system.") - } - if nOuts == 0 { - t.Errorf("no outputs found on system.") - } -} - -func testInput(i sio.Input, t *testing.T) { - i.Close() - time.Sleep(50 * time.Millisecond) -} - -func testOutput(o sio.Output, t *testing.T) { - o.Close() - time.Sleep(50 * time.Millisecond) -} diff --git a/host/doc.go b/host/doc.go new file mode 100644 index 0000000..ec6bda4 --- /dev/null +++ b/host/doc.go @@ -0,0 +1,7 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package host provides host sound entry point support. +// +// Package host is part of http://zikichombo.org +package host /* import "zikichombo.org/sio/host" */ diff --git a/host/entry.go b/host/entry.go new file mode 100644 index 0000000..f89fa4a --- /dev/null +++ b/host/entry.go @@ -0,0 +1,350 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +import ( + "fmt" + "reflect" + "sync" + "time" + + "zikichombo.org/sio/libsio" + "zikichombo.org/sound" + "zikichombo.org/sound/ops" + "zikichombo.org/sound/sample" +) + +// Entry is the type of a connection to host sound I/O capabilities. +// +// Entries may either be high level (eg AudioFlinger) or low level (eg ALSA hw). +// +// Multiple entries may exist for a given host. see Names() in the relevant +// entry_{runtime.GOOS}.go file for details. +type Entry interface { + // Name returns the name of the entry and should be a valid + // name for the host. + Name() string + // DefaultForm returns the default form. In case one form is not + // implemented or uniform accross all supported functionality (input, output, device), + // output default should be returned. + DefaultForm() sound.Form + + // DefaultSampleCodec returns the default sample codec. In case one sample codec + // is not implemented or uniform accross all supported functionality, output default + // should be returned. + DefaultSampleCodec() sample.Codec + + // DefaultBufSize returns the default size of the buffer in frames provided to + // the caller for I/O interactions. + DefaultBufSize() int + + // CanOpenSource returns true if OpenSource does not return + // ErrUnspported. + CanOpenSource() bool + + // OpenSource starts capturing audio. + // + // dev is the specified device, and should be nil if and only if the current + // entry has .HasDevices(). Otherwise, dev should be a device returned by + // the current entry .ScanDevices() or Devices() + // + // v indicates the desired form (channels, sample rate) of the source. + // + // sco indicates the desired sample.Codec. + // + // bufSz indicates the size of buffer, in frames, which data from the + // hardware or software implementing the entry to sound.Source.Receive. + // For hardware, this is normally the size of the part of the ring buffer exposed for + // reading. Implementations should use a minimal total buffer size to accomodate + // this constraint. + // + // OpenSource returns a triple (s, t, e) with + // s: sound.Source which represents captured audio. + // t: the start time of the first sample. + // e: any error + OpenSource(dev *libsio.Dev, v sound.Form, sco sample.Codec, bufSz int) (sound.Source, time.Time, error) + + // Can open sink returns true if OpenSink does not return + // ErrUnsupported + CanOpenSink() bool + // OpenSink starts playing audio. + // + // dev is the specified device, and should be nil if and only if the current + // entry has .HasDevices(). Otherwise, dev should be a device returned by + // the current entry via ScanDevices() or Devices(). + // + // v indicates the desired form (channels, sample rate) of the source. + // + // sco indicates the desired sample.Codec. + // + // bufSz indicates the size of buffer whose data is placed + // in sound.Source.Receive. This is normally the size of the part of + // the ring buffer exposed for playback. Implementations should + // use a minimal total buffer size to safely accomodate this constraint. + // + // OpenSink returns a tuple (s, *t, e) with + // s: sound.Sink which represents audio for playback. + // *t: a pointer to the start time of the first played sample, which is set + // after the first successful send via the returned sound.Sink. + // e: any error + // + // OpenSink should + OpenSink(dev *libsio.Dev, v sound.Form, sco sample.Codec, bufSz int) (sound.Sink, *time.Time, error) + + // CanOpenDuplex returns true if OpenDuplex does not return ErrUnsupported. + CanOpenDuplex() bool + // OpenDuplex starts a duplex connection to the host. + // + // dev is the specified device, and should be nil if and only if the current + // entry has .HasDevices(). Otherwise, dev should be a device returned by + // the current entry via ScanDevices() or Devices(). + // + // iv, ov represent the form of input and output respectively. + // + // sco indicates the desired sample.Codec. + // + // bufSz indicates the size of the buffer, in frames, whose data is + // placed in or retreived from a []float64 in Duplex.SendReceive. Implementations + // should use a minimal total buffer size to safely accomodate this constraint. + // + // OpenDuplex returns a quadruple(d, ct, *pt, e) with + // + // d a sound.Duplex implementation + // ct the time of the first sample of captured data. + // *pt: a pointer to the time of the first sample of played data. + // e: an error if any while opening the duplex connection. + OpenDuplex(dev *libsio.Dev, iv, ov sound.Form, sco sample.Codec, bufSz int) (sound.Duplex, time.Time, *time.Time, error) + + // HasDevices returns true if the entry supports the concept of devices. + HasDevices() bool + + // ScanDevices scans the host for devices. It does not use a cache. + // + // ScanDevices returns a non-nil error if no devices can be scanned. + // Errors associated with each device scan are available in the + // DevScanResults. + ScanDevices() ([]*DevScanResult, error) + + // Devices returns a slice of devices on the host. The returned list may be cached. + // Devices() should call ScanDevices() at least once and cache the resulting slice + // of devices subsequently. + Devices() []*libsio.Dev + + // DevicesNotify sends notifications of device changes on c. + // + // DevicesNotify returns ErrUnsupported if the entry does not support + // notifications. + DevicesNotify(c chan<- *DevChange) error + + // DevicesNotifyClose stops sending device notifications on c. + DevicesNotifyClose(c chan<- *DevChange) + + // returns nil if no input supported or HasDevices() is false. + DefaultInputDev() *libsio.Dev + // returns nil if no output supported or HasDevices() is false. + DefaultOutputDev() *libsio.Dev + // returns nil if no duplex supported or HasDevices() is false. + DefaultDuplexDev() *libsio.Dev +} + +// DevScanResult +type DevScanResult struct { + Dev *libsio.Dev + E error +} + +// DevChangeSense indicates whether a DevChange +// is a connection or a disconnection. +type DevChangeSense int + +const ( + DeviceConnect DevChangeSense = iota + DeviceDisconnect +) + +// DevChange describes an event related to +// device connectivity. +type DevChange struct { + Sense DevChangeSense + Dev *libsio.Dev +} + +// Capture tries to open an input such as a microphone. +func Capture(e Entry) (sound.Source, error) { + return CaptureWith(e, e.DefaultForm(), e.DefaultSampleCodec(), e.DefaultBufSize()) +} + +// CaptureWith opens audio capture such as via a microphone +// with the specified sound.Form v, sample.Codec co, and buffer size b. +func CaptureWith(e Entry, v sound.Form, co sample.Codec, b int) (sound.Source, error) { + if !e.CanOpenSource() { + return nil, ErrUnsupported + } + dev := e.DefaultInputDev() + s, _, err := e.OpenSource(dev, v, co, b) + return s, err +} + +// Play plays a sound.Source +func Play(e Entry, src sound.Source) error { + snk, err := Player(e, src) + if err != nil { + return err + } + return ops.Copy(snk, src) +} + +// PlayWith returns a Player for src with output sample codec co +// and buffer size b. +func PlayWith(e Entry, src sound.Source, co sample.Codec, b int) error { + snk, err := PlayerWith(e, src, co, b) + if err != nil { + return err + } + return ops.Copy(snk, src) +} + +// Player tries to return a sound.Sink to which +// writes are played. +func Player(e Entry, v sound.Form) (sound.Sink, error) { + return PlayerWith(e, v, e.DefaultSampleCodec(), e.DefaultBufSize()) +} + +// PlayerWith opens a sound.Sink for playback with the specified sound.Form, +// sample.Codec, and buffersize. +func PlayerWith(e Entry, v sound.Form, co sample.Codec, b int) (sound.Sink, error) { + if !e.CanOpenSink() { + return nil, ErrUnsupported + } + dev := e.DefaultOutputDev() + snk, _, err := e.OpenSink(dev, v, co, b) + if err != nil { + return nil, err + } + return snk, nil +} + +// Duplex returns a duplex with entry defaults. +func Duplex(e Entry, iv, ov sound.Form) (sound.Duplex, error) { + return DuplexWith(e, iv, ov, e.DefaultSampleCodec(), e.DefaultBufSize()) +} + +// DuplexWith opens a Duplex connection with the specified input, output forms, +// sample codec, and buffer size. +func DuplexWith(e Entry, iv, ov sound.Form, co sample.Codec, b int) (sound.Duplex, error) { + if !e.CanOpenDuplex() { + return nil, ErrUnsupported + } + dev := e.DefaultDuplexDev() + dpx, _, _, err := e.OpenDuplex(dev, iv, ov, co, b) + return dpx, err +} + +type entry struct { + Entry + pkgPath string +} + +func pkgPath(v interface{}) string { + typ := reflect.ValueOf(v).Type() + return typ.PkgPath() +} + +var entries map[string][]*entry = make(map[string][]*entry) + +// RegisterEntry registers an Entry. +func RegisterEntry(e Entry) error { + nm := e.Name() + found := false + for _, okName := range Names() { + if nm == okName { + found = true + break + } + } + if !found { + return ErrInvalidEntryName + } + privEntry := &entry{ + Entry: e, + pkgPath: pkgPath(e)} + entries[nm] = append(entries[nm], privEntry) + return nil +} + +var hMu sync.Mutex +var eMu sync.Mutex +var theEntry Entry + +// Connect opens the default entry for the host. +// +// Connect returns ErrNoEntryAvailable if there are no entries for the host. +// +// Connect returns ErrEntryInUse if a non-default host entry is in use. +// +// Connect can be called many times without cost of re-initialising a +// connection. Connect can be called in different goroutines. +func Connect(pkgSel func(string) bool) (Entry, error) { + nms := Names() + if len(nms) == 0 { + return nil, ErrNoEntryAvailable + } + return ConnectTo(nms[0], pkgSel) +} + +// ConnectTo connects to the named entry. +// +// ConnectTo returns ErrNoEntryAvailable if there are no entries for the host. +// +// ConnectTo returns ErrEntryInUse if another host entry other than one +// requested is in use. +// +// Connect can be called many times without cost of re-initialising a +// connection. Connect can be called in different goroutines. +func ConnectTo(name string, pkgSel func(string) bool) (Entry, error) { + hMu.Lock() + defer hMu.Unlock() + if theEntry != nil { + return theEntry, nil + } + res := findEntry(entries[name], pkgSel) + if res == nil { + return nil, fmt.Errorf("couldn't locate entry.") + } + if theEntry != nil && theEntry != res { + return nil, ErrEntryInUse + } + eMu.Lock() + theEntry = res + return res, nil +} + +// Disconnect disconnects any current entry making +// other entries available. +// +// Disconnect is safe for calling in several goroutines. +func Disconnect() { + hMu.Lock() + defer hMu.Unlock() + if theEntry != nil { + theEntry = nil + eMu.Unlock() + } +} + +func findEntry(s []*entry, pkgSel func(string) bool) Entry { + for _, e := range s { + if pkgSel == nil || pkgSel(e.Name()) { + return e.Entry + } + } + return nil +} + +// Names names the sound system entry points for the host. +func Names() []string { + res := make([]string, len(names)) + copy(res, names[:]) + return res +} diff --git a/host/entry_android.go b/host/entry_android.go new file mode 100644 index 0000000..4593f9c --- /dev/null +++ b/host/entry_android.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{"AudioFlinger", "TinyALSA", "ALSA"} diff --git a/host/entry_darwin.go b/host/entry_darwin.go new file mode 100644 index 0000000..38625f2 --- /dev/null +++ b/host/entry_darwin.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{"CoreAudio Audio Queue Services", "CoreAudio AUHAL", "CoreAudio RemoteIO"} diff --git a/host/entry_dragonfly.go b/host/entry_dragonfly.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_dragonfly.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/entry_freebsd.go b/host/entry_freebsd.go new file mode 100644 index 0000000..545da75 --- /dev/null +++ b/host/entry_freebsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{"PulseAudio", "VirtualChannels"} diff --git a/host/entry_js.go b/host/entry_js.go new file mode 100644 index 0000000..b48b1c4 --- /dev/null +++ b/host/entry_js.go @@ -0,0 +1,8 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// +build js + +package host + +var names = [...]string{"WebAudio"} diff --git a/host/entry_linux.go b/host/entry_linux.go new file mode 100644 index 0000000..56e4ba6 --- /dev/null +++ b/host/entry_linux.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{"Linux -- ALSACGO", "Linux -- ALSAGO", "Linux -- PulseAudio"} diff --git a/host/entry_netbsd.go b/host/entry_netbsd.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_netbsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/entry_openbsd.go b/host/entry_openbsd.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_openbsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/entry_plan9.go b/host/entry_plan9.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_plan9.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/entry_solaris.go b/host/entry_solaris.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_solaris.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/entry_windows.go b/host/entry_windows.go new file mode 100644 index 0000000..14ded87 --- /dev/null +++ b/host/entry_windows.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package host + +var names = [...]string{} diff --git a/host/err.go b/host/err.go new file mode 100644 index 0000000..d7313b0 --- /dev/null +++ b/host/err.go @@ -0,0 +1,18 @@ +package host + +import "errors" + +var ( + // ErrInvalidEntryName is used on RegisterEntry to + // enforce the use of known entry points. + ErrInvalidEntryName = errors.New("invalid entry name") + // ErrUnsupported is returned when the entry in use + // does not support the requested operation. + ErrUnsupported = errors.New("unsupported") + // ErrNoEntryAvailable indicates there are no entry ports + // for the host. + ErrNoEntryAvailable = errors.New("no entry available") + // ErrEntryInUse indicates that the caller requested an + // entry when another is in use. + ErrEntryInUse = errors.New("entry in use") +) diff --git a/host/null.go b/host/null.go new file mode 100644 index 0000000..2e6ea9d --- /dev/null +++ b/host/null.go @@ -0,0 +1,90 @@ +package host + +import ( + "time" + + "zikichombo.org/sio/libsio" + "zikichombo.org/sound" + "zikichombo.org/sound/sample" +) + +// NullEntry is an entry which implements nothing and +// yet fullfills the Entry interface. +// +// It is useful for embedding in entry points which don't +// implement everything to set defaults. +type NullEntry struct { +} + +func (n *NullEntry) Name() string { + return "null" +} + +func (n *NullEntry) DefaultForm() sound.Form { + return sound.MonoCd() +} + +func (n *NullEntry) DefaultSampleCodec() sample.Codec { + return sample.SFloat32L +} + +func (n *NullEntry) DefaultBufSize() int { + return 256 +} + +func (n *NullEntry) CanOpenSource() bool { + return false +} + +func (n *NullEntry) OpenSource(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Source, time.Time, error) { + var t time.Time + return nil, t, ErrUnsupported +} + +func (n *NullEntry) CanOpenSink() bool { + return false +} + +func (n *NullEntry) OpenSink(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Sink, *time.Time, error) { + return nil, nil, ErrUnsupported +} + +func (n *NullEntry) CanOpenDuplex() bool { + return false +} + +func (n *NullEntry) OpenDuplex(d *libsio.Dev, iv, ov sound.Form, co sample.Codec, b int) (sound.Duplex, time.Time, *time.Time, error) { + var t time.Time + return nil, t, nil, ErrUnsupported +} + +func (n *NullEntry) HasDevices() bool { + return false +} + +func (n *NullEntry) ScanDevices() ([]*DevScanResult, error) { + return nil, ErrUnsupported +} + +func (n *NullEntry) Devices() []*libsio.Dev { + return nil +} + +func (n *NullEntry) DevicesNotify(chan<- *DevChange) error { + return nil +} + +func (n *NullEntry) DevicesNotifyClose(c chan<- *DevChange) { +} + +func (n *NullEntry) DefaultInputDev() *libsio.Dev { + return nil +} + +func (n *NullEntry) DefaultOutputDev() *libsio.Dev { + return nil +} + +func (n *NullEntry) DefaultDuplexDev() *libsio.Dev { + return nil +} diff --git a/init_android.go b/init_android.go new file mode 100644 index 0000000..dc11dbf --- /dev/null +++ b/init_android.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/android" diff --git a/init_darwin.go b/init_darwin.go new file mode 100644 index 0000000..4efecdc --- /dev/null +++ b/init_darwin.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/darwin" diff --git a/init_dragonfly.go b/init_dragonfly.go new file mode 100644 index 0000000..92b2de9 --- /dev/null +++ b/init_dragonfly.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/dragonfly" diff --git a/init_freebsd.go b/init_freebsd.go new file mode 100644 index 0000000..b4ec3e7 --- /dev/null +++ b/init_freebsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/freebsd" diff --git a/init_js.go b/init_js.go new file mode 100644 index 0000000..eb3428c --- /dev/null +++ b/init_js.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/js" diff --git a/init_linux.go b/init_linux.go new file mode 100644 index 0000000..3350e4d --- /dev/null +++ b/init_linux.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/linux" diff --git a/init_netbsd.go b/init_netbsd.go new file mode 100644 index 0000000..c36a2f0 --- /dev/null +++ b/init_netbsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/netbsd" diff --git a/init_openbsd.go b/init_openbsd.go new file mode 100644 index 0000000..2be0388 --- /dev/null +++ b/init_openbsd.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/openbsd" diff --git a/init_plan9.go b/init_plan9.go new file mode 100644 index 0000000..7d4b643 --- /dev/null +++ b/init_plan9.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/plan9" diff --git a/init_solaris.go b/init_solaris.go new file mode 100644 index 0000000..810c8bf --- /dev/null +++ b/init_solaris.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/solaris" diff --git a/init_windows.go b/init_windows.go new file mode 100644 index 0000000..c97f8c1 --- /dev/null +++ b/init_windows.go @@ -0,0 +1,6 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import _ "zikichombo.org/sio/ports/windows" diff --git a/dev_common.go b/libsio/dev_common.go similarity index 76% rename from dev_common.go rename to libsio/dev_common.go index 2721941..8a10a46 100644 --- a/dev_common.go +++ b/libsio/dev_common.go @@ -1,7 +1,7 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package libsio import ( "fmt" @@ -89,22 +89,3 @@ func (d *Dev) CanDuplexForm(sr freq.T, inC, outC int) bool { } return true } - -// DefaultInputDev is the default input device (audio capture). -var DefaultInputDev *Dev - -// DefaultOutputDev is the default output device (audio playback). -var DefaultOutputDev *Dev - -// DefaultForm specifies the default sampling rate and number of channels -// (Form). -var DefaultForm sound.Form - -// DefaultCodec is the default Sample codec. -var DefaultCodec sample.Codec - -// DefaultOutputBufferSize, in number of frames per packet. -var DefaultOutputBufferSize int - -// DefaultInputBufferSize, in number of frames per packet. -var DefaultInputBufferSize int diff --git a/libsio/doc.go b/libsio/doc.go new file mode 100644 index 0000000..79db6bb --- /dev/null +++ b/libsio/doc.go @@ -0,0 +1,7 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package libsio provides support for the different ports. +// +// Package libsio is part of http://zikichombo.org +package libsio /* import "zikichombo.org/sio/libsio" */ diff --git a/duplex.go b/libsio/duplex.go similarity index 95% rename from duplex.go rename to libsio/duplex.go index f481932..06df0d3 100644 --- a/duplex.go +++ b/libsio/duplex.go @@ -1,7 +1,7 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package libsio import "zikichombo.org/sound" diff --git a/gcr.md b/libsio/gcr.md similarity index 91% rename from gcr.md rename to libsio/gcr.md index 10a00a2..9da6727 100644 --- a/gcr.md +++ b/libsio/gcr.md @@ -1,5 +1,9 @@ # Ring Buffer connecting Go and C +this document contains some (still buggy) thoughts on +using atomic to synchronise hardware sound buffers with +go slices/libsio.Packet + # Design characteristics: - elements are pairs (*C.char, *Packet) containing per-slice buffer sizes, diff --git a/input.go b/libsio/input.go similarity index 64% rename from input.go rename to libsio/input.go index 67fac2d..374b8c3 100644 --- a/input.go +++ b/libsio/input.go @@ -1,13 +1,15 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package libsio import ( "io" "zikichombo.org/sound" - "zikichombo.org/sound/sample" ) // Input encapsulates input from a device. @@ -26,36 +28,6 @@ type Input interface { C() <-chan *Packet } -// NewInput tries to open and start an input device. -// v Gives the valve information, c the dataformat of individual samples, -// and n the buffer size, in frames, of each packet. -func NewInput(v sound.Form, c sample.Codec, n int) (Input, error) { - return DefaultInputDev.Input(v, c, n) -} - -// DefaultInput tries to open and start an input -// device with default sampling rate, number of channels and -// buffersize. -func DefaultInput() (Input, error) { - return NewInput(DefaultForm, DefaultCodec, DefaultInputBufferSize) -} - -func Record() (sound.Source, error) { - i, e := DefaultInput() - if e != nil { - return nil, e - } - return InputSource(i), nil -} - -func RecordWith(v sound.Form, c sample.Codec, n int) (sound.Source, error) { - i, e := NewInput(v, c, n) - if e != nil { - return nil, e - } - return InputSource(i), nil -} - type chn struct { sound.Form in Input diff --git a/iomode.go b/libsio/iomode.go similarity index 98% rename from iomode.go rename to libsio/iomode.go index 27b032b..98b9a44 100644 --- a/iomode.go +++ b/libsio/iomode.go @@ -1,7 +1,7 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package libsio // IoMode represents direction of audio data in a device. type IoMode int diff --git a/output.go b/libsio/output.go similarity index 64% rename from output.go rename to libsio/output.go index 6d12f08..9e7a27d 100644 --- a/output.go +++ b/libsio/output.go @@ -1,14 +1,12 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package libsio import ( "errors" "zikichombo.org/sound" - "zikichombo.org/sound/ops" - "zikichombo.org/sound/sample" ) // Output encapsulates an output device such as to a speaker. @@ -42,19 +40,6 @@ type Output interface { PlayC() chan<- *Packet } -// NewOutput attempts to open and start an output device. -// v Gives the form (channels and sample rate), c the dataformat of individual samples, -// and n the buffer size, in frames, of each packet. -func NewOutput(v sound.Form, c sample.Codec, n int) (Output, error) { - return DefaultOutputDev.Output(v, c, n) -} - -// DefaultOutput attempts to open an output stream with default -// valve, sample codec, and output buffer size. -func DefaultOutput() (Output, error) { - return NewOutput(DefaultForm, DefaultCodec, DefaultOutputBufferSize) -} - type osnk struct { sound.Form out Output @@ -108,26 +93,3 @@ func OutputSink(o Output) sound.Sink { fillC: o.FillC(), playC: o.PlayC()} } - -// Play plays the Source src using the defaults. -// -// Play returns a non-nil error in case any problems -// occured either creating an output from a device or -// in the actual playback. -func Play(src sound.Source) error { - return PlaySource(src, DefaultOutputDev.SampleCodecs[0], DefaultOutputBufferSize) -} - -// PlaySource is like Play, but allows setting the sample -// codec and packet buffer size. -// -// On some systems (such as alsa), the packet buffer size is only a hint. -func PlaySource(src sound.Source, cod sample.Codec, bufSz int) error { - o, err := NewOutput(src, cod, bufSz) - if err != nil { - return err - } - snk := OutputSink(o) - defer snk.Close() - return ops.Copy(snk, src) -} diff --git a/packet.go b/libsio/packet.go similarity index 97% rename from packet.go rename to libsio/packet.go index dc50ff4..f7940ed 100644 --- a/packet.go +++ b/libsio/packet.go @@ -1,7 +1,7 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package libsio import "time" diff --git a/pcm_alsa_in_test.go b/pcm_alsa_in_test.go deleted file mode 100644 index 48b7ce3..0000000 --- a/pcm_alsa_in_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build cgo -// +build linux -// +build listen - -package sio_test - -import ( - "testing" - "time" - - "zikichombo.org/codec/wav" - "zikichombo.org/sio" - "zikichombo.org/sound" - "zikichombo.org/sound/ops" - "zikichombo.org/sound/sample" -) - -func TestAlsaIn(t *testing.T) { - v := sound.StereoCd() - q, e := sio.DefaultInputDev.Input(v, sample.SFloat32L, 128) - if e != nil { - t.Fatal(e) - } - defer q.Close() - src := ops.LimitDur(sio.InputSource(q), 4*time.Second) - if e := wav.Save(src, "alsa-in.wav"); e != nil { - t.Fatal(e) - } -} diff --git a/pcm_alsa_play_test.go b/pcm_alsa_play_test.go deleted file mode 100644 index 54be3c9..0000000 --- a/pcm_alsa_play_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build linux -// +build cgo -// +build listen - -package sio_test - -import ( - "fmt" - "testing" - "time" - - "zikichombo.org/sio" - "zikichombo.org/sound" - "zikichombo.org/sound/freq" - "zikichombo.org/sound/gen" - "zikichombo.org/sound/sample" -) - -func TestAlsaOut(t *testing.T) { - key := 440 * freq.Hertz - //z := gen.Notes(key, (key*3)/2, (key*6)/5, 2*key) - z := gen.Sin(key) - q, e := sio.DefaultOutputDev.Output(sound.StereoCd(), sample.SFloat32L, 256) - if e != nil { - t.Fatal(e) - } - defer q.Close() - fmt.Printf("started q at %s\n", time.Now()) - ttl := 0 - buf := make([]float64, 1) - start := time.Now() - - for ttl < 44000 { - pkt, ok := <-q.FillC() - if !ok { - break - } - pkt.D = pkt.D[:cap(pkt.D)] - j := 0 - - for j < len(pkt.D) { - n, e := z.Receive(buf) - if e != nil { - t.Fatal(e) - } - if n != 1 { - t.Fatalf("expected %d got %d\n", 1, n) - } - pkt.D[j], pkt.D[j+1] = buf[0], buf[0] - j += 2 - } - ttl += j / 2 - pkt.D = pkt.D[:j] - q.PlayC() <- pkt - } - fmt.Printf("starting delayed at %s.\n", time.Now()) - off := 44000 // half second delay scheduled. - lim := ttl + 44000 - for ttl < lim { - pkt, ok := <-q.FillC() - if !ok { - break - } - pkt.D = pkt.D[:cap(pkt.D)] - j := 0 - for j < len(pkt.D) { - n, e := z.Receive(buf) - if e != nil { - t.Fatal(e) - } - if n != 1 { - t.Fatalf("expected %d got %d\n", 1, n) - } - pkt.D[j], pkt.D[j+1] = buf[0], buf[0] - j += 2 - } - if off != 0 { - pkt.N += off - off = 0 - } - ttl += j / 2 - pkt.D = pkt.D[:j] - - q.PlayC() <- pkt - } - fmt.Printf("sent %d in %s\n", ttl, time.Since(start)) - time.Sleep(time.Second) -} diff --git a/pcm_alsa_test.go b/pcm_alsa_test.go deleted file mode 100644 index 76862cc..0000000 --- a/pcm_alsa_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source -// code is governed by a license that can be found in the License file. - -// +build linux -// +build cgo - -package sio - -import ( - "testing" - "time" - - "zikichombo.org/sound" - "zikichombo.org/sound/sample" -) - -func TestAlsaOpen(t *testing.T) { - for _, c := range []sample.Codec{sample.SInt16L} { //sample.Codecs { - for _, v := range []sound.Form{sound.StereoCd(), sound.MonoCd()} { - in := newAlsaPcmIn("default", v, c, 256) - if err := in.open(); err != nil { - t.Error(err) - in.Close() - continue - } - time.Sleep(time.Second) - in.Close() - out := newAlsaPcmOut("default", v, c, 128) - if err := out.open(); err != nil { - t.Error(err) - out.Close() - continue - } - out.Close() - } - } -} diff --git a/play_test.go b/play_test.go new file mode 100644 index 0000000..f1792e1 --- /dev/null +++ b/play_test.go @@ -0,0 +1,23 @@ +// +build listen + +package sio_test + +import ( + "testing" + "time" + + "zikichombo.org/sio" + "zikichombo.org/sound/freq" + "zikichombo.org/sound/gen" + "zikichombo.org/sound/ops" +) + +func TestPlay(t *testing.T) { + src := ops.LimitDur(gen.Note(440*freq.Hertz), time.Second) + start := time.Now() + if err := sio.Play(src); err != nil { + t.Fatal(err) + } + dur := time.Since(start) + t.Logf("played for %s\n", dur) +} diff --git a/ports/android/doc.go b/ports/android/doc.go new file mode 100644 index 0000000..29608fe --- /dev/null +++ b/ports/android/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package android zc sound/io entry points. +package android diff --git a/aq_darwin.go b/ports/darwin/aq_darwin.go similarity index 91% rename from aq_darwin.go rename to ports/darwin/aq_darwin.go index 953fbbe..342df50 100644 --- a/aq_darwin.go +++ b/ports/darwin/aq_darwin.go @@ -1,13 +1,14 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package darwin import ( "fmt" "time" "unsafe" + "zikichombo.org/sio/libsio" "zikichombo.org/sound" "zikichombo.org/sound/sample" ) @@ -24,7 +25,7 @@ type aq struct { codec sample.Codec qRef C.AudioQueueRef qBufs [3]C.AudioQueueBufferRef - gBufs [3]Packet + gBufs [3]libsio.Packet gbp int fmt C.AudioStreamBasicDescription } @@ -80,9 +81,9 @@ func (q *aq) allocateBufs(nFrames int) error { func (q *aq) initBufs(sz int) { c := int(q.fmt.mChannelsPerFrame) - q.gBufs[0] = Packet{D: make([]float64, sz*c)} - q.gBufs[1] = Packet{D: make([]float64, sz*c)} - q.gBufs[2] = Packet{D: make([]float64, sz*c)} + q.gBufs[0] = libsio.Packet{D: make([]float64, sz*c)} + q.gBufs[1] = libsio.Packet{D: make([]float64, sz*c)} + q.gBufs[2] = libsio.Packet{D: make([]float64, sz*c)} } func (q *aq) enqueueBufs() { diff --git a/aqin_darwin.c b/ports/darwin/aqin_darwin.c similarity index 100% rename from aqin_darwin.c rename to ports/darwin/aqin_darwin.c diff --git a/aqin_darwin.go b/ports/darwin/aqin_darwin.go similarity index 81% rename from aqin_darwin.go rename to ports/darwin/aqin_darwin.go index 474a28a..11e6052 100644 --- a/aqin_darwin.go +++ b/ports/darwin/aqin_darwin.go @@ -1,13 +1,14 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package darwin import ( "fmt" "sync" "unsafe" + "zikichombo.org/sio/libsio" "zikichombo.org/sound" "zikichombo.org/sound/sample" ) @@ -34,7 +35,7 @@ type aqin struct { mu sync.Mutex once sync.Once id int - ch chan *Packet + ch chan *libsio.Packet cch chan struct{} aq running bool @@ -119,7 +120,7 @@ func (q *aqin) Close() error { return nil } -func (q *aqin) C() <-chan *Packet { +func (q *aqin) C() <-chan *libsio.Packet { return q.ch } @@ -150,35 +151,9 @@ func (q *aqin) init(v sound.Form, co sample.Codec, bufSize int) error { return err } q.enqueueBufs() - q.ch = make(chan *Packet, 1) + q.ch = make(chan *libsio.Packet, 1) q.cch = make(chan struct{}, 1) q.running = false q.n = 0 return nil } - -// globals so we can not have go pointers to go pointers in c. -// instead we refer to ids. -var _inaqs [maxAqins]aqin -var _inaqFree chan int -var _inaqNew chan int - -// set up process of ids and free list. -// now, since each id refers to fixed place in memory -// we can use it without locking in the sound data -// processing loop. -func init() { - _inaqFree = make(chan int, maxAqins) - _inaqNew = make(chan int) - for i := 0; i < maxAqins; i++ { - _inaqs[i].id = i - _inaqFree <- i - } - go func() { - var f int - for { - f = <-_inaqFree - _inaqNew <- f - } - }() -} diff --git a/aqo_darwin.c b/ports/darwin/aqo_darwin.c similarity index 100% rename from aqo_darwin.c rename to ports/darwin/aqo_darwin.c diff --git a/aqo_darwin.go b/ports/darwin/aqo_darwin.go similarity index 93% rename from aqo_darwin.go rename to ports/darwin/aqo_darwin.go index 3f41a0a..a540012 100644 --- a/aqo_darwin.go +++ b/ports/darwin/aqo_darwin.go @@ -1,7 +1,7 @@ // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source // code is governed by a license that can be found in the License file. -package sio +package darwin import ( "fmt" @@ -10,6 +10,7 @@ import ( "time" "unsafe" + "zikichombo.org/sio/libsio" "zikichombo.org/sound" "zikichombo.org/sound/sample" ) @@ -32,7 +33,7 @@ const ( type aqo struct { mu sync.Mutex id int - chs [2]chan *Packet + chs [2]chan *libsio.Packet cch chan struct{} aq running bool @@ -95,11 +96,11 @@ func (q *aqo) _close() { q.free() } -func (q *aqo) FillC() <-chan *Packet { +func (q *aqo) FillC() <-chan *libsio.Packet { return q.chs[0] } -func (q *aqo) PlayC() chan<- *Packet { +func (q *aqo) PlayC() chan<- *libsio.Packet { return q.chs[1] } @@ -130,8 +131,8 @@ func (q *aqo) init(v sound.Form, c sample.Codec, bufSize int) error { return err } C.AudioQueueSetParameter(q.qRef, C.kAudioQueueParam_Volume, 1.0) - q.chs[0] = make(chan *Packet) - q.chs[1] = make(chan *Packet, 1) + q.chs[0] = make(chan *libsio.Packet) + q.chs[1] = make(chan *libsio.Packet, 1) q.cch = make(chan struct{}, 1) q.n = 0 return nil @@ -181,7 +182,7 @@ func (q *aqo) callback(qb *C.struct_AudioQueueBuffer, d *C.char, cap int) { } } -func (q *aqo) handleBuf(in *Packet, qb *C.struct_AudioQueueBuffer, d *C.char, cap int) { +func (q *aqo) handleBuf(in *libsio.Packet, qb *C.struct_AudioQueueBuffer, d *C.char, cap int) { dsz := q.codec.Bytes() //C.sizeof_Float32 bsz := len(in.D) * dsz slice := (*[1 << 30]byte)(unsafe.Pointer(d))[:bsz] //cap] diff --git a/ports/darwin/aqs_darwin.go b/ports/darwin/aqs_darwin.go new file mode 100644 index 0000000..38d84f9 --- /dev/null +++ b/ports/darwin/aqs_darwin.go @@ -0,0 +1,88 @@ +package darwin + +import ( + "log" + "time" + + "zikichombo.org/sio/host" + "zikichombo.org/sio/libsio" + "zikichombo.org/sound" + "zikichombo.org/sound/sample" +) + +type aqsEntry struct { + host.NullEntry +} + +func (e *aqsEntry) Name() string { + return "CoreAudio Audio Queue Services" +} + +func (e *aqsEntry) DefaultBufferSize() int { + return 256 +} + +func (e *aqsEntry) DefaultSampleCodec() sample.Codec { + return sample.SFloat32L +} + +func (e *aqsEntry) DefaultForm() sound.Form { + return sound.MonoCd() +} + +func (e *aqsEntry) CanOpenSource() bool { + return true +} + +func (e *aqsEntry) OpenSource(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Source, time.Time, error) { + var t time.Time + aq, err := newAqin(v, co, b) + if err != nil { + return nil, t, err + } + src := libsio.InputSource(aq) + return src, aq.gBufs[0].Start, err +} + +func (e *aqsEntry) CanOpenSink() bool { + return true +} + +func (e *aqsEntry) OpenSink(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Sink, *time.Time, error) { + aqo, err := newAqo(v, co, b) + if err != nil { + return nil, nil, err + } + snk := libsio.OutputSink(aqo) + return snk, &aqo.gBufs[0].Start, nil +} + +// globals so we can not have go pointers to go pointers in c. +// instead we refer to ids. +var _inaqs [maxAqins]aqin +var _inaqFree chan int +var _inaqNew chan int + +// set up process of ids and free list. +// now, since each id refers to fixed place in memory +// we can use it without locking in the sound data +// processing loop. +func init() { + _inaqFree = make(chan int, maxAqins) + _inaqNew = make(chan int) + for i := 0; i < maxAqins; i++ { + _inaqs[i].id = i + _inaqFree <- i + } + go func() { + var f int + for { + f = <-_inaqFree + _inaqNew <- f + } + }() + e := &aqsEntry{NullEntry: host.NullEntry{}} + if err := host.RegisterEntry(e); err != nil { + log.Printf("zc failed load %s: %s\n", e.Name(), err.Error()) + } +} diff --git a/asbd_darwin.go b/ports/darwin/asbd_darwin.go similarity index 98% rename from asbd_darwin.go rename to ports/darwin/asbd_darwin.go index 0bd97bd..52398d1 100644 --- a/asbd_darwin.go +++ b/ports/darwin/asbd_darwin.go @@ -4,7 +4,7 @@ // +build darwin // +build cgo -package sio +package darwin import ( "encoding/binary" diff --git a/au_darwin.c b/ports/darwin/au_darwin.c similarity index 100% rename from au_darwin.c rename to ports/darwin/au_darwin.c diff --git a/au_darwin.go b/ports/darwin/au_darwin.go similarity index 93% rename from au_darwin.go rename to ports/darwin/au_darwin.go index 4cbe4d1..116afbc 100644 --- a/au_darwin.go +++ b/ports/darwin/au_darwin.go @@ -4,7 +4,7 @@ // +build darwin // +build cgo -package sio +package darwin import ( "fmt" @@ -12,6 +12,7 @@ import ( "math" "unsafe" + "zikichombo.org/sio/libsio" "zikichombo.org/sound" "zikichombo.org/sound/sample" ) @@ -28,8 +29,8 @@ import "C" type auhal struct { u C.AudioUnit - dev *Dev - iom IoMode + dev *libsio.Dev + iom libsio.IoMode form sound.Form codec sample.Codec bufSz int @@ -38,8 +39,8 @@ type auhal struct { type bus struct { } -func newAuio(dev *Dev, iom IoMode, v sound.Form, co sample.Codec, bufSz int) (*auhal, error) { - if err := dev.SetSampleRate(v.SampleRate()); err != nil { +func newAuio(dev *libsio.Dev, iom libsio.IoMode, v sound.Form, co sample.Codec, bufSz int) (*auhal, error) { + if err := setSampleRate(dev, v.SampleRate()); err != nil { return nil, err } var ud C.AudioComponentDescription @@ -108,7 +109,7 @@ func (u *auhal) enableIO() error { return nil } -func (u *auhal) setDev(dev *Dev) error { +func (u *auhal) setDev(dev *libsio.Dev) error { iom := u.iom if dev.MaxOutChannels == 0 && iom.Outputs() { return fmt.Errorf("no output channels on device %s\n", dev.Name) @@ -116,7 +117,7 @@ func (u *auhal) setDev(dev *Dev) error { if dev.MaxInChannels == 0 && iom.Inputs() { return fmt.Errorf("no output channels on device %s\n", dev.Name) } - if err := dev.SetBufferSize(u.bufSz); err != nil { + if err := setBufferSize(dev, u.bufSz); err != nil { return err } id := C.AudioObjectID(u.dev.Id) diff --git a/dev_darwin.go b/ports/darwin/dev_darwin.go similarity index 85% rename from dev_darwin.go rename to ports/darwin/dev_darwin.go index 6fbb46f..38458a5 100644 --- a/dev_darwin.go +++ b/ports/darwin/dev_darwin.go @@ -4,7 +4,7 @@ // +build darwin // +build cgo -package sio +package darwin import ( "errors" @@ -13,9 +13,8 @@ import ( "math" "unsafe" - "zikichombo.org/sound" + "zikichombo.org/sio/libsio" "zikichombo.org/sound/freq" - "zikichombo.org/sound/sample" ) // #cgo LDFLAGS: -framework CoreServices -framework CoreAudio -framework AudioToolbox @@ -27,17 +26,6 @@ import ( // #include import "C" -var devices = []*Dev{ - &Dev{Name: "CoreAudio -- AudioQueueService (on default devices)", - SampleCodecs: []sample.Codec{ - sample.SInt8, sample.SInt16L, sample.SInt16B, sample.SInt24L, - sample.SInt24B, sample.SInt32L, sample.SInt32B, sample.SFloat32L, - sample.SFloat32B}}} - -func Devices() []*Dev { - return devices -} - // SetSampleRate attempts to set the nominal sample rate // of d to sr. // @@ -46,7 +34,7 @@ func Devices() []*Dev { // // The nominal sample rate can be interpreted as the rate // at which the device tries to operate. -func (d *Dev) SetSampleRate(sr freq.T) error { +func setSampleRate(d *libsio.Dev, sr freq.T) error { fsr := C.Float64(sr.Float64()) var pAddr C.AudioObjectPropertyAddress pAddr.mScope = C.kAudioDevicePropertyScopeOutput @@ -61,7 +49,7 @@ func (d *Dev) SetSampleRate(sr freq.T) error { return caStatus(st) } -func (d *Dev) SetBufferSize(sz int) error { +func setBufferSize(d *libsio.Dev, sz int) error { csz := C.UInt32(sz) var pAddr C.AudioObjectPropertyAddress pAddr.mSelector = C.kAudioDevicePropertyBufferFrameSize @@ -86,22 +74,6 @@ func (d *Dev) SetBufferSize(sz int) error { return nil } -// Input attempts to create and start an Input. -func (d *Dev) Input(v sound.Form, co sample.Codec, n int) (Input, error) { - if !d.SupportsCodec(co) { - return nil, fmt.Errorf("unsupported sample codec: %s\n", co) - } - return newAqin(v, co, n) -} - -// Output attempts to create and start an Output, such as to a speaker. -func (d *Dev) Output(v sound.Form, co sample.Codec, n int) (Output, error) { - if !d.SupportsCodec(co) { - return nil, fmt.Errorf("unsupported sample codec: %s\n", co) - } - return newAqo(v, co, n) -} - func list() error { var sys C.AudioObjectID sys = C.kAudioObjectSystemObject @@ -129,11 +101,11 @@ func list() error { if err != nil { return err } - Devs := make([]Dev, 0, 7) + Devs := make([]libsio.Dev, 0, 7) for _, dev := range devs { N := len(Devs) - Devs = append(Devs, Dev{Id: uint64(dev)}) + Devs = append(Devs, libsio.Dev{Id: uint64(dev)}) D := &Devs[N] nm, err := name(dev) if err != nil { @@ -156,11 +128,9 @@ func list() error { D.MaxOutChannels = oc if dev == dIn { D.IsDefaultIn = true - DefaultInputDev = D } if dev == dOut { D.IsDefaultOut = true - DefaultOutputDev = D } if dev == dSys { D.IsDefaultSys = true @@ -305,16 +275,3 @@ func strProperty(dev C.AudioObjectID, sel C.AudioObjectPropertySelector) (string C.CFStringGetCString(cfs, cName, C.long(length), C.kCFStringEncodingUTF8) return C.GoString(cName), nil } - -func init() { - list() - devs := Devices() - if len(devs) > 0 { - DefaultInputDev = devs[0] - DefaultOutputDev = devs[0] - } - DefaultForm = sound.StereoCd() - DefaultCodec = sample.SFloat32L - DefaultOutputBufferSize = 256 - DefaultInputBufferSize = 256 -} diff --git a/ports/darwin/doc.go b/ports/darwin/doc.go new file mode 100644 index 0000000..bfdc8d5 --- /dev/null +++ b/ports/darwin/doc.go @@ -0,0 +1,7 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package darwin zc sound/io entry points. +// +// Package darwin is part of http://zikichombo.org +package darwin /* import "zikichombo.org/sio/ports/darwin" */ diff --git a/ports/dragonfly/doc.go b/ports/dragonfly/doc.go new file mode 100644 index 0000000..b68f204 --- /dev/null +++ b/ports/dragonfly/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package dragonfly zc sound/io entry points. +package dragonfly diff --git a/ports/freebsd/doc.go b/ports/freebsd/doc.go new file mode 100644 index 0000000..d8220ee --- /dev/null +++ b/ports/freebsd/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package freebsd zc sound/io entry points. +package freebsd diff --git a/ports/js/doc.go b/ports/js/doc.go new file mode 100644 index 0000000..e38a8dc --- /dev/null +++ b/ports/js/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package js zc sound/io entry points. +package js diff --git a/ports/linux/dev_alsa.go b/ports/linux/dev_alsa.go new file mode 100644 index 0000000..2838909 --- /dev/null +++ b/ports/linux/dev_alsa.go @@ -0,0 +1,137 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// +build linux +// +build cgo + +package linux + +import ( + "log" + "time" + + "zikichombo.org/sio/host" + "zikichombo.org/sio/libsio" + "zikichombo.org/sound" + "zikichombo.org/sound/sample" +) + +type alsaEntry struct { + host.NullEntry +} + +func (e *alsaEntry) Name() string { + return "Linux -- ALSACGO" +} + +func (e *alsaEntry) DefaultBufferSize() int { + return 512 +} + +func (e *alsaEntry) DefaultSampleCodec() sample.Codec { + return sample.SInt16L +} + +func (e *alsaEntry) DefaultForm() sound.Form { + return sound.StereoCd() +} + +func (e *alsaEntry) CanOpenSource() bool { + return true +} + +func (e *alsaEntry) OpenSource(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Source, time.Time, error) { + var t time.Time + pcm := newAlsaPcmIn(d.Name, v, co, b) + if err := pcm.open(); err != nil { + return nil, t, err + } + return libsio.InputSource(pcm), pcm.pkts[0].Start, nil +} + +func (e *alsaEntry) CanOpenSink() bool { + return true +} + +func (e *alsaEntry) OpenSink(d *libsio.Dev, v sound.Form, co sample.Codec, b int) (sound.Sink, *time.Time, error) { + pcm := newAlsaPcmOut(d.Name, v, co, b) + if err := pcm.open(); err != nil { + return nil, nil, err + } + return libsio.OutputSink(pcm), &pcm.pkts[0].Start, nil +} + +func (e *alsaEntry) HasDevices() bool { + return true +} + +func (e *alsaEntry) Devices() []*libsio.Dev { + return devices +} + +func (e *alsaEntry) DefaultInputDev() *libsio.Dev { + return devices[0] +} + +func (e *alsaEntry) DefaultOutputDev() *libsio.Dev { + return devices[0] +} + +func (e *alsaEntry) DefaultDuplexDev() *libsio.Dev { + return devices[0] +} + +// set up process of ids and free list. +// now, since each id refers to fixed place in memory +// we can use it without locking in the sound data +// processing loop. +func init() { + e := &alsaEntry{NullEntry: host.NullEntry{}} + if err := host.RegisterEntry(e); err != nil { + log.Printf("zc failed load %s: %s\n", e.Name(), err.Error()) + } +} + +//TBD(wsc) figure out how to do this right. +var devices = []*libsio.Dev{ + &libsio.Dev{Name: "default"}, + &libsio.Dev{Name: "plughw:0,0"}, + &libsio.Dev{Name: "hw:0,0"}} + +// name says it all. +/* +func oldBrokenInit() { + devs := Devices() + if len(devs) > 0 { + DefaultInputDev = devs[0] + DefaultOutputDev = devs[0] + } + j := 0 + for _, d := range devs { + var pcm *C.snd_pcm_t + var name = C.CString(d.Name) + var hwp *C.snd_pcm_hw_params_t + C.snd_pcm_hw_params_malloc(&hwp) + defer C.snd_pcm_hw_params_free(hwp) + defer C.free(unsafe.Pointer(name)) + ret := C.snd_pcm_open(&pcm, name, C.SND_PCM_STREAM_CAPTURE, 0) + if ret < 0 { + continue + } + C.snd_pcm_hw_params_any(pcm, hwp) + for _, codec := range sample.Codecs { + ret = C.snd_pcm_hw_params_test_format(pcm, hwp, + scodec2Alsa[codec]) + if ret < 0 { + continue + } + d.SampleCodecs = append(d.SampleCodecs, codec) + } + fmt.Printf("%s: %v\n", d, d.SampleCodecs) + devs[j] = d + j++ + C.snd_pcm_drain(pcm) + C.snd_pcm_close(pcm) + } +} +*/ diff --git a/ports/linux/doc.go b/ports/linux/doc.go new file mode 100644 index 0000000..52a6283 --- /dev/null +++ b/ports/linux/doc.go @@ -0,0 +1,7 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package linux zc sound/io entry points. +// +// Package linux is part of http://zikichombo.org +package linux /* import "zikichombo.org/sio/ports/linux" */ diff --git a/pcm_alsa.go b/ports/linux/pcm_alsa.go similarity index 95% rename from pcm_alsa.go rename to ports/linux/pcm_alsa.go index a4c0a99..12ce380 100644 --- a/pcm_alsa.go +++ b/ports/linux/pcm_alsa.go @@ -4,7 +4,7 @@ // +build linux // +build cgo -package sio +package linux import ( "errors" @@ -14,6 +14,7 @@ import ( "time" "unsafe" + "zikichombo.org/sio/libsio" "zikichombo.org/sound" "zikichombo.org/sound/freq" "zikichombo.org/sound/sample" @@ -34,10 +35,10 @@ type alsaPcm struct { swParams *C.snd_pcm_sw_params_t perBuf *C.char start time.Time - pkts [3]Packet + pkts [3]libsio.Packet pi int doneC chan struct{} - pktC [2]chan *Packet + pktC [2]chan *libsio.Packet once sync.Once periodSize C.ulong periods int @@ -46,15 +47,15 @@ type alsaPcm struct { func newAlsaPcmIn(name string, v sound.Form, sc sample.Codec, nf int) *alsaPcm { res := newAlsaPcm(name, v, sc, nf) res.dir = C.SND_PCM_STREAM_CAPTURE - res.pktC[0] = make(chan *Packet, 1) + res.pktC[0] = make(chan *libsio.Packet, 1) return res } func newAlsaPcmOut(name string, v sound.Form, sc sample.Codec, nf int) *alsaPcm { res := newAlsaPcm(name, v, sc, nf) res.dir = C.SND_PCM_STREAM_PLAYBACK - res.pktC[0] = make(chan *Packet, 1) - res.pktC[1] = make(chan *Packet) + res.pktC[0] = make(chan *libsio.Packet, 1) + res.pktC[1] = make(chan *libsio.Packet) return res } @@ -246,7 +247,7 @@ func (dev *alsaPcm) servePlay() { return } } - var pkt *Packet + var pkt *libsio.Packet var ok bool pi := 0 N := 0 @@ -342,15 +343,15 @@ wi: return nil } -func (dev *alsaPcm) C() <-chan *Packet { +func (dev *alsaPcm) C() <-chan *libsio.Packet { return dev.pktC[0] } -func (dev *alsaPcm) FillC() <-chan *Packet { +func (dev *alsaPcm) FillC() <-chan *libsio.Packet { return dev.pktC[0] } -func (dev *alsaPcm) PlayC() chan<- *Packet { +func (dev *alsaPcm) PlayC() chan<- *libsio.Packet { return dev.pktC[1] } diff --git a/sample_alsa.go b/ports/linux/sample_alsa.go similarity index 98% rename from sample_alsa.go rename to ports/linux/sample_alsa.go index 5862d8b..c0e8a19 100644 --- a/sample_alsa.go +++ b/ports/linux/sample_alsa.go @@ -4,7 +4,7 @@ // +build linux // +build cgo -package sio +package linux import "zikichombo.org/sound/sample" diff --git a/ports/netbsd/doc.go b/ports/netbsd/doc.go new file mode 100644 index 0000000..6f2933f --- /dev/null +++ b/ports/netbsd/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package netbsd zc sound/io entry points. +package netbsd diff --git a/ports/openbsd/doc.go b/ports/openbsd/doc.go new file mode 100644 index 0000000..288d0f8 --- /dev/null +++ b/ports/openbsd/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package openbsd zc sound/io entry points. +package openbsd diff --git a/ports/plan9/doc.go b/ports/plan9/doc.go new file mode 100644 index 0000000..3c6eab8 --- /dev/null +++ b/ports/plan9/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package plan9 zc sound/io entry points. +package plan9 diff --git a/ports/solaris/doc.go b/ports/solaris/doc.go new file mode 100644 index 0000000..20c8c32 --- /dev/null +++ b/ports/solaris/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package solaris zc sound/io entry points. +package solaris diff --git a/ports/windows/doc.go b/ports/windows/doc.go new file mode 100644 index 0000000..d3891e9 --- /dev/null +++ b/ports/windows/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +// Package windows zc sound/io entry points. +package windows diff --git a/sio.go b/sio.go new file mode 100644 index 0000000..3ac049d --- /dev/null +++ b/sio.go @@ -0,0 +1,112 @@ +// Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source +// code is governed by a license that can be found in the License file. + +package sio + +import ( + "zikichombo.org/sio/host" + "zikichombo.org/sound" + "zikichombo.org/sound/sample" +) + +// Capture tries to open the default capture device with +// default settings with the default host, returning +// a non-nil in case of failure. +func Capture() (sound.Source, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.Capture(ent) +} + +// CaptureWith opens a sound.Source with the specified sample codec and +// buffer size. +func CaptureWith(v sound.Form, co sample.Codec, b int) (sound.Source, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.CaptureWith(ent, v, co, b) +} + +// Play tries to play a sound.Source +// default settings with the default entry, returning +// a non-nil in case of failure. +func Play(src sound.Source) error { + ent, err := Connect(nil) + if err != nil { + return err + } + return host.Play(ent, src) +} + +// PlayWith +func PlayWith(src sound.Source, co sample.Codec, b int) error { + ent, err := Connect(nil) + if err != nil { + return err + } + return host.PlayWith(ent, src, co, b) +} + +// Player tries to return a sound.Sink to which Sends +// are played to some system output. Default entry +// and settings are applied. +func Player(v sound.Form) (sound.Sink, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.Player(ent, v) +} + +// PlayerWith tries to return a sound.Sink for playback +// with the specified sample codec and buffer size b. +func PlayerWith(v sound.Form, co sample.Codec, b int) (sound.Sink, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.PlayerWith(ent, v, co, b) +} + +// Duplex tries to return a sound.Duplex. +func Duplex(in, out sound.Form) (sound.Duplex, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.Duplex(ent, in, out) +} + +// DuplexWith tries to return a sound.Duplex. +func DuplexWith(in, out sound.Form, co sample.Codec, b int) (sound.Duplex, error) { + ent, err := Connect(nil) + if err != nil { + return nil, err + } + return host.DuplexWith(ent, in, out, co, b) +} + +// Connect tries to return the default connection to the +// host entry point. +func Connect(pkgSel func(string) bool) (host.Entry, error) { + return host.Connect(pkgSel) +} + +// ConnectTo +func ConnectTo(name string, pkgSel func(string) bool) (host.Entry, error) { + return host.ConnectTo(name, pkgSel) +} + +// Disconnect closes the currently in use entry, if any, so that +// another one may be used. +func Disconnect() { + host.Disconnect() +} + +// EntryNames returns the names of host entry points. +func EntryNames() []string { + return host.Names() +}