jazz_model 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +14 -0
- data/Gemfile +4 -0
- data/README.rdoc +186 -0
- data/Rakefile +14 -0
- data/lib/jazz_model.rb +34 -0
- data/lib/jazz_model/base.rb +15 -0
- data/lib/jazz_model/chord.rb +141 -0
- data/lib/jazz_model/chord_collection.rb +68 -0
- data/lib/jazz_model/chord_quality.rb +19 -0
- data/lib/jazz_model/chord_scale.rb +30 -0
- data/lib/jazz_model/chord_symbol.rb +27 -0
- data/lib/jazz_model/chord_symbol_collection.rb +17 -0
- data/lib/jazz_model/chord_tone.rb +32 -0
- data/lib/jazz_model/definition.rb +66 -0
- data/lib/jazz_model/definitions/default.rb +502 -0
- data/lib/jazz_model/definitions/keys.rb +48 -0
- data/lib/jazz_model/key.rb +49 -0
- data/lib/jazz_model/key_context.rb +28 -0
- data/lib/jazz_model/mode.rb +47 -0
- data/lib/jazz_model/mode_context.rb +32 -0
- data/lib/jazz_model/mode_sequence.rb +15 -0
- data/lib/jazz_model/note_sequence.rb +15 -0
- data/lib/jazz_model/notes_collection.rb +89 -0
- data/lib/jazz_model/scale.rb +136 -0
- data/lib/jazz_model/scale_tone.rb +24 -0
- data/lib/jazz_model/tone.rb +51 -0
- data/lib/jazz_model/tone_sequence.rb +62 -0
- data/lib/jazz_model/version.rb +3 -0
- data/lib/jazz_model/voicing.rb +12 -0
- data/lib/jazz_model/voicing_tone.rb +11 -0
- data/spec/chord_collection_spec.rb +21 -0
- data/spec/chord_quality_spec.rb +27 -0
- data/spec/chord_scale_spec.rb +7 -0
- data/spec/chord_spec.rb +29 -0
- data/spec/chord_symbol_spec.rb +6 -0
- data/spec/chord_tone_spec.rb +6 -0
- data/spec/definition_spec.rb +35 -0
- data/spec/key_context_spec.rb +27 -0
- data/spec/key_spec.rb +15 -0
- data/spec/mode_spec.rb +9 -0
- data/spec/notes_collection_spec.rb +25 -0
- data/spec/scale_spec.rb +42 -0
- data/spec/scale_tone_spec.rb +6 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/tone_sequence_spec.rb +17 -0
- data/spec/tone_spec.rb +4 -0
- data/spec/voicing_spec.rb +6 -0
- data/spec/voicing_tone_spec.rb +6 -0
- metadata +194 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
JazzModel::Definition.define :keys do
|
2
|
+
JazzModel::Key.create!(:name => 'C', :long_name => 'C', :index => 0, :letter_index => 0, :cycle_index => 0)
|
3
|
+
JazzModel::Key.create!(:name => 'B#', :long_name => 'B Sharp', :index => 0, :letter_index => 6, :cycle_index => 0, :primary => false)
|
4
|
+
JazzModel::Key.create!(:name => 'Dbb', :long_name => 'D Double-Flat', :index => 0, :letter_index => 1, :cycle_index => 0, :primary => false)
|
5
|
+
|
6
|
+
JazzModel::Key.create!(:name => 'F', :long_name => 'F', :index => 5, :letter_index => 3, :cycle_index => 1)
|
7
|
+
JazzModel::Key.create!(:name => 'E#', :long_name => 'E Sharp', :index => 5, :letter_index => 2, :cycle_index => 1, :primary => false)
|
8
|
+
JazzModel::Key.create!(:name => 'Gbb', :long_name => 'G Double-Flat', :index => 5, :letter_index => 4, :cycle_index => 1, :primary => false)
|
9
|
+
|
10
|
+
JazzModel::Key.create!(:name => 'Bb', :long_name => 'B Flat', :index => 10, :letter_index => 6, :cycle_index => 2)
|
11
|
+
JazzModel::Key.create!(:name => 'A#', :long_name => 'A Sharp', :index => 10, :letter_index => 5, :cycle_index => 2, :primary => false)
|
12
|
+
JazzModel::Key.create!(:name => 'Cbb', :long_name => 'C Double-Flat', :index => 10, :letter_index => 0, :cycle_index => 2, :primary => false)
|
13
|
+
|
14
|
+
JazzModel::Key.create!(:name => 'Eb', :long_name => 'E Flat', :index => 3, :letter_index => 2, :cycle_index => 3)
|
15
|
+
JazzModel::Key.create!(:name => 'D#', :long_name => 'D Sharp', :index => 3, :letter_index => 1, :cycle_index => 3, :primary => false)
|
16
|
+
JazzModel::Key.create!(:name => 'Fbb', :long_name => 'F Double Flat', :index => 3, :letter_index => 3, :cycle_index => 3, :primary => false)
|
17
|
+
|
18
|
+
JazzModel::Key.create!(:name => 'Ab', :long_name => 'A Flat', :index => 8, :letter_index => 5, :cycle_index => 4)
|
19
|
+
JazzModel::Key.create!(:name => 'G#', :long_name => 'G Sharp', :index => 8, :letter_index => 4, :cycle_index => 4, :primary => false)
|
20
|
+
|
21
|
+
JazzModel::Key.create!(:name => 'Db', :long_name => 'D Flat', :index => 1, :letter_index => 1, :cycle_index => 5)
|
22
|
+
JazzModel::Key.create!(:name => 'C#', :long_name => 'C Sharp', :index => 1, :letter_index => 0, :cycle_index => 5, :primary => false)
|
23
|
+
JazzModel::Key.create!(:name => 'B##', :long_name => 'B Double-Sharp', :index => 1, :letter_index => 6, :cycle_index => 5, :primary => false)
|
24
|
+
|
25
|
+
JazzModel::Key.create!(:name => 'Gb', :long_name => 'G Flat', :index => 6, :letter_index => 4, :cycle_index => 6)
|
26
|
+
JazzModel::Key.create!(:name => 'F#', :long_name => 'F Sharp', :index => 6, :letter_index => 3, :cycle_index => 6, :primary => false)
|
27
|
+
JazzModel::Key.create!(:name => 'E##', :long_name => 'E Double-Sharp', :index => 6, :letter_index => 2, :cycle_index => 6, :primary => false)
|
28
|
+
|
29
|
+
JazzModel::Key.create!(:name => 'B', :long_name => 'B', :index => 11, :letter_index => 6, :cycle_index => 7)
|
30
|
+
JazzModel::Key.create!(:name => 'Cb', :long_name => 'C Flat', :index => 11, :letter_index => 0, :cycle_index => 7, :primary => false)
|
31
|
+
JazzModel::Key.create!(:name => 'A##', :long_name => 'A Double-Sharp', :index => 11, :letter_index => 5, :cycle_index => 7, :primary => false)
|
32
|
+
|
33
|
+
JazzModel::Key.create!(:name => 'E', :long_name => 'E', :index => 4, :letter_index => 2, :cycle_index => 8)
|
34
|
+
JazzModel::Key.create!(:name => 'Fb', :long_name => 'F Flat', :index => 4, :letter_index => 3, :cycle_index => 8, :primary => false)
|
35
|
+
JazzModel::Key.create!(:name => 'D##', :long_name => 'D Double-Sharp', :index => 4, :letter_index => 1, :cycle_index => 8, :primary => false)
|
36
|
+
|
37
|
+
JazzModel::Key.create!(:name => 'A', :long_name => 'A', :index => 9, :letter_index => 5, :cycle_index => 9)
|
38
|
+
JazzModel::Key.create!(:name => 'Bbb', :long_name => 'B Double-Flat', :index => 9, :letter_index => 6, :cycle_index => 9, :primary => false)
|
39
|
+
JazzModel::Key.create!(:name => 'G##', :long_name => 'G Double-Sharp', :index => 9, :letter_index => 4, :cycle_index => 9, :primary => false)
|
40
|
+
|
41
|
+
JazzModel::Key.create!(:name => 'D', :long_name => 'D', :index => 2, :letter_index => 1, :cycle_index => 10)
|
42
|
+
JazzModel::Key.create!(:name => 'C##', :long_name => 'C Double-Sharp', :index => 2, :letter_index => 0, :cycle_index => 10, :primary => false)
|
43
|
+
JazzModel::Key.create!(:name => 'Ebb', :long_name => 'E Double-Flat', :index => 2, :letter_index => 2, :cycle_index => 10, :primary => false)
|
44
|
+
|
45
|
+
JazzModel::Key.create!(:name => 'G', :long_name => 'G', :index => 7, :letter_index => 4, :cycle_index => 11)
|
46
|
+
JazzModel::Key.create!(:name => 'F##', :long_name => 'F Double-Sharp', :index => 7, :letter_index => 3, :cycle_index => 11, :primary => false)
|
47
|
+
JazzModel::Key.create!(:name => 'Abb', :long_name => 'A Double-Flat', :index => 7, :letter_index => 5, :cycle_index => 11, :primary => false)
|
48
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# A key represents a specific theoretic tone mapped to a pitch and a letter
|
3
|
+
# index. Keys are defined for all letters A-G and all sharp/flat values
|
4
|
+
# within two semitons (e.g. Dbb, D Double-Flat enharmonic with C).
|
5
|
+
#
|
6
|
+
# Each key is associated an index between 0-11 to represent the actual pitch
|
7
|
+
# within the chromatic scale.
|
8
|
+
# Each key is also assigned a "letter index" which represents a relative letter
|
9
|
+
# value. This index is crucial as it disambiguates theoretical tone differences
|
10
|
+
# such as Eb and D#. Doing so also supports correct double-sharp and double-flat
|
11
|
+
# interpretations which are rare but theoretically different from their enharmonic
|
12
|
+
# counterparts.
|
13
|
+
#
|
14
|
+
class Key < JazzModel::Base
|
15
|
+
Letters = ['C' => 0, 'D' => 1, 'E' => 2, 'F' => 3, 'G' => 4, 'A' => 5, 'B' => 6]
|
16
|
+
|
17
|
+
# Finds a key given a tonal index 0-11 and a letter index (to disambiguate enharmonic keys)
|
18
|
+
def self.from_index(value, preferred_letter = nil)
|
19
|
+
self.all.find {|k| k.index == value && (preferred_letter.nil? || k.letter_index == preferred_letter)}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds a key object given the name (such as Eb)
|
23
|
+
def self.from_name(value)
|
24
|
+
self.all.find {|k| k.name == value}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Finds a key object based on the name:
|
28
|
+
# +Key['Eb']+
|
29
|
+
#
|
30
|
+
def self.[](value)
|
31
|
+
self.from_name(value) || self.from_index(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an array of the 12 primary keys (definitions around the cycle of fourths)
|
35
|
+
def self.primaries
|
36
|
+
self.find_all_by_primary(true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.default
|
40
|
+
self['C']
|
41
|
+
end
|
42
|
+
|
43
|
+
# Tests whether the current key is enharmonic with +another_key+.
|
44
|
+
def enharmonic_with?(another_key)
|
45
|
+
self.index == another_key.index
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# Key context is a module intended to be mixed in to classes requiring
|
3
|
+
# the notion of "key context". Concepts such as chords and scales exist
|
4
|
+
# as mathematical relationships absent key, but can be put into key context.
|
5
|
+
# Adds a key instance variable to the including class and two new methods:
|
6
|
+
#
|
7
|
+
# * +in_key_of+(new_key) - Sets the key context to the specified new_key.
|
8
|
+
# * +without_key+ - Removes key context.
|
9
|
+
#
|
10
|
+
module KeyContext
|
11
|
+
def self.included(klass)
|
12
|
+
klass.class_eval do
|
13
|
+
attr_accessor :key
|
14
|
+
|
15
|
+
def in_key_of(name)
|
16
|
+
self.key = Key.find_by_name(name)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
alias_method :in, :in_key_of
|
20
|
+
|
21
|
+
def without_key
|
22
|
+
self.key = nil
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# In music theory a mode is essentially a slice of a scale with an offset.
|
3
|
+
# For example second mode of the C major scale would begin and end on D
|
4
|
+
# but otherwise have the exact same tones as the C major scale.
|
5
|
+
#
|
6
|
+
# This class represents a mode within the context of a +Scale+.
|
7
|
+
#
|
8
|
+
# == Associations
|
9
|
+
#
|
10
|
+
# * +scale+ - The related scale.
|
11
|
+
# * +chord_scales+ - +ChordScale+ relationships to this mode.
|
12
|
+
# * +chords+ - Direct access to a collection of chords through +chord_scales+.
|
13
|
+
#
|
14
|
+
# == Tones and Mode Context
|
15
|
+
#
|
16
|
+
# Tones are directly related to scales, not modes. There is a +tones+ method
|
17
|
+
# (and a +notes+ method that delegates to +tones+) here that simulates the tones for
|
18
|
+
# the mode by setting the mode context of the scale tones, effectively returning
|
19
|
+
# the tones shifted by X where X is the mode number.
|
20
|
+
# Therefore, you can access tones directly off of the mode because they are proxied
|
21
|
+
# through scale with the correct offset.
|
22
|
+
#
|
23
|
+
# == Examples
|
24
|
+
#
|
25
|
+
# Since modes should be accessed within the context of scales, mode-related examples are
|
26
|
+
# actually in scale. See +Scale+.
|
27
|
+
#
|
28
|
+
class Mode < JazzModel::Base
|
29
|
+
include KeyContext
|
30
|
+
|
31
|
+
belongs_to :scale
|
32
|
+
|
33
|
+
has_many :chord_scales
|
34
|
+
has_many :chords, :through => :chord_scales, :extend => ChordCollection
|
35
|
+
|
36
|
+
delegate :notes, :to => :tones
|
37
|
+
|
38
|
+
def tones
|
39
|
+
if self.key
|
40
|
+
self.scale.tones.in_mode(self.mode).in_key_of(self.key)
|
41
|
+
else
|
42
|
+
self.scale.tones.in_mode(self.mode)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# Mode context is a module intended to be mixed in to classes requiring
|
3
|
+
# the notion of "mode context". Right now the only class that uses this
|
4
|
+
# is scale. Mode context allows scales to take on a mode context which
|
5
|
+
# effectively offsets tone sequences to retrieve tones in the given scale mode.
|
6
|
+
#
|
7
|
+
# * +in_mode(new_mode)+ - Sets the mode context to the specified mode (integer).
|
8
|
+
# * +without_mode+ - Removes mode context.
|
9
|
+
#
|
10
|
+
module ModeContext
|
11
|
+
def self.included(klass)
|
12
|
+
klass.class_eval do
|
13
|
+
attr_accessor :mode
|
14
|
+
|
15
|
+
def in_mode(value)
|
16
|
+
if value.is_a?(String)
|
17
|
+
mode_object = self.respond_to?(:modes) ? self.modes.find_by_name(value) : Mode.find_by_name(value)
|
18
|
+
self.mode = mode_object.mode unless mode_object.nil?
|
19
|
+
else
|
20
|
+
self.mode = value
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def without_mode
|
26
|
+
self.mode = nil
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# Extends the Scale.modes association by allowing indexed access to modes.
|
3
|
+
# Whn using it in this manner, key context is passed along.
|
4
|
+
#
|
5
|
+
module ModeSequence
|
6
|
+
def [](value)
|
7
|
+
mode_object = self.find(:first, :conditions => ["name = ? OR mode = ?", value, value])
|
8
|
+
return nil if mode_object.nil?
|
9
|
+
|
10
|
+
# Pass Along Key Information
|
11
|
+
mode_object.key = proxy_owner.key if proxy_owner.respond_to?(:key) and !proxy_owner.key.nil?
|
12
|
+
mode_object
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# Mixins for a sequence of note represented as an array.
|
3
|
+
module NoteSequence
|
4
|
+
def to_xml(options = {})
|
5
|
+
options[:indent] ||= 2
|
6
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
7
|
+
xml.instruct! unless options[:skip_instruct]
|
8
|
+
xml.notes do
|
9
|
+
self.each do |note|
|
10
|
+
xml.tag!(:note, note.to_s)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module JazzModel
|
4
|
+
# NotesCollection represents a collection of notes. Using this collection,
|
5
|
+
# you can determine chord and scale/mode matches in specific or all keys.
|
6
|
+
# This class is currently under heavily development and will eventually be home to heavy logic involved
|
7
|
+
# in associating an arbitrary collection of notes with chords, given that
|
8
|
+
# some scales tone may be "omitable", etc.
|
9
|
+
#
|
10
|
+
# == Creating a NotesCollection object
|
11
|
+
#
|
12
|
+
# Creating the collection of notes should be done by indexing
|
13
|
+
# the Notes class directly with a comma-separated string of notes.
|
14
|
+
#
|
15
|
+
# == Current Limitations
|
16
|
+
#
|
17
|
+
# Will only do exact 100% match of notes given and notes in chords. No fuzzyness
|
18
|
+
# such as the ability to omit the root and other non-important tones. Does not yet
|
19
|
+
# find supersets or subsets.
|
20
|
+
#
|
21
|
+
# == Examples
|
22
|
+
#
|
23
|
+
# === Finding All Associated Chords
|
24
|
+
#
|
25
|
+
# NotesCollection['C, E, G, A'].chords.symbols
|
26
|
+
# # => "Amin7, Cmaj6"
|
27
|
+
#
|
28
|
+
# === Finding Associated Chords in Given Key
|
29
|
+
#
|
30
|
+
# NotesCollection['C, E, G, A'].in_key_of('A').chords.symbols
|
31
|
+
# # => "Amin7"
|
32
|
+
#
|
33
|
+
class NotesCollection
|
34
|
+
include Enumerable
|
35
|
+
include KeyContext
|
36
|
+
|
37
|
+
attr_accessor :keys
|
38
|
+
attr_accessor :invalid_keys
|
39
|
+
|
40
|
+
def initialize(value = [])
|
41
|
+
@value_as_given = value
|
42
|
+
@keys = []
|
43
|
+
@invalid_keys = []
|
44
|
+
|
45
|
+
value = value.split(/,| /).map(&:strip) if value.instance_of?(String)
|
46
|
+
value.each do |key_name|
|
47
|
+
key_object = Key[key_name]
|
48
|
+
|
49
|
+
if key_object.nil?
|
50
|
+
@invalid_keys << key_name
|
51
|
+
else
|
52
|
+
@keys << key_object
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class << self
|
58
|
+
alias_method :[], :new
|
59
|
+
end
|
60
|
+
|
61
|
+
delegate :each, :to => :keys
|
62
|
+
|
63
|
+
# Chords associated with this collection of notes
|
64
|
+
def chords
|
65
|
+
if key
|
66
|
+
chords_in_key(key)
|
67
|
+
else
|
68
|
+
Key.primaries.map do |in_key|
|
69
|
+
chords_in_key(in_key)
|
70
|
+
end.flatten
|
71
|
+
end.extend(ChordCollection)
|
72
|
+
end
|
73
|
+
|
74
|
+
# XML Representation
|
75
|
+
def to_xml
|
76
|
+
'<NotesCollection name="' + @value_as_given.to_s + '"></NotesCollection>'
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def chords_in_key(in_key)
|
83
|
+
Chord.all.select do |c|
|
84
|
+
c.tones.map(&:tone).to_set == keys.map {|k| (k.index - (in_key ? in_key.index : 0)) % 12 }.to_set
|
85
|
+
end.map {|c| c.key = in_key; c}
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module JazzModel
|
2
|
+
# A scale is an ordered collection of tones.
|
3
|
+
#
|
4
|
+
# == Creating a Scale
|
5
|
+
#
|
6
|
+
# Scale objects should be created by indexing the +Scale+ class directly:
|
7
|
+
#
|
8
|
+
# Scale['Major']
|
9
|
+
#
|
10
|
+
# An alternative method of resolving a chord is to use the +resolve+ method,
|
11
|
+
# though this is likely to become deprecated in the future when chord progression
|
12
|
+
# support/representation is added:
|
13
|
+
#
|
14
|
+
# Scale.resolve(name)
|
15
|
+
#
|
16
|
+
# == Associations
|
17
|
+
#
|
18
|
+
# * +modes+ - Associated modes.
|
19
|
+
# * +main_mode+ - Associated mode with a mode index of 1 (first mode). This sometimes
|
20
|
+
# the only defined mode such as for the whole tone scale.
|
21
|
+
# * +tones+ - A collection of +ScaleTone+s.
|
22
|
+
# * +notes+ - An array of notes (delegated through +tones+)
|
23
|
+
# * +chords+ - A collection of associated chords with the first mode of this scale.
|
24
|
+
#
|
25
|
+
# == Modes
|
26
|
+
#
|
27
|
+
# Each scale can have one or many modes, which really define the relationship to chords.
|
28
|
+
# Modes are accessible like so (all methods are equal):
|
29
|
+
#
|
30
|
+
# Scale['Major']['Dorian']
|
31
|
+
# Scale['Major'][2]
|
32
|
+
# Scale['Major'].modes['Dorian']
|
33
|
+
# Scale['Major'].modes[2]
|
34
|
+
#
|
35
|
+
# == Examples
|
36
|
+
#
|
37
|
+
# === Listing Tones in Scales
|
38
|
+
#
|
39
|
+
# Scale['Major'].notes
|
40
|
+
# # => ["C", "D", "E", "F", "G", "A", "B"]
|
41
|
+
#
|
42
|
+
# Scale['Major'].in_key_of('Eb').notes
|
43
|
+
# Scale['Eb Major'].notes # Same as above
|
44
|
+
# # => ["Eb", "F", "G", "Ab", "Bb", "C", "D"]
|
45
|
+
#
|
46
|
+
# === Correctly Interpets Theoretical Keys (not just pitches)
|
47
|
+
#
|
48
|
+
# Scale['Gb Major'].notes
|
49
|
+
# # => ["Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F"]
|
50
|
+
# # Note the use of Cb which is theoretically correct over enharmonic B.
|
51
|
+
#
|
52
|
+
# Also correctly interpet tones off of enharmonic base keys:
|
53
|
+
#
|
54
|
+
# Scale['Gb Major'].notes
|
55
|
+
# # => ["Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F"]
|
56
|
+
# Scale['F# Major'].notes
|
57
|
+
# # => ["F#", "G#", "A#", "B", "C#", "D#", "E#"]
|
58
|
+
#
|
59
|
+
# === Listing Tones within the context of a mode
|
60
|
+
#
|
61
|
+
# Scale['Major'].modes['Dorian']
|
62
|
+
# Scale['Major']['Dorian'] # Same as above
|
63
|
+
# # => ["D", "E", "F", "G", "A", "B", "C"]
|
64
|
+
#
|
65
|
+
# Scale['Melodic Minor'].modes['Super Locrian'].notes
|
66
|
+
# # => ["B", "C", "D", "Eb", "F", "G", "A"] # aka. Altered Scale or Dim. Whole Tone
|
67
|
+
#
|
68
|
+
# == Listing Related Chords
|
69
|
+
#
|
70
|
+
# Scale['Major'].chords.symbols
|
71
|
+
# # => ['maj7', 'maj6', '6/9']
|
72
|
+
# Scale['Major']['Dorian'].chords.symbols
|
73
|
+
# # => ['min7', 'min6']
|
74
|
+
#
|
75
|
+
class Scale < JazzModel::Base
|
76
|
+
include KeyContext
|
77
|
+
include ModeContext
|
78
|
+
|
79
|
+
has_many :modes, :extend => ModeSequence, :dependent => :destroy
|
80
|
+
has_many :tones, :class_name => 'ScaleTone', :extend => ToneSequence, :dependent => :destroy
|
81
|
+
|
82
|
+
delegate :notes, :to => :tones
|
83
|
+
delegate :chords, :to => :main_mode
|
84
|
+
|
85
|
+
class << self
|
86
|
+
def resolve(symbol)
|
87
|
+
in_key = nil
|
88
|
+
|
89
|
+
return nil if symbol.nil?
|
90
|
+
symbol = symbol.dup
|
91
|
+
|
92
|
+
Key.all.each do |k|
|
93
|
+
if symbol.starts_with?(k.name)
|
94
|
+
in_key = k
|
95
|
+
symbol.gsub!(/^#{k.name}/, '').strip!
|
96
|
+
break
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
scale = Scale.find_by_name(symbol)
|
101
|
+
|
102
|
+
# Perhaps the matched key was really part of the name of the chord, try that:
|
103
|
+
if scale.nil? && !in_key.nil?
|
104
|
+
symbol = in_key.name + symbol
|
105
|
+
scale = Scale.all.detect {|s| s.name == symbol}
|
106
|
+
end
|
107
|
+
|
108
|
+
# If still not found, must be invalid:
|
109
|
+
return nil if scale.nil?
|
110
|
+
|
111
|
+
scale.key = in_key
|
112
|
+
scale
|
113
|
+
end
|
114
|
+
alias_method :[], :resolve
|
115
|
+
end
|
116
|
+
|
117
|
+
def [](name)
|
118
|
+
name.is_a?(String) ? self.modes.find_by_name(name) : self.modes.find_by_mode(name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def main_mode
|
122
|
+
self[1]
|
123
|
+
end
|
124
|
+
|
125
|
+
def symmetric?
|
126
|
+
!self.symmetry_index.nil?
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def to_xml(options = {})
|
131
|
+
super(options.merge(:skip_types => true))
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|