musikov 0.15

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/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: []