rhythmruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.project +12 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/examples/Meshuggah_example.rb +91 -0
- data/examples/drum_kit_example.rb +80 -0
- data/examples/single_rhythm_example.rb +51 -0
- data/lib/rhythmruby.rb +5 -0
- data/lib/rhythmruby/MidiWriter.rb +60 -0
- data/lib/rhythmruby/RhythmCompiler.rb +78 -0
- data/lib/rhythmruby/RhythmParser.rb +59 -0
- data/lib/rhythmruby/version.rb +3 -0
- data/rhythmruby.gemspec +20 -0
- metadata +77 -0
data/.gitignore
ADDED
data/.project
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<projectDescription>
|
3
|
+
<name>rhythmruby</name>
|
4
|
+
<comment></comment>
|
5
|
+
<projects>
|
6
|
+
</projects>
|
7
|
+
<buildSpec>
|
8
|
+
</buildSpec>
|
9
|
+
<natures>
|
10
|
+
<nature>com.aptana.ruby.core.rubynature</nature>
|
11
|
+
</natures>
|
12
|
+
</projectDescription>
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Luuk van der Velden
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
RhythmRuby: Midi rhythms through String manipulation
|
2
|
+
====================================================
|
3
|
+
|
4
|
+
**Author**: Luuk van der Velden, (Amsterdam, 2013)
|
5
|
+
**Website**:
|
6
|
+
**Git**: https://github.com/Lvelden/rhythmruby.git
|
7
|
+
|
8
|
+
Synopsis
|
9
|
+
--------
|
10
|
+
|
11
|
+
RhythmRuby enables the creation of midi rhythms, through string manipulation.
|
12
|
+
It provides methods for rhythm/string creation, parsing and writing to midi files.
|
13
|
+
Ruby and MIDI based rhythm delight!
|
14
|
+
|
15
|
+
Basics
|
16
|
+
------
|
17
|
+
|
18
|
+
**installation:** gem install rhythmruby
|
19
|
+
|
20
|
+
**1. rhythm string** is a string consisting of two symbols,
|
21
|
+
one for silence (default '-') and one for an event or hit (default '#'). An example:
|
22
|
+
'#---#---#---#---', is read as one (drum) hit every three silences.
|
23
|
+
|
24
|
+
**2. rhythm snippets** are the building blocks of a rhythm string. The
|
25
|
+
StringCompiler Class allows the creation of rhythm snippets and compiles them into a rhythm string.
|
26
|
+
These snippets capture the repetitive aspects of most rhythms.
|
27
|
+
|
28
|
+
**3. countBase** is the rhythmical length that is assigned to one symbol in a string.
|
29
|
+
It is measured in quartenote lengths (relative to bpm), thus countBase: 1.0, means one quarter note per symbol.
|
30
|
+
|
31
|
+
Classes
|
32
|
+
-------
|
33
|
+
The **RhythmCompiler** (class) generates rhythm snippets and combines these into rhythm strings.
|
34
|
+
These can be parsed to MIDI ready data by the **RhythmParser** (class).
|
35
|
+
The parsed output can be written to a MIDI file by the **MidiWriter** (class instance).
|
36
|
+
|
37
|
+
Examples and Docs
|
38
|
+
-----------------
|
39
|
+
|
40
|
+
Check out the examples provided and the documentation based on YARD. The examples are aimed at
|
41
|
+
creating single and multi instrument rhythms. For instance **Meshuggah_example.rb** explains the creation of the
|
42
|
+
intro rhythm of 'Perpetual Black Second' by Meshuggah (Nothing, 2002), using ruby scripting.
|
43
|
+
|
44
|
+
|
45
|
+
**minimal example**
|
46
|
+
|
47
|
+
In the example below we have skipped the creation of the rhythm string.
|
48
|
+
A predefined rhythm is written to midi.
|
49
|
+
|
50
|
+
require 'rhythmruby'
|
51
|
+
|
52
|
+
midiNote = 50 # midi note assigned to drum hits
|
53
|
+
bpm = 120 # beats per minute of midi file
|
54
|
+
fileName = 'testing.midi' # name of midi file
|
55
|
+
|
56
|
+
countBase = 1.0/4.0 # one symbol represents a sixteenth note (one fourth of a quarter note)
|
57
|
+
|
58
|
+
rhythm = '#---#---' # two quarter note drum hits, on sixteenth note base
|
59
|
+
|
60
|
+
# parse the rhythm string to MIDI ready information (array of [midiNote, noteLength] sub-arrays)
|
61
|
+
midiInfo = RhythmParser.parseRhythm(rhythm, countBase, midiNote)
|
62
|
+
|
63
|
+
midiWriter = MidiWriter.new(bpm) # midiWriter instance administrating one MIDI song
|
64
|
+
midiTrack = midiWriter.createTrack() # create track in the MIDI song
|
65
|
+
midiWriter.writeSeqToTrack(midiInfo, midiTrack) # write the parsed rhythm to a MIDI track
|
66
|
+
midiWriter.writeToFile(fileName) # write MIDI song to file
|
67
|
+
|
68
|
+
Workflow on Linux
|
69
|
+
--------
|
70
|
+
|
71
|
+
On linux the MIDI files created with rhythmruby can be played from the command-line by programs like
|
72
|
+
pmidi. pmidi can send the MIDI events to any ALSA channel f.i. the MIDI-in of the Hydrogen sampler, which
|
73
|
+
is a nice drum sampler and sequencer. In this way you can listen to the rhythms you create without leaving your
|
74
|
+
IDE or editor.
|
75
|
+
|
76
|
+
Rational
|
77
|
+
--------
|
78
|
+
|
79
|
+
RhythmRuby's strength lies in the rhythm string abstraction. It allows easy computer
|
80
|
+
manipulation of rhythm snippets to create rhythm patterns. These can then be
|
81
|
+
written to a midi file, possibly consisting of several tracks (f.i. hihat, snare, kick)
|
82
|
+
|
83
|
+
With RhythmRuby you can capture polyrhythmics at their essence by creating patterns of rhythm snippets.
|
84
|
+
The resulting MIDI files are perfect for further use in DAW's such as (cubase, logic, etc.)
|
85
|
+
|
86
|
+
RhythmRuby was made to create drum rhythms for my band Hologram Earth (www.hologramearth.nl)
|
87
|
+
|
88
|
+
Acknowledgment
|
89
|
+
--------------
|
90
|
+
thanks Jim Menard for **midilib**, a pure ruby MIDI library.
|
91
|
+
https://github.com/jimm/midilib
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# ruby version testing
|
2
|
+
versionInfo = RUBY_VERSION.split('.').map{|num| num.to_i}
|
3
|
+
|
4
|
+
if versionInfo[1]==9
|
5
|
+
require "rhythmruby"
|
6
|
+
elsif versionInfo[1]<=8
|
7
|
+
require "rubygems"
|
8
|
+
require "rhythmruby"
|
9
|
+
end
|
10
|
+
|
11
|
+
# In this example we recreate the initial drum pattern of the song:
|
12
|
+
#'Perpetual Black Second' by Meshuggah, as played on the album Nothing (2002)
|
13
|
+
# hi-hat and snare are quite straightforward, but we will use
|
14
|
+
# RhythmRuby to concisely capture the polyrhythmics in the kick pattern
|
15
|
+
|
16
|
+
fileName = 'Meshuggah.midi'
|
17
|
+
bpm = 140 # beats per minute of midi Song
|
18
|
+
countBase = 1.0/4.0 # countbase in multiples/divisions of the quarternote (1/4 countbase is sixteenth notes, 4 notes per quarter note)
|
19
|
+
|
20
|
+
# define midiNotes for the midi percussion tracks,
|
21
|
+
# these default notes work with the Hydrogen sampler
|
22
|
+
midiNotes = {'kick'=>36, 'snare'=>38, 'hihat'=>42}
|
23
|
+
|
24
|
+
# empty hash to store rhythm parameters
|
25
|
+
stringParam ={'kick'=>{}, 'snare'=>{}, 'hihat'=>{}}
|
26
|
+
|
27
|
+
|
28
|
+
# create rhythm by specifying snippets and snippet patterns
|
29
|
+
|
30
|
+
# basic quarter note beat on the hihat
|
31
|
+
stringParam['hihat']['snippetL'] = [4] # snippet has length of 4 sixteenth notes (see countBase)
|
32
|
+
stringParam['hihat']['eventPos'] = [[0]] # event/hit happens at position 0
|
33
|
+
stringParam['hihat']['idx'] = [0] # only snippet '0' is repeated
|
34
|
+
stringParam['hihat']['nRep'] = [32] # repeat the snippet 32 times (32 quarternotes)
|
35
|
+
stringParam['hihat']['rhythmRep'] = 1 # repeat this rhythm once
|
36
|
+
|
37
|
+
# snare pattern of four quarter note, snare on third quarter note
|
38
|
+
stringParam['snare']['snippetL'] = [4,4] # two snippets of length 4 sixteenth notes
|
39
|
+
stringParam['snare']['eventPos'] = [[], [0]] #. one snippet with hit on first sixteenth note and one empty snippet
|
40
|
+
stringParam['snare']['idx'] = [0,0,1,0] # pattern of snippets (empty, empty, hit, empty)
|
41
|
+
stringParam['snare']['nRep'] = nil # repeat snippets once
|
42
|
+
stringParam['snare']['rhythmRep'] = 8 # repeat complete rhythm 8 times
|
43
|
+
|
44
|
+
|
45
|
+
# interesting kick pattern of 14 sixteenth notes long, it shifts respective to the hi-hat
|
46
|
+
# it can be understood as 7/8 (kick) over 4/4 (snare) polyrhythmic
|
47
|
+
stringParam['kick']['snippetL'] = [14, 2] # main pattern and 2 note filler at end
|
48
|
+
|
49
|
+
# the main 14 sixteenth note snippet looks like this: #--#--#-##-#--
|
50
|
+
stringParam['kick']['eventPos'] = [[0, 3, 6, 8, 9, 11],[0]] # positions at which hits/events occur per snippet
|
51
|
+
stringParam['kick']['idx'] = [0,1] # sequential order of snippets, first repeat main snippet then add filler
|
52
|
+
stringParam['kick']['nRep'] = [9,1] # repeat main snippet 9 times, then play filler pattern once
|
53
|
+
stringParam['kick']['rhythmRep'] = 1 # repeat total rhythm once
|
54
|
+
|
55
|
+
# for each instrument
|
56
|
+
# create rhythm snippet strings and combine them in a rhythm string
|
57
|
+
rhythms = {} # empty hash for storing rhythms per instrument
|
58
|
+
stringParam.each_key do
|
59
|
+
|instr|
|
60
|
+
snippets = RhythmCompiler.createSnippets(stringParam[instr]['snippetL'],stringParam[instr]['eventPos'])
|
61
|
+
# compile rhythm pattern/string
|
62
|
+
rhythms[instr] = RhythmCompiler.createRhythm(snippets, stringParam[instr]['idx'], stringParam[instr]['nRep'], stringParam[instr]['rhythmRep'])
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
# print out the compiled rhythm
|
67
|
+
puts 'rhythm'
|
68
|
+
for instr in ['hihat','snare','kick']
|
69
|
+
puts rhythms[instr]
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# create the midiWriter instance, to write the midi info to a file
|
74
|
+
midiWriter = MidiWriter.new(bpm) # instance creates a midi song with a fixed bpm
|
75
|
+
|
76
|
+
# parse and write rhythm to midi track for each instrument
|
77
|
+
rhythms.each_key do
|
78
|
+
|instr|
|
79
|
+
# parse the string and return the midi info
|
80
|
+
midiSequence = RhythmParser.parseRhythm(rhythms[instr], countBase, midiNotes[instr])
|
81
|
+
|
82
|
+
# create an empty midi track within the midiSong, to write the midi info to
|
83
|
+
midiTrack = midiWriter.createTrack()
|
84
|
+
|
85
|
+
# write the midi info/sequence to the midiTrack
|
86
|
+
midiWriter.writeSeqToTrack(midiSequence, midiTrack)
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
# write the midiSong to a file
|
91
|
+
midiWriter.writeToFile(fileName)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# ruby version testing
|
2
|
+
versionInfo = RUBY_VERSION.split('.').map{|num| num.to_i}
|
3
|
+
|
4
|
+
if versionInfo[1]==9
|
5
|
+
require "rhythmruby"
|
6
|
+
elsif versionInfo[1]<=8
|
7
|
+
require "rubygems"
|
8
|
+
require "rhythmruby"
|
9
|
+
end
|
10
|
+
|
11
|
+
fileName = 'drumtest.midi'
|
12
|
+
bpm = 120 # beats per minute of midi Song
|
13
|
+
countBase = 1.0/4.0 # countbase in multiples/divisions of the quarter note (1/4 countbase is sixteenth notes, 4 notes per quarter note)
|
14
|
+
|
15
|
+
|
16
|
+
midiNotes = {'kick'=>36, 'snare'=>38, 'hihat'=>42}
|
17
|
+
stringParam ={'kick'=>{}, 'snare'=>{}, 'hihat'=>{}}
|
18
|
+
|
19
|
+
|
20
|
+
# parameters for rhythm composition
|
21
|
+
|
22
|
+
# quarter note beat on the hi-hat
|
23
|
+
stringParam['hihat']['snippetL'] = [4] # snippet is 4 siixteenthnotes long (one quarter note)
|
24
|
+
stringParam['hihat']['eventPos'] = [[0]] # hi-hat hit is on the first sixteenth note (the beat)
|
25
|
+
stringParam['hihat']['idx'] = [0] # rhythm is made up out of one snippet
|
26
|
+
stringParam['hihat']['nRep'] = [8] # repeat snippet 30 times for 30 quarter notes
|
27
|
+
stringParam['hihat']['rhythmRep'] = 1 # repeat total rhythm once for 8 beat bar
|
28
|
+
|
29
|
+
|
30
|
+
# snare on two and four
|
31
|
+
stringParam['snare']['snippetL'] = [4,4] # two snippets of 4 sixteenth notes
|
32
|
+
stringParam['snare']['eventPos'] = [[],[0]] # two snippets, one has no hits, one has it on the first sixteenth note
|
33
|
+
stringParam['snare']['idx'] = [0,1] # alternate the two snippets
|
34
|
+
stringParam['snare']['nRep'] = nil # play snippets once before alternating
|
35
|
+
stringParam['snare']['rhythmRep'] = 4 # repeat the two beat rhythm 4 times for 8 beat bar
|
36
|
+
|
37
|
+
# kick on first two eight notes
|
38
|
+
stringParam['kick']['snippetL'] = [4,4] # two snippets of length 4
|
39
|
+
stringParam['kick']['eventPos'] = [[0,2],[]] # kick on first and third sixteenth note and empty snippet
|
40
|
+
stringParam['kick']['idx'] = [0,1] # alternate the snippets
|
41
|
+
stringParam['kick']['nRep'] = nil # play snippets once before alternating
|
42
|
+
stringParam['kick']['rhythmRep'] = 4 # repeat rhythm 4 times for 8 beat bar
|
43
|
+
|
44
|
+
# for each instrument
|
45
|
+
# create rhythm snippet strings and combine them in a rhythm string
|
46
|
+
rhythms = {} # empty hash for storing rhythms per instrument
|
47
|
+
stringParam.each_key do
|
48
|
+
|instr|
|
49
|
+
snippets = RhythmCompiler.createSnippets(stringParam[instr]['snippetL'],stringParam[instr]['eventPos'])
|
50
|
+
# compile rhythm pattern/string
|
51
|
+
rhythms[instr] = RhythmCompiler.createRhythm(snippets, stringParam[instr]['idx'], stringParam[instr]['nRep'], stringParam[instr]['rhythmRep'])
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
# print out the compiled rhythm
|
56
|
+
puts 'rhythm'
|
57
|
+
for instr in ['hihat', 'snare', 'kick']
|
58
|
+
puts rhythms[instr]
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# create the midiWriter instance, to write the midi info to a file
|
63
|
+
midiWriter = MidiWriter.new(bpm) # instance creates a midi song with a fixed bpm
|
64
|
+
|
65
|
+
# parse and write rhythm to midi track for each instrument
|
66
|
+
rhythms.each_key do
|
67
|
+
|instr|
|
68
|
+
# parse the string and return the midi info
|
69
|
+
midiSequence = RhythmParser.parseRhythm(rhythms[instr], countBase, midiNotes[instr])
|
70
|
+
|
71
|
+
# create an empty midi track within the midiSong, to write the midi info to
|
72
|
+
midiTrack = midiWriter.createTrack()
|
73
|
+
|
74
|
+
# write the midi info/sequence to the midiTrack
|
75
|
+
midiWriter.writeSeqToTrack(midiSequence, midiTrack)
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
# write the midiSong to a file
|
80
|
+
midiWriter.writeToFile(fileName)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# ruby version testing
|
2
|
+
versionInfo = RUBY_VERSION.split('.').map{|num| num.to_i}
|
3
|
+
|
4
|
+
if versionInfo[1]==9
|
5
|
+
require "rhythmruby"
|
6
|
+
elsif versionInfo[1]<=8
|
7
|
+
require "rubygems"
|
8
|
+
require "rhythmruby"
|
9
|
+
end
|
10
|
+
|
11
|
+
fileName = 'test.midi'
|
12
|
+
bpm = 140 # beats per minute of midi Song
|
13
|
+
midiNote = 46 # midiNote of events, we are testing with one instrument, so one note
|
14
|
+
countBase = 1.0/4.0 # countbase in multiples/divisions of the quarter note (1/4 countbase is sixteenth notes, 4 notes per quarter note)
|
15
|
+
|
16
|
+
# note 36=kick, 38=snare 46=hi-hat
|
17
|
+
|
18
|
+
# snippet creation parameters
|
19
|
+
snippetLengths = [4] # length per snippet (or length for all snippets, if snippeLength.length == 1)
|
20
|
+
eventPositions = [[0]] # positions per snippet, where events happen (zero-based)
|
21
|
+
|
22
|
+
# pattern creation parameters
|
23
|
+
snippetIdx = [0] # indexes of snippets to be added sequentially to the rhythm string
|
24
|
+
snippetRep = [10] # number of repeats per snippet, if undefined (nil) all snippets are repeated once
|
25
|
+
totalRepeat = 1 # repeat total rhythm once
|
26
|
+
|
27
|
+
# create rhythm snippet strings
|
28
|
+
snippets = RhythmCompiler.createSnippets(snippetLengths, eventPositions)
|
29
|
+
|
30
|
+
puts 'snippets', snippets
|
31
|
+
|
32
|
+
# compile rhythm pattern/string
|
33
|
+
rhythm = RhythmCompiler.createRhythm(snippets, snippetIdx, snippetRep, totalRepeat)
|
34
|
+
|
35
|
+
# print out the compiled rhythm
|
36
|
+
puts 'rhythm', rhythm
|
37
|
+
|
38
|
+
# parse the string and return the midi info
|
39
|
+
midiSequence = RhythmParser.parseRhythm(rhythm, countBase, midiNote)
|
40
|
+
|
41
|
+
# create the midiWriter instance, to write the midi info to a file
|
42
|
+
midiWriter = MidiWriter.new(bpm)
|
43
|
+
|
44
|
+
# create an empty midi track within the midiSong, to write the midi info to
|
45
|
+
midiTrack = midiWriter.createTrack()
|
46
|
+
|
47
|
+
# write the midi info/sequence to the midiTrack
|
48
|
+
midiWriter.writeSeqToTrack(midiSequence, midiTrack)
|
49
|
+
|
50
|
+
# write the midiSong to a file
|
51
|
+
midiWriter.writeToFile(fileName)
|
data/lib/rhythmruby.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# ruby version testing
|
2
|
+
versionInfo = RUBY_VERSION.split('.').map{|num| num.to_i}
|
3
|
+
|
4
|
+
# MIDI interface was build on midilib, by Jim Menard, https://github.com/jimm/midilib
|
5
|
+
if versionInfo[1]==9
|
6
|
+
require "midilib"
|
7
|
+
elsif versionInfo[1]<=8
|
8
|
+
require "rubygems"
|
9
|
+
require "midilib"
|
10
|
+
end
|
11
|
+
|
12
|
+
# writes sequences of noteData (from a RhythmParser class) to midi tracks
|
13
|
+
# a 'MidiWriter' represents one midi Song, which can be written to one file,
|
14
|
+
# the midiSong can contain multiple tracks/noteSequences
|
15
|
+
#
|
16
|
+
# @param [Float] bpm beats per minute of the midi song/file that the tracks are part of
|
17
|
+
# @return [Object] MidiWriteInstance instance of the midi writer.
|
18
|
+
# creator of one midi file with potential multiple tracks
|
19
|
+
class MidiWriter
|
20
|
+
def initialize(bpm)
|
21
|
+
@song = MIDI::Sequence.new() # this is a midi song/sequence
|
22
|
+
@bpm = bpm # beats per minute of the midi song/sequence
|
23
|
+
end
|
24
|
+
|
25
|
+
# creates a track within the midi song of current MidiWriter instance
|
26
|
+
# @return [MIDI::Track] newTrack a new track within the midi song
|
27
|
+
def createTrack()
|
28
|
+
@song.tracks << (newTrack = MIDI::Track.new(@song))
|
29
|
+
newTrack.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(@bpm))
|
30
|
+
newTrack.events << MIDI::ProgramChange.new(0, 0)
|
31
|
+
return newTrack
|
32
|
+
end
|
33
|
+
|
34
|
+
# writes a note sequence generate by a RhythmParser to a midi track
|
35
|
+
# @param [Array<Array>] midiSeq an array of [midiNote, noteLength] (created by RhythmParser)
|
36
|
+
# @param [MIDI:Track] midiTrack target track, generated with 'createTrack'.
|
37
|
+
# if a track is reused the event sequence will be appended
|
38
|
+
def writeSeqToTrack(midiSeq, midiTrack)
|
39
|
+
midiSeq.each do
|
40
|
+
|midiNote, noteLength|
|
41
|
+
writeNote(midiNote, noteLength, midiTrack)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# writes one midiNote to a midiTrack
|
46
|
+
# @param [Fixnum] midiNote of the event to be written to a midiTrack
|
47
|
+
# @param [Float] noteLength length of the midiEvent, in multiples of the quarternote
|
48
|
+
# @param [MIDI::Track] midiTrack where the event is written to
|
49
|
+
def writeNote(midiNote, noteLength, midiTrack)
|
50
|
+
midiTrack.events << MIDI::NoteOnEvent.new(0, midiNote, 127, 0)
|
51
|
+
midiTrack.events << MIDI::NoteOffEvent.new(0, midiNote, 127, \
|
52
|
+
@song.length_to_delta(noteLength))
|
53
|
+
end
|
54
|
+
|
55
|
+
# write the midiSong to file consisting of all the created midiTracks
|
56
|
+
# @param [String] fileName where the midiSong is written to, can be a path
|
57
|
+
def writeToFile(fileName)
|
58
|
+
open(fileName, 'w') {|f| @song.write(f) }
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
# rhythm composition class (use as a class)
|
3
|
+
# compiles rhythm snippets into a rhythm string/pattern
|
4
|
+
class RhythmCompiler
|
5
|
+
@@silenceMarker = '-' # symbol for silence
|
6
|
+
@@eventMarker = '#' # symbol for an event / hit
|
7
|
+
|
8
|
+
# create an array of snippets of length in snippetLengths,
|
9
|
+
# with events at position(s) in eventPositions
|
10
|
+
# @param [Array<Fixnum>] snippetLengths length of each snippet
|
11
|
+
# (if snippetLengths.length == 1, length is constant for all snippets )
|
12
|
+
# @param [Array<Array>] eventPositions containing arrays of event positions per snippet
|
13
|
+
# (if eventPositions.length == 1, then event positions are constant for all snippets)
|
14
|
+
# @return [Array<String>] snippets array of string rhythm snippets,
|
15
|
+
# for use with 'createString' method
|
16
|
+
def self.createSnippets(snippetLengths, eventPositions)
|
17
|
+
snippets = [] # empty array to append the snippets
|
18
|
+
|
19
|
+
# make input arrays suitable for fur
|
20
|
+
if snippetLengths.length == 1
|
21
|
+
# make snippetLengths as long as eventPositions
|
22
|
+
snippetLengths *= eventPositions.length
|
23
|
+
|
24
|
+
elsif eventPositions.length == 1
|
25
|
+
# make eventPositions as long as snippetLengths
|
26
|
+
eventPositions *= snippetLengths.length
|
27
|
+
|
28
|
+
else # both input arrays have more than one element and should be the same length
|
29
|
+
if not(eventPositions.length == snippetLengths.length)
|
30
|
+
raise ArgumentError, 'lengths of both arrays should be the same, \
|
31
|
+
or one should have length 1'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#create a rhythm snippet for each set of event positions and snippetlength
|
36
|
+
snippetLengths.zip(eventPositions).each do
|
37
|
+
|length, positions| # unpack the length and position information
|
38
|
+
# create a snippet of defined length consisting of silences
|
39
|
+
snippet = @@silenceMarker*length
|
40
|
+
|
41
|
+
positions.each{|pos| # for each provided event positon of the snippet
|
42
|
+
snippet[pos] = @@eventMarker # add an event marker at the position
|
43
|
+
}
|
44
|
+
|
45
|
+
snippets<<snippet # add the snippet to the returned snippets array
|
46
|
+
end
|
47
|
+
return snippets # return the array with the snippets
|
48
|
+
end
|
49
|
+
|
50
|
+
# creates a rhythm string from rhythm snippets
|
51
|
+
# @param [Array<String>] snippets array of rhythm snippets (from 'createSnippets)
|
52
|
+
# @param [Array<Fixnum>] snippetIDx indexes, defining sequential order of snippets
|
53
|
+
# @param [Array<Fixnum>] snippetRep defining how often snippets are repeated...
|
54
|
+
# if nil, all snippets are repeated once, if snippetRep.length==1, all snippets are repeated the same amount
|
55
|
+
# otherwise snippetRep should be the same length as snippetIdx and define repetition per snippet
|
56
|
+
# @param [Fixnum] totalRepeat, number of times the total rhythm is repeated
|
57
|
+
# @return [String] rhythmString, symbolic rhythm string
|
58
|
+
def self.createRhythm(snippets, snippetIDx, snippetRep, totalRepeat)
|
59
|
+
if snippetRep == nil # test whether snippetRep is defined, if not all snippets are repeated once
|
60
|
+
snippetRep = [1]*snippetIDx.length # make snippetRep and snippetIDx the same length for iteration
|
61
|
+
end
|
62
|
+
if totalRepeat == nil # if undefined set repeats equal to 1
|
63
|
+
totalRepeat = 1
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
rhythmString = "" # empty string where all the snippets will be added to
|
68
|
+
|
69
|
+
# iterate over combinations of snippetID and repeats
|
70
|
+
snippetIDx.zip(snippetRep).each do
|
71
|
+
|snippetID, repeats|
|
72
|
+
# add the snippet with snippetID, repeat times, to the rhythm string
|
73
|
+
rhythmString += snippets[snippetID]*repeats
|
74
|
+
end
|
75
|
+
|
76
|
+
return rhythmString*totalRepeat # return the rhythm string repeated totalRepeat times, ready for parsing
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
# Parser (use as class) of the rhythm strings, consisting event and silence symbols
|
3
|
+
class RhythmParser
|
4
|
+
@@silenceNote = 1 # pitch of a note played at during initial silence in a sequence
|
5
|
+
@@silenceMark = '-' # symbol identified as a silence
|
6
|
+
@@eventMark = '#' # symbol identified as an event
|
7
|
+
@@eventNote = 50 # (unused default) midi note of an event
|
8
|
+
@@countBase = 1.0/4.0 # (unused default) time duration of one symbol in quarter note lengths
|
9
|
+
|
10
|
+
# parses the rhythm string and generates midi info ready for MidiWriter
|
11
|
+
# side note: a string starting with a silence needs an initial 'silence note' (note value: @@silenceNote)
|
12
|
+
# @param [String] rhythmString sequence of silence and event markers
|
13
|
+
# @param [Float] countBase rhyhtmic length of one symbol (in quarternote units)
|
14
|
+
# @param [Fixnum] midiNote note assigned to events in the parsed output
|
15
|
+
# @return [Array<Array>] midiSequence array of [midiNote, noteLength]
|
16
|
+
# can be written to midi with midiWriter.writeSegToTrack
|
17
|
+
def self.parseRhythm(rhythmString, countBase, midiNote)
|
18
|
+
@@countBase = countBase # time duration of one symbol (quarternote lengths)
|
19
|
+
@@eventNote = midiNote # midi note assigned to events
|
20
|
+
|
21
|
+
midiSequence = [] # empty array to store parsed midi data
|
22
|
+
curNote = nil # initialize current midi note
|
23
|
+
curLen = nil # initialze current midi note length
|
24
|
+
first = true # state variable testing for first / sequential events
|
25
|
+
|
26
|
+
rhythmString.each_char do
|
27
|
+
|symbol| # symbol in the string either a silence or an event
|
28
|
+
|
29
|
+
if first # if true this is the first parsed symbol
|
30
|
+
first = false # skip on next symbol
|
31
|
+
|
32
|
+
# if first symbol is an event (handles when the rhythm starts with a silence)
|
33
|
+
if symbol == @@eventMark
|
34
|
+
curNote = @@eventNote
|
35
|
+
curLen = 1
|
36
|
+
|
37
|
+
elsif symbol == @@silenceMark
|
38
|
+
curNote = @@silenceNote
|
39
|
+
curLen = 1
|
40
|
+
|
41
|
+
end
|
42
|
+
else # when first symbol has already been parsed
|
43
|
+
if symbol == @@eventMark # an event is parsed, write previous event and generate new one
|
44
|
+
# add midi event to midiSequence (note length in countbase units)
|
45
|
+
midiSequence << [curNote,(curLen*@@countBase)]
|
46
|
+
curNote = @@eventNote # generate new event
|
47
|
+
curLen = 1 # reset note length to 1
|
48
|
+
|
49
|
+
elsif symbol == @@silenceMark # a silence is parsed
|
50
|
+
curLen += 1 # add one countbase note length to the current note
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return midiSequence
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/rhythmruby.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rhythmruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "rhythmruby"
|
8
|
+
gem.version = Rhythmruby::VERSION
|
9
|
+
gem.authors = ["Luuk van der Velden"]
|
10
|
+
gem.email = ["l.j.j.vandervelden@gmail.com"]
|
11
|
+
gem.description = %q{represent rhythms as symbolic Strings and write them to MIDI}
|
12
|
+
gem.summary = %q{represent rhythms as symbolic Strings and write them to MIDI}
|
13
|
+
gem.homepage = "https://github.com/Lvelden/rhythmruby"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_dependency 'midilib'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rhythmruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Luuk van der Velden
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: midilib
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: represent rhythms as symbolic Strings and write them to MIDI
|
31
|
+
email:
|
32
|
+
- l.j.j.vandervelden@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- .project
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- examples/Meshuggah_example.rb
|
44
|
+
- examples/drum_kit_example.rb
|
45
|
+
- examples/single_rhythm_example.rb
|
46
|
+
- lib/rhythmruby.rb
|
47
|
+
- lib/rhythmruby/MidiWriter.rb
|
48
|
+
- lib/rhythmruby/RhythmCompiler.rb
|
49
|
+
- lib/rhythmruby/RhythmParser.rb
|
50
|
+
- lib/rhythmruby/version.rb
|
51
|
+
- rhythmruby.gemspec
|
52
|
+
homepage: https://github.com/Lvelden/rhythmruby
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: represent rhythms as symbolic Strings and write them to MIDI
|
76
|
+
test_files: []
|
77
|
+
has_rdoc:
|