jazz_model 0.1.0 → 0.2.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +187 -0
  4. data/db/schema.rb +83 -0
  5. data/lib/jazz_model/base.rb +13 -6
  6. data/lib/jazz_model/chord.rb +50 -70
  7. data/lib/jazz_model/chord_collection.rb +21 -45
  8. data/lib/jazz_model/chord_quality.rb +5 -6
  9. data/lib/jazz_model/chord_scale.rb +13 -13
  10. data/lib/jazz_model/chord_symbol.rb +14 -12
  11. data/lib/jazz_model/chord_symbol_collection.rb +3 -10
  12. data/lib/jazz_model/chord_tone.rb +20 -21
  13. data/lib/jazz_model/definition.rb +18 -18
  14. data/lib/jazz_model/definitions/default.rb +316 -321
  15. data/lib/jazz_model/definitions/keys.rb +36 -36
  16. data/lib/jazz_model/definitions.rb +3 -0
  17. data/lib/jazz_model/key.rb +22 -25
  18. data/lib/jazz_model/key_context.rb +31 -22
  19. data/lib/jazz_model/mode.rb +30 -35
  20. data/lib/jazz_model/mode_context.rb +22 -26
  21. data/lib/jazz_model/mode_sequence.rb +13 -12
  22. data/lib/jazz_model/notes_collection.rb +64 -71
  23. data/lib/jazz_model/scale.rb +63 -85
  24. data/lib/jazz_model/scale_tone.rb +14 -15
  25. data/lib/jazz_model/tone.rb +47 -48
  26. data/lib/jazz_model/tone_sequence.rb +56 -58
  27. data/lib/jazz_model/version.rb +2 -2
  28. data/lib/jazz_model/voicing.rb +6 -7
  29. data/lib/jazz_model/voicing_tone.rb +5 -6
  30. data/lib/jazz_model.rb +7 -10
  31. metadata +104 -141
  32. data/CHANGELOG.rdoc +0 -14
  33. data/Gemfile +0 -4
  34. data/README.rdoc +0 -186
  35. data/Rakefile +0 -14
  36. data/lib/jazz_model/note_sequence.rb +0 -15
  37. data/spec/chord_collection_spec.rb +0 -21
  38. data/spec/chord_quality_spec.rb +0 -27
  39. data/spec/chord_scale_spec.rb +0 -7
  40. data/spec/chord_spec.rb +0 -29
  41. data/spec/chord_symbol_spec.rb +0 -6
  42. data/spec/chord_tone_spec.rb +0 -6
  43. data/spec/definition_spec.rb +0 -35
  44. data/spec/key_context_spec.rb +0 -27
  45. data/spec/key_spec.rb +0 -15
  46. data/spec/mode_spec.rb +0 -9
  47. data/spec/notes_collection_spec.rb +0 -25
  48. data/spec/scale_spec.rb +0 -42
  49. data/spec/scale_tone_spec.rb +0 -6
  50. data/spec/spec_helper.rb +0 -12
  51. data/spec/tone_sequence_spec.rb +0 -17
  52. data/spec/tone_spec.rb +0 -4
  53. data/spec/voicing_spec.rb +0 -6
  54. data/spec/voicing_tone_spec.rb +0 -6
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1ae14c88e9a943a376b18425ad41afe9e0d849f49b2e5dcc41461deff3876776
4
+ data.tar.gz: 32efbd9b5965a81b914107eb98c3cf953c87a1b6ccdc96ca5d2c58882ad06568
5
+ SHA512:
6
+ metadata.gz: 692112258b9f3e43c79baf63372ab00faf5e3ad97a52e720733ba83747b2b7534b3751b2b7b867215282b46c8f4e2fb65b8bb71ee622c5e6e06c71c04899a270
7
+ data.tar.gz: 37f10d76333e8bf5e10bf01b19d643487663d0aa33c0b4793c6e24c4c533a5252dbc12b7fd3d86e74a51d68974a2163836ee51bf53be98645ae99332c520fb85
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2008-2026 Ben Hughes
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.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Jazz Model
2
+
3
+ [![CI](https://github.com/eigenben/jazz_model/actions/workflows/ci.yml/badge.svg)](https://github.com/eigenben/jazz_model/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/jazz_model.svg)](https://rubygems.org/gems/jazz_model)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
6
+
7
+ An ActiveRecord object model of jazz theory. Resolve chord symbols, spell chords and scales correctly in any key, and walk the relationships between chords, scales, and modes.
8
+
9
+ ```ruby
10
+ Chord["Bmaj7#11"].notes
11
+ # => ["B", "D#", "F#", "A#", "E#"] # E#, not F: theoretically correct spelling
12
+
13
+ Scale["Melodic Minor"]["Super Locrian"].notes
14
+ # => ["B", "C", "D", "Eb", "F", "G", "A"]
15
+
16
+ Scale["Major"]["Dorian"].chords.symbols
17
+ # => ["min", "min7", "min6"]
18
+ ```
19
+
20
+ ## How it works
21
+
22
+ Every chord, scale, and mode is defined as a mathematical system: a sequence of **semitone offsets** (0–11) paired with **letter offsets** (0–6). Transposing to a new key is modular arithmetic on both, and the letter offset is what lets Jazz Model tell `E#` from `F` and `Cb` from `B` rather than just naming pitches.
23
+
24
+ All of the theory, such as which tones make up a C7 or which modes fit a min7 chord, lives in data rather than code. Definitions populate an in-memory SQLite database through ActiveRecord, so the whole model can be queried and traversed with familiar associations.
25
+
26
+ ## Installation
27
+
28
+ Add the gem to your Gemfile:
29
+
30
+ ```ruby
31
+ gem "jazz_model"
32
+ ```
33
+
34
+ Then load the default definitions when your application boots:
35
+
36
+ ```ruby
37
+ require "jazz_model"
38
+ JazzModel::Base.load_definitions
39
+
40
+ # or, equivalently
41
+ require "jazz_model/definitions"
42
+ ```
43
+
44
+ Requires Ruby 3.2+ and ActiveRecord 7.2 or 8.x.
45
+
46
+ ## Usage
47
+
48
+ Everything lives under the `JazzModel` namespace. The examples below assume `include JazzModel`.
49
+
50
+ ### Chords
51
+
52
+ ```ruby
53
+ Chord["maj7"] # without key context (defaults to C)
54
+ Chord["Bbmaj7"] # with key context
55
+ Chord["Abmaj7#11"].name
56
+ # => "Major 7 #11"
57
+
58
+ Chord["Ebmaj7"].notes
59
+ # => ["Eb", "G", "Bb", "D"]
60
+
61
+ Chord["maj7"].in_key_of("Eb").notes
62
+ # => ["Eb", "G", "Bb", "D"]
63
+
64
+ Chord["F7b9#9"].notes
65
+ # => ["F", "A", "Eb", "Gb", "G#", "C#"]
66
+
67
+ # Enharmonic keys are spelled from their own letter names
68
+ Chord["Gbmaj7"].notes
69
+ # => ["Gb", "Bb", "Db", "F"]
70
+ Chord["F#maj7"].notes
71
+ # => ["F#", "A#", "C#", "E#"]
72
+ ```
73
+
74
+ ### Scales and modes
75
+
76
+ ```ruby
77
+ Scale["Major"].notes
78
+ # => ["C", "D", "E", "F", "G", "A", "B"]
79
+
80
+ Scale["Eb Major"].notes # same as Scale["Major"].in_key_of("Eb")
81
+ # => ["Eb", "F", "G", "Ab", "Bb", "C", "D"]
82
+
83
+ Scale["Whole Tone"].notes
84
+ # => ["C", "D", "E", "F#", "G#", "Bb"]
85
+
86
+ Scale["Bebop"].notes
87
+ # => ["C", "D", "E", "F", "G", "A", "Bb", "B"]
88
+
89
+ # Modes by name or number
90
+ Scale["Major"]["Dorian"] # same as Scale["Major"].modes["Dorian"]
91
+ Scale["Major"][2]
92
+
93
+ Scale["Major"].in_key_of("Eb")["Dorian"].notes
94
+ # => ["F", "G", "Ab", "Bb", "C", "D", "Eb"]
95
+
96
+ Scale["Melodic Minor"]["Lydian Dominant"].notes
97
+ # => ["F", "G", "A", "B", "C", "D", "Eb"]
98
+ ```
99
+
100
+ ### Chord–scale relationships
101
+
102
+ ```ruby
103
+ Chord["min7"].modes.map(&:name)
104
+ # => ["Dorian", "Aeolian"]
105
+
106
+ Scale["Major"][4].chords.symbols
107
+ # => ["maj7#11"]
108
+
109
+ Scale["Major"].chords.map { |c| "#{c.name}: #{c.notes.join(", ")}" }
110
+ # => ["Major Triad: C, E, G",
111
+ # "Major 7: C, E, G, B",
112
+ # "Major 6: C, E, G, A",
113
+ # "Dominant 6/9: C, E, G, Bb, D, A"]
114
+ ```
115
+
116
+ ### Identifying chords from notes
117
+
118
+ ```ruby
119
+ NotesCollection["C E G A"].chords.symbols
120
+ # => ["C6", "Amin7"]
121
+
122
+ NotesCollection["C E G A"].in_key_of("A").chords.symbols
123
+ # => ["Amin7"]
124
+ ```
125
+
126
+ ## Definitions
127
+
128
+ Definitions are packaged sets of instructions that create the model's data. Two ship with the gem:
129
+
130
+ - **`keys`**: the 12 pitch classes and their enharmonic spellings. Always load this for Western harmony.
131
+ - **`default`**: scales, modes, chords, chord symbols, and chord–scale relationships. Depends on `keys`.
132
+
133
+ `JazzModel::Base.load_definitions` loads `default` (and therefore `keys`). Pass a name to load something else:
134
+
135
+ ```ruby
136
+ JazzModel::Base.load_definitions(:my_custom_set)
137
+ ```
138
+
139
+ ### Writing your own
140
+
141
+ Definitions declare dependencies like Rake tasks:
142
+
143
+ ```ruby
144
+ JazzModel::Definition.define my_definition: [:keys] do
145
+ JazzModel::Scale.create!(name: "Hirajoshi").tap do |s|
146
+ s.tones.create([
147
+ {tone: 0, letter_index: 0},
148
+ {tone: 2, letter_index: 1},
149
+ {tone: 3, letter_index: 2},
150
+ {tone: 7, letter_index: 4},
151
+ {tone: 8, letter_index: 5}
152
+ ])
153
+ s.modes.create!(mode: 1, name: "Hirajoshi")
154
+ end
155
+ end
156
+ ```
157
+
158
+ See [`lib/jazz_model/definitions`](lib/jazz_model/definitions) for complete examples.
159
+
160
+ ## Development
161
+
162
+ ```sh
163
+ bundle install
164
+ bundle exec rake # runs the specs and Standard
165
+ ```
166
+
167
+ To run the specs against ActiveRecord 7.2:
168
+
169
+ ```sh
170
+ BUNDLE_GEMFILE=gemfiles/activerecord_7_2.gemfile bundle install
171
+ BUNDLE_GEMFILE=gemfiles/activerecord_7_2.gemfile bundle exec rspec
172
+ ```
173
+
174
+ ## Ideas
175
+
176
+ - Chord progression analysis
177
+ - MIDI input and output
178
+ - Melodic material and licks
179
+ - Fuller voicing support
180
+
181
+ ## History
182
+
183
+ Jazz Model began in 2008 as the engine behind a jazz theory web app and was extracted into a gem in 2010. In 2026 it was updated for current Ruby and ActiveRecord.
184
+
185
+ ## License
186
+
187
+ Released under the [MIT License](LICENSE.txt).
data/db/schema.rb ADDED
@@ -0,0 +1,83 @@
1
+ JazzModel::Base.with_connection do |c|
2
+ c.create_table :keys do |t|
3
+ t.string :name
4
+ t.string :long_name
5
+ t.boolean :primary, :default => true
6
+ t.integer :index
7
+ t.integer :letter_index
8
+ t.integer :cycle_index
9
+ end
10
+
11
+ c.create_table :chord_qualities do |t|
12
+ t.string :name
13
+ t.string :code
14
+ end
15
+
16
+ c.create_table :chords do |t|
17
+ t.belongs_to :chord_quality
18
+ t.integer :parent_id
19
+ t.string :name
20
+ t.text :synonyms
21
+ t.text :information
22
+ end
23
+
24
+ c.create_table :chord_symbols do |t|
25
+ t.belongs_to :chord
26
+ t.string :name
27
+ t.boolean :case_sensitive, :default => false
28
+ t.integer :strength
29
+ t.boolean :primary, :default => false
30
+ end
31
+
32
+ c.create_table :chord_tones do |t|
33
+ t.belongs_to :chord
34
+ t.integer :position
35
+ t.integer :tone
36
+ t.integer :letter_index
37
+ t.integer :strength
38
+ t.boolean :omitable, :default => false
39
+ t.text :information
40
+ end
41
+
42
+ c.create_table :scales do |t|
43
+ t.string :name
44
+ t.string :information
45
+ t.integer :symmetry_index
46
+ end
47
+
48
+ c.create_table :scale_tones do |t|
49
+ t.belongs_to :scale
50
+ t.integer :position
51
+ t.integer :tone
52
+ t.integer :letter_index
53
+ end
54
+
55
+ c.create_table :modes do |t|
56
+ t.belongs_to :scale
57
+ t.integer :mode
58
+ t.string :name
59
+ t.text :synonyms
60
+ t.integer :dissonance
61
+ end
62
+
63
+ c.create_table :chord_scales do |t|
64
+ t.belongs_to :chord
65
+ t.belongs_to :mode
66
+ t.integer :strength
67
+ t.text :information
68
+ end
69
+
70
+ c.create_table :voicings do |t|
71
+ t.belongs_to :chord
72
+ t.string :name
73
+ t.text :information
74
+ end
75
+
76
+ c.create_table :voicing_tones do |t|
77
+ t.belongs_to :voicing
78
+ t.integer :position
79
+ t.integer :tone
80
+ t.integer :tone_reference_offset, :default => 0
81
+ t.text :information
82
+ end
83
+ end
@@ -1,15 +1,22 @@
1
1
  module JazzModel
2
2
  class Base < ActiveRecord::Base
3
3
  self.abstract_class = true
4
-
5
- establish_connection :adapter => "sqlite3", :database => ":memory:"
6
- load File.join(File.dirname(__FILE__), "../../db/schema.rb")
7
-
4
+
5
+ # A named, shared-cache in-memory database so every connection in the pool
6
+ # sees the same data. idle_timeout: 0 keeps the pool from closing idle
7
+ # connections, which would otherwise discard the in-memory database.
8
+ establish_connection(
9
+ adapter: "sqlite3",
10
+ database: "file:jazz_model?mode=memory&cache=shared",
11
+ idle_timeout: 0
12
+ )
13
+ load File.expand_path("../../db/schema.rb", __dir__)
14
+
8
15
  def self.load_definitions(definition_name = :default)
9
16
  definition = JazzModel::Definition[definition_name]
10
17
  raise ArgumentError, "Definition #{definition_name} not found." unless definition
11
-
18
+
12
19
  definition.load
13
20
  end
14
21
  end
15
- end
22
+ end
@@ -1,141 +1,121 @@
1
1
  module JazzModel
2
2
  # A chord object represents a chord defined as an unordered collection of tones (non-octaval).
3
3
  # Mixes in the +KeyContext+ module to provide optional key context on the chord.
4
- #
4
+ #
5
5
  # == Creating a Chord
6
- #
6
+ #
7
7
  # Chords should be created by indexing the +Chord+ class like an array, like so:
8
- #
8
+ #
9
9
  # Chord[symbol]
10
- #
11
- # An alternative method of resolving a chord is to use the +resolve+ method,
10
+ #
11
+ # An alternative method of resolving a chord is to use the +resolve+ method,
12
12
  # though this is likely to become deprecated in the future when chord progression
13
13
  # support/representation is added:
14
14
  #
15
15
  # Chord.resolve(symbol)
16
- #
17
- # This resolves a specified chord symbol into a new chord object using
16
+ #
17
+ # This resolves a specified chord symbol into a new chord object using
18
18
  # data regarding chord symbol-to-chord relationships.
19
- # +symbol+ can be any standard jazz chord symbol representable as
19
+ # +symbol+ can be any standard jazz chord symbol representable as
20
20
  # plain ASCII (unicode support for other symbols such as full-diminished circle
21
21
  # in the works). Can be prefixed with a key.
22
- #
22
+ #
23
23
  # == Object Relationships
24
- #
24
+ #
25
25
  # A chord object has the following associations, exposed by methods:
26
- #
26
+ #
27
27
  # * +symbols+ - Associated chord symbols.
28
28
  # * +primary_symbol+ - Primary chord symbol.
29
29
  # * +chord_scales+ - Chord-scale relationship objects. See ChordScale.
30
30
  # * +modes+ - Direct access to associated scale modes through +chord_scales+.
31
31
  # * +tones+ - Sequence of tones associated with the chord. See +ToneSequence+.
32
32
  # * +voicings+ - Voicings associated with this chord.#
33
- #
33
+ #
34
34
  # == Example Usage
35
- #
35
+ #
36
36
  # === Creating Chords Without Key Context
37
- #
37
+ #
38
38
  # Chord['maj']
39
39
  # Chord['min7'
40
40
  # Chord['maj7#11']
41
41
  # Chord['7#9']
42
42
  #
43
43
  # === Creating Chords With Key Context
44
- #
44
+ #
45
45
  # Chord['C7']
46
46
  # Chord['Bbalt']
47
47
  # Chord['Gbmaj7']
48
- #
48
+ #
49
49
  # === Getting Chord Notes
50
50
  # Use +notes+ to retrieve a collection of notes, which actually delegates to +tones.notes+:
51
51
  #
52
52
  # Chord['C7'].notes
53
53
  # # => ['C', 'E', 'G', 'Bb']
54
- #
54
+ #
55
55
  # Chord['Cmaj7#11'].notes
56
56
  # # => ["C", "E", "G", "B", "F#"]
57
- #
57
+ #
58
58
  # === Correctly Interpets Theoretical Keys (not just pitches)
59
59
  # Note the #11 is correctly interpreted as E# and not enharmonic F here:
60
- #
60
+ #
61
61
  # Chord['Bmaj7#11'].notes
62
62
  # # => ["B", "D#", "F#", "A#", "E#"]
63
- #
63
+ #
64
64
  # Also correctly interpet tones off of enharmonic base keys:
65
- #
65
+ #
66
66
  # Chord['Gbmaj7'].notes
67
67
  # # => ["Gb", "Bb", "Db", "F"]
68
68
  # Chord['F#maj7'].notes
69
69
  # # => ["F#", "A#", "C#", "E#"]
70
- #
70
+ #
71
71
  # === See Related Scales
72
- #
72
+ #
73
73
  # Chord['min7'].modes.names # .names == .map(&:name)
74
74
  # # => ['Dorian']
75
75
  # Chord['min7'].modes[0].scale.name
76
76
  # # => "Major"
77
- #
77
+ #
78
78
  # Chord['Amin7'].modes.names
79
79
  # # => ['A Dorian']
80
- #
80
+ #
81
81
  class Chord < JazzModel::Base
82
+ include ActsAsTree
82
83
  include KeyContext
83
-
84
+
84
85
  acts_as_tree
85
-
86
+
86
87
  belongs_to :chord_quality
87
-
88
- has_many :symbols, :class_name => 'ChordSymbol', :extend => ChordSymbolCollection
89
- has_one :primary_symbol, :class_name => 'ChordSymbol', :conditions => {:primary => true}
90
-
88
+
89
+ has_many :symbols, class_name: "ChordSymbol", extend: ChordSymbolCollection
90
+ has_one :primary_symbol, -> { where(primary: true) }, class_name: "ChordSymbol"
91
+
91
92
  has_many :chord_scales
92
- has_many :modes, :through => :chord_scales
93
- has_many :tones, :class_name => 'ChordTone', :extend => ToneSequence
93
+ has_many :modes, through: :chord_scales
94
+ has_many :tones, class_name: "ChordTone", extend: ToneSequence
94
95
  has_many :voicings
95
-
96
- delegate :notes, :to => :tones
97
-
96
+
97
+ delegate :notes, to: :tones
98
+
98
99
  def symbols_list
99
- self.symbols.map {|s| s.name }.join(', ')
100
+ symbols.map(&:name).join(", ")
100
101
  end
101
-
102
+
102
103
  class << self
103
-
104
- # Resolves a chord symbol into a chord.
105
- # Implementation is somewhat flakey due to the potential ambiguities arising
106
- # from specifying key and symbols together.
104
+ # Resolves a chord symbol, optionally prefixed with a key, into a chord.
105
+ # Ambiguous prefixes are tried longest key first, so "Cbmaj7" is a C-flat
106
+ # major 7th while "Cb5" falls back to a C chord with a flat 5th.
107
107
  def resolve(symbol)
108
- in_key = nil
109
-
110
108
  return nil if symbol.nil?
111
- symbol = symbol.dup
112
-
113
- Key.all.each do |k|
114
- if symbol.starts_with?(k.name)
115
- in_key = k
116
- symbol.sub!(k.name, '').strip
117
- break
118
- end
119
- end
120
-
121
- chord_symbol = ChordSymbol[symbol]
122
-
123
- # Perhaps the matched key was really part of the name of the chord, try that:
124
- if chord_symbol.nil? && !in_key.nil?
125
- symbol = in_key.name + symbol
126
- chord_symbol = ChordSymbol[symbol]
109
+
110
+ KeyContext.each_split(symbol) do |in_key, rest|
111
+ chord_symbol = ChordSymbol[rest] or next
112
+ chord = chord_symbol.chord
113
+ chord.key = in_key
114
+ return chord
127
115
  end
128
-
129
- # If still not found, must be invalid:
130
- return nil if chord_symbol.nil?
131
-
132
- chord = chord_symbol.chord
133
- chord.key = in_key unless in_key.nil?
134
- chord
116
+ nil
135
117
  end
136
118
  alias_method :[], :resolve
137
-
138
119
  end
139
-
140
120
  end
141
- end
121
+ end
@@ -1,68 +1,44 @@
1
1
  module JazzModel
2
- # Intended to be used as a dynamic object extension of an Array representing
2
+ # Intended to be used as a dynamic object extension of an Array representing
3
3
  # a collection of chords for some added convenience.
4
- #
4
+ #
5
5
  # For example, even though the base class is an Array, we can write:
6
- #
6
+ #
7
7
  # * +scale.chords.symbols+ - For an array of symbols.
8
8
  # * +scale.chords.names+ - For an array of full names.
9
- #
9
+ #
10
10
  module ChordCollection
11
-
12
11
  # Formats chord collection
13
12
  def to_s(format = :symbols)
14
13
  case format
15
- when :symbols then symbols.join(', ')
16
- when :names then names.join(', ')
14
+ when :symbols then symbols.join(", ")
15
+ when :names then names.join(", ")
17
16
  end
18
17
  end
19
-
18
+
20
19
  # Returns array of chord symbols only
21
20
  def symbols
22
- self.map {|c| "#{c.key.name if c.key}#{c.primary_symbol.name}" }
21
+ map { |c| "#{c.key&.name}#{c.primary_symbol.name}" }
23
22
  end
24
-
23
+
25
24
  # Returns array of chord names only
26
25
  def names
27
- self.map {|c| "#{c.key.name if c.key} #{c.name}".strip }
26
+ map { |c| "#{c.key&.name} #{c.name}".strip }
28
27
  end
29
-
30
-
31
- # WARNING: This is basically duplicated from Chord
32
- # Try to make this DRY!
33
- #
34
- # Resolves a chord symbol into a chord.
35
- # Implementation is somewhat flakey due to the potential ambiguities arising
36
- # from specifying key and symbols together.
28
+
29
+ # Resolves a chord symbol, optionally prefixed with a key, into one of the
30
+ # chords in this collection. See Chord.resolve.
37
31
  def resolve(symbol)
38
- in_key = nil
39
-
40
32
  return nil if symbol.nil?
41
-
42
- Key.all.each do |k|
43
- if symbol.starts_with?(k.name)
44
- in_key = k
45
- symbol.sub!(k.name, '').strip
46
- break
47
- end
33
+
34
+ KeyContext.each_split(symbol) do |in_key, rest|
35
+ chord_symbol = flat_map { |c| c.symbols.to_a }.detect { |cs| cs.matches?(rest) } or next
36
+ chord = chord_symbol.chord
37
+ chord.key = in_key
38
+ return chord
48
39
  end
49
-
50
- chord_symbol = self.map {|c| c.symbols.to_a}.flatten.detect {|cs| cs.name == symbol}
51
-
52
- # Perhaps the matched key was really part of the name of the chord, try that:
53
- if chord_symbol.nil? && !in_key.nil?
54
- symbol = in_key.name + symbol
55
- chord_symbol = self.symbols[symbol]
56
- end
57
-
58
- # If still not found, must be invalid:
59
- return nil if chord_symbol.nil?
60
-
61
- chord = chord_symbol.chord
62
- chord.key = in_key unless in_key.nil?
63
- chord
40
+ nil
64
41
  end
65
42
  alias_method :[], :resolve
66
-
67
43
  end
68
- end
44
+ end
@@ -1,19 +1,18 @@
1
1
  module JazzModel
2
- # A basic chord quality such as major or minor. This may be abandoned in the future in favor of a
2
+ # A basic chord quality such as major or minor. This may be abandoned in the future in favor of a
3
3
  # deep heirarchy based soley around chords.
4
- #
4
+ #
5
5
  # == Associations
6
6
  # * +chords+ - A collection of chords associated with the chord quality.
7
- #
7
+ #
8
8
  class ChordQuality < JazzModel::Base
9
9
  has_many :chords
10
10
 
11
11
  class << self
12
12
  def resolve(name)
13
- self.find_by_name(name)
13
+ find_by_name(name)
14
14
  end
15
15
  alias_method :[], :resolve
16
16
  end
17
-
18
17
  end
19
- end
18
+ end
@@ -1,30 +1,30 @@
1
1
  module JazzModel
2
- # A relationship of one chord to one scale, with some other optional
3
- # information such as +strength+, representing the strength of the
2
+ # A relationship of one chord to one scale, with some other optional
3
+ # information such as +strength+, representing the strength of the
4
4
  # relationships.
5
- #
5
+ #
6
6
  # == Associations
7
- #
7
+ #
8
8
  # * +chord+ - Associated chord.
9
9
  # * +mode+ - Associated scale mode.
10
- # * +scale+ - Associated scale through mode.
11
- #
10
+ # * +scale+ - Associated scale through mode.
11
+ #
12
12
  # == Key Context
13
13
  #
14
14
  # Delegates +key+ to the associated chord to access key context through this relationship.
15
- #
15
+ #
16
16
  class ChordScale < JazzModel::Base
17
17
  belongs_to :chord
18
18
  belongs_to :mode
19
-
20
- delegate :scale, :to => :mode
21
- delegate :key, :to => :chord
22
-
19
+
20
+ delegate :scale, to: :mode
21
+ delegate :key, to: :chord
22
+
23
23
  def self.specify(mode, strength = 1)
24
- chord_scale = self.new
24
+ chord_scale = new
25
25
  chord_scale.mode = mode
26
26
  chord_scale.strength = strength
27
27
  chord_scale
28
28
  end
29
29
  end
30
- end
30
+ end