Muzyka algorytmiczna

Zajęcia 3 (2024-10-16)

Cel zajęć

  1. Tablicowe struktury danych w Sonic Pi
  2. Zarządzanie przepływem programu w Sonic Pi
  3. Tworzenie własnych skal

Zadanie (na 2024-10-23)

Przygotowanie krótkiego utworu (~30s) w środowisku Sonic Pi, spełniającego następujące wymagania:

  1. 5 wybranych dźwięków we własnej skali
  2. wykorzystuje mechanizmy synchronizacji
  3. wykorzystuje tablicowych struktur danych Sonic Pi

Zrzut kodu z zajęć

Plik: 03.rb

Tablicowe struktury danych w Sonic Pi

Dodatkowe informacje: porównanie Pythona i Rubiego

Sonic Pi: wektory

Odpowiednik tablic z dodatkowymi metodami.

Tworzone funkcją vector

Wspierane operacje
+
dodaje liczbę do elementów / łączy wektory
-
odejmuje liczbę od elementów / róznica zbiorów
*
powtarza elementy n razy
all?
czy predykat jest prawdziwy dla każdego z elementów
any?
czy predykat jest prawdziwy dla któregokolwiek z elementów
butlast
elementy poza ostatnim
choose
wybierz losowy element
drop
pomiń n elementów
drop_last
pomiń n ostatnich elementów
each
iteruj po elementach
each_with_index
iteruj po elementach z indeksem
empty?
czy wektor jest pusty
filter
filtruj elementy według predykatu
first
pierwsze n elementów
flat_map
złącz wyniki iteracji w wektor
flatten
złącz elementy w jedną listę
last
zwróć n ostatnich elementów
length
długość wektora
map
iteruj elementy, tworząc wektor z wyników iteracji
max
maksimum
min
minimum
mirror
złącz wektor z jego lustrzanym odbiciem
ramp
Zwróć wektor ograniczony z elementów wektora
reflect
złącz wektor z jego lustrzanym odbiciem, nie powtarzaj środkowego elementu
repeat
złącz wektor n razy
reverse
odwróć kolejność elementów w wektorze
ring
zwróć pierśćień z elementów wektora
rotate
przesuń elementy wektora
scale
skaluj elementy (mnóż po elementach)
shuffle
losowa permutacja elementów
size
rozmiar wektora
sort
zwróć elementy posortowane
stretch
powtórz elementy obok siebie
take
weź n elementów
take_last
weź n elementów od końca
uniq
zwróć elementy bez powtórzeń

Sonic Pi: pierścienie

Pierścień p to n-elementowy wektor, gdzie p[i] == p[i%n].

Tworzone funkcją ring

Sonic Pi: wektor ograniczony

Wektor ograniczony v to n-elementowy wektor, gdzie:

Tworzony funkcją ramp

Przykładowe wykorzystanie wektorów

use_synth :piano

times = (1..8).map do |x|
  1r / x
end.ring.mirror

play_pattern_timed (ring :f4, :g4).repeat(times.length/2), times

Synchronizacja, cue,sync

Kontynuacja materiału z poprzednich zajęć.

Problem sekcji krytycznej

in_thread do
  loop do
    play :c5
    sleep 1
    cue :second
    sync :first
  end
end

in_thread do
  loop do
    play :c4
    sleep 1
    cue :first
    sync :second
  end
end

Problemy synchronizacji z cue i sync

Aby cue osiągnęło pożądany efekt, musi być sync, które aktywnie oczekuje na cue. Komunikaty cue znikają jeśli nie ma oczekującego na nie sync. Możliwe rozwiązania:

  1. Dodanie mikroprzerw w celu zwiększenia szansy na wykonanie sync przed cue (przykład poniżej)
  2. Uruchomienie utworu z wyprzedzeniem (przykład na zajęciach).
in_thread do
  loop do
    cue :ready1
    sync :a
    4.times do
      play :c4, duration: 0.5
      play :e4, duration: 0.5
      play :g4, duration: 0.5
      sleep 0.5
    end
    cue :done
  end
end

in_thread do
  loop do
    cue :ready2
    sync :b
    play_pattern_timed (ring :c5, :e5, :g5), 0.5
    cue :done
  end
end

sync :ready1
sync :ready2
sleep 0.0001
loop do
  cue (ring :a, :b).tick
  sync :done
end

Skale

Skala - szereg dźwięków, zazwyczaj pomiędzy dźwiękiem, a jego oktawą. Oktawa - dźwięk, o dwukrotnie wyższej częstotliwości.

Encyklopedia ciągów liczbowych

Skale wbudowane w Sonic Pi

scale zwraca skalę w postaci pierścienia. scale_names zwraca listę skal w postaci pierścienia.

scale :C4, :major # ≡ (ring 60, 62, 64, 65, 67, 69, 71, 72)
scale :C4, :minor # ≡ (ring 60, 62, 63, 65, 67, 68, 70, 72)
scale :C4, :egyptian # ≡ (ring 60, 62, 65, 67, 70, 72)

Skala pitagorejska

Źródło odległości między dźwiękami

pit = [0, 203.91, 294.13, 498.04, 701.96, 905.87, 996.09].map do |x| note(:d4) + x/100 end
D1, E1, F1, G1, A1, B1, C1 = pit
tet = [:d4, :e4, :f4, :g4, :a4, :b4, :c5]

pit.zip(tet).each do |a, b|
  print a, b
  play_pattern_timed (ring a, b), 1
end

# Można krócej:
# play_pattern_timed (ring pit.zip(tet)).flatten, 1

Generowanie skal równomiernie temperowanych

def równomiernie_temperowany(start, n)
  sounds = []
  n.times do |i|
    sounds << hz_to_midi(midi_to_hz(start) * (2 ** (1r/n)) ** i)
  end
  sounds.ring
end

S, K, A, L, A = równomiernie_temperowany(:a4, 5)
play S
play A
sleep 1

tet19 = równomiernie_temperowany(:a4, 19)
play_pattern_timed tet19, 1/8r, release: 0.1
sleep 1

Zatrzymywanie fragmentu utworu (po zajęciach)

Cel: pętla, której wykonanie można zatrzymać i wznowić spoza pętli. Implementacja wykorzystuje mechanizm omówiony na przyszłych zajęciach.

Podstawowe zatrzymanie

W jednym panelu:

set :wait, false

in_thread(name: :foo) do
  loop do
    if get[:wait] then sync :continue end
    play :c4
    sleep 1
  end
end

W innym panelu, by zatrzymać:

set :wait, true

W innym panelu (może być ten sam w którym zatrzymano), by uruchomić ponownie:

set :wait, false
cue :continue

Z dodatkowym oczekiwaniem na realizację przerwania pętli

set :wait, false

in_thread(name: :foo) do
  loop do
    if get[:wait] then
      cue :stopped
      sync :continue
    end
    play :c4
    sleep 1
  end
end

in_thread do
  # Fragment wykonuje się razem z pętlą powyżej
  2.times do
    play :e4
    play :g4
    sleep 1
  end

  set :wait, true
  sync :stopped

  # Fragment wykonuje się jak pętla zatrzymała wykonywanie
  2.times do
    play :c2
    play :e2
    play :g2
    sleep 1
  end

  set :wait, false
  cue :continue

  # Fragment wykonuje się jak pętla wznowiła wykonywanie
  3.times do
    play :e4
    sleep 1
    play :g4
    sleep 1
  end
end