jazz_model 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|