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
@@ -0,0 +1,39 @@
1
+ module Musicality
2
+
3
+ class RandomRhythmGenerator
4
+ attr_reader :durations, :probabilities
5
+
6
+ def initialize palette_with_probs
7
+ @durations, @probabilities = palette_with_probs.entries.transpose
8
+ @dur_sampler = RandomSampler.new(@durations,@probabilities)
9
+ end
10
+
11
+ def random_dur
12
+ @dur_sampler.sample
13
+ end
14
+
15
+ def random_rhythm target_dur, end_retries = 5
16
+ rhythm = []
17
+ total_dur = 0
18
+ retries = 0
19
+
20
+ while(total_dur < target_dur && retries < end_retries)
21
+ dur = random_dur
22
+
23
+ if (dur + total_dur) <= target_dur
24
+ total_dur += dur
25
+ rhythm.push(dur)
26
+ else
27
+ retries += 1
28
+ end
29
+ end
30
+
31
+ if total_dur < target_dur
32
+ rhythm.push(target_dur - total_dur)
33
+ end
34
+
35
+ return rhythm
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,33 @@
1
+ module Musicality
2
+
3
+ class PitchClass
4
+ MOD = Pitch::SEMITONES_PER_OCTAVE
5
+
6
+ def self.from_i i
7
+ i % MOD
8
+ end
9
+
10
+ def self.invert val
11
+ (MOD - val.to_pc).to_pc
12
+ end
13
+ end
14
+
15
+ class Pitch
16
+ def to_pc
17
+ PitchClass.from_i semitone
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ class Fixnum
24
+ def to_pc
25
+ PitchClass.from_i self
26
+ end
27
+ end
28
+
29
+ module Enumerable
30
+ def to_pcs
31
+ map {|value| value.to_pc }
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module Musicality
2
+
3
+ module PitchClasses
4
+ C = 0
5
+ Db = 1
6
+ D = 2
7
+ Eb = 3
8
+ E = 4
9
+ F = 5
10
+ Gb = 6
11
+ G = 7
12
+ Ab = 8
13
+ A = 9
14
+ Bb = 10
15
+ B = 11
16
+ end
17
+
18
+ PITCH_CLASSES = PitchClasses.constants.map do |sym|
19
+ PitchClasses.const_get(sym)
20
+ end.sort
21
+
22
+ end
@@ -0,0 +1,34 @@
1
+ module Musicality
2
+
3
+ class Scale
4
+ attr_reader :pitch_class
5
+ def initialize pitch_class, intervals
6
+ @pitch_class = pitch_class
7
+ @intervals = intervals
8
+ end
9
+
10
+ def intervals; @intervals.entries; end
11
+
12
+ def size
13
+ @intervals.size
14
+ end
15
+
16
+ def transpose diff
17
+ new_pc = (@pitch_class + diff).to_pc
18
+ Scale.new(new_pc,@intervals)
19
+ end
20
+
21
+ def rotate n
22
+ diff = AddingSequence.new(@intervals).at(n)
23
+ new_pc = (@pitch_class + diff).to_pc
24
+ new_intervals = @intervals.rotate(n)
25
+ Scale.new(new_pc,new_intervals)
26
+ end
27
+
28
+ def at_octave octave
29
+ start_pitch = Pitch.new(octave: octave, semitone: @pitch_class)
30
+ AddingSequence.new(@intervals, start_pitch)
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,37 @@
1
+ module Musicality
2
+
3
+ class ScaleClass
4
+ include Enumerable
5
+
6
+ def initialize intervals
7
+ if intervals.detect {|x| x <= 0 }
8
+ raise NonPositiveError, "One or more scale intervals (#{intervals}) is non-positive"
9
+ end
10
+ @intervals = intervals
11
+ end
12
+
13
+ def intervals; self.entries; end
14
+
15
+ def ==(other)
16
+ self.entries == other.entries
17
+ end
18
+
19
+ def each
20
+ return @intervals.each unless block_given?
21
+ @intervals.each {|x| yield x }
22
+ end
23
+
24
+ def to_pitch_seq start_pitch
25
+ AddingSequence.new(@intervals, start_pitch)
26
+ end
27
+
28
+ def to_scale pitch_class
29
+ Scale.new(pitch_class, @intervals)
30
+ end
31
+
32
+ def rotate n = 1
33
+ ScaleClass.new(@intervals.rotate(n))
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,91 @@
1
+ module Musicality
2
+
3
+ module ScaleClasses
4
+ CHROMATIC = ScaleClass.new([1,1,1,1,1,1,1,1,1,1,1,1])
5
+
6
+ module Pentatonic
7
+ MINOR = ScaleClass.new([3,2,2,3,2])
8
+ MAJOR = MINOR.rotate(1)
9
+ EGYPTIAN = MINOR.rotate(2)
10
+ MINOR_BLUES = MINOR.rotate(3)
11
+ MAJOR_BLUES = MINOR.rotate(4)
12
+
13
+ MODES = {
14
+ 1 => MINOR,
15
+ 2 => MAJOR,
16
+ 3 => EGYPTIAN,
17
+ 4 => MINOR_BLUES,
18
+ 5 => MAJOR_BLUES
19
+ }
20
+ end
21
+
22
+ module Hexatonic
23
+ WHOLE_TONE = ScaleClass.new([2,2,2,2,2,2])
24
+ AUGMENTED = ScaleClass.new([3,1,3,1,3,1])
25
+ MYSTIC = PROMETHEAN = ScaleClass.new([2,2,2,3,1,2])
26
+ BLUES = ScaleClass.new([3,2,1,1,3,2])
27
+ TRITONE = PETRUSHKA = ScaleClass.new([1,3,2,1,3,2])
28
+ end
29
+
30
+ module Heptatonic
31
+ # This is where the standard Major scale and its modes are found, among others.
32
+ module Prima
33
+ IONIAN = MAJOR = ScaleClass.new([2,2,1,2,2,2,1])
34
+ DORIAN = IONIAN.rotate(1)
35
+ PHRYGIAN = IONIAN.rotate(2)
36
+ LYDIAN = IONIAN.rotate(3)
37
+ MIXOLYDIAN = IONIAN.rotate(4)
38
+ AEOLIAN = MINOR = IONIAN.rotate(5)
39
+ LOCRIAN = IONIAN.rotate(6)
40
+
41
+ MODES = {
42
+ 1 => IONIAN,
43
+ 2 => DORIAN,
44
+ 3 => PHRYGIAN,
45
+ 4 => LYDIAN,
46
+ 5 => MIXOLYDIAN,
47
+ 6 => AEOLIAN,
48
+ 7 => LOCRIAN
49
+ }
50
+ end
51
+
52
+ module Secunda
53
+ JAZZ_MINOR = MELODIC_MINOR = ScaleClass.new([2,1,2,2,2,2,1])
54
+ PHRYGIAN_RAISED_SIXTH = MELODIC_MINOR.rotate(1)
55
+ LYDIAN_RAISED_FIFTH = MELODIC_MINOR.rotate(2)
56
+ ACOUSTIC = LYDIAN_DOMINANT = MELODIC_MINOR.rotate(3)
57
+ MAJOR_MINOR = MELODIC_MINOR.rotate(4)
58
+ HALF_DIMINISHED = MELODIC_MINOR.rotate(5)
59
+ ALTERED = MELODIC_MINOR.rotate(6)
60
+
61
+ MODES = {
62
+ 1 => MELODIC_MINOR,
63
+ 2 => PHRYGIAN_RAISED_SIXTH,
64
+ 3 => LYDIAN_RAISED_FIFTH,
65
+ 4 => ACOUSTIC,
66
+ 5 => MAJOR_MINOR,
67
+ 6 => HALF_DIMINISHED,
68
+ 7 => ALTERED
69
+ }
70
+ end
71
+
72
+ module Other
73
+ GYPSY = ScaleClass.new([1,3,1,2,1,3,1])
74
+ HUNGARIAN = ScaleClass.new([2,1,3,1,1,3,1])
75
+ PHRYGIAN_MAJOR = ScaleClass.new([1,3,1,2,1,2,2])
76
+ SCALA_ENIGMATICA = ScaleClass.new([1,3,2,2,2,1,1])
77
+ end
78
+ end
79
+
80
+ module Octatonic
81
+ WHOLE_HALF = ScaleClass.new([2,1,2,1,2,1,2,1])
82
+ HALF_WHOLE = WHOLE_HALF.rotate(1)
83
+
84
+ MODES = {
85
+ 1 => WHOLE_HALF,
86
+ 2 => HALF_WHOLE
87
+ }
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,31 @@
1
+ module Musicality
2
+
3
+ def make_note dur, pitch_group
4
+ if dur > 0
5
+ Musicality::Note.new(dur,pitch_group)
6
+ else
7
+ Musicality::Note.new(-dur)
8
+ end
9
+ end
10
+ module_function :make_note
11
+
12
+ # Whichever is longer, rhythm or pitch_groups, is iterated over once while
13
+ # the smaller will cycle as necessary.
14
+ def make_notes rhythm, pitch_groups
15
+ m,n = rhythm.size, pitch_groups.size
16
+ raise EmptyError, "rhythm is empty" if m == 0
17
+ raise EmptyError, "pitch_groups is empty" if n == 0
18
+
19
+ if m > n
20
+ Array.new(m) do |i|
21
+ make_note(rhythm[i],pitch_groups[i % n])
22
+ end
23
+ else
24
+ Array.new(n) do |i|
25
+ make_note(rhythm[i % m],pitch_groups[i])
26
+ end
27
+ end
28
+ end
29
+ module_function :make_notes
30
+
31
+ end
@@ -0,0 +1,8 @@
1
+ module Musicality
2
+
3
+ def transpose notes, diff
4
+ notes.map {|n| n.transpose(diff) }
5
+ end
6
+ module_function :transpose
7
+
8
+ end
@@ -0,0 +1,24 @@
1
+ module Musicality
2
+
3
+ class AddingSequence
4
+ include BiInfiniteSequence
5
+
6
+ attr_reader :start_value
7
+ def initialize pattern, start_val = 0
8
+ raise EmptyError if pattern.empty?
9
+ @pattern = pattern
10
+ @n = pattern.size
11
+ @start_value = start_val
12
+ end
13
+
14
+ def pattern_size; @pattern.size; end
15
+
16
+ def next_value cur_val, cur_idx
17
+ cur_val + @pattern[cur_idx % @n]
18
+ end
19
+ def prev_value cur_val, cur_idx
20
+ cur_val - @pattern[(cur_idx-1) % @n]
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,130 @@
1
+ module Enumerable
2
+ def map_with_index
3
+ return enum_for(:map_with_index) unless block_given?
4
+ ary = entries
5
+ Array.new(ary.size) do |i|
6
+ yield ary[i], i
7
+ end
8
+ end
9
+ end
10
+
11
+ module Musicality
12
+
13
+ module BiInfiniteSequence
14
+ def at offset
15
+ if offset.is_a? Enumerable
16
+ return enum_for(:at,offset) unless block_given?
17
+ else
18
+ return at_one(offset)
19
+ end
20
+
21
+ offset_index_pairs = offset.map_with_index {|x,i| [x,i] }.sort
22
+ results = Array.new(offset.size)
23
+
24
+ past = offset_index_pairs.select {|p| p[0] < 0 }
25
+ present = offset_index_pairs.select {|p| p[0] == 0 }
26
+ future = offset_index_pairs.select {|p| p[0] > 0 }
27
+
28
+ start_val = start_value
29
+
30
+ if past.any?
31
+ value = start_val
32
+ j = past.size - 1
33
+ tgt_offset = past[j][0]
34
+ 0.downto(past.first[0]+1) do |i|
35
+ value = prev_value(value,i)
36
+ while (i-1) == tgt_offset
37
+ results[past[j][1]] = value
38
+ j -= 1
39
+ tgt_offset = j >= 0 ? past[j][0] : nil
40
+ end
41
+ end
42
+ end
43
+
44
+ if present.any?
45
+ present.each do |off,index|
46
+ results[index] = start_val
47
+ end
48
+ end
49
+
50
+ if future.any?
51
+ value = start_val
52
+ j = 0
53
+ tgt_offset = future[j][0]
54
+ 0.upto(future.last[0]-1) do |i|
55
+ value = next_value(value,i)
56
+ while (i+1) == tgt_offset
57
+ results[future[j][1]] = value
58
+ j += 1
59
+ tgt_offset = j < future.size ? future[j][0] : nil
60
+ end
61
+ end
62
+ end
63
+
64
+ results.each {|x| yield x }
65
+ end
66
+
67
+ def take n
68
+ raise NegativeError if n < 0
69
+ return enum_for(:take,n) unless block_given?
70
+ return if n == 0
71
+
72
+ value = start_value
73
+ 0.upto(n - 1) do |i|
74
+ yield value
75
+ value = next_value(value,i)
76
+ end
77
+ end
78
+
79
+ def take_back n
80
+ raise NegativeError if n < 0
81
+ return enum_for(:take_back,n) unless block_given?
82
+ return if n == 0
83
+
84
+ value = start_value
85
+ 0.downto(1 - n) do |i|
86
+ yield value = prev_value(value,i)
87
+ end
88
+ end
89
+
90
+ def over range
91
+ min, max = range.minmax
92
+ raise EmptyError, "given range (#{range}) is empty" if min.nil?
93
+ return enum_for(:over,range) unless block_given?
94
+
95
+ if min >= 0 && max >= 0
96
+ value = at(min)
97
+ range.each do |i|
98
+ yield value
99
+ value = next_value(value,i)
100
+ end
101
+ elsif max < 0
102
+ value = at(max+1)
103
+ values = range.entries.reverse.map do |i|
104
+ value = prev_value(value,i+1)
105
+ end
106
+ values.reverse_each {|x| yield x }
107
+ else
108
+ take_back(-min).reverse_each {|x| yield x }
109
+ take(max + 1){ |x| yield x }
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def at_one offset
116
+ value = start_value
117
+ if offset >= 0
118
+ 0.upto(offset-1) do |i|
119
+ value = next_value(value,i)
120
+ end
121
+ else
122
+ 0.downto(offset+1) do |i|
123
+ value = prev_value(value,i)
124
+ end
125
+ end
126
+ return value
127
+ end
128
+ end
129
+
130
+ end