jazz_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.rdoc +14 -0
  2. data/Gemfile +4 -0
  3. data/README.rdoc +186 -0
  4. data/Rakefile +14 -0
  5. data/lib/jazz_model.rb +34 -0
  6. data/lib/jazz_model/base.rb +15 -0
  7. data/lib/jazz_model/chord.rb +141 -0
  8. data/lib/jazz_model/chord_collection.rb +68 -0
  9. data/lib/jazz_model/chord_quality.rb +19 -0
  10. data/lib/jazz_model/chord_scale.rb +30 -0
  11. data/lib/jazz_model/chord_symbol.rb +27 -0
  12. data/lib/jazz_model/chord_symbol_collection.rb +17 -0
  13. data/lib/jazz_model/chord_tone.rb +32 -0
  14. data/lib/jazz_model/definition.rb +66 -0
  15. data/lib/jazz_model/definitions/default.rb +502 -0
  16. data/lib/jazz_model/definitions/keys.rb +48 -0
  17. data/lib/jazz_model/key.rb +49 -0
  18. data/lib/jazz_model/key_context.rb +28 -0
  19. data/lib/jazz_model/mode.rb +47 -0
  20. data/lib/jazz_model/mode_context.rb +32 -0
  21. data/lib/jazz_model/mode_sequence.rb +15 -0
  22. data/lib/jazz_model/note_sequence.rb +15 -0
  23. data/lib/jazz_model/notes_collection.rb +89 -0
  24. data/lib/jazz_model/scale.rb +136 -0
  25. data/lib/jazz_model/scale_tone.rb +24 -0
  26. data/lib/jazz_model/tone.rb +51 -0
  27. data/lib/jazz_model/tone_sequence.rb +62 -0
  28. data/lib/jazz_model/version.rb +3 -0
  29. data/lib/jazz_model/voicing.rb +12 -0
  30. data/lib/jazz_model/voicing_tone.rb +11 -0
  31. data/spec/chord_collection_spec.rb +21 -0
  32. data/spec/chord_quality_spec.rb +27 -0
  33. data/spec/chord_scale_spec.rb +7 -0
  34. data/spec/chord_spec.rb +29 -0
  35. data/spec/chord_symbol_spec.rb +6 -0
  36. data/spec/chord_tone_spec.rb +6 -0
  37. data/spec/definition_spec.rb +35 -0
  38. data/spec/key_context_spec.rb +27 -0
  39. data/spec/key_spec.rb +15 -0
  40. data/spec/mode_spec.rb +9 -0
  41. data/spec/notes_collection_spec.rb +25 -0
  42. data/spec/scale_spec.rb +42 -0
  43. data/spec/scale_tone_spec.rb +6 -0
  44. data/spec/spec_helper.rb +12 -0
  45. data/spec/tone_sequence_spec.rb +17 -0
  46. data/spec/tone_spec.rb +4 -0
  47. data/spec/voicing_spec.rb +6 -0
  48. data/spec/voicing_tone_spec.rb +6 -0
  49. 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