musa-dsl 0.14.32 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/README.md +5 -1
  4. data/lib/musa-dsl.rb +54 -11
  5. data/lib/musa-dsl/core-ext.rb +7 -13
  6. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
  7. data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
  8. data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
  9. data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
  10. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
  11. data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
  12. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
  13. data/lib/musa-dsl/core-ext/with.rb +26 -0
  14. data/lib/musa-dsl/datasets.rb +8 -3
  15. data/lib/musa-dsl/datasets/dataset.rb +3 -0
  16. data/lib/musa-dsl/datasets/delta-d.rb +12 -0
  17. data/lib/musa-dsl/datasets/e.rb +61 -0
  18. data/lib/musa-dsl/datasets/gdv.rb +51 -411
  19. data/lib/musa-dsl/datasets/gdvd.rb +179 -0
  20. data/lib/musa-dsl/datasets/helper.rb +41 -0
  21. data/lib/musa-dsl/datasets/p.rb +68 -0
  22. data/lib/musa-dsl/datasets/packed-v.rb +19 -0
  23. data/lib/musa-dsl/datasets/pdv.rb +22 -15
  24. data/lib/musa-dsl/datasets/ps.rb +113 -0
  25. data/lib/musa-dsl/datasets/score.rb +210 -0
  26. data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
  27. data/lib/musa-dsl/datasets/score/render.rb +31 -0
  28. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
  29. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
  30. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
  31. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
  32. data/lib/musa-dsl/datasets/v.rb +23 -0
  33. data/lib/musa-dsl/generative.rb +5 -5
  34. data/lib/musa-dsl/generative/backboner.rb +274 -0
  35. data/lib/musa-dsl/generative/darwin.rb +102 -96
  36. data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
  37. data/lib/musa-dsl/generative/markov.rb +56 -53
  38. data/lib/musa-dsl/generative/variatio.rb +234 -222
  39. data/lib/musa-dsl/logger.rb +1 -0
  40. data/lib/musa-dsl/logger/logger.rb +31 -0
  41. data/lib/musa-dsl/matrix.rb +1 -0
  42. data/lib/musa-dsl/matrix/matrix.rb +210 -0
  43. data/lib/musa-dsl/midi.rb +2 -2
  44. data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
  45. data/lib/musa-dsl/midi/midi-voices.rb +183 -182
  46. data/lib/musa-dsl/music.rb +5 -5
  47. data/lib/musa-dsl/music/chord-definition.rb +54 -50
  48. data/lib/musa-dsl/music/chord-definitions.rb +13 -9
  49. data/lib/musa-dsl/music/chords.rb +236 -238
  50. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
  51. data/lib/musa-dsl/music/scales.rb +331 -332
  52. data/lib/musa-dsl/musicxml.rb +1 -0
  53. data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
  54. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
  55. data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
  56. data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
  57. data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
  58. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
  59. data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
  60. data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
  61. data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
  62. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
  63. data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
  64. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
  65. data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
  66. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
  67. data/lib/musa-dsl/neumalang.rb +1 -1
  68. data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
  69. data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
  70. data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
  71. data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
  72. data/lib/musa-dsl/neumalang/process.citrus +21 -0
  73. data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
  74. data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
  75. data/lib/musa-dsl/neumas.rb +5 -0
  76. data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
  77. data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
  78. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
  79. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
  80. data/lib/musa-dsl/neumas/neumas.rb +37 -0
  81. data/lib/musa-dsl/neumas/string-to-neumas.rb +33 -0
  82. data/lib/musa-dsl/repl.rb +1 -1
  83. data/lib/musa-dsl/repl/repl.rb +103 -110
  84. data/lib/musa-dsl/sequencer.rb +1 -1
  85. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
  86. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +548 -321
  88. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  89. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
  91. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
  92. data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
  93. data/lib/musa-dsl/series.rb +1 -1
  94. data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
  95. data/lib/musa-dsl/series/base-series.rb +171 -168
  96. data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
  97. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  98. data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
  99. data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
  100. data/lib/musa-dsl/series/proxy-serie.rb +6 -6
  101. data/lib/musa-dsl/series/queue-serie.rb +5 -5
  102. data/lib/musa-dsl/series/series.rb +2 -0
  103. data/lib/musa-dsl/transcription.rb +4 -0
  104. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
  105. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
  106. data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
  107. data/lib/musa-dsl/transcription/transcription.rb +55 -0
  108. data/lib/musa-dsl/transport.rb +6 -6
  109. data/lib/musa-dsl/transport/clock.rb +26 -26
  110. data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
  111. data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
  112. data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
  113. data/lib/musa-dsl/transport/timer-clock.rb +72 -71
  114. data/lib/musa-dsl/transport/timer.rb +28 -26
  115. data/lib/musa-dsl/transport/transport.rb +100 -95
  116. data/musa-dsl.gemspec +3 -3
  117. metadata +73 -24
  118. data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
  119. data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
  120. data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
  121. data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
  122. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
  123. data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
  124. data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
  125. data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
  126. data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
  127. data/lib/musa-dsl/generative/rules.rb +0 -282
  128. data/lib/musa-dsl/neuma.rb +0 -1
  129. data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -1,204 +1,208 @@
1
+ require_relative 'scales'
2
+
1
3
  module Musa
2
- class TwelveSemitonesScaleSystem < ScaleSystem
3
- class << self
4
- @@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 }
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
- def id
7
- :et12
8
- end
9
+ def id
10
+ :et12
11
+ end
9
12
 
10
- def notes_in_octave
11
- 12
12
- end
13
+ def notes_in_octave
14
+ 12
15
+ end
13
16
 
14
- def part_of_tone_size
15
- 1
16
- end
17
+ def part_of_tone_size
18
+ 1
19
+ end
17
20
 
18
- def intervals
19
- @@intervals
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
- class ChromaticScaleKind < ScaleKind
36
- class << self
37
- @@pitches =
38
- [{ functions: [:_1], pitch: 0 },
39
- { functions: [:_2], pitch: 1 },
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
- def id
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
- def grades
102
- 7
103
- end
104
-
105
- def id
106
- :major
107
- end
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
- EquallyTempered12ToneScaleSystem.register MajorScaleKind
111
- end
112
-
113
- class MinorScaleKind < ScaleKind
114
- class << self
115
- @@pitches =
116
- [{ functions: %i[i _1 tonic],
117
- pitch: 0 },
118
- { functions: %i[ii _2 supertonic],
119
- pitch: 2 },
120
- { functions: %i[iii _3 mediant relative relative_major],
121
- pitch: 3 },
122
- { functions: %i[iv _4 subdominant],
123
- pitch: 5 },
124
- { functions: %i[v _5 dominant],
125
- pitch: 7 },
126
- { functions: %i[vi _6 submediant],
127
- pitch: 8 },
128
- { functions: %i[vii _7],
129
- pitch: 10 },
130
- { functions: %i[viii _8],
131
- pitch: 12 },
132
- { functions: %i[ix _9],
133
- pitch: 12 + 2 },
134
- { functions: %i[x _10],
135
- pitch: 12 + 3 },
136
- { functions: %i[xi _11],
137
- pitch: 12 + 5 },
138
- { functions: %i[xii _12],
139
- pitch: 12 + 7 },
140
- { functions: %i[xiii _13],
141
- pitch: 12 + 8 }].freeze
142
-
143
- def pitches
144
- @@pitches
145
- end
146
-
147
- def grades
148
- 7
149
- end
150
-
151
- def id
152
- :minor
153
- end
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
- EquallyTempered12ToneScaleSystem.register MinorScaleKind
157
- end
158
-
159
- class MinorHarmonicScaleKind < ScaleKind
160
- class << self
161
- @@pitches =
162
- [{ functions: %i[i _1 tonic],
163
- pitch: 0 },
164
- { functions: %i[ii _2 supertonic],
165
- pitch: 2 },
166
- { functions: %i[iii _3 mediant relative relative_major],
167
- pitch: 3 },
168
- { functions: %i[iv _4 subdominant],
169
- pitch: 5 },
170
- { functions: %i[v _5 dominant],
171
- pitch: 7 },
172
- { functions: %i[vi _6 submediant],
173
- pitch: 8 },
174
- { functions: %i[vii _7 leading],
175
- pitch: 11 },
176
- { functions: %i[viii _8],
177
- pitch: 12 },
178
- { functions: %i[ix _9],
179
- pitch: 12 + 2 },
180
- { functions: %i[x _10],
181
- pitch: 12 + 3 },
182
- { functions: %i[xi _11],
183
- pitch: 12 + 5 },
184
- { functions: %i[xii _12],
185
- pitch: 12 + 7 },
186
- { functions: %i[xiii _13],
187
- pitch: 12 + 8 }].freeze
188
-
189
- def pitches
190
- @@pitches
191
- end
192
-
193
- def grades
194
- 7
195
- end
196
-
197
- def id
198
- :minor_harmonic
199
- end
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
- EquallyTempered12ToneScaleSystem.register MinorHarmonicScaleKind
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
- class << self
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
- class ScaleSystem
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 chromatic_class
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
- def ==(other)
138
- self.class == other.class
129
+ def ==(other)
130
+ self.class == other.class
131
+ end
139
132
  end
140
- end
141
133
 
142
- class ScaleSystemTuning
143
- extend Forwardable
134
+ class ScaleSystemTuning
135
+ extend Forwardable
144
136
 
145
- def initialize(scale_system, a_frequency)
146
- @scale_system = scale_system
147
- @a_frequency = a_frequency
148
- @scale_kinds = {}
137
+ def initialize(scale_system, a_frequency)
138
+ @scale_system = scale_system
139
+ @a_frequency = a_frequency
140
+ @scale_kinds = {}
149
141
 
150
- @chromatic_scale_kind = self[@scale_system.chromatic_class.id]
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
- attr_reader :a_frequency, :scale_system
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
- def [](scale_kind_class_id)
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
- def chromatic
164
- @chromatic_scale_kind
165
- end
153
+ def_delegators :@scale_system, :notes_in_octave, :offset_of_interval
166
154
 
167
- def frequency_of_pitch(pitch, root)
168
- @scale_system.frequency_of_pitch(pitch, root, @a_frequency)
169
- end
155
+ attr_reader :a_frequency, :scale_system
170
156
 
171
- def ==(other)
172
- self.class == other.class &&
173
- @scale_system == other.scale_system &&
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
- def inspect
178
- "<ScaleSystemTuning: scale_system = #{@scale_system} a_frequency = #{@a_frequency}>"
179
- end
161
+ def chromatic
162
+ @chromatic_scale_kind
163
+ end
180
164
 
181
- alias to_s inspect
165
+ def frequency_of_pitch(pitch, root)
166
+ @scale_system.frequency_of_pitch(pitch, root, @a_frequency)
167
+ end
182
168
 
183
- private
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
- def method_missing(method_name, *args, **key_args, &block)
186
- if @scale_system.scale_kind_class?(method_name) && args.empty? && key_args.empty? && !block
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
- def respond_to_missing?(method_name, include_private)
194
- @scale_system.scale_kind_class?(method_name) || super
179
+ alias to_s inspect
195
180
  end
196
- end
197
181
 
198
- class ScaleKind
199
- extend Forwardable
182
+ class ScaleKind
183
+ extend Forwardable
200
184
 
201
- def initialize(tuning)
202
- @tuning = tuning
203
- @scales = {}
204
- end
185
+ def initialize(tuning)
186
+ @tuning = tuning
187
+ @scales = {}
188
+ end
205
189
 
206
- attr_reader :tuning
190
+ attr_reader :tuning
207
191
 
208
- def [](root_pitch)
209
- @scales[root_pitch] = Scale.new(self, root_pitch: root_pitch) unless @scales.key?(root_pitch)
210
- @scales[root_pitch]
211
- end
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
- def absolut
214
- self[0]
215
- end
197
+ def absolut
198
+ self[0]
199
+ end
216
200
 
217
- def ==(other)
218
- self.class == other.class && @tuning == other.tuning
219
- end
201
+ def ==(other)
202
+ self.class == other.class && @tuning == other.tuning
203
+ end
220
204
 
221
- def inspect
222
- "<#{self.class.name}: tuning = #{@tuning}>"
223
- end
205
+ def inspect
206
+ "<#{self.class.name}: tuning = #{@tuning}>"
207
+ end
224
208
 
225
- alias to_s inspect
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 find_index(symbol)
257
- init unless @index
258
- @index[symbol]
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 init
265
- @index = {}
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
- @index[function] = i
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
- class Scale
277
- extend Forwardable
263
+ class Scale
264
+ extend Forwardable
278
265
 
279
- def initialize(kind, root_pitch:)
280
- @notes_by_grade = {}
281
- @notes_by_pitch = {}
266
+ def initialize(kind, root_pitch:)
267
+ @notes_by_grade = {}
268
+ @notes_by_pitch = {}
282
269
 
283
- @kind = kind
270
+ @kind = kind
284
271
 
285
- @root_pitch = root_pitch
286
- end
272
+ @root_pitch = root_pitch
287
273
 
288
- def_delegators :@kind, :a_tuning
274
+ @kind.class.grades_functions.each do |name|
275
+ define_singleton_method name do
276
+ self[name]
277
+ end
278
+ end
289
279
 
290
- attr_reader :kind, :root_pitch
280
+ end
291
281
 
292
- def root
293
- self[0]
294
- end
282
+ def_delegators :@kind, :a_tuning
295
283
 
296
- def chromatic
297
- @kind.tuning.chromatic[@root_pitch]
298
- end
284
+ attr_reader :kind, :root_pitch
299
285
 
300
- def absolut
301
- @kind[0]
302
- end
286
+ def root
287
+ self[0]
288
+ end
303
289
 
304
- def octave(octave)
305
- raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
290
+ def chromatic
291
+ @kind.tuning.chromatic[@root_pitch]
292
+ end
306
293
 
307
- @kind[@root_pitch + octave * @kind.class.grades]
308
- end
294
+ def absolut
295
+ @kind[0]
296
+ end
309
297
 
310
- def [](grade_or_symbol)
298
+ def octave(octave)
299
+ raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
311
300
 
312
- 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)
301
+ @kind[@root_pitch + octave * @kind.class.grades]
302
+ end
313
303
 
314
- wide_grade, sharps = grade_of(grade_or_symbol)
304
+ def [](grade_or_symbol)
315
305
 
316
- unless @notes_by_grade.key?(wide_grade)
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
- octave = wide_grade / @kind.class.grades
319
- grade = wide_grade % @kind.class.grades
308
+ wide_grade, sharps = grade_of(grade_or_symbol)
320
309
 
321
- pitch = @root_pitch +
322
- octave * @kind.tuning.notes_in_octave +
323
- @kind.class.pitches[grade][:pitch]
310
+ unless @notes_by_grade.key?(wide_grade)
324
311
 
325
- note = NoteInScale.new self, grade, octave, pitch
312
+ octave = wide_grade / @kind.class.grades
313
+ grade = wide_grade % @kind.class.grades
326
314
 
327
- @notes_by_grade[wide_grade] = @notes_by_pitch[pitch] = note
328
- end
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
- @notes_by_grade[wide_grade].sharp(sharps)
332
- end
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
- raise ArgumentError, "Cannot parse sign on #{grade_or_string_or_symbol}" if sign
325
+ @notes_by_grade[wide_grade].sharp(sharps)
326
+ end
338
327
 
339
- grade = @kind.class.find_index name if name
328
+ def grade_of(grade_or_string_or_symbol)
329
+ name, wide_grade, accidentals = parse_grade(grade_or_string_or_symbol)
340
330
 
341
- octave = wide_grade / @kind.class.grades if wide_grade
342
- grade = wide_grade % @kind.class.grades if wide_grade
331
+ grade = @kind.class.grade_of_function name if name
343
332
 
344
- octave ||= 0
333
+ octave = wide_grade / @kind.class.grades if wide_grade
334
+ grade = wide_grade % @kind.class.grades if wide_grade
345
335
 
346
- return octave * @kind.class.grades + grade, accidentals
347
- end
336
+ octave ||= 0
348
337
 
349
- def note_of_pitch(pitch, allow_chromatic: nil, allow_nearest: nil)
350
- allow_chromatic ||= false
351
- allow_nearest ||= false
338
+ return octave * @kind.class.grades + grade, accidentals
339
+ end
352
340
 
353
- note = @notes_by_pitch[pitch]
341
+ def parse_grade(neuma_grade)
342
+ name = wide_grade = nil
343
+ accidentals = 0
354
344
 
355
- unless note
356
- pitch_offset = pitch - @root_pitch
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
- pitch_offset_in_octave = pitch_offset % @kind.tuning.scale_system.notes_in_octave
359
- pitch_offset_octave = pitch_offset / @kind.tuning.scale_system.notes_in_octave
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
- grade = @kind.class.pitches.find_index { |pitch_definition| pitch_definition[:pitch] == pitch_offset_in_octave }
362
+ else
363
+ raise ArgumentError, "Cannot eval #{neuma_grade} as name or grade position."
364
+ end
362
365
 
363
- if grade
364
- wide_grade = pitch_offset_octave * @kind.class.grades + grade
365
- note = self[wide_grade]
366
+ return name, wide_grade, accidentals
367
+ end
366
368
 
367
- elsif allow_nearest
368
- sharps = 0
369
+ def note_of_pitch(pitch, allow_chromatic: nil, allow_nearest: nil)
370
+ allow_chromatic ||= false
371
+ allow_nearest ||= false
369
372
 
370
- until note
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
- elsif allow_chromatic
376
- nearest = note_of_pitch(pitch, allow_nearest: true)
375
+ unless note
376
+ pitch_offset = pitch - @root_pitch
377
377
 
378
- 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)
379
- end
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
- note
383
- end
381
+ grade = @kind.class.pitches.find_index { |pitch_definition| pitch_definition[:pitch] == pitch_offset_in_octave }
384
382
 
385
- def offset_of_interval(interval_name)
386
- @kind.tuning.offset_of_interval(interval_name)
387
- end
383
+ if grade
384
+ wide_grade = pitch_offset_octave * @kind.class.grades + grade
385
+ note = self[wide_grade]
388
386
 
389
- def chord_of(*grades_or_symbols)
390
- Chord.new(notes: grades_or_symbols.collect { |g| self[g] })
391
- end
387
+ elsif allow_nearest
388
+ sharps = 0
392
389
 
393
- def ==(other)
394
- self.class == other.class &&
395
- @kind == other.kind &&
396
- @root_pitch == other.root_pitch
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
- def inspect
400
- "<Scale: kind = #{@kind} root_pitch = #{@root_pitch}>"
401
- end
395
+ elsif allow_chromatic
396
+ nearest = note_of_pitch(pitch, allow_nearest: true)
402
397
 
403
- alias to_s inspect
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
- private
402
+ note
403
+ end
406
404
 
407
- def method_missing(method_name, *args, **key_args, &block)
408
- if @kind.class.find_index(method_name) && args.empty? && key_args.empty? && !block
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
- def respond_to_missing?(method_name, include_private)
416
- @kind.class.find_index(method_name) || super
417
- end
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
- class NoteInScale
421
-
422
- # @param scale [Scale]
423
- # @param grade []
424
- # @param octave [Integer]
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
- attr_reader :grade, :pitch
419
+ def inspect
420
+ "<Scale: kind = #{@kind} root_pitch = #{@root_pitch}>"
421
+ end
440
422
 
441
- def functions
442
- @scale.kind.class.pitches[grade][:functions]
423
+ alias to_s inspect
443
424
  end
444
425
 
445
- def octave(octave = nil)
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
- @scale[@grade + (@octave + octave) * @scale.kind.class.grades]
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
- def with_background(scale:, grade: nil, octave: nil, sharps: nil)
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
- attr_reader :background_scale
453
+ def functions
454
+ @scale.kind.class.pitches[grade][:functions]
455
+ end
464
456
 
465
- def background_note
466
- @background_scale[@background_grade + (@background_octave || 0) * @background_scale.kind.class.grades] if @background_grade
467
- end
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
- attr_reader :background_sharps
463
+ @scale[@grade + (@octave + octave) * @scale.kind.class.grades]
464
+ end
465
+ end
470
466
 
471
- def wide_grade
472
- @grade + @octave * @scale.kind.class.grades
473
- end
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
- def up(interval_name_or_interval, natural_or_chromatic = nil, sign: nil)
475
+ attr_reader :background_scale
476
476
 
477
- sign ||= 1
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
- if interval_name_or_interval.is_a?(Numeric)
480
- natural_or_chromatic ||= :natural
481
- else
482
- natural_or_chromatic = :chromatic
481
+ attr_reader :background_sharps
482
+
483
+ def wide_grade
484
+ @grade + @octave * @scale.kind.class.grades
483
485
  end
484
486
 
485
- if natural_or_chromatic == :chromatic
486
- interval = if interval_name_or_interval.is_a?(Symbol)
487
- @scale.kind.tuning.offset_of_interval(interval_name_or_interval)
488
- else
489
- interval_name_or_interval
490
- end
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
- calculate_note_of_pitch(@pitch, sign * interval)
493
- else
494
- @scale[@grade + sign * interval_name_or_interval]
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
- def calculate_note_of_pitch(in_scale_pitch, sharps)
499
- pitch = in_scale_pitch + sharps * @scale.kind.tuning.scale_system.part_of_tone_size
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
- if pitch == @pitch
502
- self
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
- private :calculate_note_of_pitch
525
+ private :calculate_note_of_pitch
514
526
 
515
- def down(interval_name_or_interval, natural_or_chromatic = nil)
516
- up(interval_name_or_interval, natural_or_chromatic, sign: -1)
517
- end
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
- def sharp(count = nil)
520
- count ||= 1
521
- calculate_note_of_pitch(@pitch, count)
522
- end
531
+ def sharp(count = nil)
532
+ count ||= 1
533
+ calculate_note_of_pitch(@pitch, count)
534
+ end
523
535
 
524
- def flat(count = nil)
525
- count ||= 1
526
- sharp(-count)
527
- end
536
+ def flat(count = nil)
537
+ count ||= 1
538
+ sharp(-count)
539
+ end
528
540
 
529
- def frequency
530
- @scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root)
531
- end
541
+ def frequency
542
+ @scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root)
543
+ end
532
544
 
533
- def scale(kind_id_or_kind = nil)
534
- if kind_id_or_kind.nil?
535
- @scale
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
- @scale.kind.tuning[kind_id_or_kind][@pitch]
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
- Musa::Chord.new(root: self, allow_chromatic: allow_chromatic, features: features)
554
- end
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
- def inspect
565
- "<NoteInScale: grade = #{@grade} octave = #{@octave} pitch = #{@pitch} scale = (#{@scale.kind.class.name} on #{scale.root_pitch})>"
566
- end
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
- alias to_s inspect
565
+ Musa::Chords::Chord.new(root: self, allow_chromatic: allow_chromatic, features: features)
566
+ end
569
567
 
570
- private
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
- def method_missing(method_name, *args, **key_args, &block)
573
- if @scale.kind.tuning[method_name] && args.empty? && key_args.empty? && !block
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
- def respond_to_missing?(method_name, include_private)
581
- @scale.kind.tuning[method_name] || super
580
+ alias to_s inspect
582
581
  end
583
582
  end
584
583
  end