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.
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.
zgomot has been tested on Ruby 2.1.0, 2.1.1 and 2.2.1 running on OS X Yosemite.
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 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,
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 inzgomot.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)
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.
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 inzgomot.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)
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.
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)
Percussion supports the following transformations,
-
bpm!(bpm)
: change the bits per minute at which the note is played.
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.
-
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 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 are1, 2, 4,..., max
. Where max is the inverse resolution defined inzgomot.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)
-
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 examplecp([:Fs, 5], :dorian)[1,5,5,7]
will play the sequence1, 5, 5, 7
instead of1, 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 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 inzgomot.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)
-
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 examplenp([: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.
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 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
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.
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.
-
add(transition_probs, &blk)
: Add a pattern to the Markov matrix. Arguments are:transitition_probs
a list that defines the transition probabilities between patterns andblk
is a block in which the pattern is defined. -
next
: Called within astr
block to return the next random pattern.
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
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
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
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.
The names of MIDI sources are listed with,
sources
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.
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
andfalse
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.
Type zgomot
to start the shell. The shell uses pry
, pryrepl.org, so .pryrc
can be used for initialization.
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 thestr
command. -
lcc
: List all loaded MIDI CC definitions created withadd_cc
. -
lconfig
: List the current configuration. -
clk
: Show the current time on the global MIDI clock. Displayed format ismeasure:beat:tick
. -
run name
: Play the named stream. If no name is given allpaused
streams are started.run
is an alias forplay
discussed previously. In the shellrun
must be used sincepry
has aplay
command. -
pause name
: Pause the named stream. If no name is given pause allplaying
streams. -
stop name
: An alias forpause
. -
tog name
: Toggle the status of the named stream.name
is required. -
watch dir
: Automatically load any new or updated files indir
. The default value ofdir
is the current directory.
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
: Quitdash
. -
p
: Play all streams. -
s
: Stop all streams. -
t
: Toggle theplaying/paused
status of selected streams. Whent
is entered use theup/down
keys to traverse the list of streams and typereturn
to select a stream. Multiple streams can be selected. Typet
again to toggle the status of the selected streams. -
d
: Delete selected streams. Whend
is entered use theup/down
keys to traverse the list of streams and typereturn
to select a stream. Multiple streams can be selected. Typed
again to delete the selected streams.
When running composition programs callbacks are useful. The following are available,
-
before_start
: called before application starts.
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
Many examples can be found at github.com/troystribling/zgomot/tree/master/examples/.
Copyright © 2009 Troy Stribling. See LICENSE for details.