rhythmruby 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|