A DSL for composing MIDI based computational music.
Ruby
Pull request Compare This branch is 148 commits behind troystribling:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin
examples
lib
.document
.gitignore
LICENSE
README.rdoc
Rakefile
VERSION
zgomot.gemspec

README.rdoc

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 Garage Band, Logic or Abelton 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|
    ch << 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, which generate MIDI data streams. In the following details of the object model and supported transformations will be described.

OS X MIDI Device Driver

For OS X the IAC Driver must be enabled for programs to communicate with the digital audio software used to render the MIDI generated by programs. 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. Also, when the when first activated be sure that no other MIDI devices are connected to your computer.

Configuration

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

  • time_signature: Beats per measure.

  • beats_per_minute: To map to real time the beats per minute are specified.

  • 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.

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.

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.

An 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.

Progressions internally are arrays of notes so all methods supported by the Ruby Array class are also supported.

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.

Progressions internally are arrays of notes so all methods supported by the Ruby Array class are also supported.

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. Patterns also support all methods supported by Ruby the Array class.

MIDI Channels

The MIDI Channel is used to specify MIDI device IO. A channel is created with the call,

ch(num)

Where number is the MIDI channel number which must be between 0 and 15. The default value is 0.

The << operator is used to write patterns to the MIDI IO device.

Write a note pattern to MIDI channel 1,

ch(1) << [n([:C,5]), n(:B), n(:R), n(:G), n(:C,:l=>2), n([:E,5],:l=>2)]

Channels are created within stream blocks which are discussed in the next section.

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.

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|
    ch(0) << do_stuff_1(pattern)
end

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

play

Within a str block the following attributes are available,

  • count: Current iteration.

  • patterns: Chronological list of patterns.

Callbacks

  • 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.

Examples

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

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
  ch << 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|
  ch(0) << 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  do |pattern|
  ch(1) << pattern
end

play

Progression with Length and Velocity Defined for each Note

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

str 'prog', 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], :lim=>6 do |pattern|
  ch << pattern.mode!(count)
end

play

Copyright

Copyright © 2009 Troy Stribling. See LICENSE for details.