My first try on algorithmic composition

This is my first try on algorithmic composition. Any critics and suggestions are welcome, because this really sounds like a bad composition. Please share your opinion, techniques and theories to me. I am still learning. Thank you.

#Algorithmic Tune
#by Agustinus Cao
#randomizing notes in C Major scale, without any of the
#sharp notes, ranging from C3 to C5. Song length
#is specified by songlen variable. Each note duration
#is randomized from notesdur list. The duration of the
#last note is determined by subtracting songlen by total
#duration the notes. The composed tune is then played twice.

notes = [0, :C3, :D3, :E3, :F3, :G3, :A3, :B3, :C4, :D4, :E4, :F4, :G4, :A4, :B4, :C5]
notesdur = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]

songlen = 16      #Must be multiples of 4
rndnotes = []
rndnotesdur = []

use_random_seed Time.now.to_i

use_bpm rrand_i(60, 100)

ttldur = 0

#Randomize
(songlen/0.25).times do
  rndnotes.push(notes[rand_i(notes.length())])
  rndno = rand_i(notesdur.length())
  if ttldur + notesdur[rndno] < songlen
    ttldur = ttldur + notesdur[rndno]
    rndnotesdur.push(notesdur[rndno])
  else
    rndnotesdur.push(songlen-ttldur)
    break
  end
end

rndarrlen = rndnotes.length()

#Metronome sound
in_thread do
  (songlen/4*2).times do
    play_pattern_timed [:a5, 0,0,0,:e5, 0,0,0,:e5, 0,0,0,:e5,0,0,0], [0.25], release: 0.25, amp: 0.1
  end
end

#Playing the randomised composition
in_thread do
  2.times do
    n = 0
    rndarrlen.times do
      if rndnotes[n]!=0
        play rndnotes[n], release: rndnotesdur[n], volume: 0.5
      end
      sleep rndnotesdur[n]
      n = n+1
    end
  end
end

Hi @Aguscao

thank you for sharing. I had a listen and ran your piece four times. It’s short enough, so not an issue. I have a couple of observations and that of the four runs, the melody seems to output the Major 7th rather a lot. So seems a bit disproportionately weighted to this outcome. Worth looking at, unless your aim was only for mild variation and this is a feature.

I liked the metronome implementation, in fact I wondered if you could implement this to function not just as metronome but perhaps as the ground.

Taking a cue from serialism, you could produce the inversion, retrograde and retrograde inversion of your scale based melody.

Afraid I’m not a strong enough coder at the moment to offer advice on improving your code, beyond the above, but looking forward to how this work develops.

Hi, thanks for your opinion. I am still working on writing a better algorithm. The major 7th output is actually unintentional because this is really just random notes. For now, I am not thinking of writing rythm/drums, chords or anything, just focusing on better tune composition. But your suggestions are welcome and I really appreciate it. I’ll put your suggestions into consideration. Thank you very much.

Hi @Aguscao,
thanks for sharing your idea! Very good start. Regarding programming technique, I would like to give you some ideas on how things can be realized in an alternative way:

songlen = 16
use_random_seed Time.now.to_i
use_bpm rrand_i(60, 100)

notes = [:C3, :D3, :E3, :F3, :G3, :A3, :B3, :C4, :D4, :E4, :F4, :G4, :A4, :B4, :C5]
notesdur = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]

# control loop
set :stop, 0
live_loop :control do
  if tick == songlen
    set :stop, 1
    stop
  end
  sleep 1
end

# metronome sound
live_loop :metronome do
  stop if (get :stop) == 1
  play [:a5, :e5, :e5, :e5].tick, release: 0.25, amp: 0.5
  sleep 1
end


# melody
live_loop :melody do
  stop if (get :stop) == 1
  d = notesdur.choose
  play notes.choose, release: d
  sleep d
end
  1. live_loop is a good way of doing a loop inside a thread
  2. If you want to limit the duration of a piece, a good idea is to use a control loop that sends a signal :stop to the all other loops that will terminate when they receive the signal. This can be done using the set / get mechanism of SPI
  3. You do not need to prepare random sequences in advance, notes can be picked at random from a list using choose
  4. tick is a counter increasing by 1 on every call to tick inside a loop and it can be used for many purposes such as ticking through an array (see metronome loop) or determining the length of a piece (see control loop)
  5. Instead of writing down all notes explicitely, you could use scale:
notes = scale :C3, :major, num_octaves: 2

which is identical to your list of notes

True, but there’s a bit more to it than that. The tick function is a thread-local counter, meaning that it advances the counter of the current thread (ie, if you are inside an in_thread or live_loop) every time it is called within it. Also, all code in a buffer is actually contained in an implicit top level thread when you hit ‘Run’, so if you call a tick that’s not wrapped in any in_threads or live_loops, this advances the top level thread of that ‘Run’ too :slight_smile:

Hi, Nechoj, thanks for your tips. It’s helpful.

Hi Ethan, nice to see you again. Thanks for the insights of ticks.

1 Like

If you’d like the metronome sound to continue, you could replace the note list by a ring, so that it wraps and the tick can advance inside it continuously:

# metronome sound
live_loop :metronome do
  stop if (get :stop) == 1
  play (ring :a5, :e5, :e5, :e5).tick, release: 0.25, amp: 0.5
  sleep 1
end

It will wrap without a ring when using tick :wink:

Ah, it’s because I’m using SonicPi 3.1 on an older OS. What I said applies to that, but in the new version the tick automatically turns the list into a ring. My bad!

Thank you all for the tips. Appreciate it.