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:

6 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
1 Like

@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!

I love this! It’s funny because in one of Sam’s interviews or talks he says nobody wants to learn sorting algorithms (if I recall correctly). This year in teaching a junior high, I’ve been trying to cover more “computer science” than just programming. So I started the year teaching representation, then sorting, à la Harvard’s David Malan’s CS50. Now that I’ve discovered Sonic Pi, I like the idea of combining them. Here’s one of my first efforts: https://www.youtube.com/watch?v=Y-AZE6prepg

1 Like

Ha! Stumbled to this and tried bubble sorting a string played with the ziffers:

load "~/ziffers/ziffers.rb"

def bubble_sort(str)
  str = str.dup
  return str if str.size <= 1
  swap = true
  while swap
    swap = false
    (str.length - 1).times do |n|
      if str[n] > str[n+1]
        str[n], str[n+1] = str[n+1], str[n]
        zplay str, groups: false, sleep: 0.125
        swap = true
      end
    end
  end
  str
end

bubble_sort("4321")
sleep 1
bubble_sort("132435469878675643")

@dcbriccetti Thank you! :smiley:

I loved what you did in your video as well! I think it’s very cool that you can hear AND see what the algorithm is doing. I’d be very curious to know how your students are responding to this kind of approach: if you feel it makes it easier for them to understand what’s going on? Or maybe just the fact of coding sounds and seeing visual results makes it more interesting for them to understand the code?

I’m starting my first series of live coding workshops soon and I’d love to have some insights from experienced educators :slight_smile:

@amiika Loving your ziffers system (and the code sounds very cool) :smiley: . Will play around with it more, I have a feeling it might simplify my life a lot for live coding performances, really helps to have this extra layer to make the code shorter to write.

Here’s my latest experiment in the sorting world with Sonic Pi, this time I tried out with merge sort. This one was super interesting and I learned a lot about how different types of algorithms can be used for different textures and effects. Because of its divide and conquer nature, I played a lot with the panning to hear subdivisions of the list depending on whether the algorithm is processing the “right side” or “left side”. Also, I found that the recursive calls have an interesting “groove” to them, which I tried to highlist by layering the algorithm over a very simple and straight beat. Would love to know what you think…

Here is the link to a YouTube video where I have some fun with this idea:

https://www.youtube.com/watch?v=-NKNfP7jfL0

And here’s the code if anyone wants to have fun with it:

use_bpm 70
unsorted_arr = [79, 76, 67, 59, 55, 71, 52, 64, 74, 83, 62, 86]

def play_list arr, vol, p = 0
  if arr.length < 1
    sample :ambi_swoosh, pan: p, amp: 2.5
  else
    arr.each do |n|
      play n, pan: p, amp: vol, release: 0.05, sustain: 0.1, decay: 0.05
      sleep 0.25
    end
  end
end

# Non-recursive merge function
def merge left, right
  
  sample :perc_bell2, amp: 1
  
  use_synth :beep
  in_thread do
    play_list left, 0.8, -1
  end
  play_list right, 0.8, 1
  
  sorted = []
  while !left.empty? && !right.empty?
    if left.first < right.first
      sorted.push(left.shift)
    else
      sorted.push(right.shift)
    end
    
    in_thread do
      play_list left, 0.5, -1
    end
    
    play_list right, 0.5, 1
  end
  
  play_list left, 1, -1
  play_list right, 1, 1
  play_list sorted.concat(right).concat(left), 1
  sorted
end

# Main function
def merge_sort arr, side = nil
  
  sample :glitch_perc1, amp: 1
  
  use_synth :square
  
  p = (side == "left") ? -1 : (side == "right") ?  1 : 0
  play_list arr, 1, p
  
  if arr.length <= 1
    sample :elec_chime, amp: 1.5
    sleep 0.5
    return arr
  end
  
  mid = arr.length / 2
  left = merge_sort arr.slice(0...mid), "left"
  right = merge_sort arr.slice(mid..arr.length), "right"
  
  merge left, right
end


with_fx :reverb, room: 0.9 do
  live_loop :merge_sort do
    merge_sort unsorted_arr.dup
  end
  
  live_loop :beat do
    in_thread do
      sample :bd_zome, amp: 2
      sleep 0.5
      3.times do
        sample :drum_cymbal_closed, amp: 0.6
        sleep 0.5
      end
    end
    use_synth :dsaw
    play :e2, cutoff: 50, amp: 0.9, sustain: 1, release: 0.5, decay: 0.5, attack: 0.25
    sleep 2
  end
end
2 Likes