How to tune a guitar with Ruby and FFT

From time to time, when nobody sees me, I like to play the guitar and every time I face a challenge – how to tune it properly. And like in any other case Ruby comes to the rescue!


Tuned classic guitar has known frequencies – 329,63Hz for the open first string and so on. So to tune the guitar we need to know frequency shift for any string and simply fix it.

Let’s start by recording the sound of the first open string.

In .wav file we have “signal vs time” data and to look at the wave form we can use some software, like Audacity:

Open string recording in Audacity

or we can plot it with Ruby and Gnuplot.

To obtain sound vs time data from .wav file let’s use SoX:

sox guitar_first_string.wav guitar_first_string.dat

guitar_first_string.dat will consist of several columns – time and volumes of every channel:

You can download .dat file from here.

Plotter code:

require 'open3'

class GNUPlotter <, :params)
  def plot
    image, s = Open3.capture2("gnuplot", stdin_data: gnuplot_commands, binmode: true)
    system "open #{params[:image_name]}"

  def gnuplot_commands
    commands = %{
      set terminal png font "/Library/Fonts/Arial.ttf" 14
      set title "#{params[:title]}"
      set xlabel "#{params[:x_axis_title]}"
      set ylabel "#{params[:y_axis_title]}"
      set output "#{params[:image_name]}"
      set key off
      plot "-" with points

    data.each do |x, y|
      commands << "#{x} #{y}\n"

    commands << "e\n"

sound_data ="guitar_first_string.dat").split("\n")[2..-1].map { |row| }.
  map { |r| r.first(2) }

plot_params = {
  image_name: "plot.png",
  title: "Guitar first string sound",
  x_axis_title: "Time, s",
  y_axis_title: ".wav signal"

plotter =, plot_params)

Fast Fourier Transform

Mathematical transformation employed to transform signals between time domain and frequency domain by Wikipedia

We will use library for converting .wav file data (sound vs time) to its spectrum (magnitude vs frequency).

Now we have everything to calculate spectrum.

require "fftw3"
require_relative "plotter"

def read_channel_data(filename, channel_number)
  data ="\n")[2..-1].map { |row| }
  duration = data.last[0]
  signal = { |r| r[channel_number] }

  return signal, duration

def calculate_fft(signal, duration, max_points = 3000)
  na = NArray[signal]
  fc = FFTW3.fft(na)

  spectrum = fc.real.to_a.flatten.first(na.length / 2).first(max_points) do |val, index|
    [index / duration, val.abs]

signal, duration = read_channel_data("guitar_first_string.dat", 1)
spectrum = calculate_fft(signal, duration)

max_frequency = spectrum.sort_by(&:last).last.first.round(2)

spectrum_plot_params = {
  image_name: "spectrum.png",
  title: "First guitar string spectrum #{max_frequency}Hz",
  x_axis_title: "Frequency, Hz",
  y_axis_title: "Magnitude"

plotter =, spectrum_plot_params)

And a final plot:

As you can see we need to pull the string for 77.09Hz :)

comments powered by Disqus