zgomot

zgomot is a DSL for composing MIDI music. It does not do synthesis so to create sound it requires digital audio software such as Apple’s GarageBand, Logic or Ableton Live. A program that plays a simple tune only requires a few lines of code.

# mytune.rb
require 'rubygems'
require 'zgomot'

# define a tune pattern with 5 notes and a rest
tune = [n([:C,5]), n(:B), n(:R), n(:G), n(:C,:l=>2), n([:E,5],:l=>2)]

# define a MIDI stream writing to channel 0 which plays the pattern 3 times
str 'notes', tune, :lim=>3 do |pattern|
   pattern
end

# write the MIDI stream
play

Now, specify beats per minute, time signature and resolution in zgomot.yml.

time_signature: 4/4
beats_per_minute: 120
resolution: 1/64

Install the gem,

sudo gem install zgomot

Run the program to play the tune,

ruby mytune.rb

A simple object model is defined by zgomot that makes it possible to write iterative transformations on note patterns within str blocks that generate MIDI data streams. In the following details of the object model and supported transformations will be described.

OS X IAC Driver

For OS X the IAC Driver must be enabled for zgomot programs to communicate with the digital audio software used to render the generated MIDI stream. To enable the IAC Driver open Audio MIDI Setup. Under the Window menu item select Show MIDI Window. Find the IAC Driver, double click it and be sure Device is online is selected and at least one port exists.

Supported Platforms

zgomot has been tested on Ruby 1.8.7, 1.9.1, 1.9.2, 1.9.3. The only OS supported is OS X.

Configuration

Three parameters are defined in the configuration file, zgomot.yml or programmatically, that specify the timing of a composition.

  • time_signature: Beats per measure. The default value is 4/4.

  • beats_per_minute: To map to real time the beats per minute are specified. The default value 120.

  • resolution: Defines the length of a clock tick and is defined by the duration of the shortest note that can be played. In the first example this is a 64’th note. The maximum resolution is 1/1024 if your computer can do it. The default value 1/32.

To set the configuration programmatically use,

set_config(:beats_per_minute=>120, :time_signature=>"4/4", :resolution=>"1/64")

Pitch

Pitch is defined by a 2 dimensional array specifying the pitch class and octave, For example [:C, 4] would denote the note C at octave 4. Octave is an integer between -1 and 9 and acceptable values for pitch class with enharmonics, where s denotes a sharp, b a flat, and rest by :R are,

:C,  :Bs;
:Cs, :Db
:D
:Ds, :Ed
:E,  :Fd
:F,  :Es
:Fs, :Gb
:G,
:Gs, :Ab
:A,
:As, :Bb,
:B,  :Cb,
:R,

Notes

A note is defined by,

n(pitch, opts)

Accepted options are,

  • :l: Reciprocal length of note, Accepted values are 1, 2, 4,…, max. Where max is the inverse resolution defined in zgomot.yml. Mapping to standard durations gives; 1 a whole note, 2 a half note, 4 a quarter note, 8 and eighth note, … The default value is 4, a quarter note.

  • :v: The velocity of the note is a number between 0 ad 1 defining its loudness. Low values correspond to piano and larger values forte. The default is 0.6.

An F# half note at octave 5 with velocity 0.5 would be defined by,

n([:Fs, 5], :l => 2, :v => 0.5)

Transforms

Notes support the following transformations,

  • bpm!(bpm): change the bits per minute at which the note is played.

  • octave!(ocatve): change the octave of the note.

Chords

A chord is defined by,

c(root, interval, opts)

Only trichords are supported. Here root is the chord root pitch and interval is the interval type. Accepted values of the interval type are: :maj, :min, :dim, :aug, :sus2, :sus4, representing major, minor, diminished, augmented, suspended second and suspended forth chord intervals respectively. If not specified the default value of interval is :maj.

Accepted options are,

  • :l: Reciprocal length of chord, Accepted values are 1, 2, 4,…, max. Where max is the inverse resolution defined in zgomot.yml. Mapping to standard durations gives; 1 a whole note, 2 a half note, 4 a quarter note, 8 and eighth note, … The default value is 4, a quarter note.

  • :v: The velocity of the note is a number between 0 ad 1 defining its loudness. Low values correspond to piano and larger values forte. The default is 0.6.

An F# half note minor chord at octave 5 with velocity 0.5 would be defined by,

c([:Fs, 5], :min, :l => 2, :v => 0.5)

Transforms

Chords support the following transformations,

  • bpm!(bpm): change the bits per minute at which the chord is played.

  • octave!(ocatve): change the octave of the chord.

  • arp!(length): arpeggiate the chord using the specified length in units of note length. Accepted values are 1, 2, 4, 8, … resolution, representing arpeggiation by a whole note, half note, quarter note, eighth note up to the specified clock resolution.

  • inv!(number): Invert the chord. When 0 the chord is unchanged, 1 is the first inversion and 2 is the second. Higher inversions just shift the chord to a higher octave.

  • rev!: Reverse the order in which the notes are played. Only noticeable if the chord is also arpeggiated.

Percussion

The General MIDI Percussion Map that maps percussion type to MIDI note is supported.

:acoustic_bass_drum => [:B,1],
:bass_drum_1        => [:C,2],  :side_stick     => [:Cs,2], :acoustic_snare => [:D,2],
:hand_clap          => [:Ds,2], :electric_snare => [:E,2],  :low_floor_tom  => [:F,2],
:closed_hi_hat      => [:Fs,2], :high_floor_tom => [:G,2],  :pedal_hi_hat   => [:Gs,2],
:low_tom            => [:A,2],  :open_hi_hat    => [:As,2], :low_mid_tom    => [:B,2],
:high_mid_tom       => [:C,3],  :crash_cymbal_1 => [:Cs,3], :high_tom       => [:D,3],
:ride_cymbal_1      => [:Ds,3], :chinese_cymbal => [:E,3],  :ride_bell      => [:F,3],
:tambourine         => [:Fs,3], :splash_cymbal  => [:G,3],  :cowbell        => [:Gs,3],
:crash_cymbal_2     => [:A,3],  :vibraslap      => [:As,3], :ride_cymbal_2  => [:B,3],
:high_bongo         => [:C,4],  :low_bongo      => [:Cs,4], :mute_hi_conga  => [:D,4],
:open_hi_conga      => [:Ds,4], :low_conga      => [:E,4],  :high_timbale   => [:F,4],
:low_timbale        => [:Fs,4], :high_agogo     => [:G,4],  :low_agogo      => [:Gs,4],
:cabasa             => [:A,4],  :maracas        => [:As,4], :short_whistle  => [:B,4],
:long_whistle       => [:C,5],  :short_guiro    => [:Cs,5], :long_guiro     => [:D,5],
:claves             => [:Ds,5], :hi_woodblock   => [:E,5],  :low_woodblock  => [:F,5],
:mute_cuica         => [:Fs,5], :open_cuica     => [:G,5],  :mute_triangle  => [:Gs,5],
:open_triangle      => [:A,5],
:R                  => :R,

A percussive tone is defined by

pr(perc, opts)

Where perc is the General MIDI Percussion code defined above that has a default value of :acoustic_bass_drum.

Accepted options are,

  • :l: Reciprocal length of note, Accepted values are 1, 2, 4,…, max. Where max is the inverse resolution defined in zgomot.yml. Mapping to standard durations gives: 1 a whole note, 2 a half note, 4 a quarter note, 8 and eighth note, … The default value is 4, a quarter note.

  • :v: The velocity of the note is a number between 0 ad 1 defining its loudness. Low values correspond to piano and larger values forte. The default is 0.6.

A :closed_hi_hat percussive tone of half note length with velocity 0.5 would be defined by,

pr(:closed_hi_hat, :l => 2, :v => 0.5)

Transforms

Percussion supports the following transformations,

  • bpm!(bpm): change the bits per minute at which the note is played.

Note List

A Note List is a set of notes or percussive notes that start playing simultaneously. The Note List may contain any number of notes and its duration is the duration of the longest note in the list. It is defined by,

nl(n1, n2, ..., nN)

where nN is the N'th note in the list.

Transforms

  • shift: Remove and return the first note from the progression.

  • unshift: Add a note to the beginning of the progression.

  • pop: Remove and return the last note from the progression.

  • push: Add a note to the end of the progression.

  • reverse!: Reverse the notes in the progression.

Chord Progressions

Chord Progressions or Roman Numeral Notation permit the definition of a melody that is independent of key. Using Chord progressions it is possible to iteratively shift the key of a specified sequence of chords.

A chord progression consisting of the 7 notes of a specified key in a diatonic mode played sequentially will be defined by,

cp(tonic, mode, opts)

Where tonic is the tonic pitch of the key, mode is one of the 7 diatonic modes: :ionian, :dorian, :phrygian, :lydian, :mixolydian, :aeolian, :locrian or a number between 0 and 6 mapping sequentially onto the these modes.

Accepted options are,

  • :l: Reciprocal length of chord, Accepted values are 1, 2, 4,..., max. Where max is the inverse resolution defined in zgomot.yml. Mapping to standard durations gives; 1 a whole note, 2 a half note, 4 a quarter note, 8 and eighth note, … The default value is 4, a quarter note.

  • :v: The velocity of the note is a number between 0 ad 1 defining its loudness. Low values correspond to piano and larger values forte. The default is 0.6.

A chord progression in a key of F# dorian at octave 5 with notes of half note length and velocity 0.5 would be defined by,

cp([:Fs, 5], :dorian, :l => 2, :v => 0.5)

Transforms

  • tonic!(tonic): Change the tonic pitch of the progression.

  • mode!(mode): Change the mode of the progression

  • [](*args): By default when a progression is created it only consists of one each of the notes in the key played sequentially. Using this transformation it is possible to change the the notes played in the progression. For example cp([:Fs, 5], :dorian)[1,5,5,7] will play the sequence 1, 5, 5, 7 instead of 1, 2, 3, 4, 5, 6, 7.

  • velocity!(v): Change the velocity of all notes in the progression.

  • length!(v): Change the length of all notes in the progression.

  • bpm!(bpm): change the bits per minute at which the chord is played.

  • octave!(ocatve): change the octave of all notes in the progression.

  • arp!(length): arpeggiate the chords in the progression using the specified length in units of note length. Accepted values are 1, 2, 4, 8, … resolution, representing arpeggiation by a whole note, half note, quarter note, eighth note up to the specified clock resolution.

  • inv!(number): The inversion number. A value of zero will leave the chord unchanged, 1 is the first inversion and 2 is the second. Higher inversions just shift the chord to a higher octave.

  • rev!: Reverse the order in which the notes are played. Only noticeable if the chords in the progression are also arpeggiated.

  • shift: Remove and return the first chord from the progression.

  • unshift: Add a chord to the beginning of the progression.

  • pop: Remove and return the last chord from the progression.

  • push: Add a chord to the end of the progression.

  • reverse!: Reverse the chords in the progression.

Note Progressions

Note Progressions are similar to chord progressions but are composed of notes instead of chords. Most of the options and transformation are the same. To define a Note Progression use,

np(tonic, mode, opts)

Where tonic is the tonic pitch of the key, mode is one of the 7 diatonic modes: :ionian, :dorian, :phrygian, :lydian, :mixolydian, :aeolian, :locrian or a number between 0 and 6 mapping sequentially onto the these modes.

Accepted options are,

  • :l: Reciprocal length of note, Accepted values are 1, 2, 4,…, max. Where max is the inverse resolution defined in zgomot.yml. Mapping to standard durations gives; 1 a whole note, 2 a half note, 4 a quarter note, 8 and eighth note, … The default value is 4, a quarter note.

  • :v: The velocity of the note is a number between 0 ad 1 defining its loudness. Low values correspond to piano and larger values forte. The default is 0.6.

An note progression in a key of F# dorian at octave 5 with notes of half note length and velocity 0.5 would be defined by,

np([:Fs, 5], :dorian, :l => 2, :v => 0.5)

Transforms

  • tonic!(tonic): Change the tonic pitch of the progression.

  • mode!(mode): Change the mode of the progression

  • [](*args): By default when a progression is created it only consists of one each of the notes in the key played sequentially using this transformation it is possible to change the the notes played in the progression. For example np([:Fs, 5], :dorian)[1,5,5,7] will play the sequence 1, 5, 5, 7 instead of 1, 2, 3, 4, 5, 6, 7.

  • velocity!(v): Change the velocity of all notes in the progression.

  • length!(v): Change the length of all notes in the progression.

  • bpm!(bpm): Change the bits per minute at which the chord is played.

  • octave!(ocatve): Change the octave of all notes in the progression.

  • shift: Remove and return the first note from the progression.

  • unshift: Add a note to the beginning of the progression.

  • pop: Remove and return the last note from the progression.

  • push: Add a note to the end of the progression.

  • reverse!: Reverse the notes in the progression.

Progression with Defined Length and Velocity by Note

Different durations and velocities for each note in a progression can be defined by by using arrays for the length and velocity options.

cp([:A,4],nil,:l=>[4,4,8,8,4], :v=>[0.6, 0.4, 0.7, 0.6, 0.4])[7,5,3,3,1]

Patterns

Patterns are heterogeneous arrays of notes, chords, Chord Progressions and Note Progressions. Operations applied to the pattern will be delegated to the appropriate elements of the pattern array.

Also, custom transformations can be applied to the items of a pattern using map,

pattern.map do |item|
  transform(item)
end

Streams

A stream is used to define iteration on a pattern and outputs a stream of MIDI data.

str(name, pattern, opt, &blk)

Where name is an identifying string defining, pattern is an initial pattern, which may be nil, and blk is used to define operations on pattern and is yielded pattern.

Accepted options are,

  • :lim: The number of iterations performed by the stream. The default value is infinite.

  • :del: The number beats delayed before the stream begins to play. The default value is 0.

  • :ch: The MIDI channel used for output. The default value is 0.

A program will consist of one or more str calls followed by a play call. Blocks passed to str perform operations on the yielded pattern and write the results to a MIDI channel. On the call to play a thread is spawned for each str which calls the defined blocks the specified number of times.

str 'grovin-1', cp([:C,3],:ionian), :lim=>3 do |pattern|
    do_stuff_1(pattern)
end

str 'grovin-2', cp([:A,5],:dorian), :lim=>3, :ch=>1 do |pattern|
    do_stuff_2(pattern)
end

play

Within a str block the following attributes are available,

  • count: Current iteration.

  • patterns: Chronological list of patterns.

Markov Matrix

The Markov Matrix randomly plays a list of specified patterns with specified probabilities. The size of the matrix is determined by the number of patterns. For each pattern a list transition probabilities must be defined for all other patterns.

Methods

  • add(transition_probs, &blk): Add a pattern to the Markov matrix. Arguments are: transitition_probs a list that defines the transition probabilities between patterns and blk is a block in which the pattern is defined.

  • next: Called within a str block to return the next random pattern.

Code Sample

A simple Markov Matrix with two patterns.

m = mark
m.add([0.6, 0.4]) do
  np([:A,4],:dorian,:l=>4)[7,5,3,1,]
end
m.add([0.4, 0.6]) do
  np([:A,4],:ionian,:l=>4)[7,5,3,1]
end

str 'markov' do
  m.next
end

play

Multiple MIDI Channels

A program can write to multiple MIDI channels with multiple str calls. The following example writes the same melody to two different MIDI channels at different bit rates producing a phasing effect.

str 'melody-1', np([:B,3],nil,:l=>4)[1,4,5,5], :lim=>:inf do |pattern|
  pattern.mode!((count/4) % 7 + 1)
end

str 'melody-2', np([:B,3],:ionian,:l=>4)[1,4,5,5].bpm!(16.0/15.0), :lim=>:inf, :ch=>1  do |pattern|
  pattern
end

play

Chord Note Routing

The notes of a chord and chord progression can be routed to different MIDI channels to be rendered by different instruments using the note(note_number) command. This makes harmonizing instruments easy. The program sample below demonstrates how this is done,

chords = cp([:B,3],:ionian,:l=>4)[1,4,5,5]

str 'note-0', chords.note(0), :ch=>0 do |pattern|
  pattern
end

str 'note-1', chords.note(1), :ch=>1 do |pattern|
  pattern
end

str 'note-2', chords.note(2), :ch=>2 do |pattern|
  pattern
end

run

MIDI Input

A MIDI input device can be used to send messages to programs. Commands to list MIDI sources, add MIDI inputs devices and assign MIDI CC messages to program variables are available.

MIDI Sources

The names of MIDI sources are listed with,

sources

Add Input Device

A MIDI input device is added with,

add_input(name)

where name is the name of the MIDI source returned by the sources command. To remove a MIDI input use,

remove_input(name)

where name is the name of the input. There can be only one MIDI input. Adding another will automatically remove the input added previously.

Input CC Messages

MIDI CC messages can be used to assign values to variables that can be read by programs. To assign a CC message to a variable use,

add_cc(name, cc, options)

Where name is the variable name, ,cc is the MIDI CC identifier, a number between 0 and 255 and options are the following,

  • :max: The maximum value of the CC message. The default is 1.

  • :min: The minimum value of the CC message. The default is 0.

  • :channel: The CC channel number. The default is 1.

  • :type: The CC message type. Accepted values are :cont and :switch. :cont type varies continuously between :max and :min. :switch type is boolean.

  • :init: The initial value of the CC message. The default is 0 if type is :cont and false if type is :switch.

A block can also be passed to add_cc which is yielded the updated cc parameters. For example a CC which pauses a stream would look like,

add_cc('pause', 17, :type => :switch) do
  pause('my_stream')
end

To read a variable assigned to a CC message use,

cc(name, ch)

where name is the variable name and ch is the channel number. The default value of ch is 1.

Shell

Type zgomot to start the shell. The shell uses pry, pryrepl.org, so .pryrc can be used for initialization.

Commands

All commands and objects described previously are available and can be typed directly into the shell or can be loaded from programs you have written. Additionally the following are useful to manage a composition,

  • sources: List the names of all MIDI sources.

  • destinations: List the names of all MIDI destinations.

  • output: Show the name of the current MIDI source assigned as output. This value cannot be changed.

  • input: Show the name of the current MIDI destination assigned as input.

  • lstr: List all loaded stream objects created with the str command.

  • lcc: List all loaded MIDI CC definitions created with add_cc.

  • lconfig: List the current configuration.

  • clk: Show the current time on the global MIDI clock. Displayed format is measure:beat:tick.

  • run name: Play the named stream. If no name is given all paused streams are started. run is an alias for play discussed previously. In the shell run must be used since pry has a play command.

  • pause name: Pause the named stream. If no name is given pause all playing streams.

  • stop name: An alias for pause.

  • tog name: Toggle the status of the named stream. name is required.

  • watch dir: Automatically load any new or updated files in dir. The default value of dir is the current directory.

Dashboard

The dashboard is started by typing dash in the zgomot shell. The dash shows the current configuration and global time and lists the status of all loaded streams and defined CCs. The time, stream and CC status are updated every beat. Also, streams can be started and stopped. When dash is loaded the following control commands are available,

  • q: Quit dash.

  • p: Play all streams.

  • s: Stop all streams.

  • t: Toggle the playing/paused status of selected streams. When t is entered use the up/down keys to traverse the list of streams and type return to select a stream. Multiple streams can be selected. Type t again to toggle the status of the selected streams.

  • d: Delete selected streams. When d is entered use the up/down keys to traverse the list of streams and type return to select a stream. Multiple streams can be selected. Type d again to delete the selected streams.

Callbacks

When running composition programs callbacks are useful. The following are available,

  • before_start: called before application starts.

Logging

By default logging is performed to STDOUT with level Logger::WARN. This can be changed by defining a new logger or specifying a new logger level in before_start. To write DEBUG logger statements while in the shell enter,

Zgomot.logger.level = Logger::DEBUG

to disable logger statements enter,

Zgomot.logger.level = Logger::ERROR

Examples

Many examples can be found at github.com/troystribling/zgomot/tree/master/examples/.

Copyright © 2009 Troy Stribling. See LICENSE for details.