musa-dsl 0.14.29 → 0.21.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/Gemfile +0 -1
- data/README.md +5 -1
- data/lib/musa-dsl.rb +54 -11
- data/lib/musa-dsl/core-ext.rb +7 -13
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
- data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
- data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
- data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
- data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
- data/lib/musa-dsl/core-ext/with.rb +26 -0
- data/lib/musa-dsl/datasets.rb +8 -3
- data/lib/musa-dsl/datasets/dataset.rb +3 -0
- data/lib/musa-dsl/datasets/delta-d.rb +12 -0
- data/lib/musa-dsl/datasets/e.rb +61 -0
- data/lib/musa-dsl/datasets/gdv.rb +51 -411
- data/lib/musa-dsl/datasets/gdvd.rb +179 -0
- data/lib/musa-dsl/datasets/helper.rb +41 -0
- data/lib/musa-dsl/datasets/p.rb +68 -0
- data/lib/musa-dsl/datasets/packed-v.rb +19 -0
- data/lib/musa-dsl/datasets/pdv.rb +22 -15
- data/lib/musa-dsl/datasets/ps.rb +113 -0
- data/lib/musa-dsl/datasets/score.rb +210 -0
- data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
- data/lib/musa-dsl/datasets/score/render.rb +31 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
- data/lib/musa-dsl/datasets/v.rb +23 -0
- data/lib/musa-dsl/generative.rb +5 -5
- data/lib/musa-dsl/generative/backboner.rb +274 -0
- data/lib/musa-dsl/generative/darwin.rb +102 -96
- data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
- data/lib/musa-dsl/generative/markov.rb +56 -53
- data/lib/musa-dsl/generative/variatio.rb +234 -222
- data/lib/musa-dsl/logger.rb +1 -0
- data/lib/musa-dsl/logger/logger.rb +31 -0
- data/lib/musa-dsl/matrix.rb +1 -0
- data/lib/musa-dsl/matrix/matrix.rb +210 -0
- data/lib/musa-dsl/midi.rb +2 -2
- data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
- data/lib/musa-dsl/midi/midi-voices.rb +187 -182
- data/lib/musa-dsl/music.rb +5 -5
- data/lib/musa-dsl/music/chord-definition.rb +54 -50
- data/lib/musa-dsl/music/chord-definitions.rb +13 -9
- data/lib/musa-dsl/music/chords.rb +236 -238
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
- data/lib/musa-dsl/music/scales.rb +331 -332
- data/lib/musa-dsl/musicxml.rb +1 -0
- data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
- data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
- data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
- data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
- data/lib/musa-dsl/neumalang.rb +1 -1
- data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
- data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
- data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
- data/lib/musa-dsl/neumalang/process.citrus +21 -0
- data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
- data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
- data/lib/musa-dsl/neumas.rb +5 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
- data/lib/musa-dsl/neumas/neumas.rb +37 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
- data/lib/musa-dsl/repl.rb +1 -1
- data/lib/musa-dsl/repl/repl.rb +128 -105
- data/lib/musa-dsl/sequencer.rb +1 -1
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +552 -308
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
- data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
- data/lib/musa-dsl/series.rb +1 -1
- data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
- data/lib/musa-dsl/series/base-series.rb +171 -168
- data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
- data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
- data/lib/musa-dsl/series/proxy-serie.rb +6 -6
- data/lib/musa-dsl/series/queue-serie.rb +5 -5
- data/lib/musa-dsl/series/series.rb +2 -0
- data/lib/musa-dsl/transcription.rb +4 -0
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
- data/lib/musa-dsl/transcription/transcription.rb +55 -0
- data/lib/musa-dsl/transport.rb +6 -6
- data/lib/musa-dsl/transport/clock.rb +26 -26
- data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
- data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
- data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
- data/lib/musa-dsl/transport/timer-clock.rb +72 -71
- data/lib/musa-dsl/transport/timer.rb +28 -26
- data/lib/musa-dsl/transport/transport.rb +111 -93
- data/musa-dsl.gemspec +3 -3
- metadata +73 -24
- data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
- data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
- data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
- data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
- data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
- data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
- data/lib/musa-dsl/generative/rules.rb +0 -282
- data/lib/musa-dsl/neuma.rb +0 -1
- data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -1,204 +1,208 @@
|
|
1
|
+
require_relative 'scales'
|
2
|
+
|
1
3
|
module Musa
|
2
|
-
|
3
|
-
class
|
4
|
-
|
4
|
+
module Scales
|
5
|
+
class TwelveSemitonesScaleSystem < ScaleSystem
|
6
|
+
class << self
|
7
|
+
@@intervals = { P0: 0, m2: 1, M2: 2, m3: 3, M3: 4, P4: 5, TT: 6, P5: 7, m6: 8, M6: 9, m7: 10, M7: 11, P8: 12 }
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
def id
|
10
|
+
:et12
|
11
|
+
end
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
def notes_in_octave
|
14
|
+
12
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
def part_of_tone_size
|
18
|
+
1
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
def intervals
|
22
|
+
@@intervals
|
23
|
+
end
|
20
24
|
end
|
21
25
|
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class EquallyTempered12ToneScaleSystem < TwelveSemitonesScaleSystem
|
25
|
-
class << self
|
26
|
-
def frequency_of_pitch(pitch, _root_pitch, a_frequency)
|
27
|
-
(a_frequency * Rational(2)**Rational(pitch - 69, 12)).to_f
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
Scales.register EquallyTempered12ToneScaleSystem, default: true
|
32
|
-
end
|
33
|
-
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
{ functions: [:_3], pitch: 2 },
|
41
|
-
{ functions: [:_4], pitch: 3 },
|
42
|
-
{ functions: [:_5], pitch: 4 },
|
43
|
-
{ functions: [:_6], pitch: 5 },
|
44
|
-
{ functions: [:_7], pitch: 6 },
|
45
|
-
{ functions: [:_8], pitch: 7 },
|
46
|
-
{ functions: [:_9], pitch: 8 },
|
47
|
-
{ functions: [:_10], pitch: 9 },
|
48
|
-
{ functions: [:_11], pitch: 10 },
|
49
|
-
{ functions: [:_12], pitch: 11 }].freeze
|
50
|
-
|
51
|
-
def pitches
|
52
|
-
@@pitches
|
27
|
+
class EquallyTempered12ToneScaleSystem < TwelveSemitonesScaleSystem
|
28
|
+
class << self
|
29
|
+
def frequency_of_pitch(pitch, _root_pitch, a_frequency)
|
30
|
+
(a_frequency * Rational(2)**Rational(pitch - 69, 12)).to_f
|
31
|
+
end
|
53
32
|
end
|
54
33
|
|
55
|
-
|
56
|
-
:chromatic
|
57
|
-
end
|
58
|
-
|
59
|
-
def chromatic?
|
60
|
-
true
|
61
|
-
end
|
34
|
+
Scales.register EquallyTempered12ToneScaleSystem, default: true
|
62
35
|
end
|
63
36
|
|
64
|
-
EquallyTempered12ToneScaleSystem.register ChromaticScaleKind
|
65
|
-
end
|
66
|
-
|
67
|
-
class MajorScaleKind < ScaleKind
|
68
|
-
class << self
|
69
|
-
@@pitches =
|
70
|
-
[{ functions: %i[I _1 tonic],
|
71
|
-
pitch: 0 },
|
72
|
-
{ functions: %i[II _2 supertonic],
|
73
|
-
pitch: 2 },
|
74
|
-
{ functions: %i[III _3 mediant],
|
75
|
-
pitch: 4 },
|
76
|
-
{ functions: %i[IV _4 subdominant],
|
77
|
-
pitch: 5 },
|
78
|
-
{ functions: %i[V _5 dominant],
|
79
|
-
pitch: 7 },
|
80
|
-
{ functions: %i[VI _6 submediant relative relative_minor],
|
81
|
-
pitch: 9 },
|
82
|
-
{ functions: %i[VII _7 leading],
|
83
|
-
pitch: 11 },
|
84
|
-
{ functions: %i[VIII _8],
|
85
|
-
pitch: 12 },
|
86
|
-
{ functions: %i[IX _9],
|
87
|
-
pitch: 12 + 2 },
|
88
|
-
{ functions: %i[X _10],
|
89
|
-
pitch: 12 + 4 },
|
90
|
-
{ functions: %i[XI _11],
|
91
|
-
pitch: 12 + 5 },
|
92
|
-
{ functions: %i[XII _12],
|
93
|
-
pitch: 12 + 7 },
|
94
|
-
{ functions: %i[XIII _13],
|
95
|
-
pitch: 12 + 9 }].freeze
|
96
|
-
|
97
|
-
def pitches
|
98
|
-
@@pitches
|
99
|
-
end
|
100
37
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
38
|
+
class ChromaticScaleKind < ScaleKind
|
39
|
+
class << self
|
40
|
+
@@pitches =
|
41
|
+
[{ functions: [:_1], pitch: 0 },
|
42
|
+
{ functions: [:_2], pitch: 1 },
|
43
|
+
{ functions: [:_3], pitch: 2 },
|
44
|
+
{ functions: [:_4], pitch: 3 },
|
45
|
+
{ functions: [:_5], pitch: 4 },
|
46
|
+
{ functions: [:_6], pitch: 5 },
|
47
|
+
{ functions: [:_7], pitch: 6 },
|
48
|
+
{ functions: [:_8], pitch: 7 },
|
49
|
+
{ functions: [:_9], pitch: 8 },
|
50
|
+
{ functions: [:_10], pitch: 9 },
|
51
|
+
{ functions: [:_11], pitch: 10 },
|
52
|
+
{ functions: [:_12], pitch: 11 }].freeze
|
53
|
+
|
54
|
+
def pitches
|
55
|
+
@@pitches
|
56
|
+
end
|
57
|
+
|
58
|
+
def id
|
59
|
+
:chromatic
|
60
|
+
end
|
61
|
+
|
62
|
+
def chromatic?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
EquallyTempered12ToneScaleSystem.register ChromaticScaleKind
|
108
68
|
end
|
109
69
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
70
|
+
class MajorScaleKind < ScaleKind
|
71
|
+
class << self
|
72
|
+
@@pitches =
|
73
|
+
[{ functions: %i[I _1 tonic],
|
74
|
+
pitch: 0 },
|
75
|
+
{ functions: %i[II _2 supertonic],
|
76
|
+
pitch: 2 },
|
77
|
+
{ functions: %i[III _3 mediant],
|
78
|
+
pitch: 4 },
|
79
|
+
{ functions: %i[IV _4 subdominant],
|
80
|
+
pitch: 5 },
|
81
|
+
{ functions: %i[V _5 dominant],
|
82
|
+
pitch: 7 },
|
83
|
+
{ functions: %i[VI _6 submediant relative relative_minor],
|
84
|
+
pitch: 9 },
|
85
|
+
{ functions: %i[VII _7 leading],
|
86
|
+
pitch: 11 },
|
87
|
+
{ functions: %i[VIII _8],
|
88
|
+
pitch: 12 },
|
89
|
+
{ functions: %i[IX _9],
|
90
|
+
pitch: 12 + 2 },
|
91
|
+
{ functions: %i[X _10],
|
92
|
+
pitch: 12 + 4 },
|
93
|
+
{ functions: %i[XI _11],
|
94
|
+
pitch: 12 + 5 },
|
95
|
+
{ functions: %i[XII _12],
|
96
|
+
pitch: 12 + 7 },
|
97
|
+
{ functions: %i[XIII _13],
|
98
|
+
pitch: 12 + 9 }].freeze
|
99
|
+
|
100
|
+
def pitches
|
101
|
+
@@pitches
|
102
|
+
end
|
103
|
+
|
104
|
+
def grades
|
105
|
+
7
|
106
|
+
end
|
107
|
+
|
108
|
+
def id
|
109
|
+
:major
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
EquallyTempered12ToneScaleSystem.register MajorScaleKind
|
154
114
|
end
|
155
115
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
116
|
+
class MinorScaleKind < ScaleKind
|
117
|
+
class << self
|
118
|
+
@@pitches =
|
119
|
+
[{ functions: %i[i _1 tonic],
|
120
|
+
pitch: 0 },
|
121
|
+
{ functions: %i[ii _2 supertonic],
|
122
|
+
pitch: 2 },
|
123
|
+
{ functions: %i[iii _3 mediant relative relative_major],
|
124
|
+
pitch: 3 },
|
125
|
+
{ functions: %i[iv _4 subdominant],
|
126
|
+
pitch: 5 },
|
127
|
+
{ functions: %i[v _5 dominant],
|
128
|
+
pitch: 7 },
|
129
|
+
{ functions: %i[vi _6 submediant],
|
130
|
+
pitch: 8 },
|
131
|
+
{ functions: %i[vii _7],
|
132
|
+
pitch: 10 },
|
133
|
+
{ functions: %i[viii _8],
|
134
|
+
pitch: 12 },
|
135
|
+
{ functions: %i[ix _9],
|
136
|
+
pitch: 12 + 2 },
|
137
|
+
{ functions: %i[x _10],
|
138
|
+
pitch: 12 + 3 },
|
139
|
+
{ functions: %i[xi _11],
|
140
|
+
pitch: 12 + 5 },
|
141
|
+
{ functions: %i[xii _12],
|
142
|
+
pitch: 12 + 7 },
|
143
|
+
{ functions: %i[xiii _13],
|
144
|
+
pitch: 12 + 8 }].freeze
|
145
|
+
|
146
|
+
def pitches
|
147
|
+
@@pitches
|
148
|
+
end
|
149
|
+
|
150
|
+
def grades
|
151
|
+
7
|
152
|
+
end
|
153
|
+
|
154
|
+
def id
|
155
|
+
:minor
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
EquallyTempered12ToneScaleSystem.register MinorScaleKind
|
200
160
|
end
|
201
161
|
|
202
|
-
|
162
|
+
class MinorHarmonicScaleKind < ScaleKind
|
163
|
+
class << self
|
164
|
+
@@pitches =
|
165
|
+
[{ functions: %i[i _1 tonic],
|
166
|
+
pitch: 0 },
|
167
|
+
{ functions: %i[ii _2 supertonic],
|
168
|
+
pitch: 2 },
|
169
|
+
{ functions: %i[iii _3 mediant relative relative_major],
|
170
|
+
pitch: 3 },
|
171
|
+
{ functions: %i[iv _4 subdominant],
|
172
|
+
pitch: 5 },
|
173
|
+
{ functions: %i[v _5 dominant],
|
174
|
+
pitch: 7 },
|
175
|
+
{ functions: %i[vi _6 submediant],
|
176
|
+
pitch: 8 },
|
177
|
+
{ functions: %i[vii _7 leading],
|
178
|
+
pitch: 11 },
|
179
|
+
{ functions: %i[viii _8],
|
180
|
+
pitch: 12 },
|
181
|
+
{ functions: %i[ix _9],
|
182
|
+
pitch: 12 + 2 },
|
183
|
+
{ functions: %i[x _10],
|
184
|
+
pitch: 12 + 3 },
|
185
|
+
{ functions: %i[xi _11],
|
186
|
+
pitch: 12 + 5 },
|
187
|
+
{ functions: %i[xii _12],
|
188
|
+
pitch: 12 + 7 },
|
189
|
+
{ functions: %i[xiii _13],
|
190
|
+
pitch: 12 + 8 }].freeze
|
191
|
+
|
192
|
+
def pitches
|
193
|
+
@@pitches
|
194
|
+
end
|
195
|
+
|
196
|
+
def grades
|
197
|
+
7
|
198
|
+
end
|
199
|
+
|
200
|
+
def id
|
201
|
+
:minor_harmonic
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
EquallyTempered12ToneScaleSystem.register MinorHarmonicScaleKind
|
206
|
+
end
|
203
207
|
end
|
204
208
|
end
|
@@ -1,47 +1,36 @@
|
|
1
1
|
module Musa
|
2
2
|
module Scales
|
3
|
-
|
4
|
-
def register(scale_system, default: nil)
|
3
|
+
module Scales
|
4
|
+
def self.register(scale_system, default: nil)
|
5
5
|
@scale_systems ||= {}
|
6
6
|
@scale_systems[scale_system.id] = scale_system
|
7
7
|
|
8
8
|
@default_scale_system = scale_system if default
|
9
|
+
|
10
|
+
self.class.define_method scale_system.id do
|
11
|
+
scale_system
|
12
|
+
end
|
13
|
+
|
9
14
|
self
|
10
15
|
end
|
11
16
|
|
12
|
-
def [](id)
|
17
|
+
def self.[](id)
|
13
18
|
raise KeyError, "Scale system :#{id} not found" unless @scale_systems.key?(id)
|
14
19
|
|
15
20
|
@scale_systems[id]
|
16
21
|
end
|
17
22
|
|
18
|
-
def default_system
|
23
|
+
def self.default_system
|
19
24
|
@default_scale_system
|
20
25
|
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def method_missing(method_name, *args, **key_args, &block)
|
25
|
-
if @scale_systems.key?(method_name) && args.empty? && key_args.empty? && !block
|
26
|
-
self[method_name]
|
27
|
-
else
|
28
|
-
super
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def respond_to_missing?(method_name, include_private)
|
33
|
-
@scale_systems.key?(method_name) || super
|
34
|
-
end
|
35
26
|
end
|
36
|
-
end
|
37
27
|
|
38
|
-
|
39
|
-
class << self
|
28
|
+
class ScaleSystem
|
40
29
|
# @abstract Subclass is expected to implement names
|
41
30
|
# @!method id
|
42
31
|
# @return [Symbol] the id of the ScaleSystem as a symbol
|
43
32
|
#
|
44
|
-
def id
|
33
|
+
def self.id
|
45
34
|
raise 'Method not implemented. Should be implemented in subclass.'
|
46
35
|
end
|
47
36
|
|
@@ -49,7 +38,7 @@ module Musa
|
|
49
38
|
# @!method notes_in_octave
|
50
39
|
# @return [Integer] the number of notes in one octave in the ScaleSystem
|
51
40
|
#
|
52
|
-
def notes_in_octave
|
41
|
+
def self.notes_in_octave
|
53
42
|
raise 'Method not implemented. Should be implemented in subclass.'
|
54
43
|
end
|
55
44
|
|
@@ -57,7 +46,7 @@ module Musa
|
|
57
46
|
# @!method part_of_tone_size
|
58
47
|
# @return [Integer] the size inside the ScaleSystem of the smaller part of a tone; used for calculate sharp and flat notes
|
59
48
|
#
|
60
|
-
def part_of_tone_size
|
49
|
+
def self.part_of_tone_size
|
61
50
|
raise 'Method not implemented. Should be implemented in subclass.'
|
62
51
|
end
|
63
52
|
|
@@ -65,7 +54,7 @@ module Musa
|
|
65
54
|
# @!method intervals
|
66
55
|
# @return [Hash] the intervals of the ScaleSystem as { name: semitones#, ... }
|
67
56
|
#
|
68
|
-
def intervals
|
57
|
+
def self.intervals
|
69
58
|
# TODO: implementar intérvalos sinónimos (p.ej, m3 = A2)
|
70
59
|
# TODO: implementar identificación de intérvalos, teniendo en cuenta no sólo los semitonos sino los grados de separación
|
71
60
|
# TODO: implementar inversión de intérvalos
|
@@ -79,7 +68,7 @@ module Musa
|
|
79
68
|
# @param a_frequency [Number] The reference frequency of the mid A note
|
80
69
|
# @return [Number] the frequency of the fundamental tone of the pitch
|
81
70
|
#
|
82
|
-
def frequency_of_pitch(pitch, root_pitch, a_frequency)
|
71
|
+
def self.frequency_of_pitch(pitch, root_pitch, a_frequency)
|
83
72
|
raise 'Method not implemented. Should be implemented in subclass.'
|
84
73
|
end
|
85
74
|
|
@@ -87,11 +76,11 @@ module Musa
|
|
87
76
|
# @!method default_a_frequency
|
88
77
|
# @return [Number] the frequency A by default
|
89
78
|
#
|
90
|
-
def default_a_frequency
|
79
|
+
def self.default_a_frequency
|
91
80
|
440.0
|
92
81
|
end
|
93
82
|
|
94
|
-
def [](a_frequency)
|
83
|
+
def self.[](a_frequency)
|
95
84
|
a_frequency = a_frequency.to_f
|
96
85
|
|
97
86
|
@a_tunings ||= {}
|
@@ -100,15 +89,15 @@ module Musa
|
|
100
89
|
@a_tunings[a_frequency]
|
101
90
|
end
|
102
91
|
|
103
|
-
def offset_of_interval(name)
|
92
|
+
def self.offset_of_interval(name)
|
104
93
|
intervals[name]
|
105
94
|
end
|
106
95
|
|
107
|
-
def default_tuning
|
96
|
+
def self.default_tuning
|
108
97
|
self[default_a_frequency]
|
109
98
|
end
|
110
99
|
|
111
|
-
def register(scale_kind_class)
|
100
|
+
def self.register(scale_kind_class)
|
112
101
|
@scale_kind_classes ||= {}
|
113
102
|
@scale_kind_classes[scale_kind_class.id] = scale_kind_class
|
114
103
|
if scale_kind_class.chromatic?
|
@@ -117,468 +106,478 @@ module Musa
|
|
117
106
|
self
|
118
107
|
end
|
119
108
|
|
120
|
-
def scale_kind_class(id)
|
109
|
+
def self.scale_kind_class(id)
|
121
110
|
raise KeyError, "Scale kind class [#{id}] not found in scale system [#{self.id}]" unless @scale_kind_classes.key? id
|
122
111
|
|
123
112
|
@scale_kind_classes[id]
|
124
113
|
end
|
125
114
|
|
126
|
-
def scale_kind_class?(id)
|
115
|
+
def self.scale_kind_class?(id)
|
127
116
|
@scale_kind_classes.key? id
|
128
117
|
end
|
129
118
|
|
130
|
-
def
|
119
|
+
def self.scale_kind_classes
|
120
|
+
@scale_kind_classes
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.chromatic_class
|
131
124
|
raise "Chromatic scale kind class for [#{self.id}] scale system undefined" if @chromatic_scale_kind_class.nil?
|
132
125
|
|
133
126
|
@chromatic_scale_kind_class
|
134
127
|
end
|
135
|
-
end
|
136
128
|
|
137
|
-
|
138
|
-
|
129
|
+
def ==(other)
|
130
|
+
self.class == other.class
|
131
|
+
end
|
139
132
|
end
|
140
|
-
end
|
141
133
|
|
142
|
-
|
143
|
-
|
134
|
+
class ScaleSystemTuning
|
135
|
+
extend Forwardable
|
144
136
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
137
|
+
def initialize(scale_system, a_frequency)
|
138
|
+
@scale_system = scale_system
|
139
|
+
@a_frequency = a_frequency
|
140
|
+
@scale_kinds = {}
|
149
141
|
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
# TODO: allow scales not based in octaves but in other intervals (like fifths or other ratios). Possibly based on intervals definition of ScaleSystem plus a "generator interval" attribute
|
154
|
-
|
155
|
-
def_delegators :@scale_system, :notes_in_octave, :offset_of_interval
|
142
|
+
@chromatic_scale_kind = self[@scale_system.chromatic_class.id]
|
156
143
|
|
157
|
-
|
144
|
+
@scale_system.scale_kind_classes.each_key do |name|
|
145
|
+
define_singleton_method name do
|
146
|
+
self[name]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
158
150
|
|
159
|
-
|
160
|
-
@scale_kinds[scale_kind_class_id] ||= @scale_system.scale_kind_class(scale_kind_class_id).new self
|
161
|
-
end
|
151
|
+
# TODO: allow scales not based in octaves but in other intervals (like fifths or other ratios). Possibly based on intervals definition of ScaleSystem plus a "generator interval" attribute
|
162
152
|
|
163
|
-
|
164
|
-
@chromatic_scale_kind
|
165
|
-
end
|
153
|
+
def_delegators :@scale_system, :notes_in_octave, :offset_of_interval
|
166
154
|
|
167
|
-
|
168
|
-
@scale_system.frequency_of_pitch(pitch, root, @a_frequency)
|
169
|
-
end
|
155
|
+
attr_reader :a_frequency, :scale_system
|
170
156
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
@a_frequency == other.a_frequency
|
175
|
-
end
|
157
|
+
def [](scale_kind_class_id)
|
158
|
+
@scale_kinds[scale_kind_class_id] ||= @scale_system.scale_kind_class(scale_kind_class_id).new self
|
159
|
+
end
|
176
160
|
|
177
|
-
|
178
|
-
|
179
|
-
|
161
|
+
def chromatic
|
162
|
+
@chromatic_scale_kind
|
163
|
+
end
|
180
164
|
|
181
|
-
|
165
|
+
def frequency_of_pitch(pitch, root)
|
166
|
+
@scale_system.frequency_of_pitch(pitch, root, @a_frequency)
|
167
|
+
end
|
182
168
|
|
183
|
-
|
169
|
+
def ==(other)
|
170
|
+
self.class == other.class &&
|
171
|
+
@scale_system == other.scale_system &&
|
172
|
+
@a_frequency == other.a_frequency
|
173
|
+
end
|
184
174
|
|
185
|
-
|
186
|
-
|
187
|
-
self[method_name]
|
188
|
-
else
|
189
|
-
super
|
175
|
+
def inspect
|
176
|
+
"<ScaleSystemTuning: scale_system = #{@scale_system} a_frequency = #{@a_frequency}>"
|
190
177
|
end
|
191
|
-
end
|
192
178
|
|
193
|
-
|
194
|
-
@scale_system.scale_kind_class?(method_name) || super
|
179
|
+
alias to_s inspect
|
195
180
|
end
|
196
|
-
end
|
197
181
|
|
198
|
-
|
199
|
-
|
182
|
+
class ScaleKind
|
183
|
+
extend Forwardable
|
200
184
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
185
|
+
def initialize(tuning)
|
186
|
+
@tuning = tuning
|
187
|
+
@scales = {}
|
188
|
+
end
|
205
189
|
|
206
|
-
|
190
|
+
attr_reader :tuning
|
207
191
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
192
|
+
def [](root_pitch)
|
193
|
+
@scales[root_pitch] = Scale.new(self, root_pitch: root_pitch) unless @scales.key?(root_pitch)
|
194
|
+
@scales[root_pitch]
|
195
|
+
end
|
212
196
|
|
213
|
-
|
214
|
-
|
215
|
-
|
197
|
+
def absolut
|
198
|
+
self[0]
|
199
|
+
end
|
216
200
|
|
217
|
-
|
218
|
-
|
219
|
-
|
201
|
+
def ==(other)
|
202
|
+
self.class == other.class && @tuning == other.tuning
|
203
|
+
end
|
220
204
|
|
221
|
-
|
222
|
-
|
223
|
-
|
205
|
+
def inspect
|
206
|
+
"<#{self.class.name}: tuning = #{@tuning}>"
|
207
|
+
end
|
224
208
|
|
225
|
-
|
209
|
+
alias to_s inspect
|
226
210
|
|
227
|
-
class << self
|
228
211
|
# @abstract Subclass is expected to implement id
|
229
212
|
# @!method id
|
230
213
|
# @return [Symbol] the id of the ScaleKind as a symbol
|
231
|
-
def id
|
214
|
+
def self.id
|
232
215
|
raise 'Method not implemented. Should be implemented in subclass.'
|
233
216
|
end
|
234
217
|
|
235
218
|
# @abstract Subclass is expected to implement pitches
|
236
219
|
# @!method pitches
|
237
220
|
# @return [Array] the pitches array of the ScaleKind as [ { functions: [ <symbol>, ...], pitch: <Number> }, ... ]
|
238
|
-
def pitches
|
221
|
+
def self.pitches
|
239
222
|
raise 'Method not implemented. Should be implemented in subclass.'
|
240
223
|
end
|
241
224
|
|
242
225
|
# @abstract Subclass is expected to implement chromatic?. Only one of the subclasses should return true.
|
243
226
|
# @!method chromatic?
|
244
227
|
# @return [Boolean] wether the scales is a full scale (with all the notes in the ScaleSystem), sorted and to be considered canonical. I.e. a chromatic 12 semitones uprising serie in a 12 tone tempered ScaleSystem.
|
245
|
-
def chromatic?
|
228
|
+
def self.chromatic?
|
246
229
|
false
|
247
230
|
end
|
248
231
|
|
249
232
|
# @abstract Subclass is expected to implement grades when the ScaleKind is defining more pitches than notes by octave has the scale. This can happen when there are pitches defined on upper octaves (i.e., to define XII grade, as a octave + fifth)
|
250
233
|
# @!method grades
|
251
234
|
# @return [Integer] Number of grades inside of a octave of the scale
|
252
|
-
def grades
|
235
|
+
def self.grades
|
253
236
|
pitches.length
|
254
237
|
end
|
255
238
|
|
256
|
-
def
|
257
|
-
|
258
|
-
@
|
239
|
+
def self.grade_of_function(symbol)
|
240
|
+
create_grade_functions_index unless @grade_names_index
|
241
|
+
@grade_names_index[symbol]
|
259
242
|
end
|
260
243
|
|
244
|
+
def self.grades_functions
|
245
|
+
create_grade_functions_index unless @grade_names_index
|
246
|
+
@grade_names_index.keys
|
247
|
+
end
|
261
248
|
|
262
249
|
private
|
263
250
|
|
264
|
-
def
|
265
|
-
@
|
251
|
+
def self.create_grade_functions_index
|
252
|
+
@grade_names_index = {}
|
266
253
|
pitches.each_index do |i|
|
267
254
|
pitches[i][:functions].each do |function|
|
268
|
-
@
|
255
|
+
@grade_names_index[function] = i
|
269
256
|
end
|
270
257
|
end
|
258
|
+
|
271
259
|
self
|
272
260
|
end
|
273
261
|
end
|
274
|
-
end
|
275
262
|
|
276
|
-
|
277
|
-
|
263
|
+
class Scale
|
264
|
+
extend Forwardable
|
278
265
|
|
279
|
-
|
280
|
-
|
281
|
-
|
266
|
+
def initialize(kind, root_pitch:)
|
267
|
+
@notes_by_grade = {}
|
268
|
+
@notes_by_pitch = {}
|
282
269
|
|
283
|
-
|
270
|
+
@kind = kind
|
284
271
|
|
285
|
-
|
286
|
-
end
|
272
|
+
@root_pitch = root_pitch
|
287
273
|
|
288
|
-
|
274
|
+
@kind.class.grades_functions.each do |name|
|
275
|
+
define_singleton_method name do
|
276
|
+
self[name]
|
277
|
+
end
|
278
|
+
end
|
289
279
|
|
290
|
-
|
280
|
+
end
|
291
281
|
|
292
|
-
|
293
|
-
self[0]
|
294
|
-
end
|
282
|
+
def_delegators :@kind, :a_tuning
|
295
283
|
|
296
|
-
|
297
|
-
@kind.tuning.chromatic[@root_pitch]
|
298
|
-
end
|
284
|
+
attr_reader :kind, :root_pitch
|
299
285
|
|
300
|
-
|
301
|
-
|
302
|
-
|
286
|
+
def root
|
287
|
+
self[0]
|
288
|
+
end
|
303
289
|
|
304
|
-
|
305
|
-
|
290
|
+
def chromatic
|
291
|
+
@kind.tuning.chromatic[@root_pitch]
|
292
|
+
end
|
306
293
|
|
307
|
-
|
308
|
-
|
294
|
+
def absolut
|
295
|
+
@kind[0]
|
296
|
+
end
|
309
297
|
|
310
|
-
|
298
|
+
def octave(octave)
|
299
|
+
raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
|
311
300
|
|
312
|
-
|
301
|
+
@kind[@root_pitch + octave * @kind.class.grades]
|
302
|
+
end
|
313
303
|
|
314
|
-
|
304
|
+
def [](grade_or_symbol)
|
315
305
|
|
316
|
-
|
306
|
+
raise ArgumentError, "grade_or_symbol '#{grade_or_symbol}' should be a Numeric, String or Symbol" unless grade_or_symbol.is_a?(Symbol) || grade_or_symbol.is_a?(String) || grade_or_symbol.is_a?(Integer)
|
317
307
|
|
318
|
-
|
319
|
-
grade = wide_grade % @kind.class.grades
|
308
|
+
wide_grade, sharps = grade_of(grade_or_symbol)
|
320
309
|
|
321
|
-
|
322
|
-
octave * @kind.tuning.notes_in_octave +
|
323
|
-
@kind.class.pitches[grade][:pitch]
|
310
|
+
unless @notes_by_grade.key?(wide_grade)
|
324
311
|
|
325
|
-
|
312
|
+
octave = wide_grade / @kind.class.grades
|
313
|
+
grade = wide_grade % @kind.class.grades
|
326
314
|
|
327
|
-
|
328
|
-
|
315
|
+
pitch = @root_pitch +
|
316
|
+
octave * @kind.tuning.notes_in_octave +
|
317
|
+
@kind.class.pitches[grade][:pitch]
|
329
318
|
|
319
|
+
note = NoteInScale.new self, grade, octave, pitch
|
330
320
|
|
331
|
-
|
332
|
-
|
321
|
+
@notes_by_grade[wide_grade] = @notes_by_pitch[pitch] = note
|
322
|
+
end
|
333
323
|
|
334
|
-
def grade_of(grade_or_string_or_symbol)
|
335
|
-
sign, name, wide_grade, accidentals = Musa::Datasets::GDVd::Parser.parse_grade(grade_or_string_or_symbol)
|
336
324
|
|
337
|
-
|
325
|
+
@notes_by_grade[wide_grade].sharp(sharps)
|
326
|
+
end
|
338
327
|
|
339
|
-
|
328
|
+
def grade_of(grade_or_string_or_symbol)
|
329
|
+
name, wide_grade, accidentals = parse_grade(grade_or_string_or_symbol)
|
340
330
|
|
341
|
-
|
342
|
-
grade = wide_grade % @kind.class.grades if wide_grade
|
331
|
+
grade = @kind.class.grade_of_function name if name
|
343
332
|
|
344
|
-
|
333
|
+
octave = wide_grade / @kind.class.grades if wide_grade
|
334
|
+
grade = wide_grade % @kind.class.grades if wide_grade
|
345
335
|
|
346
|
-
|
347
|
-
end
|
336
|
+
octave ||= 0
|
348
337
|
|
349
|
-
|
350
|
-
|
351
|
-
allow_nearest ||= false
|
338
|
+
return octave * @kind.class.grades + grade, accidentals
|
339
|
+
end
|
352
340
|
|
353
|
-
|
341
|
+
def parse_grade(neuma_grade)
|
342
|
+
name = wide_grade = nil
|
343
|
+
accidentals = 0
|
354
344
|
|
355
|
-
|
356
|
-
|
345
|
+
case neuma_grade
|
346
|
+
when Symbol, String
|
347
|
+
match = /\A(?<name>[^[#|_]]*)(?<accidental_sharps>#*)(?<accidental_flats>_*)\Z/.match neuma_grade.to_s
|
357
348
|
|
358
|
-
|
359
|
-
|
349
|
+
if match
|
350
|
+
if match[:name] == match[:name].to_i.to_s
|
351
|
+
wide_grade = match[:name].to_i
|
352
|
+
else
|
353
|
+
name = match[:name].to_sym unless match[:name].empty?
|
354
|
+
end
|
355
|
+
accidentals = match[:accidental_sharps].length - match[:accidental_flats].length
|
356
|
+
else
|
357
|
+
name = neuma_grade.to_sym unless (neuma_grade.nil? || neuma_grade.empty?)
|
358
|
+
end
|
359
|
+
when Numeric
|
360
|
+
wide_grade = neuma_grade.to_i
|
360
361
|
|
361
|
-
|
362
|
+
else
|
363
|
+
raise ArgumentError, "Cannot eval #{neuma_grade} as name or grade position."
|
364
|
+
end
|
362
365
|
|
363
|
-
|
364
|
-
|
365
|
-
note = self[wide_grade]
|
366
|
+
return name, wide_grade, accidentals
|
367
|
+
end
|
366
368
|
|
367
|
-
|
368
|
-
|
369
|
+
def note_of_pitch(pitch, allow_chromatic: nil, allow_nearest: nil)
|
370
|
+
allow_chromatic ||= false
|
371
|
+
allow_nearest ||= false
|
369
372
|
|
370
|
-
|
371
|
-
note = note_of_pitch(pitch - (sharps += 1) * @kind.tuning.scale_system.part_of_tone_size)
|
372
|
-
note ||= note_of_pitch(pitch + sharps * @kind.tuning.scale_system.part_of_tone_size)
|
373
|
-
end
|
373
|
+
note = @notes_by_pitch[pitch]
|
374
374
|
|
375
|
-
|
376
|
-
|
375
|
+
unless note
|
376
|
+
pitch_offset = pitch - @root_pitch
|
377
377
|
|
378
|
-
|
379
|
-
|
380
|
-
end
|
378
|
+
pitch_offset_in_octave = pitch_offset % @kind.tuning.scale_system.notes_in_octave
|
379
|
+
pitch_offset_octave = pitch_offset / @kind.tuning.scale_system.notes_in_octave
|
381
380
|
|
382
|
-
|
383
|
-
end
|
381
|
+
grade = @kind.class.pitches.find_index { |pitch_definition| pitch_definition[:pitch] == pitch_offset_in_octave }
|
384
382
|
|
385
|
-
|
386
|
-
|
387
|
-
|
383
|
+
if grade
|
384
|
+
wide_grade = pitch_offset_octave * @kind.class.grades + grade
|
385
|
+
note = self[wide_grade]
|
388
386
|
|
389
|
-
|
390
|
-
|
391
|
-
end
|
387
|
+
elsif allow_nearest
|
388
|
+
sharps = 0
|
392
389
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
end
|
390
|
+
until note
|
391
|
+
note = note_of_pitch(pitch - (sharps += 1) * @kind.tuning.scale_system.part_of_tone_size)
|
392
|
+
note ||= note_of_pitch(pitch + sharps * @kind.tuning.scale_system.part_of_tone_size)
|
393
|
+
end
|
398
394
|
|
399
|
-
|
400
|
-
|
401
|
-
end
|
395
|
+
elsif allow_chromatic
|
396
|
+
nearest = note_of_pitch(pitch, allow_nearest: true)
|
402
397
|
|
403
|
-
|
398
|
+
note = chromatic.note_of_pitch(pitch).with_background(scale: self, grade: nearest.grade, octave: nearest.octave, sharps: (pitch - nearest.pitch) / @kind.tuning.scale_system.part_of_tone_size)
|
399
|
+
end
|
400
|
+
end
|
404
401
|
|
405
|
-
|
402
|
+
note
|
403
|
+
end
|
406
404
|
|
407
|
-
|
408
|
-
|
409
|
-
self[method_name]
|
410
|
-
else
|
411
|
-
super
|
405
|
+
def offset_of_interval(interval_name)
|
406
|
+
@kind.tuning.offset_of_interval(interval_name)
|
412
407
|
end
|
413
|
-
end
|
414
408
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
end
|
409
|
+
def chord_of(*grades_or_symbols)
|
410
|
+
Chord.new(notes: grades_or_symbols.collect { |g| self[g] })
|
411
|
+
end
|
419
412
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
# @param pitch [Number] pitch of the note, based on MIDI note numbers. Can be Integer, Rational or Float to express fractions of a semitone
|
426
|
-
#
|
427
|
-
def initialize(scale, grade, octave, pitch, background_scale: nil, background_grade: nil, background_octave: nil, background_sharps: nil)
|
428
|
-
@scale = scale
|
429
|
-
@grade = grade
|
430
|
-
@octave = octave
|
431
|
-
@pitch = pitch
|
432
|
-
|
433
|
-
@background_scale = background_scale
|
434
|
-
@background_grade = background_grade
|
435
|
-
@background_octave = background_octave
|
436
|
-
@background_sharps = background_sharps
|
437
|
-
end
|
413
|
+
def ==(other)
|
414
|
+
self.class == other.class &&
|
415
|
+
@kind == other.kind &&
|
416
|
+
@root_pitch == other.root_pitch
|
417
|
+
end
|
438
418
|
|
439
|
-
|
419
|
+
def inspect
|
420
|
+
"<Scale: kind = #{@kind} root_pitch = #{@root_pitch}>"
|
421
|
+
end
|
440
422
|
|
441
|
-
|
442
|
-
@scale.kind.class.pitches[grade][:functions]
|
423
|
+
alias to_s inspect
|
443
424
|
end
|
444
425
|
|
445
|
-
|
446
|
-
if octave.nil?
|
447
|
-
@octave
|
448
|
-
else
|
449
|
-
raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
|
426
|
+
class NoteInScale
|
450
427
|
|
451
|
-
|
428
|
+
# @param scale [Scale]
|
429
|
+
# @param grade []
|
430
|
+
# @param octave [Integer]
|
431
|
+
# @param pitch [Number] pitch of the note, based on MIDI note numbers. Can be Integer, Rational or Float to express fractions of a semitone
|
432
|
+
#
|
433
|
+
def initialize(scale, grade, octave, pitch, background_scale: nil, background_grade: nil, background_octave: nil, background_sharps: nil)
|
434
|
+
@scale = scale
|
435
|
+
@grade = grade
|
436
|
+
@octave = octave
|
437
|
+
@pitch = pitch
|
438
|
+
|
439
|
+
@background_scale = background_scale
|
440
|
+
@background_grade = background_grade
|
441
|
+
@background_octave = background_octave
|
442
|
+
@background_sharps = background_sharps
|
443
|
+
|
444
|
+
@scale.kind.tuning.scale_system.scale_kind_classes.each_key do |name|
|
445
|
+
define_singleton_method name do
|
446
|
+
scale(name)
|
447
|
+
end
|
448
|
+
end
|
452
449
|
end
|
453
|
-
end
|
454
450
|
|
455
|
-
|
456
|
-
NoteInScale.new(@scale, @grade, @octave, @pitch,
|
457
|
-
background_scale: scale,
|
458
|
-
background_grade: grade,
|
459
|
-
background_octave: octave,
|
460
|
-
background_sharps: sharps)
|
461
|
-
end
|
451
|
+
attr_reader :grade, :pitch
|
462
452
|
|
463
|
-
|
453
|
+
def functions
|
454
|
+
@scale.kind.class.pitches[grade][:functions]
|
455
|
+
end
|
464
456
|
|
465
|
-
|
466
|
-
|
467
|
-
|
457
|
+
def octave(octave = nil)
|
458
|
+
if octave.nil?
|
459
|
+
@octave
|
460
|
+
else
|
461
|
+
raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
|
468
462
|
|
469
|
-
|
463
|
+
@scale[@grade + (@octave + octave) * @scale.kind.class.grades]
|
464
|
+
end
|
465
|
+
end
|
470
466
|
|
471
|
-
|
472
|
-
|
473
|
-
|
467
|
+
def with_background(scale:, grade: nil, octave: nil, sharps: nil)
|
468
|
+
NoteInScale.new(@scale, @grade, @octave, @pitch,
|
469
|
+
background_scale: scale,
|
470
|
+
background_grade: grade,
|
471
|
+
background_octave: octave,
|
472
|
+
background_sharps: sharps)
|
473
|
+
end
|
474
474
|
|
475
|
-
|
475
|
+
attr_reader :background_scale
|
476
476
|
|
477
|
-
|
477
|
+
def background_note
|
478
|
+
@background_scale[@background_grade + (@background_octave || 0) * @background_scale.kind.class.grades] if @background_grade
|
479
|
+
end
|
478
480
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
481
|
+
attr_reader :background_sharps
|
482
|
+
|
483
|
+
def wide_grade
|
484
|
+
@grade + @octave * @scale.kind.class.grades
|
483
485
|
end
|
484
486
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
487
|
+
def up(interval_name_or_interval, natural_or_chromatic = nil, sign: nil)
|
488
|
+
|
489
|
+
sign ||= 1
|
490
|
+
|
491
|
+
if interval_name_or_interval.is_a?(Numeric)
|
492
|
+
natural_or_chromatic ||= :natural
|
493
|
+
else
|
494
|
+
natural_or_chromatic = :chromatic
|
495
|
+
end
|
491
496
|
|
492
|
-
|
493
|
-
|
494
|
-
|
497
|
+
if natural_or_chromatic == :chromatic
|
498
|
+
interval = if interval_name_or_interval.is_a?(Symbol)
|
499
|
+
@scale.kind.tuning.offset_of_interval(interval_name_or_interval)
|
500
|
+
else
|
501
|
+
interval_name_or_interval
|
502
|
+
end
|
503
|
+
|
504
|
+
calculate_note_of_pitch(@pitch, sign * interval)
|
505
|
+
else
|
506
|
+
@scale[@grade + sign * interval_name_or_interval]
|
507
|
+
end
|
495
508
|
end
|
496
|
-
end
|
497
509
|
|
498
|
-
|
499
|
-
|
510
|
+
def calculate_note_of_pitch(in_scale_pitch, sharps)
|
511
|
+
pitch = in_scale_pitch + sharps * @scale.kind.tuning.scale_system.part_of_tone_size
|
500
512
|
|
501
|
-
|
502
|
-
|
503
|
-
else
|
504
|
-
note = @scale.note_of_pitch(pitch, allow_chromatic: true)
|
505
|
-
if @background_scale
|
506
|
-
note.on(@background_scale) || note
|
513
|
+
if pitch == @pitch
|
514
|
+
self
|
507
515
|
else
|
508
|
-
note
|
516
|
+
note = @scale.note_of_pitch(pitch, allow_chromatic: true)
|
517
|
+
if @background_scale
|
518
|
+
note.on(@background_scale) || note
|
519
|
+
else
|
520
|
+
note
|
521
|
+
end
|
509
522
|
end
|
510
523
|
end
|
511
|
-
end
|
512
524
|
|
513
|
-
|
525
|
+
private :calculate_note_of_pitch
|
514
526
|
|
515
|
-
|
516
|
-
|
517
|
-
|
527
|
+
def down(interval_name_or_interval, natural_or_chromatic = nil)
|
528
|
+
up(interval_name_or_interval, natural_or_chromatic, sign: -1)
|
529
|
+
end
|
518
530
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
531
|
+
def sharp(count = nil)
|
532
|
+
count ||= 1
|
533
|
+
calculate_note_of_pitch(@pitch, count)
|
534
|
+
end
|
523
535
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
536
|
+
def flat(count = nil)
|
537
|
+
count ||= 1
|
538
|
+
sharp(-count)
|
539
|
+
end
|
528
540
|
|
529
|
-
|
530
|
-
|
531
|
-
|
541
|
+
def frequency
|
542
|
+
@scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root)
|
543
|
+
end
|
532
544
|
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
else
|
537
|
-
if kind_id_or_kind.is_a? ScaleKind
|
538
|
-
kind_id_or_kind[@pitch]
|
545
|
+
def scale(kind_id_or_kind = nil)
|
546
|
+
if kind_id_or_kind.nil?
|
547
|
+
@scale
|
539
548
|
else
|
540
|
-
|
549
|
+
if kind_id_or_kind.is_a? ScaleKind
|
550
|
+
kind_id_or_kind[@pitch]
|
551
|
+
else
|
552
|
+
@scale.kind.tuning[kind_id_or_kind][@pitch]
|
553
|
+
end
|
541
554
|
end
|
542
555
|
end
|
543
|
-
end
|
544
|
-
|
545
|
-
def on(scale)
|
546
|
-
scale.note_of_pitch @pitch
|
547
|
-
end
|
548
|
-
|
549
|
-
def chord(*feature_values, allow_chromatic: nil, **features_hash)
|
550
|
-
features = { size: :triad } if feature_values.empty? && features_hash.empty?
|
551
|
-
features ||= ChordDefinition.features_from(feature_values, features_hash)
|
552
556
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
def ==(other)
|
557
|
-
self.class == other.class &&
|
558
|
-
@scale == other.scale &&
|
559
|
-
@grade == other.grade &&
|
560
|
-
@octave == other.octave &&
|
561
|
-
@pitch == other.pitch
|
562
|
-
end
|
557
|
+
def on(scale)
|
558
|
+
scale.note_of_pitch @pitch
|
559
|
+
end
|
563
560
|
|
564
|
-
|
565
|
-
|
566
|
-
|
561
|
+
def chord(*feature_values, allow_chromatic: nil, **features_hash)
|
562
|
+
features = { size: :triad } if feature_values.empty? && features_hash.empty?
|
563
|
+
features ||= ChordDefinition.features_from(feature_values, features_hash)
|
567
564
|
|
568
|
-
|
565
|
+
Musa::Chords::Chord.new(root: self, allow_chromatic: allow_chromatic, features: features)
|
566
|
+
end
|
569
567
|
|
570
|
-
|
568
|
+
def ==(other)
|
569
|
+
self.class == other.class &&
|
570
|
+
@scale == other.scale &&
|
571
|
+
@grade == other.grade &&
|
572
|
+
@octave == other.octave &&
|
573
|
+
@pitch == other.pitch
|
574
|
+
end
|
571
575
|
|
572
|
-
|
573
|
-
|
574
|
-
scale(method_name)
|
575
|
-
else
|
576
|
-
super
|
576
|
+
def inspect
|
577
|
+
"<NoteInScale: grade = #{@grade} octave = #{@octave} pitch = #{@pitch} scale = (#{@scale.kind.class.name} on #{scale.root_pitch})>"
|
577
578
|
end
|
578
|
-
end
|
579
579
|
|
580
|
-
|
581
|
-
@scale.kind.tuning[method_name] || super
|
580
|
+
alias to_s inspect
|
582
581
|
end
|
583
582
|
end
|
584
583
|
end
|