Siren Event Generators

The EventGenerator and EventModifier packages provide for music description and performance using generic or composition-specific middle-level objects. Event generators are used to represent the common structures of the musical vocabulary such as chords, clusters, progressions, ostinati, or algorithms. Each event generator subclass knows how it is described--e.g., a chord with a root and an inversion, or an ostinato with an event list and repeat rate--and can perform itself once or repeatedly, acting like a Smalltalk-80 control structure or returning a static event list for further processing.

EventModifier objects generally hold onto a function and a property name; they can be told to apply their functions to the named property of an event list lazily or eagerly. Event generators and modifiers are described in more detail in the 1989 ICMC paper (see the file Doc/icmc.89.egens.pdf).

Examples

Clusters and Chords are simple one-dimensional event generators.

   [(Cluster dur: 2.0
      pitchSet: #(48 50 52 54 56)
      ampl: 100
      voice: 1) open]

   [((Chord majorTetradOn: 'f4' inversion: 0) duration: 1.0) open]

Rolls are also-1-D, but are rhythm-only.
Create and play a simple drum roll--another 1-D event generator.

   [((Roll length: 2000 rhythm: 50 note: 60) ampl: 80) open]

Clouds are stochastic descriptions of event lists whereby one can give the numerical range of each of several standard properties.

Create and edit a low 6 second stochastic cloud with 5 events per second.

   [ | c |
   c := (Cloud dur: 6         "lasts 6 sec."
      pitch: (48 to: 60)       "with pitches in this range"
      ampl: (80 to: 120)       "and amplitudes in this range"
      voice: (1 to: 1)         "select from these voices"
      density: 5) eventList.   "play 5 notes per sec. and get the event list"
   c open]

To create a dynamic cloud, one gives starting and ending ranges for the properties.
Play a 6-second cloud that goes from low to high and soft to loud.

   [(DynamicCloud dur: 6
      pitch: #((30 to: 44) (60 to: 60))   "given starting and ending selection ranges"
      ampl: #((20 to: 40) (90 to: 120))
      voice: #((1) (1))
      density: 12) open]

A selection cloud selects values from the data arrays that are given in the instance creation method.

   [(SelectionCloud dur: 4
      pitch: #(32 40 48 50 52 55 57 )
      ampl: #(80 40 120)
      voice: #(1)
      density: 8) open]

   [(SelectionCloud dur: 4
      pitch: ((NeapolitanMinor root: N do) asPitchesInOctave: 4)
      ampl: #(80 40 120)
      voice: #(1)
      density: 12) open]

By obvious extension, a dynamic selection cloud allows one to specify the start and finish selection sets.
The first example blow plays a selection cloud that makes a transition from one triad to another.

   [(DynamicSelectionCloud dur: 6
      pitch: #( #(48 50 52) #(72 74 76) )   "starting and ending pitch sets"
      ampl: #(60 80 120)
      voice: #(1)
      density: 12) open]

   [ | set1 set2 |
   set1 := ((Oriental root: N do) asPitchesInOctave: 4).
   set2 := ((Oriental root: N sol) asPitchesInOctave: 2).
   (DynamicSelectionCloud dur: 6
      pitch: (Array with: set1 with: set2)
      ampl: #(60 80 120)
      voice: #(1)
      density: 12) open]

   [ | c |
   c := (DynamicSelectionCloud dur: 6
      pitch: #( #(53 50 52) #(72 74 75) )   "starting and ending pitch sets"
      ampl: #(60 80 120)
      voice: #(1)
      density: 12) eventList.

   c addAll: (DynamicSelectionCloud dur: 6
      pitch: #( #(76 78 80) #(60 62 64) )   "starting and ending pitch sets"
      ampl: #(60 80 120)
      voice: #(1)
      density: 16) eventList.
   c open]

The extended DynamicSelectionCloud uses a multi-part pitch set of the format (time -> chord) (time -> chord) ... as in the following example, which creates a list of the tetrachords of the Neapolitan minor scale, then scrambles the list, then transposes every second chord up two octaves, then plays an extended dynnamic selection cloud made from this list.
   
   [ | score chords list |         "generate the tetrads from the selected scale; scramble the order"
   chords := ((NeapolitanMinor root: N do) generateChordsPoly: 4 inOctave: 2) scrambled.
   list := OrderedCollection new.
   1 to: 7 do:
      [ :ind |                              "shift every other one up 2 octaves"
      ind even ifTrue: [list add: ((ind - 1) * 3 -> ((chords at: ind) collect: [ :no | no + 24]))]
         ifFalse: [list add: ((ind - 1) * 3 -> (chords at: ind))]].
   score := (ExtDynamicSelectionCloud dur: 8   "now make a cloud from these"
      pitch: list
      ampl: 60
      voice: nil
      density: 10) eventList.
   score eventsDo: [ :ev |                     "plug in the properties for FM"
      ev inst: '/i1/pn'.
      ev modIndex: 2.0.
      ev ratio: 1.02.
      ev pos: 0.0].
   SirenSession eventList: 'EvGens/dsCloud1' put: score.
   score open]

As an example of a more sophisticated event generator, Mark Lentczner's bell peals ring the changes.

   [(Peal upon: #(60 62 65)) eventList open]

   [ | peal list |
   peal := Peal upon: #(60 62 65 67).
   list := EventList new.
   peal playOn: list durations: 240 meter: 100 at: 0.
"   list voice: #marimba."
   list open]

One can get an event generator's event list and process it using Smalltalk code blocks, as in this example, which takes a simple selection cloud and applies both a crescendo function and a beat pattern to it.

   [ | dur cloud list ramp1 ramp2 pattern start |
   dur := 6.
   cloud := (SelectionCloud
      dur: dur
      pitch: ((NeapolitanMinor root: N do) asPitchesInOctave: 4)
      ampl: #(80)
      voice: #(1)
      density: 10).
   list := cloud eventList.
   SirenSession eventList: 'Examples/NeapCloud' put: list.
   ramp1 := (ExponentialFunction from: #((0 0.2 5) (1 0.8))).   "2 ramps that will be summed"
   ramp2 := (ExponentialFunction from: #((0 0 5) (0.2 0.2))).
   pattern := #(0.1 0.1 0.2 0.2 0.1).                        "beat pattern"
   start := 0.
   1 to: list size do:                                     "loop through the list's events"
      [ :count | | assoc |
      assoc := list events at: count.                        "get the current association"
      assoc key: start sec.                              "reset the start time"
      start := start + (pattern at: (count \\ pattern size) + 1).
      scale := (ramp1 at: (count / list size)) + (ramp2 at: ((count asFloat / list size) mod: ramp2 size)).
      assoc value ampl: (scale * 80) velocity].               "scale the amplitude"
   list open]

Extending EventGenerators

New kinds of event generator can be programmed with constructor and eventList: methods. The framework facilitates the design of fancier behaviors and parameterized event generators.