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.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.midi
19
+ *~
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem rhythmruby
@@ -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.
@@ -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
@@ -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)
@@ -0,0 +1,5 @@
1
+ # main file to load seperate class files
2
+
3
+ require "rhythmruby/RhythmParser"
4
+ require "rhythmruby/RhythmCompiler"
5
+ require "rhythmruby/MidiWriter"
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module Rhythmruby
2
+ VERSION = "0.1.1"
3
+ end
@@ -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: