Teaching algorithms by creating audio representations of them with Sonic Pi

Hi everyone!

Recently I started a blog where I talk about programming and give some programming tutorials. One of the things I’m extremely passionate about is the benefits of using creativity in computer science / programming education. I’ve been a coding mentor in web development bootcamps for almost 2 years now and many times I wish I could’ve told all the students to download Sonic Pi and teach programming concepts that way… (but I probably would’ve got fired if I tried… :joy:)

I have the idea of writing a series of tutorials on different algorithms, starting with common sorting algorithms (bubble sort, insertion sort, selection sort, merge sort and quick sort). These tutorials explain each algorithm with Ruby code and the idea is to code an audio representation that is both educational (create something that allows you to hear what the algorithm is doing under the hood) and pleasant to listen to.

Basically, I’m trying to see if creating a musical piece using data “produced” by an algorithm could be a constructive and useful way to learn computer science fundamentals, not just for kids but also adults who are starting to learn Ruby or programming in general.

The first tutorial of this series is published on my blog, I start by exploring bubble sort and introduce basic Sonic Pi concepts so that people with no experience of Sonic Pi can follow along.

I would really appreciate having any kind of feedback from the community on this.

  • Do you think this is an interesting approach to computer science fundamentals?

  • Could it help beginners learn these kinds of concepts?

  • Do you have suggestions on algorithms to explore using this approach?

Here is the link to my article:
Creating an Audio Representation of Bubble Sort With Ruby and Sonic Pi

The article is also linked to a video where I play around with bubble sort using the material explained in the tutorial.

I’m not posting this for self-promotion of anything, I’m really interested to know what more experienced educators think about this approach…

Any comments or feedback is more than welcome :blush:

5 Likes

I loved this!
Had a play this afternoon and came up with doing three sorts simultaneously at different rates in different parts of the audio spectrum. Sounds mesmeric, and you get nice “comings together” at various times. I adjusted the bass note and also the rate of one of the samples according to the version playing.
The possibilities for tweaking this code are endless!!

OOPS just realised the displayed counts are all wrong. So adjusted this below, with changes marked


#Bubble Sort With Sonic Pi: triple audio sort adjusted by Robin Newman Aug 31 2019
#based on earthtoabigail's great article
# Link to tutorial: https://www.earthtoabigail.com/blog/bubble-sort-ruby-sonicpi
#VERSION 2 CORRECTS ERRORS IN PRINTED COUNTS
use_cue_logging false #added version 2
unsorted_arr = [81, 79, 69, 59, 55, 71, 83, 52, 64, 74, 76, 62, 57, 67, 86, 88]
use_bpm 90

def sorted arr, pan
  
  4.times do
    in_thread do
      arr.each { |n|
        play n,pan: pan, release: 0.1
        sleep 0.25
      }
    end
    in_thread do # Keeps track of the One
      sample :bd_tek, pan: pan
      sleep 16
    end
    # Gives a nice and steady rythm that marks we have successfully sorted the list
    sample :loop_breakbeat, beat_stretch: 4, amp: 2,pan: pan
    sleep 4
  end
end

def bubble_sort array,pan
  case pan
  when 0
    shift=-12;rate=0.5 #adjust base note and sample rate for :elec_blip2 (45bpm)
  when 1
    shift=0;rate=1 #adjust base note and sample rate for :elec_blip2 (90bpm)
  when -1
    shift=12;rate=2 #adjust base note and sample rate for :elec_blip2 (180 bpm)
  end
  arr = array.dup
  swapped = false
  r = arr.length - 2
  
  # DATA - Tracking variables
  array_states = []
  total_swaps = 0
  swaps_per_iter = []
  num_iters = 0
  time_of_exec = 0
  
  arr.each { |n| play n,pan: pan; sleep 0.25 }
  
  start_time = Time.now # Start calculating time of execution
  
  while true do
      swaps = 0
      num_iters += 1 # Keep track on the number of iterations we did so far
      
      in_thread do
        use_synth :dsaw # Gives a base frequency (take lowest value of array)
        #adjust base note with shift
        play 52+shift, amp: 0.5, attack: 2, sustain: 6, decay: 2, release: 4, cutoff: 60,pan: pan
        sample :bd_tek,pan: pan # Tracking when we are entering the loop
      end
      
      in_thread do # Gives a sense of how many iterations we've done so far
        num_iters.times do |i|
          sample :drum_cymbal_closed, amp: 1.0 + (i.to_f / 2.0), rate: 2,pan: pan
          sleep (2.0 / num_iters).round(2)
        end
      end
      
      for i in 0..r # inclusive range
        play arr[i], release: 0.1,pan: pan
        sleep 0.25
        if arr[i] > arr[i+1]
          arr[i], arr[i+1] = arr[i+1], arr[i]
          swapped = true if !swapped
          sample :elec_blip2, amp: 1.5,pan: pan,rate: rate
          sleep 0.25
          play arr[i],pan: pan # hear the value which the current value is being compared to
          sleep 0.25
          swaps += 1
        end
      end
      total_swaps += swaps
      swaps_per_iter.push(swaps) # remember how many swaps occured in this iteration
      
      swapped ? swapped = false : break
      
      array_states.push(arr.dup) # save a copy of the current state of the array
    end
    
    time_of_exec = Time.now - start_time
    
    # Calling sorted function with sorted array
    sorted arr,pan
    # return the sorted array and all the tracking data
    [arr, total_swaps, swaps_per_iter, num_iters, time_of_exec, array_states]
  end
  
  
  set :n1,0;set :n2,0; set :n3,0 #initialise counters added version 2
  
  with_fx :reverb, room: 1 do
    puts "Triple stream bubble sort with Sonic Pi"
    puts "(use a good stereo system for best results)"
    puts "Sorting at 90bpm on Full Right Pan"
    puts "Sorting at 180bpm on Full Left Pan"
    puts "Sorting at 45pbm at Center Pan 0"
    puts
    live_loop :sort do
      in_thread do
        with_bpm 45 do
          data=bubble_sort unsorted_arr,0
          set :n1,get(:n1)+1 #added version2
          #puts statement changed version 2
          puts "Centre pan (45 bpm) completed #{get(:n1)}. Sort time #{((data[4]*100).round.to_i/100.0).to_f}"
          puts "Sorted Array: #{data[0]}"
        end
      end
      
      in_thread do
        2.times do
          data=bubble_sort unsorted_arr,1
          set :n2,get(:n2)+1  #added version2
          #puts statement changed version 2
          puts "Right pan (90bpm) completed #{get(:n2)}. Sort time #{((data[4]*100).round.to_i/100.0).to_f}"
        end
      end
      
      with_bpm 180 do
        4.times do
          data=bubble_sort unsorted_arr,-1
          set :n3,get(:n3)+1  #added version2
          #puts statement changed version 2
          puts "Left pan (180bpm) completed #{get(:n3)}.  Sort time #{((data[4]*100).round.to_i/100.0).to_f}"
        end
      end
    end
  end

Wow @robin.newman thank you so much for taking the time to have a look at this. I’m so happy you liked it!

And I love what you did in your version, I think you’re right, there are many possibilities to customize the basic code and possibly use it in a performance context. It’s something I’m learning to do more and more when I perform, to write blocks of code beforehand that I can “customize” and reuse live…

On the more educational side of things, what I’d like to try in this series of tutorials is to take beginners that are starting out with ruby programming and see if I can warm them to the idea that by using creativity and music as a tool, they can increase their understanding of core programming concepts.

I’d love to hear your thoughts on this kind of approach from an educational stand point… do you think there would be some benefits in teaching the basics of programming and computer science in this kind of way?

Maybe this sounds like a simple question because I guess this is pretty much the purpose of an application like Sonic Pi :sweat_smile: Still, when I’ve been in teaching situations (both with adults in a professional program and with teenagers giving Sonic Pi workshops… not a huge experience, just over 2 years of being a coding mentor), I felt there’s a gap that’s difficult to bridge between having fun and being creative and learning something “serious” and “useful”. It’s hard to convince people (kids or adults) that something can be both.

The bubble sort experiment was kind of my attempt at communicating concepts in a way that could be perceived as both “fun” and “serious”… hoping to find ways to bridge that gap a little bit… I’m wondering how much I’m hitting the mark or what are the things that I should improve :slight_smile:

I’d love to hear your thoughts on this and thank you again for sharing and connecting! :sunny: :smiley:

Hi @EarthToAbigail,

I find your idea very appealing. Also I do like the musical outcome. I did read into your blog entry but did not read the whole piece in detail. I must admit on first reading your post I had to chuckle because if you have watched a few of Sam’s presentations you will know that sorting algorithms is what he chooses as deterrent example when it comes to teaching programming :wink: But I admire that you take the bull by the horns and face the challenge to make especially sorting algorithms an interesting and rewarding subject. I like that and am looking foward to the next sequel.

As I am teaching Sonic Pi at the university once a year I also made some steps and thought about what could be interesting and how it could be taught in an intesting way. My approach started by identifying ‘musical problems/tasks’ (such as: how to organise musical patterns such as A-A-B-A) and how to solve these with coding and especially with Sonic Pi. Nevertheless my focus for that was/is to find easy and memorisable ways which can be part of ones live coding vocabulary. On the other hand I also wanted to focus on the musical side rather than exploring features of Sonic Pi and/or Ruby.

I haven’t worked on that in a while (but will continue to do so). The reason is, that right now I am into programming for the grid (Monome), which I can pursue more seriously since I am the proud owner of one.

The grid also provides some interesting ideas to combine music, programming and how to teach both. On example (which right now exceeds my abilities by far) is a sequencer based on Conway’s game of life.

Let me know what you think if you like and let us know about your next blog post of this series. I would also be interested in the feedback you’ll get by the ones that attend your courses.

Hi @Martin,

Thank you so much for your feedback. I honestly didn’t know that Sam Aaron uses sorting as examples for teaching with Sonic Pi as well… I saw many of his live coding performance videos and watched Ted talks he gave but I admit that I could’ve done more research before taking on the subject :slightly_frowning_face: That being said, I really appreciate you bringing this to my attention, I’ll make sure to watch more of Sam’s videos (and thank you for your kind words of encouragement!) :slight_smile:

I can really relate to this. The first “problems” I was trying to solve were on a structural level and I started by identifying clear patterns in the structure of a song and “abstract” the logic into functions I could reuse and customize depending on the arguments I pass it. That kind of approach really helped me improve my coding skills and brought to my attention some more advanced concepts in programming that, as a beginner, I’d never really put attention on (like performance issues or functional design…).

Then, as a performer, this approach gave me a middle-ground between starting from a clean slate and coding everything from scratch on stage and having a lot of pre-written code that I just run and apply minimal changes to while I’m performing.

There are some very interesting ideas in your Github repo, I learned quite a few things by reading some of your code, for example on the use of at, I never thought to use it that way… also loved the Boogie Woogie bassline :). I also really like your approach to reproducing complex grooves, like the samba.

Personally, I think that there’s much more overlap between the 2 topics then we normally believe… I don’t see much difference between the process of learning a programming language and learning music theory. Somehow, I think it could be possible to explain music theory using programming terms and vice versa…

I feel I get the most satisfying results when I find the right balance between “chaos” and “order” in the music I create with Sonic Pi. Music knowledge helps me develop interesting “chaos” while programming knowledge allows me to give it a clear organized direction. For me, both types of knowledge walk hand in hand and help each other even though I don’t think it’s simple to teach it as such…

As for your work with the Monome grid that looks very interesting. I’m very curious to see where you take the project and hear it in action!

Thank you again for your input and sharing some of your ideas and experience, it gave me a lot of useful insights. I’ll make sure to post the link to the sequel of the bubble sort article on this thread once it’s out :slight_smile:

1 Like

Finally, I released the sequel to my first sorting article with bubble sort :smiley:

This article is on selection and insertion sort, I found it a lot more challenging than the first one to create something that gives a good auditive sense of what the algorithm is doing.

Here is the link to the latest article:

Understanding Selection And Insertion Sort By Creating Audio Representations Using Ruby And Sonic Pi

I learned a lot in the process and I’m quite satisfied with the results, my next step is to record a video where I play around with these concepts (like I did with bubble sort).

Next stop in the sequel for me will be to explore recursion, leading to exploring “divide and conquer” algorithms like merge sort and quick sort. I’m super curious (as well as slightly nervous) to try and implement recursive functions in Sonic Pi :nerd_face:

As always, any comments or feedback is more than welcome :slightly_smiling_face:

1 Like

(Yours is) Another really cool and beautiful sounding sort.

I did a crude merge sort example, but not nearly as nice sounding.
Here are 4 such sorts played together. (I didn’t write the algorithm, but modified it from one I found online).

 #merge sort medley by Robin Newman
use_synth :dsaw
use_random_seed 20190926
use_bpm 90
define :mergesort do |array|
  define :mg do |left_sorted, right_sorted|
    res = []
    l = 0
    r = 0
    loop do
      break if r >= right_sorted.length and l >= left_sorted.length
      
      if r >= right_sorted.length or (l < left_sorted.length and left_sorted[l] < right_sorted[r])
        res << left_sorted[l]
        l += 1
        synth :pluck,note: left_sorted[l-1],amp: 2,pan: -1
      else
        res << right_sorted[r]
        r += 1
        synth :pluck,note: right_sorted[r-1],amp: 2,pan: 1
      end
      res.each do |n|
        play n,release: 0.2
        sleep 0.2
      end
    end
    return res
  end
  
  define :ms_iter do |array_sliced|
    return array_sliced if array_sliced.length <= 1
    
    mid = array_sliced.length/2 - 1
    left_sorted = mergesort_iter(array_sliced[0..mid])
    right_sorted = mergesort_iter(array_sliced[mid+1..-1])
    sample :perc_snap,amp: 3
    return mg(left_sorted, right_sorted)
  end
  ms_iter(array)
end


a=scale(:c2,:minor_pentatonic,num_octaves:3)
c= a.shuffle
puts "original unsorted c #{c.to_a}"
d=c.reverse
sleep 1
with_fx :reverb,room: 0.8 do
  in_thread do
    puts "Sorted 1c: #{mergesort(c)}"
  end
  in_thread do
    use_bpm 180
    2.times do
      puts  "Sorted 2c: #{mergesort(c)}"
    end
  end
  use_synth :pulse
  use_transpose 12
  in_thread do
    puts  "Sorted 3d: #{mergesort(d)}"
  end
  use_bpm 180
  2.times do
    puts  "Sorted 4d: #{mergesort(c)}"
  end
end

@robin.newman Sorry for the late reply!

I quite like your merge sort version actually! I think it could sound really nice with just some minor adjustments (though I already like the way it sounds :slight_smile: ).

Just one detail I had to fix for the code to run properly, I think you meant to call ms_iter since mergesort_iter doesn’t exist…

Thanks again for posting this, really like playing around with your code snippets, I always learn something new and it also gives me more confidence for my next experiment with recursion :slight_smile:

Oops. I changed some names because of existing code in SP. These were originally Ruby def code and I changed them to defines as more in keeping with SP. missed that one.

Yes I figured it was only a silly typo :smile: just pointed it out in case someone reads this thread and wants to try out the code!