├── .ruby-version
├── .gitignore
├── .ruby-gemset
├── Gemfile
├── scale_filter_note.rb
├── scale_filter_generator
├── scale_filter_note_interval.rb
├── Gemfile.lock
├── LICENSE
├── scale_filter_generator.rb
├── README.md
└── scale_filter_device.rb
/.ruby-version:
--------------------------------------------------------------------------------
1 | ruby-2.6.3
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | output
3 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | scale_filter_generator
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # A sample Gemfile
3 | source "https://rubygems.org"
4 |
5 | gem "rb-music-theory", github: "chrisbratlien/rb-music-theory"
6 |
--------------------------------------------------------------------------------
/scale_filter_note.rb:
--------------------------------------------------------------------------------
1 | require './scale_filter_note_interval'
2 |
3 | class ScaleFilterNote < Note
4 | def general_minor_scale
5 | intervals = ScaleFilterNoteInterval.general_minor_set
6 | Scale.new(self, intervals)
7 | end
8 | end
--------------------------------------------------------------------------------
/scale_filter_generator:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems'
4 | require 'bundler'
5 | Bundler.setup(:default)
6 |
7 | require 'rb-music-theory'
8 | require './scale_filter_generator'
9 |
10 | ScaleFilterGenerator.generate_all
11 |
--------------------------------------------------------------------------------
/scale_filter_note_interval.rb:
--------------------------------------------------------------------------------
1 | class ScaleFilterNoteInterval < NoteInterval
2 | def self.general_minor_set
3 | sets = NoteInterval::SCALE_SETS
4 | set = sets[:aeolian] | sets[:harmonic_minor] | sets[:melodic_minor]
5 | set.map { |n| NoteInterval.new(n) }
6 | end
7 | end
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: git://github.com/chrisbratlien/rb-music-theory.git
3 | revision: f5aab74ecc666600f317dd074214c3e6ff220c57
4 | specs:
5 | rb-music-theory (0.1.1)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 |
11 | PLATFORMS
12 | ruby
13 |
14 | DEPENDENCIES
15 | rb-music-theory!
16 |
17 | BUNDLED WITH
18 | 1.17.2
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Michael Hines
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/scale_filter_generator.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'zlib'
3 | require 'shellwords'
4 | require './scale_filter_note'
5 | require './scale_filter_device'
6 |
7 | class ScaleFilterGenerator
8 | SCALE_NAMES = {
9 | major: "Major (Ionian)",
10 | natural_minor: "Natural Minor (Aeolian)",
11 | general_minor: "General Minor (Aeolian+Harmonic+Melodic)",
12 | dorian: "Dorian",
13 | phrygian: "Phrygian",
14 | lydian: "Lydian",
15 | mixolydian: "Mixolydian",
16 | locrian: "Locrian",
17 | harmonic_minor: "Harmonic Minor",
18 | melodic_minor: "Melodic Minor",
19 | whole_tone: "Whole Tone",
20 | diminished: "Diminished",
21 | major_pentatonic: "Major Pentatonic",
22 | minor_pentatonic: "Minor Pentatonic",
23 | minor_major_pentatonic: "Minor Major Pentatonic",
24 | enigmatic: "Enigmatic",
25 | major_neapolitan: "Major Neapolitan",
26 | minor_neapolitan: "Minor Neapolitan",
27 | minor_hungarian: "Minor Hungarian"
28 | }
29 |
30 | class << self
31 | def flat_note_name(note)
32 | ScaleFilterNote.flat_twelve_tones[note.value % 12]
33 | end
34 |
35 | def note_label(note_number)
36 | note = ScaleFilterNote.new(note_number)
37 | octave = (note_number/12) - 2
38 | "\u2666 #{note.name}#{octave}"
39 | end
40 |
41 | def generate_filter(path, filter_name, scale)
42 | Zlib::GzipWriter.open(File.join(path, "#{filter_name}.adg")) do |gz|
43 | notes_and_values = []
44 | scale_note_values = scale.note_values.map { |note_number| note_number % 12}
45 | 127.times.map do |note_number|
46 | notes_and_values << [note_label(note_number), note_number] if scale_note_values.include?(note_number % 12)
47 | end
48 | gz.write ScaleFilterDevice.new(filter_name, notes_and_values).xml
49 | end
50 | end
51 |
52 | def generate_all
53 | FileUtils.rm_rf("output")
54 | SCALE_NAMES.each do |scale_identifier, scale_name|
55 | path = "./output/Scale Filters/#{scale_name}"
56 | FileUtils.mkdir_p(path)
57 | 12.times do |note_number|
58 | root_note = ScaleFilterNote.new(note_number)
59 | scale = root_note.send("#{scale_identifier}_scale")
60 | generate_filter(path, "#{root_note.name} #{scale_name} Scale Filter", scale)
61 | generate_filter(path, "#{flat_note_name(root_note)} #{scale_name} Scale Filter", scale) unless root_note.name == flat_note_name(root_note)
62 | end
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Scale Filter MIDI Effect Devices for Ableton
4 |
5 | This is a script that generates Ableton MIDI Effect Devices that allow you to filter the piano roll in various keys and modes.
6 |
7 | ## [Download the *Scale.Filters.zip* here](https://github.com/michaelphines/scale-filter-midi-effect-device/releases)
8 |
9 |
10 |
11 | ## Demo
12 |
13 | 
14 |
15 |
16 | ## Quirks
17 |
18 | - Only tested on Live 9.6.2.
19 | - If the device is on, you won't be able to play non-diatonic notes. However folding and note names work just fine when the device is disabled. I can't figure out how to get it to start disabled by default.
20 | - It generates lots of theoretical scales like Fb major, but names the notes like A and C# instead of Bbb and Db. This is what Ableton does anyway, though.
21 | - If you do have non-diatonic notes in your MIDI already, those will not disappear.
22 | - I added a marker before each of the diatonic note names, so you can distinguish them. This has the unintended, but possibly pleasant side effect of making notes in the piano roll also have a marker when they're diatonic, but this only works when folded.
23 | - It's mostly untested, but *please do* report bugs!
24 |
25 | ## Scales generated:
26 |
27 | #### In all keys including theoretical ones like Fb
28 |
29 | - Diminished
30 | - Dorian
31 | - Enigmatic
32 | - Harmonic Minor
33 | - Locrian
34 | - Lydian
35 | - Major (Ionian)
36 | - Major Neapolitan
37 | - Major Pentatonic
38 | - Melodic Minor
39 | - Minor Hungarian
40 | - Minor Major Pentatonic
41 | - Minor Neapolitan
42 | - Minor Pentatonic
43 | - Mixolydian
44 | - Natural Minor (Aeolian)
45 | - Phrygian
46 | - Whole Tone
47 |
48 | - General Minor
49 | - This is a scale that adds the harmonic and melodic scale notes to the natural minor scale
50 |
51 |
52 | ## Implementation details
53 |
54 | I didn't have the patience to do it by hand, so I decided to try to write a script. A quick peak at the file header of the Ableton device revealed it was just a gziped XML file, and I found a Ruby gem that generates scales, so I was off to the races. It didn't work at first, but it appears that Ableton cares strangely a lot about the tabbing and whitespace in the XML, which frankly terrifies me. I hope I'm wrong about that.
55 |
56 | ## About me
57 |
58 | I produce music as [Archange7](https://archange7.com), you can find me on [Spotify](https://open.spotify.com/artist/53wzdEShKkG9TMGwVc0iEe) and [iTunes](https://geo.music.apple.com/us/artist/archange7/1207668748?mt=1&app=music).
59 |
60 | .
--------------------------------------------------------------------------------
/scale_filter_device.rb:
--------------------------------------------------------------------------------
1 | class ScaleFilterDevice
2 | attr_accessor :device_name, :note_names_and_numbers
3 |
4 | def initialize(device_name, note_names_and_numbers)
5 | @device_name = device_name
6 | @note_names_and_numbers = note_names_and_numbers
7 | end
8 |
9 | def xml
10 | preset_xml
11 | end
12 |
13 | private
14 |
15 | def midi_effect_branch_xml(note_name, note_number)
16 | <<-MIDI_EFFECT_BRANCH_XML.chomp
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
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 | MIDI_EFFECT_BRANCH_XML
151 | end
152 |
153 | def preset_xml
154 | <<-PRESET_XML
155 |
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 | 000000000256000200000C4D6163696E746F7368204844000000000000000000000000000000CFD3
199 | E476482B000001FBF7E20F474D204472756D204D61702E6164670000000000000000000000000000
200 | 0000000000000000000000000000000000000000000000000000000000000000000001FC1636D269
201 | 1A7500000000612D6C76FFFFFFFF0000092000000000000000000000000000000005546F6F6C7300
202 | 001000080000CFD42AC60000001100080000D2696ED500000001002801FBF7E201FBF7DC01FBF7D6
203 | 01FBF5D601FBF5AC006B2577005F963C005F963000093778000266F9000200974D6163696E746F73
204 | 682048443A55736572733A006D69636861656C68696E65733A004D757369633A0041626C65746F6E
205 | 3A00466163746F7279205061636B733A004C6976652038204C6567616379204C6962726172793A00
206 | 507265736574733A004D49444920456666656374733A004D49444920456666656374205261636B3A
207 | 00546F6F6C733A00474D204472756D204D61702E61646700000E0020000F0047004D002000440072
208 | 0075006D0020004D00610070002E006100640067000F001A000C004D006100630069006E0074006F
209 | 007300680020004800440012008055736572732F6D69636861656C68696E65732F4D757369632F41
210 | 626C65746F6E2F466163746F7279205061636B732F4C6976652038204C6567616379204C69627261
211 | 72792F507265736574732F4D49444920456666656374732F4D49444920456666656374205261636B
212 | 2F546F6F6C732F474D204472756D204D61702E616467001300012F00001500020013FFFF0000
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 | 000000000256000200000C4D6163696E746F7368204844000000000000000000000000000000CFD3
511 | E476482B000001FBF7E20F474D204472756D204D61702E6164670000000000000000000000000000
512 | 0000000000000000000000000000000000000000000000000000000000000000000001FC1636D269
513 | 1A7500000000612D6C76FFFFFFFF0000092000000000000000000000000000000005546F6F6C7300
514 | 001000080000CFD42AC60000001100080000D2696ED500000001002801FBF7E201FBF7DC01FBF7D6
515 | 01FBF5D601FBF5AC006B2577005F963C005F963000093778000266F9000200974D6163696E746F73
516 | 682048443A55736572733A006D69636861656C68696E65733A004D757369633A0041626C65746F6E
517 | 3A00466163746F7279205061636B733A004C6976652038204C6567616379204C6962726172793A00
518 | 507265736574733A004D49444920456666656374733A004D49444920456666656374205261636B3A
519 | 00546F6F6C733A00474D204472756D204D61702E61646700000E0020000F0047004D002000440072
520 | 0075006D0020004D00610070002E006100640067000F001A000C004D006100630069006E0074006F
521 | 007300680020004800440012008055736572732F6D69636861656C68696E65732F4D757369632F41
522 | 626C65746F6E2F466163746F7279205061636B732F4C6976652038204C6567616379204C69627261
523 | 72792F507265736574732F4D49444920456666656374732F4D49444920456666656374205261636B
524 | 2F546F6F6C732F474D204472756D204D61702E616467001300012F00001500020013FFFF0000
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 | #{note_names_and_numbers.map { |name, number| midi_effect_branch_xml(name, number) }.join("\n")}
540 |
541 |
542 |
543 | PRESET_XML
544 | end
545 | end
546 |
--------------------------------------------------------------------------------