Muzyka algorytmiczna

Zajęcia 10 (2024-12-04)

Cel zajęć

  1. Prezentacja programu Decent Sampler
  2. Prezentacja programu Reaper

Zadanie

Na kolejne zajęcia (2024-12-18)

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

Zadanie z 2 tygodniowym terminem oddania z uwagi na dodatkowe materiały na zajęciach 2024-12-11.

Przykładowe formy utworu spełniające wymagania:

Kod z zajęć

Plik: 10.rb

Linki

Gramatyki bezkontekstowe

Nieformalna definicja:

Gramatyka formalna - sposób na opis języka, czyt. sposobu na tworzenie (lub walidację) ciągów znaków w danym języku. Gramatyka bezkontekstowa to gramatyka, w której z pojedynczych symboli możemy tworzyć zdania (= nie potrzeba kontekstu, wystarczy jeden symbol by zastosować jedną z reguł).

Gramatyki najczęściej definiowane są przez reguły. Reguła A → B oznacza, że posiadając symbol A można zamienić go na symbol B.

Przykłady gramatyk

Prosta gramatyka, która zmienia przykładowy ciąg „AA” w ciąg „BBBB”:

A → BB

Gramatyka, generująca ciągi „ab”, „aabb”, „aaabbb”:

S → aSb
S →

brak treści w drugiej regule dla symbolu S jest celowy - pozwala na zakończenie generowania ciągu znaków przez możliwość „usunięcia” symbolu S. W innym przypadku bez wprowadzenia dodatkowych ograniczeń generowane byłby nieskończenie długie ciągi.

Przykład gramatyki, która generuje nieskończenie długie ciągi:

A → ABA
B → CC
C → BAB

Zastosowanie graficzne

L - systemy, umożliwiające procedularne generowanie np. drzew.

Zastosowanie muzyczne

Symbolom wygenerowanym przez gramatykę można przyporządkować znaczenie muzyczne. Ponieważ prostsze jest tworzenie gramatyk nieskończonych, wprowadzone zostaje dodatkowe ograniczenie: liczba rund w których możemy dopasować reguły.

Przykład: Symbole odpowiadają dźwiękom

Wykorzystana gramatyka:

S → KCE
K → KSSE

Pierwsze kilka rund zastosowania zasad, zaczynając od symbolu „S”:

  1. KCE
  2. KSSE
  3. KSSEKCEKCE

Funkcja automatycznie generująca kolejne rundy wygląda następująco:

define :kolejny do |str|
  kolejny = ""
  for x in str.chars
    if x == "S" then kolejny += "KCK" end
    if x == "K" then kolejny += "ESSE" end
  end
  kolejny
end

Wywoływana następująco:

kolejny("S") # zwraca „KCK”

Interpretując „C” jako :c4, „E” jako :e4, a pozostałe symbole jako zwiększanie „S” i zmniejszanie „K” tempa możemy interpretować stworzony przez gramatykę utwór następująco:

program = "S"
3.times do program = kolejny(program) end
p = 1.0
for x in program.chars
	if x == "C" then play :c4, duration: p end
	if x == "E" then play :e4, duration: p end
	if x == "S" then p /= 2 end
	if x == "K" then p *= 2 end
	sleep p
end

Gramatyka kontroluje pełny proces odtwarzania utworu

Symbole mają pełną kontrolę nad utworem - zmieniają wykorzystywany syntezator, własności wykonywanych dźwięków, tworzą nowe wątki w których wykonywane są programy.

W poniższym przykładzie:

define :kolejny do |str|
  kolejny = ""
  for x in str.chars
    if x == "C" then kolejny += "CGE" end
    if x == "E" then kolejny += "EEG" end
    if x == "G" then kolejny += "CUY" end
    if x == "U" then kolejny += "UCGEDD" end
    if x == "D" then kolejny += "DEGCU" end
    if x == "Y" then
      # nie robimy nic
    end
  end
  kolejny
end

use_bpm 240

define :interpretuj do |program, start, start_p, start_oct|
  p = start_p
  o = start_oct
  program.chars[start..].each_with_index do |x, i|
    if x == "C" then synth :dsaw, note: note(:c, octave: o), duration: p, amp: 0.5 end
    if x == "E" then synth :dsaw, note: note(:e, octave: o), duration: p, amp: 0.5 end
    if x == "G" then synth :dsaw, note: note(:g, octave: o), duration: p, amp: 0.5 end

    if x == "U" and o <= 6 then o += 1 end
    if x == "D" and o >= 2 then o -= 1 end

    if x == "Y" then
      old_p = p
      p = p * 2
      in_thread do
        interpretuj(program, start+i+1, old_p * 4, o)
      end
    end
    sleep p
  end
end

program = "C"
3.times do program = kolejny(program) end

interpretuj(program, 0, 0.5, 4)

Gramatyka kontroluje strukturę utworu

Poniższy przykład jest zmienionym utworem z zajęć o minimaliźmie - tym razem zamiast procesu losowego, kontrolowany jest on przez program.

use_bpm 60
use_synth :prophet

program = "AAWAWBCBDE"

define :interpretuj do |program, start|
  program.chars[start..].each_with_index do |x, i|
    if x == "A"
      play :f2, cutoff: :f4
      sleep 1
      play :e2, cutoff: :e4
      sleep 1
    end

    if x == "B"
      with_fx :slicer, phase: 0.5 do
        sn = synth :prophet, sustain: 4, note: :e1, cutoff: :e3, cutoff_slide: 4
        control sn, cutoff: :e4
        sleep 4
      end
    end

    if x == "C"
      blade = synth :blade, note: :e3, note_slide: 0.5, duration: 10000
      rand_i(6).times do
        blade.control note: (ring :f3, :e3).tick
        sleep 1
      end
      blade.control note: :c9, cutoff: 0, cutoff_slide: 2
    end

    if x == "D"
      rand_i(6).times do
        with_fx :slicer, phase: 0.25 do
          sn = synth :prophet, sustain: 4, note: :e1, cutoff: :e2, cutoff_slide: 4
          control sn, cutoff: :e3
          sleep 4
        end
      end
    end

    if x == "E"
      rand_i(6).times do
        play :e5
        sleep 1
        play :f5
        sleep 1
      end
    end

    if x == "W"
      in_thread do
        sleep 2
        interpretuj(program, start+i+1)
      end
    end
  end
end

interpretuj(program, 0)