musicality 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +8 -1
  3. data/bin/midify +3 -4
  4. data/examples/composition/auto_counterpoint.rb +53 -0
  5. data/examples/composition/part_generator.rb +51 -0
  6. data/examples/composition/scale_exercise.rb +41 -0
  7. data/examples/{hip.rb → notation/hip.rb} +1 -1
  8. data/examples/{missed_connection.rb → notation/missed_connection.rb} +1 -1
  9. data/examples/{song1.rb → notation/song1.rb} +1 -1
  10. data/examples/{song2.rb → notation/song2.rb} +1 -1
  11. data/lib/musicality.rb +34 -4
  12. data/lib/musicality/composition/generation/counterpoint_generator.rb +153 -0
  13. data/lib/musicality/composition/generation/random_rhythm_generator.rb +39 -0
  14. data/lib/musicality/composition/model/pitch_class.rb +33 -0
  15. data/lib/musicality/composition/model/pitch_classes.rb +22 -0
  16. data/lib/musicality/composition/model/scale.rb +34 -0
  17. data/lib/musicality/composition/model/scale_class.rb +37 -0
  18. data/lib/musicality/composition/model/scale_classes.rb +91 -0
  19. data/lib/musicality/composition/note_generation.rb +31 -0
  20. data/lib/musicality/composition/transposition.rb +8 -0
  21. data/lib/musicality/composition/util/adding_sequence.rb +24 -0
  22. data/lib/musicality/composition/util/biinfinite_sequence.rb +130 -0
  23. data/lib/musicality/composition/util/compound_sequence.rb +44 -0
  24. data/lib/musicality/composition/util/probabilities.rb +20 -0
  25. data/lib/musicality/composition/util/random_sampler.rb +26 -0
  26. data/lib/musicality/composition/util/repeating_sequence.rb +24 -0
  27. data/lib/musicality/errors.rb +2 -0
  28. data/lib/musicality/notation/conversion/score_conversion.rb +1 -1
  29. data/lib/musicality/notation/conversion/score_converter.rb +3 -3
  30. data/lib/musicality/notation/model/link.rb +26 -24
  31. data/lib/musicality/notation/model/links.rb +11 -0
  32. data/lib/musicality/notation/model/note.rb +14 -15
  33. data/lib/musicality/notation/model/part.rb +3 -3
  34. data/lib/musicality/notation/model/pitch.rb +8 -0
  35. data/lib/musicality/notation/model/score.rb +70 -44
  36. data/lib/musicality/notation/model/symbols.rb +22 -0
  37. data/lib/musicality/notation/packing/score_packing.rb +2 -3
  38. data/lib/musicality/notation/parsing/articulation_parsing.rb +4 -4
  39. data/lib/musicality/notation/parsing/articulation_parsing.treetop +2 -2
  40. data/lib/musicality/notation/parsing/link_nodes.rb +2 -14
  41. data/lib/musicality/notation/parsing/link_parsing.rb +9 -107
  42. data/lib/musicality/notation/parsing/link_parsing.treetop +4 -12
  43. data/lib/musicality/notation/parsing/note_node.rb +23 -21
  44. data/lib/musicality/notation/parsing/note_parsing.rb +70 -70
  45. data/lib/musicality/notation/parsing/note_parsing.treetop +6 -3
  46. data/lib/musicality/notation/parsing/pitch_node.rb +4 -2
  47. data/lib/musicality/performance/conversion/score_collator.rb +3 -3
  48. data/lib/musicality/performance/midi/midi_util.rb +13 -6
  49. data/lib/musicality/performance/midi/score_sequencing.rb +17 -0
  50. data/lib/musicality/printing/lilypond/errors.rb +5 -0
  51. data/lib/musicality/printing/lilypond/meter_engraving.rb +11 -0
  52. data/lib/musicality/printing/lilypond/note_engraving.rb +53 -0
  53. data/lib/musicality/printing/lilypond/part_engraver.rb +12 -0
  54. data/lib/musicality/printing/lilypond/pitch_engraving.rb +30 -0
  55. data/lib/musicality/printing/lilypond/score_engraver.rb +78 -0
  56. data/lib/musicality/version.rb +1 -1
  57. data/spec/composition/generation/random_rhythm_generator_spec.rb +50 -0
  58. data/spec/composition/model/pitch_class_spec.rb +75 -0
  59. data/spec/composition/model/pitch_classes_spec.rb +24 -0
  60. data/spec/composition/model/scale_class_spec.rb +98 -0
  61. data/spec/composition/model/scale_spec.rb +110 -0
  62. data/spec/composition/note_generation_spec.rb +113 -0
  63. data/spec/composition/transposition_spec.rb +17 -0
  64. data/spec/composition/util/adding_sequence_spec.rb +176 -0
  65. data/spec/composition/util/compound_sequence_spec.rb +50 -0
  66. data/spec/composition/util/probabilities_spec.rb +39 -0
  67. data/spec/composition/util/random_sampler_spec.rb +47 -0
  68. data/spec/composition/util/repeating_sequence_spec.rb +151 -0
  69. data/spec/notation/conversion/score_conversion_spec.rb +3 -3
  70. data/spec/notation/conversion/score_converter_spec.rb +24 -24
  71. data/spec/notation/model/link_spec.rb +27 -25
  72. data/spec/notation/model/note_spec.rb +9 -6
  73. data/spec/notation/model/pitch_spec.rb +24 -1
  74. data/spec/notation/model/score_spec.rb +57 -16
  75. data/spec/notation/packing/score_packing_spec.rb +134 -206
  76. data/spec/notation/parsing/articulation_parsing_spec.rb +1 -8
  77. data/spec/notation/parsing/convenience_methods_spec.rb +1 -1
  78. data/spec/notation/parsing/link_nodes_spec.rb +3 -4
  79. data/spec/notation/parsing/link_parsing_spec.rb +10 -4
  80. data/spec/notation/parsing/note_node_spec.rb +8 -7
  81. data/spec/notation/parsing/note_parsing_spec.rb +9 -12
  82. data/spec/performance/conversion/score_collator_spec.rb +14 -14
  83. data/spec/performance/midi/midi_util_spec.rb +26 -0
  84. data/spec/performance/midi/score_sequencer_spec.rb +1 -1
  85. metadata +57 -12
  86. data/lib/musicality/notation/model/program.rb +0 -53
  87. data/lib/musicality/notation/packing/program_packing.rb +0 -16
  88. data/spec/notation/model/program_spec.rb +0 -50
  89. 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: 1f460185c537892ef1e12ffce5b1c829095666c7
4
- data.tar.gz: fc1512fe588da6ee7599aea61ba713d268c50cc3
3
+ metadata.gz: 0f671084b7ea13c0ac038a4b52cfcdfa51aeb76e
4
+ data.tar.gz: 19e843c3f803fc34fcd3520dbbdea8bf100102f0
5
5
  SHA512:
6
- metadata.gz: ab8468ae5d3f4daf66a6f054480969c0d81e5b409b9a52efc513b3fc8ec6ccf5cdace8be25df378d63a9002ec6c6db61afb9fe6d92506b01a3c53412f8abe587
7
- data.tar.gz: 784d01d623fd8436e635853340905ab381915138d9ccf2773e3728084c0ad04b6083e16ee692546a7fd9556930eced67bd70e3703fad91abb3b427443a72695d
6
+ metadata.gz: 592b30da2943856712ddda8b78b0710d7311ef3ae9f2a9f8afad5b37a586ff4dc0227de98431b01da740b1b20d82956eaed0fe3cbfa2ddf47e7eaeba52bf968f
7
+ data.tar.gz: 38a0389ee700b706a3b384e4a8702b9e1743ccf36a98090c7046c3c4e9dfc731b3ff9de946e2adcb856318d15686e95e5b75d9f9814b07fcfb06c52364683540
data/ChangeLog.md CHANGED
@@ -1,4 +1,11 @@
1
- ### 0.3.0 / 2014-11-24
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["<output>"]
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 = Program.new([0...2, 0...2,2...4,0...2])
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 = Program.new([0...2,0...6])
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
@@ -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 = Program.new([ 0...4.0, 0...4.0 ])
10
+ s.program += [0...4]*2
11
11
 
12
12
  s.parts[1] = Part.new(Dynamics::MF) do |p|
13
13
  p.notes = "3/8C2 /4Eb2 5/16F2 /16Eb2 \
@@ -6,7 +6,7 @@ include Pitches
6
6
  include Meters
7
7
 
8
8
  score = Score::Measured.new(FOUR_FOUR, 120) do |s|
9
- s.program = Program.new([0...4.0, 0...4.0 ])
9
+ s.program += [0...4]*2
10
10
 
11
11
  s.parts[1] = Part.new(Dynamics::MF) do |p|
12
12
  p.notes = "1C4 1Bb3 1Ab3 /2G3 /2Bb3".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/link'
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