head_music 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/head_music/accidental.rb +0 -1
- data/lib/head_music/{measure.rb → bar.rb} +3 -3
- data/lib/head_music/circle.rb +0 -1
- data/lib/head_music/composition.rb +8 -8
- data/lib/head_music/consonance.rb +0 -1
- data/lib/head_music/functional_interval.rb +37 -8
- data/lib/head_music/interval.rb +0 -1
- data/lib/head_music/letter_name.rb +0 -1
- data/lib/head_music/melodic_interval.rb +29 -0
- data/lib/head_music/meter.rb +6 -6
- data/lib/head_music/note.rb +11 -7
- data/lib/head_music/octave.rb +0 -1
- data/lib/head_music/pitch.rb +0 -1
- data/lib/head_music/pitch_class.rb +0 -1
- data/lib/head_music/placement.rb +6 -1
- data/lib/head_music/position.rb +31 -13
- data/lib/head_music/quality.rb +0 -1
- data/lib/head_music/rhythmic_unit.rb +0 -1
- data/lib/head_music/rhythmic_value.rb +33 -0
- data/lib/head_music/scale_type.rb +0 -1
- data/lib/head_music/spelling.rb +0 -1
- data/lib/head_music/style/analysis.rb +17 -0
- data/lib/head_music/style/annotation.rb +28 -0
- data/lib/head_music/style/mark.rb +29 -0
- data/lib/head_music/style/rule.rb +13 -0
- data/lib/head_music/style/rules/always_move.rb +19 -0
- data/lib/head_music/style/rules/at_least_eight_notes.rb +26 -0
- data/lib/head_music/style/rules/diatonic.rb +15 -0
- data/lib/head_music/style/rules/end_on_tonic.rb +27 -0
- data/lib/head_music/style/rules/limit_range.rb +31 -0
- data/lib/head_music/style/rules/mostly_conjunct.rb +30 -0
- data/lib/head_music/style/rules/no_rests.rb +14 -0
- data/lib/head_music/style/rules/notes_same_length.rb +49 -0
- data/lib/head_music/style/rules/start_on_tonic.rb +27 -0
- data/lib/head_music/style/rules/step_down_to_final_note.rb +34 -0
- data/lib/head_music/style/rules/up_to_thirteen_notes.rb +24 -0
- data/lib/head_music/style/rulesets/cantus_firmus.rb +22 -0
- data/lib/head_music/utilities/hash_key.rb +2 -2
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music/voice.rb +48 -3
- data/lib/head_music.rb +27 -4
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3c4a7d9f36e3a577a3b3e15dde8e3b9b8719937
|
4
|
+
data.tar.gz: dc196e99e9f17077d7135fb17047a07a8fee13be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fa70d04a30f7f652420d88224192f4a2dd89a8436a363a3306129336d3a61a390ecd0d4c8baf2a7ca9cd05798dc4bbd09e0969bf0107335a7adaa3a3ae7f195
|
7
|
+
data.tar.gz: 95a836da90aa931b77935d7558c2fb6710b9b559ec69578b39451655551e058bfa19ef68fdea8541dbb24912011d34490d61e34f9baf4eff65f22fd8fec48f85
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class HeadMusic::
|
1
|
+
class HeadMusic::Bar
|
2
2
|
attr_reader :composition
|
3
3
|
|
4
4
|
delegate :key_signature, :meter, to: :composition
|
@@ -8,7 +8,7 @@ class HeadMusic::Measure
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# TODO: encapsulate key changes and meter changes
|
11
|
-
# Assume the key and meter of the previous
|
12
|
-
# all the way back to the first
|
11
|
+
# Assume the key and meter of the previous bar
|
12
|
+
# all the way back to the first bar,
|
13
13
|
# which defaults to the key and meter of the composition
|
14
14
|
end
|
data/lib/head_music/circle.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
class HeadMusic::Composition
|
2
|
-
attr_reader :name, :key_signature, :meter, :
|
2
|
+
attr_reader :name, :key_signature, :meter, :bars, :voices
|
3
3
|
|
4
4
|
def initialize(name:, key_signature: nil, meter: nil)
|
5
5
|
ensure_attributes(name, key_signature, meter)
|
6
|
-
|
6
|
+
add_bar
|
7
7
|
add_voice
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
10
|
+
def add_bar
|
11
|
+
add_bars(1)
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
@
|
14
|
+
def add_bars(number)
|
15
|
+
@bars ||= []
|
16
16
|
number.times do
|
17
|
-
@
|
17
|
+
@bars << HeadMusic::Bar.new(self)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def add_voice
|
22
22
|
@voices ||= []
|
23
|
-
@voices << HeadMusic::Voice.new(self)
|
23
|
+
@voices << HeadMusic::Voice.new(composition: self)
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -22,10 +22,9 @@ class HeadMusic::FunctionalInterval
|
|
22
22
|
seventeenth: { major: 28 }
|
23
23
|
}
|
24
24
|
|
25
|
-
attr_reader :lower_pitch, :higher_pitch
|
25
|
+
attr_reader :lower_pitch, :higher_pitch, :direction
|
26
26
|
|
27
27
|
delegate :to_s, to: :name
|
28
|
-
delegate :==, to: :to_s
|
29
28
|
delegate :perfect?, :major?, :minor?, :diminished?, :augmented?, :doubly_diminished?, :doubly_augmented?, to: :quality
|
30
29
|
|
31
30
|
def self.get(name)
|
@@ -38,7 +37,6 @@ class HeadMusic::FunctionalInterval
|
|
38
37
|
higher_pitch = HeadMusic::Pitch.from_number_and_letter(lower_pitch + semitones, higher_letter)
|
39
38
|
new(lower_pitch, higher_pitch)
|
40
39
|
end
|
41
|
-
singleton_class.send(:alias_method, :[], :get)
|
42
40
|
|
43
41
|
def self.degree_quality_semitones
|
44
42
|
@degree_quality_semitones ||= begin
|
@@ -62,7 +60,29 @@ class HeadMusic::FunctionalInterval
|
|
62
60
|
end
|
63
61
|
|
64
62
|
def initialize(pitch1, pitch2)
|
65
|
-
|
63
|
+
pitch1 = HeadMusic::Pitch.get(pitch1)
|
64
|
+
pitch2 = HeadMusic::Pitch.get(pitch2)
|
65
|
+
set_direction(pitch1, pitch2)
|
66
|
+
@lower_pitch, @higher_pitch = [pitch1, pitch2].sort
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_direction(pitch1, pitch2)
|
70
|
+
@direction =
|
71
|
+
if pitch1 == pitch2
|
72
|
+
:none
|
73
|
+
elsif pitch1 < pitch2
|
74
|
+
:ascending
|
75
|
+
else
|
76
|
+
:descending
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def descending?
|
81
|
+
direction == :descending
|
82
|
+
end
|
83
|
+
|
84
|
+
def ascending?
|
85
|
+
direction == :ascending
|
66
86
|
end
|
67
87
|
|
68
88
|
def number
|
@@ -108,7 +128,8 @@ class HeadMusic::FunctionalInterval
|
|
108
128
|
end
|
109
129
|
|
110
130
|
def shorthand
|
111
|
-
|
131
|
+
step_shorthand = number == 1 ? 'U' : number
|
132
|
+
[quality.shorthand, step_shorthand].join
|
112
133
|
end
|
113
134
|
|
114
135
|
def quality
|
@@ -157,12 +178,20 @@ class HeadMusic::FunctionalInterval
|
|
157
178
|
end
|
158
179
|
end
|
159
180
|
|
181
|
+
def step?
|
182
|
+
number <= 2
|
183
|
+
end
|
184
|
+
|
160
185
|
def skip?
|
161
|
-
number
|
186
|
+
number == 3
|
162
187
|
end
|
163
188
|
|
164
|
-
def
|
165
|
-
number
|
189
|
+
def leap?
|
190
|
+
number > 3
|
191
|
+
end
|
192
|
+
|
193
|
+
def ==(other)
|
194
|
+
self.to_s.gsub(/\W/, '_') == other.to_s.gsub(/\W/, '_')
|
166
195
|
end
|
167
196
|
|
168
197
|
NUMBER_NAMES.each do |method_name|
|
data/lib/head_music/interval.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
class HeadMusic::MelodicInterval
|
2
|
+
attr_reader :voice, :first_note, :second_note
|
3
|
+
|
4
|
+
def initialize(voice, note1, note2)
|
5
|
+
@voice = voice
|
6
|
+
@first_note = note1
|
7
|
+
@second_note = note2
|
8
|
+
end
|
9
|
+
|
10
|
+
def functional_interval
|
11
|
+
@functional_interval ||= HeadMusic::FunctionalInterval.new(first_note.pitch, second_note.pitch)
|
12
|
+
end
|
13
|
+
|
14
|
+
def position_start
|
15
|
+
first_note.position
|
16
|
+
end
|
17
|
+
|
18
|
+
def position_end
|
19
|
+
second_note.next_position
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
[direction, functional_interval].join(' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method_name, *args, &block)
|
27
|
+
functional_interval.send(method_name, *args, &block)
|
28
|
+
end
|
29
|
+
end
|
data/lib/head_music/meter.rb
CHANGED
@@ -50,11 +50,11 @@ class HeadMusic::Meter
|
|
50
50
|
top_number == 4
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
53
|
+
def beats_per_bar
|
54
54
|
compound? ? top_number / 3 : top_number
|
55
55
|
end
|
56
56
|
|
57
|
-
def
|
57
|
+
def counts_per_bar
|
58
58
|
top_number
|
59
59
|
end
|
60
60
|
|
@@ -101,12 +101,12 @@ class HeadMusic::Meter
|
|
101
101
|
|
102
102
|
def strong_counts
|
103
103
|
@strong_counts ||= begin
|
104
|
-
(1..
|
104
|
+
(1..counts_per_bar).select do |count|
|
105
105
|
count == 1 ||
|
106
|
-
count ==
|
106
|
+
count == counts_per_bar / 2.0 + 1 ||
|
107
107
|
(
|
108
|
-
|
109
|
-
|
108
|
+
counts_per_bar % 3 == 0 &&
|
109
|
+
counts_per_bar > 6 &&
|
110
110
|
count % 3 == 1
|
111
111
|
)
|
112
112
|
end
|
data/lib/head_music/note.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
class HeadMusic::Note
|
2
|
-
|
2
|
+
attr_accessor :pitch, :rhythmic_value, :voice, :position
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(pitch, rhythmic_unit, rhythmic_value_modifiers = {})
|
4
|
+
def initialize(pitch, rhythmic_value, voice = nil, position = nil)
|
7
5
|
@pitch = HeadMusic::Pitch.get(pitch)
|
8
|
-
@rhythmic_value = HeadMusic::RhythmicValue.
|
6
|
+
@rhythmic_value = HeadMusic::RhythmicValue.get(rhythmic_value)
|
7
|
+
@voice = voice || Voice.new
|
8
|
+
@position = position || HeadMusic::Position.new(@voice.composition, '1:1')
|
9
|
+
end
|
10
|
+
|
11
|
+
def placement
|
12
|
+
@placement ||= HeadMusic::Placement.new(voice, position, rhythmic_value, pitch)
|
9
13
|
end
|
10
14
|
|
11
|
-
def
|
12
|
-
|
15
|
+
def method_missing(method_name, *args, &block)
|
16
|
+
placement.send(method_name, *args, &block)
|
13
17
|
end
|
14
18
|
end
|
data/lib/head_music/octave.rb
CHANGED
@@ -8,7 +8,6 @@ class HeadMusic::Octave
|
|
8
8
|
def self.get(identifier)
|
9
9
|
from_number(identifier) || from_name(identifier) || default
|
10
10
|
end
|
11
|
-
singleton_class.send(:alias_method, :[], :get)
|
12
11
|
|
13
12
|
def self.from_number(identifier)
|
14
13
|
return nil unless identifier.to_s == identifier.to_i.to_s
|
data/lib/head_music/pitch.rb
CHANGED
data/lib/head_music/placement.rb
CHANGED
@@ -3,8 +3,9 @@ class HeadMusic::Placement
|
|
3
3
|
|
4
4
|
attr_reader :voice, :position, :rhythmic_value, :pitch
|
5
5
|
delegate :composition, to: :voice
|
6
|
+
delegate :spelling, to: :pitch, allow_nil: true
|
6
7
|
|
7
|
-
def initialize(voice, position, rhythmic_value, pitch)
|
8
|
+
def initialize(voice, position, rhythmic_value, pitch = nil)
|
8
9
|
ensure_attributes(voice, position, rhythmic_value, pitch)
|
9
10
|
end
|
10
11
|
|
@@ -16,6 +17,10 @@ class HeadMusic::Placement
|
|
16
17
|
!note?
|
17
18
|
end
|
18
19
|
|
20
|
+
def next_position
|
21
|
+
position + rhythmic_value
|
22
|
+
end
|
23
|
+
|
19
24
|
def <=>(other)
|
20
25
|
self.position <=> other.position
|
21
26
|
end
|
data/lib/head_music/position.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
class HeadMusic::Position
|
2
2
|
include Comparable
|
3
3
|
|
4
|
-
attr_reader :composition, :
|
4
|
+
attr_reader :composition, :bar_number, :count, :tick
|
5
5
|
delegate :to_s, to: :code
|
6
6
|
delegate :meter, to: :composition
|
7
7
|
|
8
|
-
def initialize(composition,
|
9
|
-
if
|
10
|
-
|
8
|
+
def initialize(composition, code_or_bar, count = nil, tick = nil)
|
9
|
+
if code_or_bar.is_a?(String) && code_or_bar =~ /\D/
|
10
|
+
bar, count, tick = code_or_bar.split(/\D+/)
|
11
|
+
ensure_state(composition, bar, count, tick)
|
11
12
|
else
|
12
|
-
ensure_state(composition,
|
13
|
+
ensure_state(composition, code_or_bar, count, tick)
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
def code
|
17
|
-
|
18
|
+
tick_string = tick.to_s.rjust(3, '0')
|
19
|
+
[bar_number, count, tick_string].join(':')
|
18
20
|
end
|
19
21
|
|
20
22
|
def state
|
@@ -22,10 +24,13 @@ class HeadMusic::Position
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def values
|
25
|
-
[
|
27
|
+
[bar_number, count, tick]
|
26
28
|
end
|
27
29
|
|
28
30
|
def <=>(other)
|
31
|
+
if other.is_a?(String) && other =~ /\D/
|
32
|
+
other = self.class.new(composition, other)
|
33
|
+
end
|
29
34
|
self.values <=> other.values
|
30
35
|
end
|
31
36
|
|
@@ -41,11 +46,22 @@ class HeadMusic::Position
|
|
41
46
|
!strong?
|
42
47
|
end
|
43
48
|
|
49
|
+
def +(rhythmic_value)
|
50
|
+
if [HeadMusic::RhythmicUnit, Symbol, String].include?(rhythmic_value.class)
|
51
|
+
rhythmic_value = HeadMusic::RhythmicValue.new(rhythmic_value)
|
52
|
+
end
|
53
|
+
self.class.new(composition, bar_number, count, tick + rhythmic_value.ticks)
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_of_next_bar
|
57
|
+
self.class.new(composition, bar_number + 1, 1, 0)
|
58
|
+
end
|
59
|
+
|
44
60
|
private
|
45
61
|
|
46
|
-
def ensure_state(composition,
|
62
|
+
def ensure_state(composition, bar_number, count, tick = nil)
|
47
63
|
@composition = composition
|
48
|
-
@
|
64
|
+
@bar_number = bar_number.to_i
|
49
65
|
@count = (count || 1).to_i
|
50
66
|
@tick = (tick || 0).to_i
|
51
67
|
roll_over_units
|
@@ -57,16 +73,18 @@ class HeadMusic::Position
|
|
57
73
|
end
|
58
74
|
|
59
75
|
def roll_over_ticks
|
60
|
-
|
76
|
+
# TODO account for meter changes in bars
|
77
|
+
while @tick >= meter.ticks_per_count
|
61
78
|
@tick -= meter.ticks_per_count.to_i
|
62
79
|
@count += 1
|
63
80
|
end
|
64
81
|
end
|
65
82
|
|
66
83
|
def roll_over_counts
|
67
|
-
|
68
|
-
|
69
|
-
@
|
84
|
+
# TODO account for meter changes in bars
|
85
|
+
while @count > meter.counts_per_bar
|
86
|
+
@count -= meter.counts_per_bar
|
87
|
+
@bar_number += 1
|
70
88
|
end
|
71
89
|
end
|
72
90
|
end
|
data/lib/head_music/quality.rb
CHANGED
@@ -31,7 +31,6 @@ class HeadMusic::Quality
|
|
31
31
|
identifier = identifier.to_s.to_sym
|
32
32
|
@qualities[identifier] ||= new(identifier) if NAMES.include?(identifier)
|
33
33
|
end
|
34
|
-
singleton_class.send(:alias_method, :[], :get)
|
35
34
|
|
36
35
|
def self.from(starting_quality, delta)
|
37
36
|
if starting_quality == :perfect
|
@@ -10,7 +10,6 @@ class HeadMusic::RhythmicUnit
|
|
10
10
|
hash_key = HeadMusic::Utilities::HashKey.for(name)
|
11
11
|
@rhythmic_units[hash_key] ||= new(name.to_s)
|
12
12
|
end
|
13
|
-
singleton_class.send(:alias_method, :[], :get)
|
14
13
|
|
15
14
|
def self.for_denominator_value(denominator)
|
16
15
|
get(FRACTIONS[Math.log2(denominator).to_i])
|
@@ -4,6 +4,39 @@ class HeadMusic::RhythmicValue
|
|
4
4
|
delegate :name, to: :unit, prefix: true
|
5
5
|
delegate :to_s, to: :name
|
6
6
|
|
7
|
+
def self.get(identifier)
|
8
|
+
case identifier
|
9
|
+
when RhythmicValue
|
10
|
+
identifier
|
11
|
+
when RhythmicUnit
|
12
|
+
new(identifier)
|
13
|
+
when Symbol, String
|
14
|
+
identifier = identifier.to_s.downcase.strip.gsub(/\W+/, '_')
|
15
|
+
from_words(identifier)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_words(identifier)
|
20
|
+
new(unit_from_words(identifier), dots: dots_from_words(identifier))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.unit_from_words(identifier)
|
24
|
+
identifier.gsub(/^\w*dotted_/, '')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.dots_from_words(identifier)
|
28
|
+
return 0 unless identifier =~ /dotted/
|
29
|
+
modifier, _ = identifier.split(/_*dotted_*/)
|
30
|
+
case modifier
|
31
|
+
when /tripl\w/
|
32
|
+
3
|
33
|
+
when /doubl\w/
|
34
|
+
2
|
35
|
+
else
|
36
|
+
1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
7
40
|
def initialize(unit, dots: nil, tied_value: nil)
|
8
41
|
@unit = HeadMusic::RhythmicUnit.get(unit)
|
9
42
|
@dots = [0, 1, 2, 3].include?(dots) ? dots : 0
|
data/lib/head_music/spelling.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module HeadMusic
|
2
|
+
module Style
|
3
|
+
class Analysis
|
4
|
+
attr_reader :ruleset, :subject, :annotations
|
5
|
+
|
6
|
+
def initialize(ruleset, subject)
|
7
|
+
@ruleset = ruleset
|
8
|
+
@subject = subject
|
9
|
+
@annotations = @ruleset.analyze(subject)
|
10
|
+
end
|
11
|
+
|
12
|
+
def fitness
|
13
|
+
annotations.map(&:fitness).reduce(1, :*)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class HeadMusic::Style::Annotation
|
2
|
+
attr_reader :fitness, :message, :marks, :subject
|
3
|
+
|
4
|
+
def initialize(subject:, fitness:, message: nil, marks: nil)
|
5
|
+
@subject = subject
|
6
|
+
@fitness = fitness
|
7
|
+
@message = message
|
8
|
+
@marks = [marks].flatten.compact
|
9
|
+
end
|
10
|
+
|
11
|
+
def voice
|
12
|
+
subject if subject.is_a?(HeadMusic::Voice)
|
13
|
+
end
|
14
|
+
|
15
|
+
def composition
|
16
|
+
voice ? voice.composition : subject
|
17
|
+
end
|
18
|
+
|
19
|
+
def marks_count
|
20
|
+
marks ? marks.length : 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def first_mark_code
|
24
|
+
marks.first.code if marks.first
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :to_s, :message
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class HeadMusic::Style::Mark
|
2
|
+
attr_reader :start_position, :end_position, :placements
|
3
|
+
|
4
|
+
def self.for(placement)
|
5
|
+
new(placement.position, placement.next_position, placement)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.for_all(placements)
|
9
|
+
placements = [placements].flatten
|
10
|
+
start_position = placements.map { |placement| placement.position }.sort.first
|
11
|
+
end_position = placements.map { |placement| placement.next_position }.sort.last
|
12
|
+
new(start_position, end_position, placements)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.for_each(placements)
|
16
|
+
placements = [placements].flatten
|
17
|
+
placements.map { |placement| new(placement.position, placement.next_position, placement) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(start_position, end_position, placements = [])
|
21
|
+
@start_position = start_position
|
22
|
+
@end_position = end_position
|
23
|
+
@placements = [placements].flatten
|
24
|
+
end
|
25
|
+
|
26
|
+
def code
|
27
|
+
[start_position, end_position].join(' to ')
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class HeadMusic::Style::Rule
|
2
|
+
# returns a score between 0 and 1
|
3
|
+
# Note: absence of a problem or 'not applicable' should score as a 1.
|
4
|
+
# for example, if the rule is to end on the tonic,
|
5
|
+
# a composition with no notes should count as a 1.
|
6
|
+
def fitness(object)
|
7
|
+
raise NotImplementedError, 'A fitness method is required for all style rules.'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.annotations(object)
|
11
|
+
raise NotImplementedError, 'An annotations method is required for all style rules.'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::AlwaysMove < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
marks = marks(voice)
|
7
|
+
fitness = HeadMusic::GOLDEN_RATIO_INVERSE**marks.length
|
8
|
+
message = "Always move to another note." if fitness < 1
|
9
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.marks(voice)
|
13
|
+
voice.melodic_intervals.map.with_index do |interval, i|
|
14
|
+
if interval.shorthand == 'PU'
|
15
|
+
HeadMusic::Style::Mark.for_all(voice.notes[i..i+1])
|
16
|
+
end
|
17
|
+
end.reject(&:nil?)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::AtLeastEightNotes < HeadMusic::Style::Rule
|
5
|
+
MINIMUM_NOTES = 8
|
6
|
+
|
7
|
+
def self.analyze(voice)
|
8
|
+
fitness = fitness(voice)
|
9
|
+
mark = mark(voice)
|
10
|
+
message = "Add notes until you have at least eight notes." if fitness < 1
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: mark, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
deficiency = MINIMUM_NOTES - voice.notes.length
|
16
|
+
deficiency > 0 ? HeadMusic::GOLDEN_RATIO_INVERSE**deficiency : 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.mark(voice)
|
20
|
+
if voice.placements.empty?
|
21
|
+
Style::Mark.new(Position.new(voice.composition, "1:1"), Position.new(voice.composition, "2:1"))
|
22
|
+
else
|
23
|
+
Style::Mark.for_all(voice.placements)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::Diatonic < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
marks = marks(voice)
|
7
|
+
fitness = HeadMusic::GOLDEN_RATIO_INVERSE**marks.length
|
8
|
+
message = "Use only notes in the key signature." if fitness < 1
|
9
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.marks(voice)
|
13
|
+
HeadMusic::Style::Mark.for_each(voice.notes_not_in_key)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::EndOnTonic < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
fitness = fitness(voice)
|
7
|
+
if fitness < 1
|
8
|
+
message = 'End on the tonic'
|
9
|
+
mark = HeadMusic::Style::Mark.for(voice.notes.last)
|
10
|
+
end
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: mark, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
return 1 if voice.notes.empty?
|
16
|
+
return 1 if ends_on_tonic?(voice)
|
17
|
+
HeadMusic::GOLDEN_RATIO_INVERSE
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.ends_on_tonic?(voice)
|
21
|
+
voice.notes &&
|
22
|
+
voice.notes.last &&
|
23
|
+
voice.composition &&
|
24
|
+
voice.composition.key_signature &&
|
25
|
+
voice.composition.key_signature.tonic_spelling == voice.notes.last.spelling
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::LimitRange < HeadMusic::Style::Rule
|
5
|
+
MAXIMUM_RANGE = 10
|
6
|
+
|
7
|
+
def self.analyze(voice)
|
8
|
+
fitness = fitness(voice)
|
9
|
+
if fitness < 1
|
10
|
+
message = 'Limit melodic range to a 10th.'
|
11
|
+
marks = marks(voice)
|
12
|
+
end
|
13
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.fitness(voice)
|
17
|
+
return 1 unless voice.notes.length > 0
|
18
|
+
HeadMusic::GOLDEN_RATIO_INVERSE**overage(voice)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.overage(voice)
|
22
|
+
voice.notes.length > 0 ? [voice.range.number - MAXIMUM_RANGE, 0].max : 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.marks(voice)
|
26
|
+
if voice.notes
|
27
|
+
extremes = (voice.highest_notes + voice.lowest_notes).sort
|
28
|
+
HeadMusic::Style::Mark.for_each(extremes)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::MostlyConjunct < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
fitness = fitness(voice)
|
7
|
+
if fitness < 1
|
8
|
+
marks = marks(voice)
|
9
|
+
message = "Use only notes in the key signature."
|
10
|
+
end
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
intervals = voice.melodic_intervals.length
|
16
|
+
steps = voice.melodic_intervals.count { |interval| interval.step? }
|
17
|
+
fitness = 1
|
18
|
+
fitness *= HeadMusic::GOLDEN_RATIO_INVERSE if steps.to_f / intervals < 0.5
|
19
|
+
fitness *= HeadMusic::GOLDEN_RATIO_INVERSE if steps.to_f / intervals < 0.25
|
20
|
+
fitness
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.marks(voice)
|
24
|
+
voice.melodic_intervals.map.with_index do |interval, i|
|
25
|
+
if !interval.step?
|
26
|
+
HeadMusic::Style::Mark.for_all(voice.notes[i..i+1])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::NoRests < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
rests = voice.rests
|
7
|
+
fitness = HeadMusic::GOLDEN_RATIO_INVERSE**rests.length
|
8
|
+
if rests.length > 0
|
9
|
+
message = "Change rests to notes."
|
10
|
+
marks = rests.map { |rest| HeadMusic::Style::Mark.for_all(rest) }
|
11
|
+
end
|
12
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::NotesSameLength < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
fitness = fitness(voice)
|
7
|
+
if fitness < 1
|
8
|
+
message = "Use consistent rhythmic unit."
|
9
|
+
end
|
10
|
+
marks = marks(voice)
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: marks, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
distinct_values = [distinct_values(voice), 1].max
|
16
|
+
HeadMusic::GOLDEN_RATIO_INVERSE**(distinct_values-1)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.distinct_values(voice)
|
20
|
+
voice.notes[0..-2].map(&:rhythmic_value).uniq.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.marks(voice)
|
24
|
+
preferred_value = first_most_common_rhythmic_value(voice)
|
25
|
+
wrong_length_notes = voice.notes.select { |note| note.rhythmic_value != preferred_value }
|
26
|
+
HeadMusic::Style::Mark.for_each(wrong_length_notes)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.first_most_common_rhythmic_value(voice)
|
30
|
+
candidates = most_common_rhythmic_values(voice)
|
31
|
+
first_match = voice.notes.detect { |note| candidates.include?(note.rhythmic_value) }
|
32
|
+
first_match ? first_match.rhythmic_value : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.most_common_rhythmic_values(voice)
|
36
|
+
return [] if voice.notes.empty?
|
37
|
+
occurrences = occurrences_by_rhythmic_value(voice)
|
38
|
+
highest_count = occurrences.values.sort.last
|
39
|
+
occurrences.select { |rhythmic_value, count| count == highest_count }.keys
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.occurrences_by_rhythmic_value(voice)
|
43
|
+
rhythmic_values(voice).inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.rhythmic_values(voice)
|
47
|
+
voice.notes.map(&:rhythmic_value)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::StartOnTonic < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
fitness = fitness(voice)
|
7
|
+
if fitness < 1
|
8
|
+
message = 'Start on the tonic.'
|
9
|
+
mark = HeadMusic::Style::Mark.for(voice.notes.last)
|
10
|
+
end
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: mark, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
return 1 if voice.notes.empty?
|
16
|
+
return 1 if starts_on_tonic?(voice)
|
17
|
+
HeadMusic::GOLDEN_RATIO_INVERSE
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.starts_on_tonic?(voice)
|
21
|
+
voice.notes &&
|
22
|
+
voice.notes.first &&
|
23
|
+
voice.composition &&
|
24
|
+
voice.composition.key_signature &&
|
25
|
+
voice.composition.key_signature.tonic_spelling == voice.notes.first.spelling
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::StepDownToFinalNote < HeadMusic::Style::Rule
|
5
|
+
def self.analyze(voice)
|
6
|
+
fitness = fitness(voice)
|
7
|
+
if fitness < 1
|
8
|
+
message = 'Step down to final note.'
|
9
|
+
mark = HeadMusic::Style::Mark.for_all(voice.notes[-2..-1])
|
10
|
+
end
|
11
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: mark, message: message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fitness(voice)
|
15
|
+
return 1 unless voice.notes.length >= 2
|
16
|
+
fitness = 1
|
17
|
+
fitness *= HeadMusic::GOLDEN_RATIO_INVERSE unless step?(voice)
|
18
|
+
fitness *= HeadMusic::GOLDEN_RATIO_INVERSE unless descending?(voice)
|
19
|
+
fitness
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.descending?(voice)
|
23
|
+
last_melodic_interval(voice).descending?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.step?(voice)
|
27
|
+
last_melodic_interval(voice).step?
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.last_melodic_interval(voice)
|
31
|
+
@last_melodic_interval ||= {}
|
32
|
+
@last_melodic_interval[voice] ||= voice.melodic_intervals.last
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module HeadMusic::Style::Rules
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rules::UpToThirteenNotes < HeadMusic::Style::Rule
|
5
|
+
MAXIMUM_NOTES = 13
|
6
|
+
|
7
|
+
def self.analyze(voice)
|
8
|
+
fitness = fitness(voice)
|
9
|
+
if fitness < 1
|
10
|
+
mark = mark(voice)
|
11
|
+
end
|
12
|
+
message = "Remove notes until you have at most thirteen notes." if fitness < 1
|
13
|
+
HeadMusic::Style::Annotation.new(subject: voice, fitness: fitness, marks: mark, message: message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.fitness(voice)
|
17
|
+
overage = voice.notes.length - MAXIMUM_NOTES
|
18
|
+
overage > 0 ? HeadMusic::GOLDEN_RATIO_INVERSE**overage : 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.mark(voice)
|
22
|
+
Style::Mark.for_all(voice.notes[13..-1])
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HeadMusic::Style::Rulesets
|
2
|
+
end
|
3
|
+
|
4
|
+
class HeadMusic::Style::Rulesets::CantusFirmus
|
5
|
+
RULESET = [
|
6
|
+
HeadMusic::Style::Rules::AlwaysMove,
|
7
|
+
HeadMusic::Style::Rules::AtLeastEightNotes,
|
8
|
+
HeadMusic::Style::Rules::Diatonic,
|
9
|
+
HeadMusic::Style::Rules::EndOnTonic,
|
10
|
+
HeadMusic::Style::Rules::LimitRange,
|
11
|
+
HeadMusic::Style::Rules::MostlyConjunct,
|
12
|
+
HeadMusic::Style::Rules::NoRests,
|
13
|
+
HeadMusic::Style::Rules::NotesSameLength,
|
14
|
+
HeadMusic::Style::Rules::StartOnTonic,
|
15
|
+
HeadMusic::Style::Rules::StepDownToFinalNote,
|
16
|
+
HeadMusic::Style::Rules::UpToThirteenNotes,
|
17
|
+
]
|
18
|
+
|
19
|
+
def self.analyze(voice)
|
20
|
+
RULESET.map { |rule| rule.analyze(voice) }
|
21
|
+
end
|
22
|
+
end
|
data/lib/head_music/version.rb
CHANGED
data/lib/head_music/voice.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
class HeadMusic::Voice
|
2
2
|
include Comparable
|
3
3
|
|
4
|
-
attr_reader :composition, :placements
|
4
|
+
attr_reader :composition, :placements, :role
|
5
|
+
delegate :key_signature, to: :composition
|
5
6
|
|
6
|
-
def initialize(composition
|
7
|
-
@composition = composition || Composition.new(name: "
|
7
|
+
def initialize(composition: nil, role: nil)
|
8
|
+
@composition = composition || Composition.new(name: "Composition")
|
9
|
+
@role = role
|
8
10
|
@placements = []
|
9
11
|
end
|
10
12
|
|
@@ -14,6 +16,49 @@ class HeadMusic::Voice
|
|
14
16
|
}
|
15
17
|
end
|
16
18
|
|
19
|
+
def notes
|
20
|
+
@placements.select(&:note?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def notes_not_in_key
|
24
|
+
key_spellings = key_signature.pitches.map(&:spelling).uniq
|
25
|
+
notes.reject { |note| key_spellings.include? note.pitch.spelling }
|
26
|
+
end
|
27
|
+
|
28
|
+
def pitches
|
29
|
+
notes.map(&:pitch)
|
30
|
+
end
|
31
|
+
|
32
|
+
def rests
|
33
|
+
@placements.select(&:rest?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def highest_pitch
|
37
|
+
pitches.sort.last
|
38
|
+
end
|
39
|
+
|
40
|
+
def lowest_pitch
|
41
|
+
pitches.sort.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def highest_notes
|
45
|
+
notes.select { |note| note.pitch == highest_pitch }
|
46
|
+
end
|
47
|
+
|
48
|
+
def lowest_notes
|
49
|
+
notes.select { |note| note.pitch == lowest_pitch }
|
50
|
+
end
|
51
|
+
|
52
|
+
def range
|
53
|
+
HeadMusic::FunctionalInterval.new(lowest_pitch, highest_pitch)
|
54
|
+
end
|
55
|
+
|
56
|
+
def melodic_intervals
|
57
|
+
notes.map.with_index do |note, i|
|
58
|
+
HeadMusic::MelodicInterval.new(self, notes[i-1], note) if i > 0
|
59
|
+
end.reject(&:nil?)
|
60
|
+
end
|
61
|
+
|
17
62
|
private
|
18
63
|
|
19
64
|
def insert_into_placements(placement)
|
data/lib/head_music.rb
CHANGED
@@ -5,8 +5,10 @@ require 'active_support/core_ext/string/access'
|
|
5
5
|
require 'humanize'
|
6
6
|
|
7
7
|
require 'head_music/accidental'
|
8
|
+
require 'head_music/bar'
|
8
9
|
require 'head_music/circle'
|
9
10
|
require 'head_music/clef'
|
11
|
+
require 'head_music/composition'
|
10
12
|
require 'head_music/consonance'
|
11
13
|
require 'head_music/functional_interval'
|
12
14
|
require 'head_music/grand_staff'
|
@@ -14,13 +16,12 @@ require 'head_music/instrument'
|
|
14
16
|
require 'head_music/interval'
|
15
17
|
require 'head_music/key_signature'
|
16
18
|
require 'head_music/letter_name'
|
17
|
-
require 'head_music/
|
18
|
-
require 'head_music/measure'
|
19
|
+
require 'head_music/melodic_interval'
|
19
20
|
require 'head_music/meter'
|
20
21
|
require 'head_music/note'
|
21
22
|
require 'head_music/octave'
|
22
|
-
require 'head_music/pitch_class'
|
23
23
|
require 'head_music/pitch'
|
24
|
+
require 'head_music/pitch_class'
|
24
25
|
require 'head_music/placement'
|
25
26
|
require 'head_music/position'
|
26
27
|
require 'head_music/quality'
|
@@ -31,8 +32,30 @@ require 'head_music/scale'
|
|
31
32
|
require 'head_music/scale_type'
|
32
33
|
require 'head_music/spelling'
|
33
34
|
require 'head_music/staff'
|
34
|
-
|
35
|
+
|
36
|
+
require 'head_music/style/analysis'
|
37
|
+
require 'head_music/style/annotation'
|
38
|
+
require 'head_music/style/mark'
|
39
|
+
require 'head_music/style/rule'
|
40
|
+
|
41
|
+
require 'head_music/style/rules/always_move'
|
42
|
+
require 'head_music/style/rules/at_least_eight_notes'
|
43
|
+
require 'head_music/style/rules/diatonic'
|
44
|
+
require 'head_music/style/rules/end_on_tonic'
|
45
|
+
require 'head_music/style/rules/limit_range'
|
46
|
+
require 'head_music/style/rules/mostly_conjunct'
|
47
|
+
require 'head_music/style/rules/no_rests'
|
48
|
+
require 'head_music/style/rules/notes_same_length'
|
49
|
+
require 'head_music/style/rules/start_on_tonic'
|
50
|
+
require 'head_music/style/rules/step_down_to_final_note'
|
51
|
+
require 'head_music/style/rules/up_to_thirteen_notes'
|
52
|
+
|
53
|
+
require 'head_music/style/rulesets/cantus_firmus'
|
54
|
+
|
35
55
|
require 'head_music/utilities/hash_key'
|
56
|
+
require 'head_music/voice'
|
36
57
|
|
37
58
|
module HeadMusic
|
59
|
+
GOLDEN_RATIO = (1 + 5**0.5) / 2.0
|
60
|
+
GOLDEN_RATIO_INVERSE = 1 / GOLDEN_RATIO
|
38
61
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: head_music
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Head
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- head_music.gemspec
|
118
118
|
- lib/head_music.rb
|
119
119
|
- lib/head_music/accidental.rb
|
120
|
+
- lib/head_music/bar.rb
|
120
121
|
- lib/head_music/circle.rb
|
121
122
|
- lib/head_music/clef.rb
|
122
123
|
- lib/head_music/composition.rb
|
@@ -127,7 +128,7 @@ files:
|
|
127
128
|
- lib/head_music/interval.rb
|
128
129
|
- lib/head_music/key_signature.rb
|
129
130
|
- lib/head_music/letter_name.rb
|
130
|
-
- lib/head_music/
|
131
|
+
- lib/head_music/melodic_interval.rb
|
131
132
|
- lib/head_music/meter.rb
|
132
133
|
- lib/head_music/note.rb
|
133
134
|
- lib/head_music/octave.rb
|
@@ -143,6 +144,22 @@ files:
|
|
143
144
|
- lib/head_music/scale_type.rb
|
144
145
|
- lib/head_music/spelling.rb
|
145
146
|
- lib/head_music/staff.rb
|
147
|
+
- lib/head_music/style/analysis.rb
|
148
|
+
- lib/head_music/style/annotation.rb
|
149
|
+
- lib/head_music/style/mark.rb
|
150
|
+
- lib/head_music/style/rule.rb
|
151
|
+
- lib/head_music/style/rules/always_move.rb
|
152
|
+
- lib/head_music/style/rules/at_least_eight_notes.rb
|
153
|
+
- lib/head_music/style/rules/diatonic.rb
|
154
|
+
- lib/head_music/style/rules/end_on_tonic.rb
|
155
|
+
- lib/head_music/style/rules/limit_range.rb
|
156
|
+
- lib/head_music/style/rules/mostly_conjunct.rb
|
157
|
+
- lib/head_music/style/rules/no_rests.rb
|
158
|
+
- lib/head_music/style/rules/notes_same_length.rb
|
159
|
+
- lib/head_music/style/rules/start_on_tonic.rb
|
160
|
+
- lib/head_music/style/rules/step_down_to_final_note.rb
|
161
|
+
- lib/head_music/style/rules/up_to_thirteen_notes.rb
|
162
|
+
- lib/head_music/style/rulesets/cantus_firmus.rb
|
146
163
|
- lib/head_music/utilities/hash_key.rb
|
147
164
|
- lib/head_music/version.rb
|
148
165
|
- lib/head_music/voice.rb
|