Representing Data as MIDI Music

Problem

You want to represent a series of data points as a musical piece, or just create music algorithmically.

Solution

Jim Menards midilib library makes it easy to generate MIDI music files from Ruby. Its available as the midilib gem.

Heres a simple method for visualizing a list of numbers as a piano piece. The largest number in the list is mapped to the highest note on the piano keyboard (MIDI note 108), and the smallest number to the lowest note (MIDI note 21).

	require 
ubygems
	require midilib # => false

	class Array
	 def to_midi(file, note_length=eighth)

	 midi_max = 108.0
	 midi_min = 21.0

	 low, high = min, max
	 song = MIDI::Sequence.new

	 # Create a new track to hold the melody, running at 120 beats per minute.
	 song.tracks << (melody = MIDI::Track.new(song))
	 melody.events << 
MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(120))

	 # Tell channel zero to use the "piano" sound.
	 melody.events << MIDI::ProgramChange.new(0, 0)

	 # Create a series of note events that play on channel zero.
	 each do |number|
	 midi_note = (midi_min + ((number-midi_min) * (midi_max-low)/high)).to_i
	 melody.events << MIDI::NoteOnEvent.new(0, midi_note, 127, 0)
	 melody.events << MIDI::NoteOffEvent.new(0, midi_note, 127,
	 song.note_to_delta(note_length))
	 end

	 open(file, w) { |f| song.write(f) }
	 end
	end

Now you can get an audible representation of any list of numbers:

	((1..100).collect { |x| x ** 2 }).to_midi(squares.mid)

Discussion

The midilib library provides a set of classes for modeling a MIDI file: you can parse a MIDI file, modify it with Ruby code, and write it back to disk.

A MIDI file is modeled by a Sequence object, which contains track objects. A track is a mainly a series of Event objects: for instance, each note in the piece has a NoteOnEvent and a NoteOffEvent.

Array#to_midi works by transforming each number in the array into a corresponding MIDI note. A standard piano keyboard can produce notes ranging from MIDI note 21 to MIDI note 108, with middle C being at MIDI note 60. Array#to_midi scales the values of the array to fit into this range as closely as possible, using the same formula youd use to convert between two temperature scales.

Working directly with the MIDI classes is difficult, especially if you want to compose music instead of just transfering a data stream into MIDI note events. Heres a subclass of MIDI::Track that provides some simplifying assumptions and some higher-level musical functions, making it easy to compose simple multitrack tunes. Each TimedTrack uses its own MIDI channel and makes sounds from only one instrument. A TimedTrack can sound chords (this is very difficult with stock midilib), and instead of having to remember the MIDI note range, you can refer to notes in terms of half-steps away from middle C.

	class TimedTrack < MIDI::Track
	 MIDDLE_C = 60
	 @@channel_counter=0

	 def initialize(number, song)
	 super(number)
	 @sequence = song
	 @time = 0
	 @channel = @@channel_counter
	 @@channel_counter += 1
	 end

	 # Tell this tracks channel to use the given instrument, and
	 # also set the tracks instrument display name.
	 def instrument=(instrument)
	 @events << 
MIDI::ProgramChange.new(@channel, instrument)
	 super(MIDI::GM_PATCH_NAMES[instrument])
	 end

	 # Add one or more notes to sound simultaneously. Increments the per-track
	 # timer so that subsequent notes will sound after this one finishes.
	 def add_notes(offsets, velocity=127, duration=quarter)
	 offsets = [offsets] unless offsets.respond_to? :each
	 offsets.each do |offset|
	 event(MIDI::NoteOnEvent.new(@channel, MIDDLE_C + offset, velocity))
	 end
	 @time += @sequence.note_to_delta(duration)
	 offsets.each do |offset|
	 event(MIDI::NoteOffEvent.new(@channel, MIDDLE_C + offset, velocity))
	 end
	 recalc_delta_from_times
	 end

	 # Uses add_notes to sound a chord (a major triad in root position), using the
	 # given note as the low note. Like add_notes, increments the per-track timer.
	 def add_major_triad(low_note, velocity=127, duration=quarter)
	 add_notes([0, 4, 7].collect { |x| x + low_note }, velocity, duration)
	 end

	 private

	 def event(event)
	 @events << event
	 event.time_from_start = @time
	 end
	end

Heres a script to write a randomly generated composition with two tracks. The melody track (a trumpet)takes a random walk around the musical scale, and the harmony track (an organ) plays a matching chord at the beginning of each measure.

	song = MIDI::Sequence.new
	song.tracks << (melody = TimedTrack.new(0, song))
	song.tracks << (background = TimedTrack.new(1, song))

	melody.instrument = 56 # Trumpet
	background.instrument = 19 # Church organ

	melody.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(120))
	melody.events << MIDI::MetaEvent.new(MIDI::META_SEQ_NAME,
	 A random Ruby composition)

	# Some musically pleasing intervals: thirds and fifths.
	intervals = [-5, -1, 0, 4, 7]

	# Start at middle C.
	note = 0
	# Create 8 measures of music in 4/4 time
	(8*4).times do |i|
	 note += intervals[rand(intervals.size)]

	 #Reset to middle C if we go out of the MIDI range
	 note = 0 if note < -39 or note > 48

	 # Add a quarter note on every beat.
	 melody.add_notes(note, 127, quarter)

	 # Add a chord of whole notes at the beginning of each measure.
	 background.add_major_triad(note, 50, whole) if i % 4 == 0
	end

	open(
andom.mid, w) { |f| song.write(f) }

See Also

  • midilib has a comprehensive set of RDoc, available online at http://midilib.rubyforge.org/
  • The librarys examples/ directory has several good programs that demonstrate how to create and "play" MIDI files
  • The TimedTrack class presented takes several ideas from Emanuel Borsbooms Midi Scripter application; the Midi Scripter generates MIDI files from Ruby code that incorporates musical notationits not really designed for use as a library, but it would make a good one (http://www.epiphyte.ca/downloads/midi_scripter/README.html)
  • The names of the standard MIDI instrument and drum sounds are kept in the arrays MIDI::GM_PATCH_NAMES and MIDI::GM_DRUM_NOTE_NAMES; this isn as useful as it could be, because youll usually end up referring to instruments by their numeric IDs; the Wikipedia has a good mapping of numbers to names (http://en.wikipedia.org/wiki/General_MIDI#Program_change_events)


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net