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