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.
- 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
@@ -0,0 +1,44 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class CompoundSequence
|
4
|
+
def initialize combine_method, sequences
|
5
|
+
@seqs = sequences
|
6
|
+
@comb_meth = combine_method
|
7
|
+
end
|
8
|
+
|
9
|
+
def at offset
|
10
|
+
if offset.is_a? Enumerable
|
11
|
+
return enum_for(:at,offset) unless block_given?
|
12
|
+
enums = @seqs.map {|s| s.at(offset) }
|
13
|
+
offset.size.times { yield combine(enums.map {|e| e.next }) }
|
14
|
+
else
|
15
|
+
vals = @seqs.map {|s| s.at(offset) }
|
16
|
+
return combine(vals)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def take n
|
21
|
+
return enum_for(:take,n) unless block_given?
|
22
|
+
enums = @seqs.map {|s| s.take(n) }
|
23
|
+
n.times { yield combine(enums.map {|e| e.next }) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def over range
|
27
|
+
return enum_for(:over,range) unless block_given?
|
28
|
+
enums = @seqs.map {|s| s.over(range) }
|
29
|
+
range.size.times { yield combine(enums.map {|e| e.next }) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def take_back n
|
33
|
+
return enum_for(:take_back,n) unless block_given?
|
34
|
+
enums = @seqs.map {|s| s.take_back(n) }
|
35
|
+
n.times { yield combine(enums.map {|e| e.next }) }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def combine vals
|
40
|
+
vals[1..-1].inject(vals.first,@comb_meth)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Probabilities
|
4
|
+
def self.uniform n
|
5
|
+
probs = [1/n.to_f]*n
|
6
|
+
sum = probs.inject(0,:+)
|
7
|
+
if sum != 1
|
8
|
+
probs[0] += (1 - sum)
|
9
|
+
end
|
10
|
+
return probs
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.random n
|
14
|
+
cumulative_probs = Array.new(n-1){ rand }.sort + [1]
|
15
|
+
x0 = 0
|
16
|
+
cumulative_probs.map {|x| y = x - x0; x0 = x; y }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class RandomSampler
|
4
|
+
attr_reader :values, :probabilities
|
5
|
+
def initialize vals, probs
|
6
|
+
@values, @probabilities = vals, probs
|
7
|
+
total_prob = probs.inject(0,:+)
|
8
|
+
raise ArgumentError, "Total probability is not 1" if total_prob != 1
|
9
|
+
|
10
|
+
offsets = AddingSequence.new(probs).over(1...probs.size).to_a
|
11
|
+
changes = vals[1..-1].map{|val| Change::Immediate.new(val) }
|
12
|
+
|
13
|
+
value_changes = Hash[ [offsets, changes].transpose ]
|
14
|
+
@val_comp = ValueComputer.new(vals.first, value_changes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sample n=nil
|
18
|
+
if n.nil?
|
19
|
+
return @val_comp.at(rand)
|
20
|
+
else
|
21
|
+
return Array.new(n){ @val_comp.at(rand) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class RepeatingSequence
|
4
|
+
include BiInfiniteSequence
|
5
|
+
|
6
|
+
attr_reader :start_value
|
7
|
+
def initialize pattern
|
8
|
+
raise EmptyError if pattern.empty?
|
9
|
+
@pattern = pattern
|
10
|
+
@n = pattern.size
|
11
|
+
@start_value = pattern.first
|
12
|
+
end
|
13
|
+
|
14
|
+
def pattern_size; @pattern.size; end
|
15
|
+
|
16
|
+
def next_value cur_val, cur_idx
|
17
|
+
@pattern[(cur_idx + 1) % @n]
|
18
|
+
end
|
19
|
+
def prev_value cur_val, cur_idx
|
20
|
+
@pattern[(cur_idx - 1) % @n]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/musicality/errors.rb
CHANGED
@@ -5,6 +5,8 @@ module Musicality
|
|
5
5
|
class NonIntegerError < StandardError; end
|
6
6
|
class NonRationalError < StandardError; end
|
7
7
|
class NonIncreasingError < StandardError; end
|
8
|
+
class DecreasingError < StandardError; end
|
8
9
|
class NotValidError < StandardError; end
|
9
10
|
class DomainError < StandardError; end
|
11
|
+
class EmptyError < StandardError; end
|
10
12
|
end
|
@@ -27,7 +27,7 @@ class Score
|
|
27
27
|
@parts.values.each do |part|
|
28
28
|
part.dynamic_changes.each {|moff,change| moffs += change.offsets(moff) }
|
29
29
|
end
|
30
|
-
moffs += @program.
|
30
|
+
moffs += @program.map {|seg| [seg.first, seg.last] }.flatten
|
31
31
|
return moffs.sort
|
32
32
|
end
|
33
33
|
|
@@ -25,9 +25,9 @@ class ScoreConverter
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.convert_program program, offset_map
|
28
|
-
|
28
|
+
program.map do |segment|
|
29
29
|
offset_map[segment.first]...offset_map[segment.last]
|
30
|
-
end
|
30
|
+
end
|
31
31
|
end
|
32
32
|
|
33
33
|
class TempoBased
|
@@ -44,7 +44,7 @@ class ScoreConverter
|
|
44
44
|
part.notes.each {|note| noffs.add(noff += note.duration) }
|
45
45
|
part.dynamic_changes.each {|noff2,change| noffs += change.offsets(noff2) }
|
46
46
|
end
|
47
|
-
noffs += @program.
|
47
|
+
noffs += @program.map {|seg| [seg.first, seg.last] }.flatten
|
48
48
|
return noffs.sort
|
49
49
|
end
|
50
50
|
|
@@ -10,24 +10,26 @@ class Link
|
|
10
10
|
Marshal.load(Marshal.dump(self))
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def ==(other)
|
17
|
-
self.class == other.class
|
18
|
-
end
|
19
|
-
|
20
|
-
def transpose diff
|
21
|
-
self.clone.transpose! diff
|
22
|
-
end
|
23
|
-
|
24
|
-
def transpose! diff
|
25
|
-
return self
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_s; "="; end
|
13
|
+
def transpose diff
|
14
|
+
self.clone.transpose! diff
|
29
15
|
end
|
30
16
|
|
17
|
+
def transpose! diff
|
18
|
+
return self
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
self.class == other.class
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
self.class::LINK_CHAR
|
27
|
+
end
|
28
|
+
|
29
|
+
class Tie < Link
|
30
|
+
LINK_CHAR = LINK_SYMBOLS[Links::TIE]
|
31
|
+
end
|
32
|
+
|
31
33
|
class TargetedLink < Link
|
32
34
|
attr_accessor :target_pitch
|
33
35
|
|
@@ -36,7 +38,7 @@ class Link
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def ==(other)
|
39
|
-
|
41
|
+
super && @target_pitch == other.target_pitch
|
40
42
|
end
|
41
43
|
|
42
44
|
def transpose diff
|
@@ -49,24 +51,24 @@ class Link
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def to_s
|
52
|
-
|
54
|
+
super + @target_pitch.to_s
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
56
58
|
class Glissando < TargetedLink
|
57
|
-
|
59
|
+
LINK_CHAR = LINK_SYMBOLS[Links::GLISSANDO]
|
58
60
|
end
|
59
61
|
|
60
62
|
class Portamento < TargetedLink
|
61
|
-
|
63
|
+
LINK_CHAR = LINK_SYMBOLS[Links::PORTAMENTO]
|
62
64
|
end
|
63
|
-
|
65
|
+
|
64
66
|
class Slur < TargetedLink
|
65
|
-
|
67
|
+
LINK_CHAR = LINK_SYMBOLS[Links::SLUR]
|
66
68
|
end
|
67
|
-
|
69
|
+
|
68
70
|
class Legato < TargetedLink
|
69
|
-
|
71
|
+
LINK_CHAR = LINK_SYMBOLS[Links::LEGATO]
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
@@ -22,7 +22,7 @@ class Note
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def check_methods
|
25
|
-
[ :ensure_positive_duration ]
|
25
|
+
[ :ensure_positive_duration, :check_pitches ]
|
26
26
|
end
|
27
27
|
|
28
28
|
def ensure_positive_duration
|
@@ -31,6 +31,13 @@ class Note
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
def check_pitches
|
35
|
+
non_pitches = @pitches.select {|p| !p.is_a?(Pitch) }
|
36
|
+
if non_pitches.any?
|
37
|
+
raise TypeError, "Found non-pitches: #{non_pitches}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
34
41
|
def == other
|
35
42
|
return (@duration == other.duration) &&
|
36
43
|
(self.pitches == other.pitches) &&
|
@@ -73,17 +80,7 @@ class Note
|
|
73
80
|
else
|
74
81
|
dur_str = d.to_s
|
75
82
|
end
|
76
|
-
|
77
|
-
art_str = case @articulation
|
78
|
-
when Articulations::SLUR then "="
|
79
|
-
when Articulations::LEGATO then "|"
|
80
|
-
when Articulations::TENUTO then "_"
|
81
|
-
when Articulations::PORTATO then "%"
|
82
|
-
when Articulations::STACCATO then "."
|
83
|
-
when Articulations::STACCATISSIMO then "'"
|
84
|
-
else ""
|
85
|
-
end
|
86
|
-
|
83
|
+
|
87
84
|
pitch_links_str = @pitches.map do |p|
|
88
85
|
if @links.has_key?(p)
|
89
86
|
p.to_s + @links[p].to_s
|
@@ -91,9 +88,11 @@ class Note
|
|
91
88
|
p.to_s
|
92
89
|
end
|
93
90
|
end.join(",")
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
|
92
|
+
art_str = ARTICULATION_SYMBOLS[@articulation] || ""
|
93
|
+
acc_str = @accented ? ACCENT_SYMBOL : ""
|
94
|
+
|
95
|
+
return dur_str + pitch_links_str + art_str + acc_str
|
97
96
|
end
|
98
97
|
|
99
98
|
def self.add_note_method(name, dur)
|
@@ -16,7 +16,7 @@ class Part
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def check_methods
|
19
|
-
[:
|
19
|
+
[:check_start_dynamic, :check_dynamic_changes]
|
20
20
|
end
|
21
21
|
|
22
22
|
def validatables
|
@@ -37,13 +37,13 @@ class Part
|
|
37
37
|
return @notes.inject(0) { |sum, note| sum + note.duration }
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
40
|
+
def check_start_dynamic
|
41
41
|
unless @start_dynamic.between?(0,1)
|
42
42
|
raise RangeError, "start dynamic #{@start_dynamic} is not between 0 and 1"
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def check_dynamic_changes
|
47
47
|
outofrange = @dynamic_changes.values.select {|v| !v.end_value.between?(0,1) }
|
48
48
|
if outofrange.any?
|
49
49
|
raise RangeError, "dynamic change values #{outofrange} are not between 0 and 1"
|
@@ -96,6 +96,14 @@ class Pitch
|
|
96
96
|
Pitch.new(cent: (@total_cents + semitones * CENTS_PER_SEMITONE).round)
|
97
97
|
end
|
98
98
|
|
99
|
+
def + semitones
|
100
|
+
transpose(semitones)
|
101
|
+
end
|
102
|
+
|
103
|
+
def - semitones
|
104
|
+
transpose(-semitones)
|
105
|
+
end
|
106
|
+
|
99
107
|
def total_semitones
|
100
108
|
Rational(@total_cents, CENTS_PER_SEMITONE)
|
101
109
|
end
|
@@ -4,15 +4,14 @@ class Score
|
|
4
4
|
include Validatable
|
5
5
|
attr_accessor :parts, :program
|
6
6
|
|
7
|
-
def initialize parts: {}, program:
|
7
|
+
def initialize parts: {}, program: []
|
8
8
|
@parts = parts
|
9
9
|
@program = program
|
10
10
|
yield(self) if block_given?
|
11
11
|
end
|
12
12
|
|
13
|
-
def validatables
|
14
|
-
|
15
|
-
end
|
13
|
+
def validatables; @parts.values; end
|
14
|
+
def check_methods; [:check_program, :check_parts ]; end
|
16
15
|
|
17
16
|
def clone
|
18
17
|
Marshal.load(Marshal.dump(self))
|
@@ -27,7 +26,7 @@ class Score
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def collated?
|
30
|
-
@program.
|
29
|
+
@program.size == 1 && @program[0].first == 0
|
31
30
|
end
|
32
31
|
|
33
32
|
class Timed < Score
|
@@ -39,16 +38,14 @@ class Score
|
|
39
38
|
class TempoBased < Score
|
40
39
|
attr_accessor :start_tempo, :tempo_changes
|
41
40
|
|
42
|
-
def initialize start_tempo, tempo_changes: {}, parts: {}, program:
|
41
|
+
def initialize start_tempo, tempo_changes: {}, parts: {}, program: []
|
43
42
|
@start_tempo = start_tempo
|
44
43
|
@tempo_changes = tempo_changes
|
45
44
|
super(parts: parts, program: program)
|
46
|
-
|
47
|
-
yield(self) if block_given?
|
48
45
|
end
|
49
46
|
|
50
47
|
def check_methods
|
51
|
-
[:check_start_tempo, :check_tempo_changes]
|
48
|
+
super() + [:check_start_tempo, :check_tempo_changes]
|
52
49
|
end
|
53
50
|
|
54
51
|
def ==(other)
|
@@ -60,16 +57,23 @@ class Score
|
|
60
57
|
self.duration
|
61
58
|
end
|
62
59
|
|
60
|
+
private
|
61
|
+
|
63
62
|
def check_start_tempo
|
64
63
|
if @start_tempo <= 0
|
65
|
-
raise NonPositiveError, "
|
64
|
+
raise NonPositiveError, "Start tempo (#{@start_tempo}) is not positive"
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
69
68
|
def check_tempo_changes
|
69
|
+
badtypes = @tempo_changes.select {|k,v| !v.end_value.is_a?(Numeric) }
|
70
|
+
if badtypes.any?
|
71
|
+
raise TypeError, "Found non-numeric tempo change values: #{badtypes}"
|
72
|
+
end
|
73
|
+
|
70
74
|
badvalues = @tempo_changes.select {|k,v| v.end_value <= 0 }
|
71
75
|
if badvalues.any?
|
72
|
-
raise NonPositiveError, "tempo changes
|
76
|
+
raise NonPositiveError, "Found non-positive tempo changes values: #{badvalues}"
|
73
77
|
end
|
74
78
|
end
|
75
79
|
end
|
@@ -84,58 +88,29 @@ class Score
|
|
84
88
|
class Measured < Score::TempoBased
|
85
89
|
attr_accessor :start_meter, :meter_changes
|
86
90
|
|
87
|
-
def initialize start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program:
|
91
|
+
def initialize start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program: []
|
88
92
|
@start_meter = start_meter
|
89
93
|
@meter_changes = meter_changes
|
90
94
|
|
91
95
|
super(start_tempo, tempo_changes: tempo_changes,
|
92
96
|
program: program, parts: parts)
|
93
|
-
yield(self) if block_given?
|
94
97
|
end
|
95
98
|
|
96
99
|
def check_methods
|
97
|
-
super() + [:
|
98
|
-
:check_meterchange_durs, :check_meterchange_offsets]
|
100
|
+
super() + [:check_start_meter, :check_meter_changes]
|
99
101
|
end
|
100
102
|
|
101
103
|
def validatables
|
102
104
|
super() + [ @start_meter ] + @meter_changes.values.map {|v| v.end_value}
|
103
105
|
end
|
104
106
|
|
105
|
-
def check_startmeter_type
|
106
|
-
unless @start_meter.is_a? Meter
|
107
|
-
raise TypeError, "start meter #{@start_meter} is not a Meter object"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def check_meterchange_types
|
112
|
-
badtypes = @meter_changes.select {|k,v| !v.end_value.is_a?(Meter) }
|
113
|
-
if badtypes.any?
|
114
|
-
raise TypeError, "meter change values #{nonmeter_values} are not Meter objects"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def check_meterchange_offsets
|
119
|
-
badoffsets = @meter_changes.select {|k,v| k != k.to_i }
|
120
|
-
if badoffsets.any?
|
121
|
-
raise NonIntegerError, "meter changes #{badoffsets} have non-integer offsets"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def check_meterchange_durs
|
126
|
-
nonzero_duration = @meter_changes.select {|k,v| !v.is_a?(Change::Immediate) }
|
127
|
-
if nonzero_duration.any?
|
128
|
-
raise NonZeroError, "meter changes #{nonzero_duration} are not immediate"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
107
|
def ==(other)
|
133
108
|
return super(other) && @start_meter == other.start_meter &&
|
134
109
|
@meter_changes == other.meter_changes
|
135
110
|
end
|
136
111
|
|
137
|
-
def measures_long
|
138
|
-
noff_end =
|
112
|
+
def measures_long note_dur = self.notes_long
|
113
|
+
noff_end = note_dur
|
139
114
|
noff_prev = 0.to_r
|
140
115
|
moff_prev, mdur_prev = 0.to_r, @start_meter.measure_duration
|
141
116
|
|
@@ -154,6 +129,57 @@ class Score
|
|
154
129
|
end
|
155
130
|
return moff_prev + Rational(noff_end - noff_prev, mdur_prev)
|
156
131
|
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def check_start_meter
|
136
|
+
unless @start_meter.is_a? Meter
|
137
|
+
raise TypeError, "Start meter #{@start_meter} is not a Meter object"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_meter_changes
|
142
|
+
badtypes = @meter_changes.select {|k,v| !v.end_value.is_a?(Meter) }
|
143
|
+
if badtypes.any?
|
144
|
+
raise TypeError, "Found meter change values that are not Meter objects: #{nonmeter_values}"
|
145
|
+
end
|
146
|
+
|
147
|
+
badoffsets = @meter_changes.select {|k,v| k != k.to_i }
|
148
|
+
if badoffsets.any?
|
149
|
+
raise NonIntegerError, "Found meter changes at non-integer offsets: #{badoffsets}"
|
150
|
+
end
|
151
|
+
|
152
|
+
nonzero_duration = @meter_changes.select {|k,v| !v.is_a?(Change::Immediate) }
|
153
|
+
if nonzero_duration.any?
|
154
|
+
raise NonZeroError, "Found meter changes that are not immediate: #{nonzero_duration}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def check_program
|
162
|
+
non_ranges = @program.select {|x| !x.is_a?(Range) }
|
163
|
+
if non_ranges.any?
|
164
|
+
raise TypeError, "Non-Range program element(s) found: #{non_ranges}"
|
165
|
+
end
|
166
|
+
|
167
|
+
non_increasing = @program.select {|seg| seg.first >= seg.last }
|
168
|
+
if non_increasing.any?
|
169
|
+
raise NonIncreasingError, "Non-increasing program range(s) found: #{non_increasing}"
|
170
|
+
end
|
171
|
+
|
172
|
+
negative = @program.select {|seg| seg.first < 0 || seg.last < 0 }
|
173
|
+
if negative.any?
|
174
|
+
raise NegativeError, "Program range(s) with negative value(s) found: #{negative}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_parts
|
179
|
+
non_parts = @parts.values.select {|x| !x.is_a?(Part) }
|
180
|
+
if non_parts.any?
|
181
|
+
raise TypeError, "Non-Part part value(s) found: #{non_parts}"
|
182
|
+
end
|
157
183
|
end
|
158
184
|
end
|
159
185
|
|