diff --git a/asound.go b/asound.go index cdbab8a..6a7bf3d 100644 --- a/asound.go +++ b/asound.go @@ -1,4 +1,4 @@ -package main +package alsa import ( "fmt" diff --git a/card.go b/card.go new file mode 100644 index 0000000..3fb69ad --- /dev/null +++ b/card.go @@ -0,0 +1,66 @@ +package alsa + +import ( + "fmt" + "os" +) + +type Card struct { + Path string + Title string + Number int + + fh *os.File + pversion PVersion + cardinfo CardInfo +} + +func (card Card) String() string { + return card.Title +} + +func OpenCards() ([]*Card, error) { + ret := make([]*Card, 0) + + max := 3 // arbitrary + for i := 0; i < max; i++ { + path := fmt.Sprintf("/dev/snd/controlC%d", i) + _, err := os.Stat(path) + if err != nil { + continue + } + max++ + + fh, err := os.Open(path) + if err != nil { + return ret, err + } + + card := Card{ + Path: path, + Number: i, + fh: fh, + } + + err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 4, CmdControlVersion), &card.pversion) + if err != nil { + return ret, err + } + + err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 376, CmdControlCardInfo), &card.cardinfo) + if err != nil { + return ret, err + } + + card.Title = gstr(card.cardinfo.Name[:]) + ret = append(ret, &card) + } + + return ret, nil +} + +func CloseCards(cards []*Card) { + for _, card := range cards { + card.fh.Close() + } +} diff --git a/cmd/beep/main.go b/cmd/beep/main.go new file mode 100644 index 0000000..0464bff --- /dev/null +++ b/cmd/beep/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "bytes" + "encoding/binary" + "fmt" + "math" + + "github.com/yobert/alsa" +) + +func main() { + + cards, err := alsa.OpenCards() + if err != nil { + fmt.Println(err) + return + } + defer alsa.CloseCards(cards) + + for _, card := range cards { + fmt.Println(card) + + if err := beep_card(card); err != nil { + fmt.Println(err) + return + } + } +} + +func beep_card(card *alsa.Card) error { + devices, err := card.Devices() + if err != nil { + return err + } + for _, device := range devices { + if device.Type != alsa.PCM || !device.Play { + continue + } + fmt.Println("───", device) + + if err := beep_device(device); err != nil { + return err + } + } + return nil +} + +func beep_device(device *alsa.Device) error { + var err error + + if err = device.Open(); err != nil { + return err + } + defer device.Close() + + channels, err := device.NegotiateChannels(1, 2) + if err != nil { + return err + } + + rate, err := device.NegotiateRate(44100) + if err != nil { + return err + } + + format, err := device.NegotiateFormat(alsa.S16_LE, alsa.S32_LE) + if err != nil { + return err + } + + buffer_size, err := device.NegotiateBufferSize(4, 1024, 8192, 16384) + if err != nil { + return err + } + + if err = device.Prepare(); err != nil { + return err + } + + buf := bytes.NewBuffer(nil) + t := 0.0 + + fmt.Printf("Negotiated parameters: %d channels, %d hz, %v, %d frame buffer\n", + channels, rate, format, buffer_size) + + for { + buf.Reset() + + for i := 0; i < buffer_size; i++ { + v := math.Sin(t * 2 * math.Pi * 440) // A4 + v *= 0.1 // make a little quieter + + switch format { + case alsa.S16_LE: + sample := int16(v * ((1 << 16) - 1)) + + for c := 0; c < channels; c++ { + binary.Write(buf, binary.LittleEndian, sample) + } + + case alsa.S32_LE: + sample := int32(v * ((1 << 32) - 1)) + + for c := 0; c < channels; c++ { + binary.Write(buf, binary.LittleEndian, sample) + } + + default: + return fmt.Errorf("Unhandled sample format: %v", format) + } + + t += 1.0 / float64(rate) + } + + if err := device.Write(buf.Bytes(), buffer_size); err != nil { + return err + } + } + + return nil +} diff --git a/device.go b/device.go new file mode 100644 index 0000000..c63f17b --- /dev/null +++ b/device.go @@ -0,0 +1,265 @@ +package alsa + +import ( + "fmt" + "os" + "unsafe" + + "color" + + "github.com/yobert/alsa/misc" + "github.com/yobert/alsa/pcm" +) + +type DeviceType int + +const ( + UnknownDeviceType DeviceType = iota + PCM +) + +func (t DeviceType) String() string { + switch t { + case PCM: + return "PCM" + default: + return fmt.Sprintf("UnknownDeviceType(%d)", t) + } +} + +type Device struct { + Type DeviceType + Number int + Play, Record bool + + Path string + Title string + + debug bool + + fh *os.File + pcminfo PCMInfo + + pversion PVersion + + hwparams Params + hwparams_prev Params + + swparams SwParams + swparams_prev SwParams +} + +func (device Device) String() string { + return device.Title +} + +func (card *Card) Devices() ([]*Device, error) { + var next int32 = -1 + ret := make([]*Device, 0) + + for { + err := ioctl(card.fh.Fd(), ioctl_encode(CmdRead, 4, CmdControlPCMNextDevice), &next) + if err != nil { + return ret, err + } + if next == -1 { + // No more devices + break + } + + for stream := int32(0); stream < 2; stream++ { + var pi PCMInfo + pi.Device = uint32(next) + pi.Subdevice = 0 + pi.Stream = stream + err = ioctl(card.fh.Fd(), ioctl_encode(CmdRead|CmdWrite, 288, CmdControlPCMInfo), &pi) + if err != nil { + // Probably means that device doesn't match that stream type + } else { + play := true + record := false + sstr := "p" + if stream == 1 { + play = false + record = true + sstr = "c" + } + + ret = append(ret, &Device{ + Type: PCM, + Path: fmt.Sprintf("/dev/snd/pcmC%dD%d%s", card.Number, next, sstr), + Play: play, + Record: record, + Number: int(next), + Title: gstr(pi.Name[:]), + pcminfo: pi, + }) + } + } + } + + return ret, nil +} + +func (device *Device) Open() error { + var err error + device.fh, err = os.OpenFile(device.Path, os.O_RDWR, 0755) + if err != nil { + return err + } + + err = ioctl(device.fh.Fd(), ioctl_encode(CmdRead, 4, CmdPCMVersion), &device.pversion) + if err != nil { + device.fh.Close() + return err + } + + ttstamp := uint32(PCMTimestampTypeGettimeofday) + err = ioctl(device.fh.Fd(), ioctl_encode(CmdWrite, 4, CmdPCMTimestampType), &ttstamp) + if err != nil { + device.fh.Close() + return err + } + + device.hwparams = Params{} + device.hwparams_prev = Params{} + + for i := range device.hwparams.Masks { + for ii := 0; ii < 2; ii++ { + device.hwparams.Masks[i].Bits[ii] = 0xffffffff + } + } + for i := range device.hwparams.Intervals { + device.hwparams.Intervals[i].Max = 0xffffffff + } + device.hwparams.Rmask = 0xffffffff + + if err := device.refine(); err != nil { + return err + } + + device.hwparams.Cmask = 0 + device.hwparams.Rmask = 0xffffffff + device.hwparams.SetAccess(RWInterleaved) + + if err := device.refine(); err != nil { + return err + } + + return nil +} + +func (device *Device) Close() { + if device.fh != nil { + device.fh.Close() + } +} + +func (device *Device) Prepare() error { + if device.debug { + fmt.Println("Final hardware parameter changes:") + fmt.Println(color.Text(color.Green)) + fmt.Print(device.hwparams.Diff(&device.hwparams_prev)) + fmt.Println(color.Reset()) + } + device.hwparams_prev = device.hwparams + + err := ioctl(device.fh.Fd(), ioctl_encode(CmdRead|CmdWrite, 608, CmdPCMHwParams), &device.hwparams) + if err != nil { + return err + } + + if device.debug { + fmt.Println("Final hardware parameter results:") + fmt.Println(color.Text(color.Magenta)) + fmt.Print(device.hwparams.Diff(&device.hwparams_prev)) + fmt.Println(color.Reset()) + } + + device.hwparams_prev = device.hwparams + + // final buf size + buf_size := int(device.hwparams.Intervals[ParamBufferSize-ParamFirstInterval].Max) + + device.swparams = SwParams{} + device.swparams_prev = SwParams{} + + device.swparams.PeriodStep = 1 + device.swparams.AvailMin = uint(buf_size) + device.swparams.XferAlign = 1 + device.swparams.StartThreshold = uint(buf_size) + device.swparams.StopThreshold = uint(buf_size * 2) + device.swparams.Proto = device.pversion + device.swparams.TstampType = 1 + + if err := device.sw_params(); err != nil { + return err + } + + if err := ioctl(device.fh.Fd(), ioctl_encode(0, 0, CmdPCMPrepare), nil); err != nil { + return err + } + + return nil +} + +func (device *Device) Write(b []byte, frames int) error { + if len(b) == 0 { + return nil + } + return ioctl(device.fh.Fd(), ioctl_encode(CmdWrite, pcm.XferISize, CmdPCMWriteIFrames), &pcm.XferI{ + Buf: uintptr(unsafe.Pointer(&b[0])), + Frames: misc.Uframes(frames), + }) +} + +func (device *Device) refine() error { + + if device.debug { + fmt.Println("Requesting changes:") + fmt.Println(color.Text(color.Green)) + fmt.Print(device.hwparams.Diff(&device.hwparams_prev)) + fmt.Println(color.Reset()) + } + device.hwparams_prev = device.hwparams + + err := ioctl(device.fh.Fd(), ioctl_encode(CmdRead|CmdWrite, 608, CmdPCMHwRefine), &device.hwparams) + if err != nil { + return err + } + + if device.debug { + fmt.Println("Results:") + fmt.Println(color.Text(color.Magenta)) + fmt.Print(device.hwparams.Diff(&device.hwparams_prev)) + fmt.Println(color.Reset()) + } + device.hwparams_prev = device.hwparams + + return nil +} + +func (device *Device) sw_params() error { + if device.debug { + fmt.Println("Requesting soft parameters:") + fmt.Println(color.Text(color.Green)) + fmt.Print(device.swparams.Diff(&device.swparams_prev)) + fmt.Println(color.Reset()) + } + device.swparams_prev = device.swparams + + err := ioctl(device.fh.Fd(), ioctl_encode(CmdRead|CmdWrite, 136, CmdPCMSwParams), &device.swparams) + if err != nil { + return err + } + + if device.debug { + fmt.Println("Results:") + fmt.Println(color.Text(color.Magenta)) + fmt.Print(device.swparams.Diff(&device.swparams_prev)) + fmt.Println(color.Reset()) + } + device.swparams_prev = device.swparams + + return nil +} diff --git a/ioctl.go b/ioctl.go index 92d3a18..338bebc 100644 --- a/ioctl.go +++ b/ioctl.go @@ -1,4 +1,4 @@ -package main +package alsa import ( "fmt" diff --git a/negotiate.go b/negotiate.go new file mode 100644 index 0000000..a562457 --- /dev/null +++ b/negotiate.go @@ -0,0 +1,89 @@ +package alsa + +import ( + "fmt" +) + +func (device *Device) NegotiateChannels(channels ...int) (int, error) { + var err error + + for _, v := range channels { + + if !device.hwparams.IntervalInRange(ParamChannels, uint32(v)) { + err = fmt.Errorf("Channels %d out of range") + continue + } + + device.hwparams.Cmask = 0 + device.hwparams.Rmask = 0xffffffff + device.hwparams.SetInterval(ParamChannels, uint32(v), uint32(v), Integer) + + err = device.refine() + if err == nil { + return v, nil + } + } + + return 0, err +} + +func (device *Device) NegotiateRate(rates ...int) (int, error) { + var err error + + for _, v := range rates { + if !device.hwparams.IntervalInRange(ParamRate, uint32(v)) { + err = fmt.Errorf("Rate %d out of range") + continue + } + + device.hwparams.Cmask = 0 + device.hwparams.Rmask = 0xffffffff + device.hwparams.SetInterval(ParamRate, uint32(v), uint32(v), Integer) + + err = device.refine() + if err == nil { + return v, nil + } + } + + return 0, err +} + +func (device *Device) NegotiateFormat(formats ...FormatType) (FormatType, error) { + var err error + + for _, v := range formats { + device.hwparams.Cmask = 0 + device.hwparams.Rmask = 0xffffffff + device.hwparams.SetFormat(v) + + err = device.refine() + if err == nil { + return v, nil + } + } + + return 0, err +} + +func (device *Device) NegotiateBufferSize(buffer_sizes ...int) (int, error) { + var err error + + for _, v := range buffer_sizes { + if !device.hwparams.IntervalInRange(ParamBufferSize, uint32(v)) { + err = fmt.Errorf("Buffer size %d out of range") + continue + } + + device.hwparams.Cmask = 0 + device.hwparams.Rmask = 0xffffffff + device.hwparams.SetInterval(ParamBufferSize, uint32(v), uint32(v), Integer) + + err = device.refine() + if err == nil { + return v, nil + } + } + + return 0, err +} diff --git a/param.go b/param.go index 922c3b2..572b3fd 100644 --- a/param.go +++ b/param.go @@ -1,11 +1,9 @@ -package main +package alsa import ( "fmt" "strings" - "color" - "github.com/yobert/alsa/pcm" ) @@ -113,44 +111,6 @@ func (p Param) name() string { } } -func refine(fd uintptr, params *Params, last *Params) error { - - fmt.Println(color.Text(color.Green)) - fmt.Print(params.Diff(last)) - *last = *params - - err := ioctl(fd, ioctl_encode(CmdRead|CmdWrite, 608, CmdPCMHwRefine), params) - if err != nil { - return err - } - - fmt.Println(color.Text(color.Magenta)) - fmt.Print(params.Diff(last)) - fmt.Print(color.Reset()) - *last = *params - - return nil -} - -func hw_params(fd uintptr, params *Params, last *Params) error { - - fmt.Println(color.Text(color.Green)) - fmt.Print(params.Diff(last)) - *last = *params - - err := ioctl(fd, ioctl_encode(CmdRead|CmdWrite, 608, CmdPCMHwParams), params) - if err != nil { - return err - } - - fmt.Println(color.Text(color.Magenta)) - fmt.Print(params.Diff(last)) - fmt.Print(color.Reset()) - *last = *params - - return nil -} - func get_status(fd uintptr) error { var status pcm.Status err := ioctl(fd, ioctl_encode(CmdRead, pcm.StatusSize, CmdPCMStatus), &status) diff --git a/swparams.go b/swparams.go index 9a59ffd..5a031d3 100644 --- a/swparams.go +++ b/swparams.go @@ -1,10 +1,8 @@ -package main +package alsa import ( "fmt" "reflect" - - "color" ) type SwParams struct { @@ -26,25 +24,6 @@ type SwParams struct { padding_for_c [4]byte } -func sw_params(fd uintptr, params *SwParams, last *SwParams) error { - - fmt.Println(color.Text(color.Green)) - fmt.Print(params.Diff(last)) - *last = *params - - err := ioctl(fd, ioctl_encode(CmdRead|CmdWrite, 136, CmdPCMSwParams), params) - if err != nil { - return err - } - - fmt.Println(color.Text(color.Magenta)) - fmt.Print(params.Diff(last)) - fmt.Print(color.Reset()) - *last = *params - - return nil -} - func (s *SwParams) String() string { return s.Diff(&SwParams{}) } diff --git a/test.go b/test.go deleted file mode 100644 index bc1609e..0000000 --- a/test.go +++ /dev/null @@ -1,285 +0,0 @@ -package main - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "os" - "time" - "unsafe" - //"github.com/edsrzf/mmap-go" - "github.com/yobert/alsa/misc" - "github.com/yobert/alsa/pcm" -) - -// _, _, errnop := syscall.Syscall(syscall.SYS_IOCTL, uintptr(file.Fd()), uintptr(TUNSETIFF), uintptr(unsafe.Pointer(&ifr))) -//errno := int(errno) - -//func ioctl(fd int, request, argp uintptr) error { -// _, _, errorp := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp) -// return os.NewSyscallError("ioctl", int(errorp)) -//} - -func main() { - if err := list_the_things(); err != nil { - fmt.Println(err) - } - - if err := boop("/dev/snd/pcmC0D0p"); err != nil { - //if err := boop("/dev/snd/pcmC2D0p"); err != nil { - fmt.Println(err) - } - -} - -func boop(path string) error { - - fh, err := os.OpenFile(path, os.O_RDWR, 0755) - if err != nil { - return err - } - defer fh.Close() - - var pv PVersion - err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 4, CmdPCMVersion), &pv) - if err != nil { - return err - } - - ttstamp := uint32(PCMTimestampTypeGettimeofday) - err = ioctl(fh.Fd(), ioctl_encode(CmdWrite, 4, CmdPCMTimestampType), &ttstamp) - if err != nil { - return err - } - - params := &Params{} - last := &Params{} - - for i := range params.Masks { - for ii := 0; ii < 2; ii++ { - params.Masks[i].Bits[ii] = 0xffffffff - } - } - for i := range params.Intervals { - params.Intervals[i].Max = 0xffffffff - } - params.Rmask = 0xffffffff - - if err := refine(fh.Fd(), params, last); err != nil { - return err - } - - if !params.IntervalInRange(ParamChannels, 2) { - return fmt.Errorf("Stereo not available") - } - - params.Cmask = 0 - params.Rmask = 0xffffffff - params.SetInterval(ParamChannels, 2, 2, Integer) - - if err := refine(fh.Fd(), params, last); err != nil { - return err - } - - rate := uint32(44100) - - if !params.IntervalInRange(ParamRate, rate) { - return fmt.Errorf("%d hz not available", rate) - } - - params.Cmask = 0 - params.Rmask = 0xffffffff - params.SetInterval(ParamRate, rate, rate, Integer) - - if err := refine(fh.Fd(), params, last); err != nil { - return err - } - - // buffer time? - max_buf_time := uint32(params.Intervals[ParamBufferTime-ParamFirstInterval].Max) - min_buf_time := uint32(1000 * 1000) - if min_buf_time > max_buf_time { - min_buf_time = max_buf_time / 2 - } - params.Cmask = 0 - params.Rmask = 0xffffffff - params.SetInterval(ParamBufferTime, min_buf_time, max_buf_time, OpenMin|OpenMax) - - if err := refine(fh.Fd(), params, last); err != nil { - return err - } - - params.Cmask = 0 - params.Rmask = 0xffffffff - params.SetAccess(RWInterleaved) - params.SetFormat(S32_LE) - - if err := refine(fh.Fd(), params, last); err != nil { - return err - } - - // params.Cmask = 0 - // params.Rmask = 0xffffffff - // params.SetIntervalToMin(ParamBufferTime) - // if err := refine(fh.Fd(), params, last); err != nil { - // return err - // } - - if err := hw_params(fh.Fd(), params, last); err != nil { - return err - } - - swparams := &SwParams{} - swlast := &SwParams{} - - swparams.PeriodStep = 1 - swparams.AvailMin = 1024 - swparams.XferAlign = 1 - //swparams.StartThreshold = 1024 - swparams.StartThreshold = 100 - swparams.StopThreshold = 16384 - swparams.Proto = pv - swparams.TstampType = 1 - - if err := sw_params(fh.Fd(), swparams, swlast); err != nil { - return err - } - - if err := get_status(fh.Fd()); err != nil { - return err - } - if err := ioctl(fh.Fd(), ioctl_encode(0, 0, CmdPCMPrepare), nil); err != nil { - return err - } - if err := get_status(fh.Fd()); err != nil { - return err - } - - /* if err := ioctl(fh.Fd(), ioctl_encode(0, 0, CmdPCMStart), nil); err != nil { - return err - } - if err := get_status(fh.Fd()); err != nil { - return err - }*/ - - buf_size := int(params.Intervals[ParamBufferSize-ParamFirstInterval].Max) - - buf_bytes := int(params.Intervals[ParamBufferBytes-ParamFirstInterval].Max) - - fmt.Println("buf", buf_bytes, "/", buf_size, "frames") - fmt.Println("rate", rate) - - /* go func() { - for { - if err := get_status(fh.Fd()); err != nil { - fmt.Println(err) - return - } - time.Sleep(time.Second) - } - }()*/ - - t := 0.0 - - for { - - amt := int(buf_size) - buf := bytes.NewBuffer(nil) - - for i := 0; i < amt; i++ { - - v := math.Sin(t * 2 * math.Pi * 440) - v += math.Sin(t * 2 * math.Pi * 261.63) - v += math.Sin(t * 2 * math.Pi * 349.23) - v *= 0.1 - - //v *= 0.5 - //v += 0.5 - - //sample := uint8(v * 255) - sample := int32(v * ((1 << 32) - 1)) - - // U24_BE is lower 3 bytes of a 4 byte word - // 16777215 is max value of a 24 bit uint - //sample := uint32(v * 16777215) - - binary.Write(buf, binary.LittleEndian, sample) - binary.Write(buf, binary.LittleEndian, sample) - - t += (1.0 / float64(rate)) - } - - err = ioctl(fh.Fd(), ioctl_encode(CmdWrite, pcm.XferISize, CmdPCMWriteIFrames), &pcm.XferI{ - Buf: uintptr(unsafe.Pointer(&buf.Bytes()[0])), - Frames: misc.Uframes(amt), - }) - if err != nil { - return err - } - //fmt.Println("xfer", xfer.Frames, xfer.Result) - //time.Sleep(time.Millisecond * 10) - _ = time.Sleep - } - - return nil -} - -func list_the_things() error { - for i := 0; i < 10; i++ { - path := fmt.Sprintf("/dev/snd/controlC%d", i) - _, err := os.Stat(path) - if err != nil { - continue - } - fh, err := os.Open(path) - if err != nil { - return err - } - defer fh.Close() - - var pv PVersion - err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 4, CmdControlVersion), &pv) - if err != nil { - return err - } - - var ci CardInfo - err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 376, CmdControlCardInfo), &ci) - if err != nil { - return err - } - - fmt.Println(ci, pv) - - var next int32 = -1 - - for { - err = ioctl(fh.Fd(), ioctl_encode(CmdRead, 4, CmdControlPCMNextDevice), &next) - if err != nil { - return err - } - - if next == -1 { - break - } - - for stream := int32(0); stream < 2; stream++ { - var pi PCMInfo - pi.Device = uint32(next) - pi.Subdevice = 0 - pi.Stream = stream - err = ioctl(fh.Fd(), ioctl_encode(CmdRead|CmdWrite, 288, CmdControlPCMInfo), &pi) - if err != nil { - //return err - //fmt.Println(err) - } else { - fmt.Println(pi) - } - } - } - - fmt.Println("") - } - return nil -}