musikov 0.15

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012, André de Amorim Fonseca
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ The views and conclusions contained in the software and documentation are those
25
+ of the authors and should not be interpreted as representing official policies,
26
+ either expressed or implied, of the FreeBSD Project.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ ## musikov
2
+
3
+ Random midi generator based on Markov Chains. (Make heavy use of [Midilib](https://github.com/jimm/midilib))
4
+
5
+ The model is quite simple: from a set of songs, a |Note, Duration| graph will be generated where the edges will indicate the probability of transitions between two of these states. A graph will be generated for each instrument on the input set of midifiles.
6
+
7
+ A song is generated randomically from an inital state, picking the subsequent states according the indicated probability.
8
+
9
+ More inform about Markov Chains : http://en.wikipedia.org/wiki/Markov_chain
10
+
11
+ ## Installation
12
+
13
+ ### RubyGems instalation
14
+
15
+ ```bash
16
+ gem install musikov
17
+ ```
18
+
19
+ or, in order to update the gem:
20
+
21
+ ```bash
22
+ gem update musikov
23
+ ```
24
+
25
+ You may need root privileges to install the gem.
26
+
27
+ ## How to use it
28
+
29
+ First launch the musikov passing a midi file, or a folder containg midi files, as the main argument:
30
+
31
+ $ musikov generate -r path-to-midis [-o output_file]
32
+
33
+ The musikov will output a random midi file named output.mid (by default), or the indicated file if the option '-o' is used.
34
+
35
+ ## TODO
36
+
37
+ - Define an option for the duration of the songs by time or by number of notes.
38
+ - Maybe classify different sacles in order to aproximate a generated song to the predominant scale.
39
+
40
+ ## Author
41
+
42
+ Andre Fonseca <andre.amorimfonseca@gmail.com>
43
+
44
+ ## License
45
+
46
+ Simplified BSD
data/bin/musikov ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ #----------------------------------------------------------------------------------------
3
+ # The program takes an initial midi file (or folder with many midis) path
4
+ # from the command line. It will generate a random midi file from the input
5
+ # based on a Markov Chain model.
6
+ #
7
+ # Author:: André Fonseca
8
+ # License:: Simplified BSD
9
+ #----------------------------------------------------------------------------------------
10
+
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
12
+
13
+ require 'rubygems'
14
+ require 'musikov'
15
+ require 'thor'
16
+ require 'midilib/consts'
17
+
18
+ class MusikovStarter < Thor
19
+
20
+ include Thor::Actions
21
+
22
+ desc "generate", "generate random midi from input midi files"
23
+ method_option :resources, :aliases => "-r", :type => :array, :desc => "File or folder list containing the input midi files."
24
+ method_option :output_folder, :aliases => "-o", :type => :string, :desc => "Output folder."
25
+ def generate
26
+ begin
27
+ repository = Musikov::MarkovRepository.new
28
+ say "Learning midi sequence from resources :"
29
+ print_in_columns options[:resources]
30
+ repository.import options[:resources]
31
+ say "\nFiles successfully imported!", Thor::Shell::Color::GREEN
32
+ say "\nGenerating your random midi..."
33
+ generate_midi(repository, options[:output_folder])
34
+ rescue
35
+ say "There was a problem importing the selected files : #{$!.message} => #{$!.backtrace.join("\n")}", Thor::Shell::Color::RED
36
+ end
37
+ end
38
+
39
+ desc "generate_midi", "midi_generation task", :hide => true
40
+ def generate_midi(repository, path)
41
+ say "Select the instruments you would like to include in the output file:"
42
+
43
+ # Shows the instruments options to the user
44
+ opts = {}
45
+ repository.models_by_instrument.each_with_index { |(program_change, hash), index|
46
+ instrument = MIDI::GM_PATCH_NAMES[program_change]
47
+ say "#{index + 1} : #{instrument}"
48
+ opts[index + 1] = program_change
49
+ }
50
+ say "* : all instruments => Default"
51
+
52
+ # Asks for the instrument selection. Non selected instruments will be excluded from the model.
53
+ # By default it will include everything...
54
+ res = ask "\nSelect (ex: 1 2 3):", Thor::Shell::Color::YELLOW
55
+ instruments = []
56
+ res.split.each { |opt|
57
+ if (opt.to_i != 0) then
58
+ instruments << opts[opt.to_i] unless opts[opt.to_i].nil?
59
+ end
60
+ }
61
+
62
+ # Selects all instruments if none was pick
63
+ instruments += opts.values if instruments.empty?
64
+
65
+ # Excluding non selected instruments...
66
+ repository.models_by_instrument.delete_if { |instrument, markov_chain|
67
+ !instruments.include?(instrument) || markov_chain.frequencies.empty?
68
+ }
69
+
70
+ # Picking song from model...
71
+ sequences = {}
72
+ repository.models_by_instrument.each { |instrument, markov_chain|
73
+ sequences[instrument] = markov_chain.generate(nil, 50)
74
+ }
75
+
76
+ # Writing in the output file...
77
+ unless path.nil? then
78
+ repository.export(sequences, path)
79
+ else
80
+ repository.export(sequences)
81
+ end
82
+
83
+ say "\nSuccess: output file generated in output.mid!", Thor::Shell::Color::GREEN
84
+ end
85
+
86
+ end
87
+
88
+ MusikovStarter.start
data/lib/musikov.rb ADDED
@@ -0,0 +1,10 @@
1
+
2
+ module Musikov
3
+
4
+ VERSION = "0.1"
5
+
6
+ end
7
+
8
+ require 'musikov/markov_builder'
9
+ require 'musikov/markov_model'
10
+ require 'musikov/midi_parser'
@@ -0,0 +1,232 @@
1
+ require 'musikov/midi_parser.rb'
2
+ require 'musikov/midi_writer.rb'
3
+ require 'musikov/markov_model.rb'
4
+
5
+ module Musikov
6
+
7
+
8
+ # This class holds the markov chains of each
9
+ # instrument built from the selected midi files.
10
+ class MarkovRepository
11
+
12
+ attr_accessor :models_by_instrument
13
+
14
+ # Initialize the map of markov chain by instrument
15
+ def initialize
16
+ @models_by_instrument = {}
17
+ end
18
+
19
+ # Call parser over indicated paths and trigger the
20
+ # markov chain building process.
21
+ # * the paths can be either folders or files
22
+ # * the file indicated by the path must exist!
23
+ def import(paths = [])
24
+ parser = MidiParser.new(paths)
25
+ sequencies = parser.parse
26
+
27
+ builder = MarkovBuilder.new()
28
+ sequencies.each { |sequency|
29
+ builder.add(sequency)
30
+ }
31
+
32
+ @models_by_instrument = builder.build
33
+ end
34
+
35
+ # Export the generated chains
36
+ def export(sequence_hash = {}, path = ".")
37
+ writer = MidiWriter.new(path)
38
+ writer.write(sequence_hash)
39
+ end
40
+
41
+ end
42
+
43
+ # This class is responsible for building the markov
44
+ # chain map from midi sequences. For every sequence,
45
+ # a "quarter" duration will be extracted and every note
46
+ # duration will be mapped in the possible values on the
47
+ # duration table. From a note and a specifc duration,
48
+ # MidiElements will be created representing a state on the
49
+ # markov chain.
50
+ class MarkovBuilder
51
+
52
+ ####################
53
+ public
54
+ ####################
55
+
56
+ # Initialize the hashes used to build the markov chain of note elements.
57
+ # * The value_chain is a hash containing a list of note events by instrument.
58
+ # * The duration table is a dynamic generated hash (generated from the "quarter" duration value) that maps
59
+ # a the duration of the notes to a string representation.
60
+ def initialize
61
+ @value_chain = Hash.new
62
+ @duration_table = Hash.new("unknow")
63
+ end
64
+
65
+ # Add a midi sequence to the model. From each sequence,
66
+ # different tracks (instruments) will be analyzed. Each
67
+ # sequence has a "tempo" that defines the "quarter" duration in ticks.
68
+ # From the "quarter" duration, creates a duration table containg (almost)
69
+ # every possible note duration.
70
+ # Creates markov chain elements by the value of the played note and the
71
+ # duration string mapped from the duration table.
72
+ def add(sequence)
73
+ # Obtains a quarter duration for this sequence
74
+ quarter_note_length = sequence.note_to_delta('quarter')
75
+
76
+ # Create the duration table based on the sequence's "quarter" value
77
+ create_duration_table(quarter_note_length)
78
+
79
+ # For each instrument on the sequence...
80
+ sequence.each { |track|
81
+ # Program change of the current track
82
+ program_change = nil
83
+
84
+ # Create a list of midi elements for an instrument
85
+ elements = []
86
+
87
+ # Iterates the track event list...
88
+ track.each { |event|
89
+
90
+ # Consider only "NoteOn" events since they represent the start of a note event (avoid duplication with "NoteOff").
91
+ if event.kind_of?(MIDI::NoteOnEvent) then
92
+ # From its correspondent "NoteOff" element, extract the duration of the note event.
93
+ duration = event.off.time_from_start - event.time_from_start + 1
94
+
95
+ # Maps the duration in ticks to a correspondent string on the duration table.
96
+ duration_representation = @duration_table[duration]
97
+
98
+ # In the case that the note do not correspond to anything in the table,
99
+ # we just truncate it to the closest value.
100
+ if duration_representation.nil? or duration_representation == "unknow" then
101
+ new_duration = 0
102
+ # Infinity value simulation
103
+ smallest_difference = 1.0/0
104
+ @duration_table.each { |key, value|
105
+ difference = (duration - key).abs
106
+ if difference < smallest_difference then
107
+ smallest_difference = difference
108
+ new_duration = key
109
+ end
110
+ }
111
+ duration_representation = @duration_table[new_duration]
112
+ end
113
+
114
+ # Create new markov chain state and put into the "elements" list
115
+ elements << MidiElement.new(event.note_to_s, duration_representation)
116
+ elsif event.kind_of?(MIDI::ProgramChange) then
117
+ program_change = event.program
118
+ end
119
+ }
120
+
121
+ if program_change.nil?
122
+ program_change = 1
123
+ end
124
+
125
+ @value_chain[program_change] ||= elements unless elements.empty?
126
+ }
127
+ end
128
+
129
+ # Build the markov chain for each instrument on the input midi file(s)
130
+ def build
131
+ model_by_instrument = {}
132
+ @value_chain.each{ |key, value|
133
+ model_by_instrument[key] = MarkovModel.new(value)
134
+ }
135
+
136
+ return model_by_instrument
137
+ end
138
+
139
+ ####################
140
+ private
141
+ ####################
142
+
143
+ # Creates the duration table from the "quarter" note duration in ticks.
144
+ # * The quarter note duration will change for every track!
145
+ def create_duration_table(quarter_note_length)
146
+ # Fuck it if the song have two dots or a combination of dots and "three"...
147
+ whole = (quarter_note_length * 4)
148
+ dotted_whole = (quarter_note_length * 6)
149
+ whole_triplet = (8 * quarter_note_length / 3)
150
+ half = (quarter_note_length * 2)
151
+ dotted_half = (quarter_note_length * 3)
152
+ half_three = (whole / 3)
153
+ dotted_quarter = (quarter_note_length * 1.5)
154
+ quarter_triplet = (half / 3)
155
+ eight = (quarter_note_length / 2)
156
+ eight_triplet = (quarter_note_length / 3)
157
+ dotted_eight = (3 * quarter_note_length / 4)
158
+ sixteenth = (quarter_note_length / 4)
159
+ sixteenth_triplet = (eight / 3)
160
+ dotted_sixteenth = (3 * quarter_note_length / 8)
161
+ thirty_second = (quarter_note_length / 8)
162
+ thirty_second_triplet = (sixteenth / 3)
163
+ dotted_thirty_second = (3 * quarter_note_length / 16)
164
+ sixty_fourth = (quarter_note_length / 16)
165
+ sixty_fourth_triplet = (thirty_second / 3)
166
+ dotted_sixty_fourth = (3 * quarter_note_length / 32)
167
+
168
+ @duration_table = {
169
+ whole => 'whole',
170
+ dotted_whole => 'dotted whole',
171
+ whole_triplet => 'whole triplet',
172
+ half => 'half',
173
+ dotted_half => 'dotted half',
174
+ half_three => 'half triplet',
175
+ quarter_note_length => 'quarter',
176
+ dotted_quarter => 'dotted quarter',
177
+ quarter_triplet => 'quarter triplet',
178
+ eight => 'eighth',
179
+ dotted_eight => 'dotted eighth',
180
+ eight_triplet => 'eighth triplet',
181
+ sixteenth => 'sixteenth',
182
+ dotted_sixteenth => 'dotted sixteenth',
183
+ sixteenth_triplet => 'sixteenth triplet',
184
+ thirty_second => 'thirtysecond',
185
+ dotted_thirty_second => 'dotted thirtysecond',
186
+ thirty_second_triplet => 'thirtysecond triplet',
187
+ sixty_fourth => 'sixtyfourth',
188
+ dotted_sixty_fourth => 'dotted sixtyfourth',
189
+ sixty_fourth_triplet => 'sixtyfourth triplet'
190
+ }
191
+ end
192
+
193
+ end
194
+
195
+ # This class represents a state on the markov chain. This state
196
+ # is built from a note and a duration string representation.
197
+ class MidiElement
198
+
199
+ attr_accessor :note, :duration
200
+
201
+ # Initializes the midi element by a note string and a string representation of the duration.
202
+ def initialize(note, duration)
203
+ @note = note
204
+ @duration = duration
205
+ end
206
+
207
+ # Overriding comparison to be used on "=="
208
+ def ==(comparison_object)
209
+ comparison_object.equal?(self) ||
210
+ (comparison_object.instance_of?(self.class) &&
211
+ @note == comparison_object.note &&
212
+ @duration == comparison_object.duration)
213
+ end
214
+
215
+ # Overriding comparison to be used on hashing
216
+ def eql?(comparison_object)
217
+ self.class.equal?(comparison_object.class) &&
218
+ @note == comparison_object.note &&
219
+ @duration == comparison_object.duration
220
+ end
221
+
222
+ def hash
223
+ @note.hash ^ @duration.hash
224
+ end
225
+
226
+ def to_s
227
+ "[Note: #{@note} Duration: #{@duration}]"
228
+ end
229
+
230
+ end
231
+
232
+ end
@@ -0,0 +1,99 @@
1
+
2
+ module Musikov
3
+
4
+ # This class represents a generic markov chain. It holds
5
+ # a hash of frequencies of subsequent states, for each state.
6
+ class MarkovModel
7
+
8
+ attr_reader :frequencies
9
+
10
+ ###################
11
+ public
12
+ ###################
13
+
14
+ # Initialize the hashes used to build the markov chain.
15
+ # Passes the initial array of values to be included in the model.
16
+ # * The "transitions" hash maps a state into another hash mapping the subsequent states to its number of subsequent occurrences
17
+ # * The "frequencies" hash maps a state into another hash mapping the subsequent states to a frequency indicating the probability of subsequent occurrences.
18
+ def initialize(value_chain = [])
19
+ @transitions = {}
20
+ @frequencies = {}
21
+ add_input(value_chain)
22
+ end
23
+
24
+ # Generate a random sequence from a markov chain
25
+ # * The initial_value is a state where the random chain must start
26
+ # * The initial_value may be nil
27
+ # * The value_number indicates the number of elements on the result random sequence
28
+ def generate(initial_value, value_number)
29
+ generated_sequence = []
30
+ selected_value = initial_value
31
+
32
+ until generated_sequence.count == value_number do
33
+ selected_value = pick_value(rand(0.1..1.0), selected_value)
34
+ generated_sequence << selected_value
35
+ end
36
+
37
+ return generated_sequence
38
+ end
39
+
40
+ # Passes the argument array of state values to be included in the model.
41
+ def add_input(value_chain = [])
42
+ prev_value = nil
43
+
44
+ value_chain.each { |value|
45
+ add_value(prev_value, value)
46
+ prev_value = value
47
+ }
48
+
49
+ compute_frequencies
50
+ end
51
+
52
+ ###################
53
+ private
54
+ ###################
55
+
56
+ # Add a state on the transitions hash
57
+ def add_value(prev_value, value)
58
+ @transitions[prev_value] ||= Hash.new{0}
59
+ @transitions[prev_value][value] += 1
60
+ end
61
+
62
+ # Pick a value on the frequencies hash based on a random number and the previous state
63
+ def pick_value(random_number, prev_value)
64
+ next_value = @frequencies[prev_value]
65
+ if next_value.nil? then
66
+ next_value = @frequencies[nil]
67
+ end
68
+
69
+ succ_list = next_value.sort_by{|key, value| value}
70
+ freq_counter = 0.0
71
+
72
+ succ_list.each { |succ_value, freq|
73
+ freq_counter += freq
74
+ return succ_value if freq_counter >= random_number
75
+ }
76
+ end
77
+
78
+ # Compute the frequencies hash based on the transistions hash
79
+ def compute_frequencies
80
+ @transitions.map { |value, transition_hash|
81
+ sum = 0.0
82
+ transition_hash.each { |succ_value, occurencies|
83
+ sum += occurencies
84
+ }
85
+
86
+ transition_hash.each { |succ_value, occurencies|
87
+ @frequencies[value] ||= Hash.new{0}
88
+ @frequencies[value][succ_value] = occurencies/sum
89
+ }
90
+ }
91
+ end
92
+
93
+ def to_s
94
+ return @frequencies
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,73 @@
1
+ require 'midilib/sequence'
2
+ require 'midilib/io/seqreader'
3
+
4
+ module Musikov
5
+
6
+ class FileNotFoundError < StandardError ; end
7
+
8
+ # This class is responsible for interacting with MidiLib in order
9
+ # to read the input midi files.
10
+ class MidiParser
11
+
12
+ ####################
13
+ public
14
+ ####################
15
+
16
+ # Initializes the parser using the file (or folder) path parameter
17
+ # * Parameter can be a single file path or a folder
18
+ def initialize(file_or_folder_paths = [])
19
+ @paths = []
20
+ @paths += file_or_folder_paths
21
+ end
22
+
23
+ # Obtains the list of midi files to parse and call the Midilib parse routine
24
+ def parse
25
+ result = []
26
+ files = []
27
+
28
+ @paths.each { |path|
29
+ begin
30
+ raise FileNotFoundError unless File.exists?(path)
31
+
32
+ if File.directory?(path) then
33
+ files += Dir.glob("#{path}/**/*.mid")
34
+ else
35
+ files << path if File.extname(path) == ".mid"
36
+ end
37
+ rescue
38
+ puts "Not a valid file path : #{path} => #{$!}"
39
+ end
40
+ }
41
+
42
+
43
+ if files.empty? then
44
+ puts "No files were added."
45
+ else
46
+ files.each { |file_path|
47
+ result << read_midi(file_path)
48
+ }
49
+ end
50
+
51
+ return result
52
+ end
53
+
54
+ ####################
55
+ private
56
+ ####################
57
+
58
+ # Call the Midilib's parse routine to read information from a midi file
59
+ def read_midi(file_path)
60
+ # Create a new, empty sequence.
61
+ seq = MIDI::Sequence.new()
62
+
63
+ # Read the contents of a MIDI file into the sequence.
64
+ File.open(file_path, 'rb') { | file |
65
+ seq.read(file)
66
+ }
67
+
68
+ return seq
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,67 @@
1
+ require 'midilib/sequence'
2
+ require 'midilib/consts'
3
+ require 'midilib/io/seqwriter'
4
+
5
+ module Musikov
6
+
7
+ # This class is responsible for interacting with MidiLib in order
8
+ # to write the output midi files.
9
+ class MidiWriter
10
+
11
+ ####################
12
+ public
13
+ ####################
14
+
15
+ # Initializes the parser using the output path parameter
16
+ # * Parameter need to be a folder folder
17
+ def initialize(output_path = ".")
18
+ @path = output_path
19
+ end
20
+
21
+ # Writes the output midi file from the generated sequence hash
22
+ def write(sequence_hash)
23
+ # Create a new, empty sequence.
24
+ seq = MIDI::Sequence.new()
25
+
26
+ track = MIDI::Track.new(seq)
27
+ seq.tracks << track
28
+ track.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(120))
29
+ i = 0
30
+
31
+ # Create a track to hold the notes. Add it to the sequence.
32
+ sequence_hash.each { |program_change, midi_elements|
33
+ track = MIDI::Track.new(seq)
34
+ seq.tracks << track
35
+
36
+ instrument = MIDI::GM_PATCH_NAMES[program_change]
37
+
38
+ # Give the track a name and an instrument name (optional).
39
+ track.instrument = instrument
40
+
41
+ # Add a volume controller event (optional).
42
+ track.events << MIDI::Controller.new(i, MIDI::CC_VOLUME, 127)
43
+ track.events << MIDI::ProgramChange.new(i, program_change, 0)
44
+ midi_elements.each { |midi_element|
45
+ track.events << MIDI::NoteOn.new(i, midi_element.note ,127,0)
46
+ track.events << MIDI::NoteOff.new(i, midi_element.note ,127, seq.note_to_delta(midi_element.duration))
47
+ }
48
+
49
+ i += 1
50
+ }
51
+
52
+ write_midi(seq)
53
+ end
54
+
55
+ ####################
56
+ private
57
+ ####################
58
+
59
+ # Call the Midilib's parse routine to read information from a midi file
60
+ def write_midi(seq)
61
+ # Writing output file
62
+ File.open("#{@path}/output.mid", 'wb') { | file | seq.write(file) }
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,27 @@
1
+ require 'rspec'
2
+ require 'musikov/markov_builder'
3
+
4
+ describe Musikov::MidiElement do
5
+
6
+ it "should respect equals based on the 'note' and 'duration' attributes" do
7
+ midi1 = Musikov::MidiElement.new("silence", 64)
8
+ midi2 = Musikov::MidiElement.new("silence", 64)
9
+
10
+ midi1.should == midi2
11
+ end
12
+
13
+ it "should same hash if objects have the same attributes" do
14
+ midi1 = Musikov::MidiElement.new("silence", 64)
15
+ midi2 = Musikov::MidiElement.new("silence", 64)
16
+
17
+ midi1.hash.should == midi2.hash
18
+ end
19
+
20
+ it "should respect eql? equality" do
21
+ midi1 = Musikov::MidiElement.new("silence", 64)
22
+ midi2 = Musikov::MidiElement.new("silence", 64)
23
+
24
+ (midi1.eql? midi2).should == true
25
+ end
26
+
27
+ end
@@ -0,0 +1,34 @@
1
+ require 'rspec'
2
+ require 'musikov/markov_model'
3
+
4
+ describe Musikov::MarkovModel do
5
+
6
+ it "should add values with its correspondent frequence" do
7
+ text = "The man is tall. The man is big."
8
+
9
+ mc = Musikov::MarkovModel.new(text.split)
10
+ mc.frequencies["is"].should == {"big." => 0.5, "tall." => 0.5}
11
+ end
12
+
13
+ it "should pick word corresponding to random number" do
14
+ text = "The man is tall. The man is big."
15
+
16
+ # Obs: testing private method through "send"
17
+ mc = Musikov::MarkovModel.new(text.split)
18
+ wd = mc.send(:pick_value, 0.8, "is")
19
+ wd.should == "tall."
20
+ end
21
+
22
+ it "should re-compute frequencies after inserting more input" do
23
+ text1 = "The man is tall. The man is big."
24
+ text2 = "The man is strange. The man is very strange."
25
+
26
+ mc = Musikov::MarkovModel.new()
27
+ mc.add_input(text1.split)
28
+ mc.frequencies["is"].should == {"big." => 0.5, "tall." => 0.5}
29
+
30
+ mc.add_input(text2.split)
31
+ mc.frequencies["is"].should == {"big." => 0.25, "tall." => 0.25, "strange." => 0.25, "very" => 0.25}
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: musikov
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.15'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andre Fonseca
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-22 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
+ - !ruby/object:Gem::Dependency
31
+ name: thor
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Musikov - Random song generator based on Markov Chains
47
+ email: andre.amorimfonseca@gmail.com
48
+ executables:
49
+ - musikov
50
+ extensions: []
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.md
54
+ files:
55
+ - LICENSE
56
+ - README.md
57
+ - bin/musikov
58
+ - lib/musikov/markov_builder.rb
59
+ - lib/musikov/markov_model.rb
60
+ - lib/musikov/midi_parser.rb
61
+ - lib/musikov/midi_writer.rb
62
+ - lib/musikov.rb
63
+ - spec/markov_model_spec.rb
64
+ - spec/markov_builder_spec.rb
65
+ homepage: https://github.com/andreAmorimF/musikov
66
+ licenses: []
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.24
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Musikov - Random song generator based on Markov Chains
89
+ test_files: []