musicality 0.3.0 → 0.5.0

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.
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