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