// Preload all tracks into memory as "sounds" so they are ready for use by // the portaudio callback. func init() { // Walk all files inside the tracks directory. filepath.Walk("tracks", func(loc string, info os.FileInfo, err error) error { if info.IsDir() { return nil } // Open .wav file with libsndfile. var i sndfile.Info snd, err := sndfile.Open(loc, sndfile.Read, &i) if err != nil { fmt.Printf("could not read %s: %v\n", loc, err) os.Exit(-1) } // Now, load all the contents into memory. var cur sound for { set := make(buffer, bufferSize) n, err := snd.ReadItems(set) if err != nil { fmt.Printf("could not read items from file %s: %v\n", loc, err) os.Exit(-1) } if int(n) < len(set) { // We reached the end of the file, exit loop. break } cur = append(cur, set) } tracks[filepath.Base(loc)] = cur return nil }) }
// LoadTrack reads an audio track from a file, and returns a pointer to // a Track struct, or an error. The boolean parameter 'loop' determines // whether or not a Track should loop when it is finished playing. // // Supported filetypes are whatever libsndfile supports, e.g. WAV or OGG. func (a *Audio) LoadTrack(filename string, loop bool) (*Track, error) { // Load file var info sndfile.Info soundFile, err := sndfile.Open(filename, sndfile.Read, &info) if err != nil { return nil, err } buffer := make([]float32, info.Frames*int64(info.Channels)) numRead, err := soundFile.ReadItems(buffer) if err != nil { return nil, err } defer soundFile.Close() // Create track track := Track{ loop: loop, buffer: buffer[:numRead], volume: 1, } a.tracks = append(a.tracks, &track) return &track, nil }
func main() { if len(os.Args) < 3 { fmt.Println("Usage: go run plotter.go /path/to/wav /path/to/png") return } var info sndfile.Info soundFile, err := sndfile.Open(os.Args[1], sndfile.Read, &info) if err != nil { log.Fatal("Error", err) } defer soundFile.Close() imageFile, err := os.Create(os.Args[2]) if err != nil { log.Fatal(err) } defer imageFile.Close() buffer := make([]float32, Seconds*info.Samplerate*info.Channels) numRead, err := soundFile.ReadItems(buffer) numSamples := int(numRead / int64(info.Channels)) numChannels := int(info.Channels) outimage := image.NewRGBA(image.Rect(0, 0, numSamples, ImageHeight*numChannels)) if err != nil { return } // Both math.Abs and math.Max operate on float64. Hm. max := float32(0) for _, v := range buffer { if v > max { max = v } else if v*-1 > max { max = v * -1 } } // Work out scaling factor to normalise signaland get best use of space. mult := float32(ImageHeight/max) / 2 // Signed float so add 1 to turn [-1, 1] into [0, 2]. for i := 0; i < numSamples; i++ { for channel := 0; channel < numChannels; channel++ { y := int(buffer[i*numChannels+channel]*mult+ImageHeight/2) + ImageHeight*channel outimage.Set(i, y, color.Black) outimage.Set(i, y+1, color.Black) } } png.Encode(imageFile, outimage) }
func (si SndFileInputRAW) Read() (sampleRate float64, channels []chan float64, errChan chan error) { errChan = make(chan error, 2) info := sndfile.Info{ Samplerate: int32(si.SampleRate), Channels: int32(si.NumChannels), Format: si.Format, } f, err := sndfile.Open(si.Filename, sndfile.Read, &info) if err != nil { errChan <- err return 0, nil, errChan } channels = make([]chan float64, info.Channels) for i, _ := range channels { channels[i] = make(chan float64, si.BufferSize) } go func() { defer func() { err2 := f.Close() if err2 != nil { errChan <- err2 } close(errChan) for _, channel := range channels { close(channel) } }() buffer := make([]float64, len(channels)*si.BufferSize) for { numItems, err := f.ReadItems(buffer) if err != nil { errChan <- err return } // EOF if numItems == 0 { break } for i, x := range buffer[:numItems] { channels[i%len(channels)] <- x } } }() return float64(info.Samplerate), channels, errChan }
// LoadSample loads an audio sample from the passed in filename // Into memory and returns the buffer. // Returns an error if there was one in audio processing. func LoadSample(filename string) ([]float32, error) { var info sndfile.Info soundFile, err := sndfile.Open(filename, sndfile.Read, &info) if err != nil { fmt.Printf("Could not open file: %s\n", filename) return nil, err } buffer := make([]float32, 10*info.Samplerate*info.Channels) numRead, err := soundFile.ReadItems(buffer) if err != nil { fmt.Printf("Error reading data from file: %s\n", filename) return nil, err } defer soundFile.Close() return buffer[:numRead], nil }
func newInstrument(t *drum.Track) (*instrument, error) { fileName := filepath.Join(*soundDir, fmt.Sprintf("%s.wav", t.Name)) var info sndfile.Info f, err := sndfile.Open(fileName, sndfile.Read, &info) if err != nil { return nil, err } defer f.Close() buffer := make([]int32, int(info.Frames)*int(info.Channels)) _, err = f.ReadFrames(buffer) if err != nil { return nil, err } return &instrument{ sample: buffer, cursor: len(buffer), }, nil }
func (so SndFileOutput) Write(sampleRate float64, channels []chan float64) (err error) { info := sndfile.Info{ Samplerate: int32(sampleRate), Channels: int32(len(channels)), Format: so.Format, } f, err := sndfile.Open(so.Filename, sndfile.Write, &info) if err != nil { return err } defer f.Close() for buffer := range soundio.Interlace(channels, so.BufferSize) { _, err = f.WriteItems(buffer) if err != nil { return err } } return nil }
func (s *SndfileOut) Start(op OutputParams) (err error) { defer func() { if err != nil { if s.file != nil { s.file.Close() } } }() s.start.Do(func() { MakeRecycleChannel(op) s.fi.Samplerate = int32(op.SampleRate) m := sndfile.Write if s.appendMode { m = sndfile.ReadWrite } bufsize := 0 ochans := 0 for _, u := range s.inputs { ochans += len(u.OutputChannels()) bufsize += (op.BufferSize * len(u.OutputChannels())) } logger.Println(s.name, ochans, "output channels") s.fi.Channels = int32(ochans) s.file, err = sndfile.Open(s.name, m, &s.fi) if err != nil { logger.Print(err) s.start = new(sync.Once) return } if s.appendMode { _, err = s.file.Seek(0, sndfile.End) if err != nil { s.start = new(sync.Once) return } } for _, u := range s.inputs { u.Start(op) } s.stop = new(sync.Once) obuf := make([]float32, bufsize) go func() { // create ticker t := TickerFor(op) defer func() { for _, u := range s.inputs { u.Stop() } // stop ticker t.Stop() s.stop.Do(func() { s.start = new(sync.Once) }) }() for { // wait for ticker select { case <-t.C: case <-s.quitchan: return } channum := 0 var wg sync.WaitGroup for _, u := range s.inputs { for _, c := range u.OutputChannels() { // logger.Println(c) wg.Add(1) go func(channum int, c chan []float32) { select { case ibuf := <-c: for snum, sval := range ibuf { // if sval != 0 { // logger.Println(obuf[snum*ochans+channum], sval) // } obuf[snum*ochans+channum] = sval // if sval != 0 { // logger.Println(obuf[snum*ochans+channum], sval) // } } go func() { RecycleBuf(ibuf, op) }() case <-s.quitchan: return } wg.Done() }(channum, c) channum++ } } wg.Wait() var n int64 n, err = s.file.WriteItems(obuf) if n != int64(len(obuf)) || err != nil { logger.Print(s.name, "couldn't write from buf length", len(obuf), "wrote", n, "err", err) return } } }() }) return }