Sound file Plotter in Go using gosndfile / libsndfile

· 313 Words

It’s no secret that golang is my new favourite language. I’ve used it to implement the latest folktunefinder¬†search engine and really enjoyed it.

On an unrelated note, whilst looking at what libraries are available I came across the gosndfile library written by Matt Kane / @nynexrepublic. It’s a wrapper for libsndfile, a C library for reading and writing sound files.

To get started, I thought I’d write a short program that plots a signal (mono, stereo, and in theory, more channels) from a sound file and makes a PNG out of it. It’s not particularly efficient, as it allocates a very large buffer up-front and doesn’t re-use it. A real implementation would use a smaller buffer and loop over the sound file until the end. Still, it’s a fun proof of concept for the library. And it would have to iterate over the whole file anyway in order to dimension the image in the first place.

Here’s a heavily cropped sample output. Click for a full version. You’ll have to zoom in, it’s very wide and not very high.

 

Here’s the code. You can download it here.

You’ll need to install libsndfile C library and gosndfile Go library.

package main

import (
	"fmt"
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
	"sndfile"
)

// Number of seconds worth of buffer to allocate.
const Seconds = 10

// Height per channel.
const ImageHeight = 200

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)
}

This looks like a really great library for doing interesting things. I’m looking forward to working out what those interesting things are!

Read more