The worldwide sensation (and eventual flaming pile of 💩) Game of Thrones premiered in the year of our Lord Stark 2011. If you’re anything like me, you would look forward to Sunday evenings – settling into your sofa with your three cats and/or significant other, waiting for the opening credits to start at 8pm CST (I was living in Omaha, Nebraska at the time). There would be so much excitement building up, but no way to release it and the opening credits are soooo long. Like, one-minute-and-37-seconds long. So I released my pent-up energy the best way I knew how – meowing along to the melody of the Game of Thrones theme song at the top of my lungs with my cats and/or significant other looking on, appalled.
And that’s how Meowifier was born. And it stayed in that unrefined form for about five years, until the year of our Lord Bolton 2016, when I was about six months into my first software engineering job. It was the end of November, winter was coming, and there were still 8 months until the next season of GoT would begin. I was going through Jon Snow withdrawals and was already stuck inside for the winter. I thought to myself, “What if I built an application that automatically meows the Game of Thrones theme song for me?” So I did, poorly.
My goal for the core functionality was that a user could provide a song file and Meowifier would output the melody of the song sung by cats. I had a very rough idea of how I would structure the app and the tools I would need to find or build. The core elements of the app are a melody extractor, a meow library, a note adjuster, and a song builder – all written in Ruby.
The melody extractor
Probably the most important job of my application was to be able to pull the melody out of a polyphonic piece of music (or music with simultaneous notes, you know, like every song you hear on the radio). Since I was still so new to programming, I was hoping to find a tool that was also easy to use. What I found was a tool called SonicAPI. It was free to use until I hit around four hours of audio files. I ended up implementing this method inside my SongParser class:
# This class makes a request to an API, receives the response,
# and then creates a collection of notes.
class SongParser
def parse
response = HTTParty.get(sonic_api_request)
@note_collection = response.dig("response", "melody_result", "notes", "note")
append_array_with_note
end
end
This is what a response from SonicAPI looks like (except imagine hundreds of
hashes inside the note
array):
{"response"=>
{"status"=>
{"code"=>"200"},
"melody_result"=>
{"notes"=>
{"note"=>
[
{"midi_pitch"=>"35.9934844970703125", "onset_time"=>"0.88172335600907025821", "duration"=>"0.061564625850340136404", "volume"=>"0.029758758842945098877"},
{"midi_pitch"=>"23.974985122680664062", "onset_time"=>"0.97460317460317458238", "duration"=>"0.24471655328798186391", "volume"=>"0.042322281748056411743"},
{"midi_pitch"=>"35.97218704223628125", "onset_time"=>"1.2346258503401359441", "duration"=>"0.046031746031746034742", "volume"=>"0.027295669540762901306"},
{"midi_pitch"=>"23.957599639892578125", "onset_time"=>"0.88172335600907025821", "duration"=>"0.79743764172335596108", "volume"=>"0.022882837802171707153"},
{"midi_pitch"=>"35.9209136962890625", "onset_time"=>"0.88172335600907025821", "duration"=>"0.10743764172335601437", "volume"=>"0.0089688096195459365845"},
]
}
}
}
}
The meow library
I needed meows and I needed a lot of them. It was difficult to find an existing
collection of meows tuned to each note on a piano. You try googling cat meows
keyboard notes
and filtering through all this noise:
So I started by recording myself meowing, but only got about five notes in before I gave up, went back to the internet, and searched even deeper. I found a free library of an auto-tuned man meowing. But there were only about four octaves worth of notes, so I ended up just biting the bullet and pitch-shifting a recording of an actual cat’s meow 87 times. It took a while.
The note adjuster
This is the part of the application that needs the most work from me (as opposed to some researcher coming up with a better melody analyzer, because that requires some next-level skill). When I originally built this, I didn’t think too deeply about what a meow would sound like shortened or lengthened. I just wanted to make the length of my meow file match the length of the note in a melody. For longer notes, I used another method to find the number of times I would need to duplicate a meow file.
def number_of_loops(file_length, note_length)
(note_length/file_length).ceil
end
After creating the looped file, I cropped it to the correct length:
def crop_looped_file(looped_file, output_file, note_duration)
cropped_loops = FFMPEG::Movie.new(looped_file)
cropped_loops.transcode(output_file, ["-ss", "0", "-t", note_duration])
end
There was one big issue with this process. For a note that required the file to be looped three times there would be three distinct cat meows instead of one long, drawn-out meow. And the last one would likely be cut off at the end. So, not exactly ideal.
But also, I had a limited amount of time and it’s not like perfecting Meowifier is going to save the world, so I’ll get to fixing that one day. When I do fix it, I intend to split each meow into three parts– beginning, middle, and end. Then I’ll use something like the process above to duplicate and crop the middle of the meow to get the right note length.
The song builder
The song builder combines all the files and creates a new audio file.
def combine
Writer.new(@file_path, OUTPUT_FORMAT) do |writer|
@collection.each do |file|
Reader.new(file).each_buffer(SAMPLE_FRAMES_PER_BUFFER) do |buffer|
writer.write(buffer)
end
end
end
end
And voilà! Listen to my meowsterpiece:
There are some issues…
It turns out melody extraction is actually very difficult. The free or cheap tools come up short. And despite the fact that Meowifier will be worth millions one day, I’m not too sure about shelling out a few thousand dollars for professional grade melody extraction tools.
So, did I find a better tool? Did I end up spending thousands after all? Is Meowifier dead in the water? Tune in next time for the thrilling conclusion.