musicality 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +8 -1
- data/bin/midify +3 -4
- data/examples/composition/auto_counterpoint.rb +53 -0
- data/examples/composition/part_generator.rb +51 -0
- data/examples/composition/scale_exercise.rb +41 -0
- data/examples/{hip.rb → notation/hip.rb} +1 -1
- data/examples/{missed_connection.rb → notation/missed_connection.rb} +1 -1
- data/examples/{song1.rb → notation/song1.rb} +1 -1
- data/examples/{song2.rb → notation/song2.rb} +1 -1
- data/lib/musicality.rb +34 -4
- data/lib/musicality/composition/generation/counterpoint_generator.rb +153 -0
- data/lib/musicality/composition/generation/random_rhythm_generator.rb +39 -0
- data/lib/musicality/composition/model/pitch_class.rb +33 -0
- data/lib/musicality/composition/model/pitch_classes.rb +22 -0
- data/lib/musicality/composition/model/scale.rb +34 -0
- data/lib/musicality/composition/model/scale_class.rb +37 -0
- data/lib/musicality/composition/model/scale_classes.rb +91 -0
- data/lib/musicality/composition/note_generation.rb +31 -0
- data/lib/musicality/composition/transposition.rb +8 -0
- data/lib/musicality/composition/util/adding_sequence.rb +24 -0
- data/lib/musicality/composition/util/biinfinite_sequence.rb +130 -0
- data/lib/musicality/composition/util/compound_sequence.rb +44 -0
- data/lib/musicality/composition/util/probabilities.rb +20 -0
- data/lib/musicality/composition/util/random_sampler.rb +26 -0
- data/lib/musicality/composition/util/repeating_sequence.rb +24 -0
- data/lib/musicality/errors.rb +2 -0
- data/lib/musicality/notation/conversion/score_conversion.rb +1 -1
- data/lib/musicality/notation/conversion/score_converter.rb +3 -3
- data/lib/musicality/notation/model/link.rb +26 -24
- data/lib/musicality/notation/model/links.rb +11 -0
- data/lib/musicality/notation/model/note.rb +14 -15
- data/lib/musicality/notation/model/part.rb +3 -3
- data/lib/musicality/notation/model/pitch.rb +8 -0
- data/lib/musicality/notation/model/score.rb +70 -44
- data/lib/musicality/notation/model/symbols.rb +22 -0
- data/lib/musicality/notation/packing/score_packing.rb +2 -3
- data/lib/musicality/notation/parsing/articulation_parsing.rb +4 -4
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +2 -2
- data/lib/musicality/notation/parsing/link_nodes.rb +2 -14
- data/lib/musicality/notation/parsing/link_parsing.rb +9 -107
- data/lib/musicality/notation/parsing/link_parsing.treetop +4 -12
- data/lib/musicality/notation/parsing/note_node.rb +23 -21
- data/lib/musicality/notation/parsing/note_parsing.rb +70 -70
- data/lib/musicality/notation/parsing/note_parsing.treetop +6 -3
- data/lib/musicality/notation/parsing/pitch_node.rb +4 -2
- data/lib/musicality/performance/conversion/score_collator.rb +3 -3
- data/lib/musicality/performance/midi/midi_util.rb +13 -6
- data/lib/musicality/performance/midi/score_sequencing.rb +17 -0
- data/lib/musicality/printing/lilypond/errors.rb +5 -0
- data/lib/musicality/printing/lilypond/meter_engraving.rb +11 -0
- data/lib/musicality/printing/lilypond/note_engraving.rb +53 -0
- data/lib/musicality/printing/lilypond/part_engraver.rb +12 -0
- data/lib/musicality/printing/lilypond/pitch_engraving.rb +30 -0
- data/lib/musicality/printing/lilypond/score_engraver.rb +78 -0
- data/lib/musicality/version.rb +1 -1
- data/spec/composition/generation/random_rhythm_generator_spec.rb +50 -0
- data/spec/composition/model/pitch_class_spec.rb +75 -0
- data/spec/composition/model/pitch_classes_spec.rb +24 -0
- data/spec/composition/model/scale_class_spec.rb +98 -0
- data/spec/composition/model/scale_spec.rb +110 -0
- data/spec/composition/note_generation_spec.rb +113 -0
- data/spec/composition/transposition_spec.rb +17 -0
- data/spec/composition/util/adding_sequence_spec.rb +176 -0
- data/spec/composition/util/compound_sequence_spec.rb +50 -0
- data/spec/composition/util/probabilities_spec.rb +39 -0
- data/spec/composition/util/random_sampler_spec.rb +47 -0
- data/spec/composition/util/repeating_sequence_spec.rb +151 -0
- data/spec/notation/conversion/score_conversion_spec.rb +3 -3
- data/spec/notation/conversion/score_converter_spec.rb +24 -24
- data/spec/notation/model/link_spec.rb +27 -25
- data/spec/notation/model/note_spec.rb +9 -6
- data/spec/notation/model/pitch_spec.rb +24 -1
- data/spec/notation/model/score_spec.rb +57 -16
- data/spec/notation/packing/score_packing_spec.rb +134 -206
- data/spec/notation/parsing/articulation_parsing_spec.rb +1 -8
- data/spec/notation/parsing/convenience_methods_spec.rb +1 -1
- data/spec/notation/parsing/link_nodes_spec.rb +3 -4
- data/spec/notation/parsing/link_parsing_spec.rb +10 -4
- data/spec/notation/parsing/note_node_spec.rb +8 -7
- data/spec/notation/parsing/note_parsing_spec.rb +9 -12
- data/spec/performance/conversion/score_collator_spec.rb +14 -14
- data/spec/performance/midi/midi_util_spec.rb +26 -0
- data/spec/performance/midi/score_sequencer_spec.rb +1 -1
- metadata +57 -12
- data/lib/musicality/notation/model/program.rb +0 -53
- data/lib/musicality/notation/packing/program_packing.rb +0 -16
- data/spec/notation/model/program_spec.rb +0 -50
- data/spec/notation/packing/program_packing_spec.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f671084b7ea13c0ac038a4b52cfcdfa51aeb76e
|
4
|
+
data.tar.gz: 19e843c3f803fc34fcd3520dbbdea8bf100102f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 592b30da2943856712ddda8b78b0710d7311ef3ae9f2a9f8afad5b37a586ff4dc0227de98431b01da740b1b20d82956eaed0fe3cbfa2ddf47e7eaeba52bf968f
|
7
|
+
data.tar.gz: 38a0389ee700b706a3b384e4a8702b9e1743ccf36a98090c7046c3c4e9dfc731b3ff9de946e2adcb856318d15686e95e5b75d9f9814b07fcfb06c52364683540
|
data/ChangeLog.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
### 0.
|
1
|
+
### 0.4.0 / 2014-12-02
|
2
|
+
* Make Program inherit from Array.
|
3
|
+
* Make Program#initialize take either a single array or variable number of args.
|
4
|
+
|
5
|
+
### 0.3.1 / 2014-12-01
|
6
|
+
* Add (optional) argument to Score::Measured#measures_long, to convert a specific note duration to measure duration. If not specified, the note duration of the longest part is used (as before).
|
7
|
+
|
8
|
+
### 0.3.0 / 2014-12-01
|
2
9
|
* Conversion of both tempo-based scores (measured and unmeasured) directly to time-based score
|
3
10
|
* Trimming of gradual changes, useful in collating scores
|
4
11
|
* Refactoring of ValueComputer using Function and Transition utility classes
|
data/bin/midify
CHANGED
@@ -7,13 +7,11 @@ Loads a musicality score from YAML file, and converts it to a MIDI file.
|
|
7
7
|
|
8
8
|
Usage:
|
9
9
|
#{exe_name} <input> [PART PROGRAM] ... [options]
|
10
|
-
#{exe_name} <input> <output> [PART PROGRAM] ... [options]
|
11
10
|
#{exe_name} -h | --help
|
12
11
|
#{exe_name} --version
|
13
12
|
|
14
13
|
Arguments:
|
15
14
|
input A musicality score file (may be packed as a hash) in YAML format
|
16
|
-
output Midi filename
|
17
15
|
PART name of a part in the score
|
18
16
|
PROGRAM MIDI program (instrument) number for the given part
|
19
17
|
|
@@ -22,6 +20,7 @@ Options:
|
|
22
20
|
score, and for sampling dynamic change values [default: 200]
|
23
21
|
-h --help Show this screen.
|
24
22
|
--version Show version.
|
23
|
+
--outfile=OUTF Name of output MIDI file
|
25
24
|
|
26
25
|
DOCOPT
|
27
26
|
|
@@ -50,7 +49,7 @@ File.open(fin_name) do |fin|
|
|
50
49
|
|
51
50
|
unless score.is_a? Score::Timed
|
52
51
|
print "Converting to timed score..."
|
53
|
-
score = score.to_timed(args["--srate"])
|
52
|
+
score = score.to_timed(args["--srate"].to_i)
|
54
53
|
puts "done"
|
55
54
|
end
|
56
55
|
|
@@ -64,7 +63,7 @@ File.open(fin_name) do |fin|
|
|
64
63
|
seq = sequencer.make_midi_seq(instr_map)
|
65
64
|
puts "done"
|
66
65
|
|
67
|
-
fout_name = args["
|
66
|
+
fout_name = args["--outfile"]
|
68
67
|
if fout_name.nil?
|
69
68
|
fout_name = "#{File.dirname(fin_name)}/#{File.basename(fin_name,File.extname(fin_name))}.mid"
|
70
69
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'musicality'
|
2
|
+
include Musicality
|
3
|
+
include Pitches
|
4
|
+
|
5
|
+
bass_pitch_palette = [ C2, D2, E2, F2, G2, A2, B2, C3 ]
|
6
|
+
guitar_pitch_palette = [ C3, D3, E3, F3, G3, A3, B3, C4 ]
|
7
|
+
|
8
|
+
bass = Part.new(Dynamics::MF)
|
9
|
+
guitar = Part.new(Dynamics::MF)
|
10
|
+
|
11
|
+
def random_melody rhythm, pitch_palette
|
12
|
+
pitches = pitch_palette.sample(rand(2..pitch_palette.size))
|
13
|
+
pitch_sampler = RandomSampler.new(pitches,Probabilities.random(pitches.size))
|
14
|
+
make_notes(rhythm, pitch_sampler.sample(rhythm.size))
|
15
|
+
end
|
16
|
+
|
17
|
+
def random_counterpoint rhythm, rhythm_palette, sample_rate, pitch_palette
|
18
|
+
cpg = CounterpointGenerator.new(rhythm,rhythm_palette)
|
19
|
+
counterpoint = cpg.best_solutions(25,0.5,sample_rate).sample
|
20
|
+
pitches = pitch_palette.sample(rand(2..pitch_palette.size))
|
21
|
+
pitch_sampler = RandomSampler.new(pitches,Probabilities.random(pitches.size))
|
22
|
+
make_notes(counterpoint, pitch_sampler.sample(counterpoint.size))
|
23
|
+
end
|
24
|
+
|
25
|
+
2.times do
|
26
|
+
[ {
|
27
|
+
:rhythm_probs => { 1/8.to_r => 0.25, 1/4.to_r => 0.25, 1/6.to_r => 0.25, 1/2.to_r => 0.25 },
|
28
|
+
:sample_rate => 48,
|
29
|
+
},
|
30
|
+
{
|
31
|
+
:rhythm_probs => { 1/8.to_r => 0.325, 1/4.to_r => 0.325, 3/16.to_r => 0.10, 1/2.to_r => 0.25 },
|
32
|
+
:sample_rate => 16,
|
33
|
+
},
|
34
|
+
].each do |params|
|
35
|
+
rrg =RandomRhythmGenerator.new(params[:rhythm_probs])
|
36
|
+
rhythm_palette = params[:rhythm_probs].keys
|
37
|
+
5.times do
|
38
|
+
rhythm = rrg.random_rhythm(3/4.to_r)
|
39
|
+
guitar.notes += random_melody(rhythm, guitar_pitch_palette)
|
40
|
+
bass.notes += random_counterpoint(rhythm, rhythm_palette,
|
41
|
+
params[:sample_rate], bass_pitch_palette)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
score = Score::Unmeasured.new(120,
|
47
|
+
parts: { "bass" => bass, "guitar" => guitar },
|
48
|
+
program: [ 0...([bass.duration,guitar.duration].min) ]
|
49
|
+
)
|
50
|
+
|
51
|
+
instr_map = { "bass" => 32, "guitar" => 25 }
|
52
|
+
seq = ScoreSequencer.new(score.to_timed(200)).make_midi_seq(instr_map)
|
53
|
+
File.open("./auto_counterpoint.mid", 'wb'){ |fout| seq.write(fout) }
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'musicality'
|
2
|
+
include Musicality
|
3
|
+
include Pitches
|
4
|
+
include PitchClasses
|
5
|
+
include Meters
|
6
|
+
include ScaleClasses
|
7
|
+
|
8
|
+
minor = Heptatonic::Prima::MINOR
|
9
|
+
major = Heptatonic::Prima::MAJOR
|
10
|
+
|
11
|
+
pitch_seqs = {
|
12
|
+
D4 => minor, C4 => major, E4 => minor, F4 => minor
|
13
|
+
}.map do |pitch,scale_class|
|
14
|
+
scale_class.to_pitch_seq(pitch)
|
15
|
+
end
|
16
|
+
|
17
|
+
rhythm_seq2 = RepeatingSequence.new([3/8.to_r]*4)
|
18
|
+
|
19
|
+
score = Score::Measured.new(SIX_EIGHT,90) do |s|
|
20
|
+
s.parts["main"] = Part.new(Dynamics::MF) do |p|
|
21
|
+
rhythm_seq = RepeatingSequence.new(([1/8.to_r]*3)*3 + [1/4.to_r,1/8.to_r])
|
22
|
+
selector = RepeatingSequence.new([4,2,0])
|
23
|
+
rhythm = rhythm_seq.take(pitch_seqs.size * rhythm_seq.pattern_size).to_a
|
24
|
+
poffsets = selector.take(rhythm_seq.pattern_size).to_a
|
25
|
+
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
26
|
+
p.notes += make_notes(rhythm,pitches)
|
27
|
+
end
|
28
|
+
|
29
|
+
s.parts["bass"] = Part.new(Dynamics::MP) do |p|
|
30
|
+
rhythm_seq = RepeatingSequence.new([3/8.to_r]*4)
|
31
|
+
selector = RepeatingSequence.new([-7])
|
32
|
+
rhythm = rhythm_seq.take(pitch_seqs.size * rhythm_seq.pattern_size).to_a
|
33
|
+
poffsets = selector.take(rhythm_seq.pattern_size).to_a
|
34
|
+
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
35
|
+
p.notes += make_notes(rhythm,pitches)
|
36
|
+
end
|
37
|
+
|
38
|
+
s.parts["pluck"] = Part.new(Dynamics::MP) do |p|
|
39
|
+
rhythm_seq = RepeatingSequence.new(([1/8.to_r]*3)*4)
|
40
|
+
selector = RepeatingSequence.new([0,2,4])
|
41
|
+
rhythm = rhythm_seq.take(pitch_seqs.size * rhythm_seq.pattern_size).to_a
|
42
|
+
poffsets = selector.take(rhythm_seq.pattern_size).to_a
|
43
|
+
pitches = pitch_seqs.map { |pseq| pseq.at(poffsets).to_a }.flatten
|
44
|
+
p.notes += make_notes(rhythm,pitches)
|
45
|
+
end
|
46
|
+
|
47
|
+
s.program = [0...s.measures_long]*2
|
48
|
+
end
|
49
|
+
|
50
|
+
seq = ScoreSequencer.new(score.to_timed(200)).make_midi_seq("main" => 81, "bass" => 80, "pluck" => 46)
|
51
|
+
File.open("./part_generator.mid", 'wb'){ |fout| seq.write(fout) }
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'musicality'
|
2
|
+
include Musicality
|
3
|
+
include Pitches
|
4
|
+
include Meters
|
5
|
+
include ScaleClasses
|
6
|
+
|
7
|
+
def scale_exercise scale_class, base_pitch, rhythm
|
8
|
+
scale = scale_class.to_pitch_seq(base_pitch)
|
9
|
+
n = scale_class.count
|
10
|
+
m = rhythm.size
|
11
|
+
|
12
|
+
rseq = RepeatingSequence.new((0...m).to_a)
|
13
|
+
aseq = AddingSequence.new([0]*(m-1) + [1])
|
14
|
+
cseq = CompoundSequence.new(:+,[aseq,rseq])
|
15
|
+
pgs = cseq.take(m*n).map {|i| scale.at(i) }
|
16
|
+
notes = make_notes(rhythm, pgs) +
|
17
|
+
make_notes([3/4.to_r,-1/4.to_r], [scale.at(n)])
|
18
|
+
|
19
|
+
rseq = RepeatingSequence.new(n.downto(n+1-m).to_a)
|
20
|
+
aseq = AddingSequence.new([0]*(m-1) + [-1])
|
21
|
+
cseq = CompoundSequence.new(:+,[aseq,rseq])
|
22
|
+
pgs = cseq.take(m*n).map {|i| scale.at(i) }
|
23
|
+
notes += make_notes(rhythm, pgs) +
|
24
|
+
make_notes([3/4.to_r,-1/4.to_r], [scale.at(0)])
|
25
|
+
|
26
|
+
return notes
|
27
|
+
end
|
28
|
+
|
29
|
+
score = Score::Measured.new(FOUR_FOUR,120) do |s|
|
30
|
+
s.parts["scale"] = Part.new(Dynamics::MP) do |p|
|
31
|
+
Heptatonic::Prima::MODES.each do |mode_n,scale_class|
|
32
|
+
[[1/4.to_r,1/4.to_r,1/2.to_r]].each do |rhythm|
|
33
|
+
p.notes += scale_exercise(scale_class, C3, rhythm)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
s.program.push 0...s.measures_long
|
38
|
+
end
|
39
|
+
|
40
|
+
seq = ScoreSequencer.new(score.to_timed(200)).make_midi_seq("scale" => 1)
|
41
|
+
File.open("./scale_exercise.mid", 'wb'){ |fout| seq.write(fout) }
|
@@ -7,7 +7,7 @@ include Articulations
|
|
7
7
|
include Meters
|
8
8
|
|
9
9
|
score = Score::Measured.new(FOUR_FOUR,120) do |s|
|
10
|
-
s.program
|
10
|
+
s.program += [0...2, 0...2, 2...4, 0...2]
|
11
11
|
s.parts["lead"] = Part.new(Dynamics::MF) do |p|
|
12
12
|
riff = "/6Bb3 /4 /12Db4= /6Db4= /36Db4 /36Eb4 /36Db4 /6Ab3 /12Db4 \
|
13
13
|
/6Bb3 /4 /12Db4= /4Db4= /8=Db4 /8C4".to_notes
|
@@ -7,7 +7,7 @@ include Articulations
|
|
7
7
|
include Meters
|
8
8
|
|
9
9
|
score = Score::Measured.new(FOUR_FOUR, 120) do |s|
|
10
|
-
s.program
|
10
|
+
s.program += [0...2, 0...6]
|
11
11
|
s.parts["bass"] = Part.new(Dynamics::MF) do |p|
|
12
12
|
p.notes = "/4Eb2 /4 /4Bb2 /4 /4Eb2 /8 /8B2 /4Bb2 /4Ab2".to_notes
|
13
13
|
p.notes += "/4Bb2 /8 /8F3= /2F3 /4Bb2 /8 /8F3= /2F3".to_notes
|
data/lib/musicality.rb
CHANGED
@@ -9,13 +9,14 @@ require 'musicality/errors'
|
|
9
9
|
|
10
10
|
require 'musicality/notation/model/pitch'
|
11
11
|
require 'musicality/notation/model/pitches'
|
12
|
-
require 'musicality/notation/model/
|
12
|
+
require 'musicality/notation/model/links'
|
13
13
|
require 'musicality/notation/model/articulations'
|
14
|
+
require 'musicality/notation/model/symbols'
|
15
|
+
require 'musicality/notation/model/link'
|
14
16
|
require 'musicality/notation/model/note'
|
15
17
|
require 'musicality/notation/model/dynamics'
|
16
18
|
require 'musicality/notation/model/change'
|
17
19
|
require 'musicality/notation/model/part'
|
18
|
-
require 'musicality/notation/model/program'
|
19
20
|
require 'musicality/notation/model/meter'
|
20
21
|
require 'musicality/notation/model/meters'
|
21
22
|
require 'musicality/notation/model/score'
|
@@ -43,7 +44,6 @@ require 'musicality/notation/parsing/convenience_methods'
|
|
43
44
|
|
44
45
|
require 'musicality/notation/packing/change_packing'
|
45
46
|
require 'musicality/notation/packing/part_packing'
|
46
|
-
require 'musicality/notation/packing/program_packing'
|
47
47
|
require 'musicality/notation/packing/score_packing'
|
48
48
|
|
49
49
|
require 'musicality/notation/util/interpolation'
|
@@ -59,6 +59,29 @@ require 'musicality/notation/conversion/measure_note_map'
|
|
59
59
|
require 'musicality/notation/conversion/score_converter'
|
60
60
|
require 'musicality/notation/conversion/score_conversion'
|
61
61
|
|
62
|
+
#
|
63
|
+
# Composition
|
64
|
+
#
|
65
|
+
|
66
|
+
require 'musicality/composition/util/biinfinite_sequence'
|
67
|
+
require 'musicality/composition/util/repeating_sequence'
|
68
|
+
require 'musicality/composition/util/adding_sequence'
|
69
|
+
require 'musicality/composition/util/compound_sequence'
|
70
|
+
require 'musicality/composition/util/random_sampler'
|
71
|
+
require 'musicality/composition/util/probabilities'
|
72
|
+
|
73
|
+
require 'musicality/composition/model/pitch_class'
|
74
|
+
require 'musicality/composition/model/pitch_classes'
|
75
|
+
require 'musicality/composition/model/scale'
|
76
|
+
require 'musicality/composition/model/scale_class'
|
77
|
+
require 'musicality/composition/model/scale_classes'
|
78
|
+
|
79
|
+
require 'musicality/composition/note_generation'
|
80
|
+
require 'musicality/composition/transposition'
|
81
|
+
|
82
|
+
require 'musicality/composition/generation/counterpoint_generator'
|
83
|
+
require 'musicality/composition/generation/random_rhythm_generator'
|
84
|
+
|
62
85
|
#
|
63
86
|
# Performance
|
64
87
|
#
|
@@ -78,4 +101,11 @@ require 'midilib'
|
|
78
101
|
require 'musicality/performance/midi/midi_util'
|
79
102
|
require 'musicality/performance/midi/midi_events'
|
80
103
|
require 'musicality/performance/midi/part_sequencer'
|
81
|
-
require 'musicality/performance/midi/score_sequencer'
|
104
|
+
require 'musicality/performance/midi/score_sequencer'
|
105
|
+
require 'musicality/performance/midi/score_sequencing'
|
106
|
+
|
107
|
+
require 'musicality/printing/lilypond/errors'
|
108
|
+
require 'musicality/printing/lilypond/pitch_engraving'
|
109
|
+
require 'musicality/printing/lilypond/note_engraving'
|
110
|
+
require 'musicality/printing/lilypond/meter_engraving'
|
111
|
+
require 'musicality/printing/lilypond/score_engraver'
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class CounterpointGenerator
|
4
|
+
attr_reader :rhythm, :palette, :total_dur, :solution_classes, :solutions
|
5
|
+
def initialize rhythm, palette, max_fact = 5
|
6
|
+
raise ArgumentError, "max_fact must be >= palette size" if max_fact < palette.size
|
7
|
+
|
8
|
+
@rhythm = rhythm
|
9
|
+
@palette = palette
|
10
|
+
@total_dur = rhythm.map {|dur| dur.abs }.inject(0,:+)
|
11
|
+
@solution_classes = self.class.solution_classes(@total_dur, @palette)
|
12
|
+
@solution_classes.keep_if do |sc|
|
13
|
+
sc.map {|k,v| k*v}.inject(0,:+) == @total_dur
|
14
|
+
end
|
15
|
+
@solutions = figure_solutions(max_fact)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.rhythm_to_computer rhythm
|
19
|
+
rhythm_accum = AddingSequence.new(rhythm).take(rhythm.size+1).to_a
|
20
|
+
x = rhythm_accum[0,rhythm.size]
|
21
|
+
y = rhythm_accum[1,rhythm.size].map {|y_| Change::Immediate.new(y_) }
|
22
|
+
value_changes = Hash[ [x,y].transpose ]
|
23
|
+
ValueComputer.new(0, value_changes)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate solution, ideal_overlap, sample_rate
|
27
|
+
if solution.inject(0,:+) != @total_dur
|
28
|
+
raise ArgumentError, "Given solution #{solution} does not have same duration as rhythm"
|
29
|
+
end
|
30
|
+
|
31
|
+
rhythm_comp = self.class.rhythm_to_computer(@rhythm)
|
32
|
+
solution_comp = self.class.rhythm_to_computer(solution)
|
33
|
+
|
34
|
+
r = rhythm_comp.sample(0...@total_dur, sample_rate)
|
35
|
+
s = solution_comp.sample(0...@total_dur, sample_rate)
|
36
|
+
n_same = [r,s].transpose.count {|pair| pair[0] == pair[1] }
|
37
|
+
n_samples = (@total_dur*sample_rate).to_i
|
38
|
+
percent_overlap = (n_same/n_samples).to_f
|
39
|
+
return (ideal_overlap - percent_overlap).abs
|
40
|
+
end
|
41
|
+
|
42
|
+
def best_solution ideal_overlap, sample_rate
|
43
|
+
@solutions.min_by {|sol| evaluate(sol,ideal_overlap,sample_rate) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def best_solutions n, ideal_overlap, sample_rate
|
47
|
+
@solutions.sort_by {|sol| evaluate(sol,ideal_overlap,sample_rate) }.take(n)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def figure_solutions max_factorial
|
53
|
+
solutions = []
|
54
|
+
@solution_classes.each do |sc|
|
55
|
+
tot = sc.values.inject(0,:+)
|
56
|
+
if tot <= max_factorial
|
57
|
+
proto = sc.map {|dur,n| [dur]*n }.flatten
|
58
|
+
solutions += proto.permutation(proto.size).to_a.uniq
|
59
|
+
else
|
60
|
+
durs, counts = sc.sort_by {|k,v| v }.transpose
|
61
|
+
self.class.limited_solution_classes(durs,counts,max_factorial).each do |ltd_sc|
|
62
|
+
ltd_proto = ltd_sc.map do |dur,ns|
|
63
|
+
ns.map {|n| [dur]*n }
|
64
|
+
end.flatten(1)
|
65
|
+
solutions += ltd_proto.permutation(max_factorial).map do |perm|
|
66
|
+
perm.flatten
|
67
|
+
end.to_a.uniq
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
return solutions.uniq
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.limited_solution_classes durs, counts, max_fact
|
76
|
+
n_counts = counts.size
|
77
|
+
if n_counts > max_fact
|
78
|
+
raise ArgumentError, "counts.size (#{n_counts}) is > max_fact (#{max_fact})"
|
79
|
+
end
|
80
|
+
|
81
|
+
total_count_rem = counts.inject(0,:+)
|
82
|
+
if total_count_rem < max_fact
|
83
|
+
raise ArgumentError, "total_count_rem (#{total_count_rem}) <= max_fact (#{max_fact})"
|
84
|
+
end
|
85
|
+
|
86
|
+
ltd_solns = []
|
87
|
+
counts.size.times do |i|
|
88
|
+
count, dur = counts.first, durs.first
|
89
|
+
counts_rem = counts.drop(1)
|
90
|
+
|
91
|
+
adj_count = [ count, max_fact - counts_rem.size ].min
|
92
|
+
|
93
|
+
positive_integer_combinations_with_sum(count,adj_count).each do |comb|
|
94
|
+
if counts_rem.empty?
|
95
|
+
ltd_solns.push(dur => comb)
|
96
|
+
else
|
97
|
+
max_fact_rem = max_fact - adj_count
|
98
|
+
limited_solution_classes(durs.drop(1), counts_rem, max_fact_rem).each do |ltd_soln2|
|
99
|
+
ltd_solns.push(ltd_soln2.merge(dur => comb))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
counts.rotate!
|
105
|
+
durs.rotate!
|
106
|
+
end
|
107
|
+
return ltd_solns.uniq
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.positive_integer_combinations_with_sum sum, n_pos_ints
|
111
|
+
raise ArgumentError, "sum must be <= number of positive integers" if n_pos_ints > sum
|
112
|
+
|
113
|
+
if n_pos_ints == 1
|
114
|
+
return [[sum]]
|
115
|
+
elsif sum == n_pos_ints
|
116
|
+
return [[1]*n_pos_ints]
|
117
|
+
else
|
118
|
+
return (1...sum).to_a.combination(n_pos_ints-1).map do |comb|
|
119
|
+
prev_post = 0
|
120
|
+
diffs = comb.map do |post|
|
121
|
+
diff = post - prev_post
|
122
|
+
prev_post = post
|
123
|
+
diff
|
124
|
+
end
|
125
|
+
(diffs + [sum-prev_post]).sort
|
126
|
+
end.uniq
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.solution_classes total_dur, palette
|
131
|
+
dur = palette.first
|
132
|
+
n = (total_dur / dur).to_i
|
133
|
+
|
134
|
+
if palette.size == 1
|
135
|
+
return [ n > 0 ? {dur => n} : {} ]
|
136
|
+
else
|
137
|
+
new_palette = palette.drop(1)
|
138
|
+
return Array.new(n+1) do |i|
|
139
|
+
subs = solution_classes(total_dur - i*dur, new_palette)
|
140
|
+
if i > 0
|
141
|
+
subs.each{|soln_class| soln_class[dur] = i }
|
142
|
+
end
|
143
|
+
subs
|
144
|
+
end.flatten
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
#rhythm = [1/4.to_r,1/4.to_r,1/8.to_r,1/8.to_r,1/8.to_r,1/8.to_r,1/4.to_r,1/2.to_r]
|
150
|
+
#palette = [1/8.to_r,1/4.to_r,1/2.to_r]
|
151
|
+
#cpg = CounterpointGenerator.new(rhythm,palette)
|
152
|
+
|
153
|
+
end
|