‘AFPlay’ – Definitive guide to manipulating Audio in your CLI Application (Ruby)

I have learned a lot in my brief time at Flatiron school. The journey so far has been intense, exhausting, and rewarding all at the same time. The Software Engineering program lasts for 15 weeks and consists of 5, 3 week modules. It’s crazy how much, and how fast you learn! I have only been a student here for a month now and have only just completed module 1. I learned a lot and I would like to share a little bit of what I learned here with you.

Our first major project was to design a CLI App utilizing Ruby and SQLite3. Basically a ‘CRUD’ app. My partner and I decided to make a Typing Speed game that would keep track of your WPM and also track highscores. It was a fun learning experience, even frustrating at times. My biggest challenge was adding audio/music that would play in the terminal… This is when I came across ‘afplay’. **DISCLAIMER – This only works on MACS** -_-

‘afplay’ is a built in function with Mac systems. You can simply use the function to point to an audio file, and it will create a Process to play that file.

pid = fork{ exec 'afplay', "../filename.mp3" }

So make sure that you have the right file path (you can upload your audio file to github and use that file path). Now this bit of code will create a Process, fork/exec, that will play your audio. Now it will play until it finishes the audio file or if it is a long audio file it will keep playing even if you exit your CLI application. So what do you do?

We have to kill the Process we started. We can do this with a similar line of code.

    pid = fork{ system 'killall', 'afplay' }

Where ever you put this it will immediately ‘kill’ the process you started to play the audio. (I used ‘system’ in this code snippet above, you can use exec or system, they are interchangeable.) I would recommend putting the kill command to execute on exit of the application.

GREAT!! Now we have auido/music working with our CLI Application! WOOT! … but wait… What if you want to play multiple audio files and switch between what is playing based on certain conditions. Can we do this? This is where it got tricky for me! I had a hard time finding any info on ‘afplay’ and switching audio files. I tried many different “solutions” that I thought would be obvious, like the below code…

if condition1 == condition2
        pid = fork{ system 'killall', 'afplay' }
        pid = fork{ exec 'afplay', "../filename.mp3" }
    elsif condition3 == condition4
        pid = fork{ system 'killall', 'afplay' }
        pid = fork{ exec 'afplay', "../filename.mp3" }

In my mind I thought this should work. My thought was that if a condition was met you could kill the previous Process, then start up another one and play a different Audio file. Makes sense right?? Well Ruby would read the ‘killall’ command and it would successfully kill the music that was playing when ‘said condition’ was met, but it wouldn’t execute the next line and start the new Process!! This was very frustrating. So I had to dig deep… DEEEP into google lol. Ok maybe I’m exaggerating, but it took me a while to find out why this wasn’t working.

So I’m sure you have noticed that I have emphasized that this line of code will create a PROCESS. So the more research I did, for I didn’t fully understand this code and how it worked right away (I just copy/pasted pretty much), I found out that ‘fork/system’ creates a built in Ruby class called Process. You can research this if you would like, I could spend a whole other blog post talking about Process. However, this got me thinking. We started a “Process” and were stopping it by using the ‘kill”ALL”‘ command… and PID actually stand for Process ID. So what if killing-all was KILLING ALL the processes and this is why I couldn’t switch audio?! This was my EUREKA moment!

So turns out you can kill individual Processes by their ID!

Another nice tip that I learned in this process was that “fork/system” could be replaced with one word => “spawn”.

pid = spawn( 'afplay', file )

Make sure that when you use “spawn” you use parentheses “()”, and not “{}” or it will not work. (I made this mistake and it kept me up at night!) Ok so now you can make this into a method so you can pass in an argument, aka the audio file you want to play!

def play_music(file)
    @pid = spawn( 'afplay', file )
end

This was the method that I used to make it easier to switch audio later. I made the “pid” an instance variable (‘@pid’) so that I could call on it later. Now how do we kill the individual process so we can start another? Try this…

def switch_song
    Process.kill "TERM", @pid
end

Notice, I put this in a method as well. I think you can probably start piecing together how I made this work now. So Process.kill (not ‘killall’) and the “TERM”, which means “TERMINATE”, and then you specify the process that you want to end. Since we made our process id an instance variable we can call on that here to end the process. This is how I used these two methods together to switch between different songs based on conditions met…

def play_theme_music
    if @current_theme.name == "The Office"
        switch_song
        play_music('music/the_office.mp3')
    elsif @current_theme.name == "Coding"
        switch_song
        play_music('music/tetris.mp3')
    elsif @current_theme.name == "Runtime Terror"
        switch_song
        play_music('music/halo.mp3')
    elsif @current_theme.name == "Russian"
        switch_song
        play_music('music/handball1.mp3')
    elsif @current_theme.name == "Numbers"
        switch_song
        play_music('music/mario64.mp3')
    elsif @current_theme.name == "Jibberish"
        switch_song
        play_music('music/mario64.mp3')
    elsif @current_theme.name == "Star Wars"
        switch_song
        play_music('music/dual_of_fates.mp3')
    end
end

Now this is the exact code from my final Mod 1 project at Flatiron. (I will post a link to my GitHub repository for this project in case you’re curious how this method worked with the rest of the project. I will post a YouTube video as well to give you a quick look at how the CLI App worked.)

I still utilized the original “killall” method (referenced earlier), but I only had it execute on the exiting of the program. There was one other thing that I was playing around with that I didn’t figure out before I ran out of time to complete the project. I wanted to play an audio file and have it loop. Now I didn’t try this once I found out the right way to switch between audio files, so It will probably be easy to figure out. I will update this post if I have the time to go back and figure it out, or if you figure it out leave a comment and let us know 🙂

I have really enjoyed my journey so far and I can’t wait to share with y’all all of the amazing stuff I learn. Thank you for reading 🙂

Enjoy the music! PEACE! MITCH OUT!

P.S. GitHub link and youtube video below of my project.

https://github.com/timothyalton/module-one-final-project-guidelines-houston-web-012720

Leave a comment