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.
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