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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/db/schema.rb +83 -0
- data/lib/jazz_model/base.rb +13 -6
- data/lib/jazz_model/chord.rb +50 -70
- data/lib/jazz_model/chord_collection.rb +21 -45
- data/lib/jazz_model/chord_quality.rb +5 -6
- data/lib/jazz_model/chord_scale.rb +13 -13
- data/lib/jazz_model/chord_symbol.rb +14 -12
- data/lib/jazz_model/chord_symbol_collection.rb +3 -10
- data/lib/jazz_model/chord_tone.rb +20 -21
- data/lib/jazz_model/definition.rb +18 -18
- data/lib/jazz_model/definitions/default.rb +316 -321
- data/lib/jazz_model/definitions/keys.rb +36 -36
- data/lib/jazz_model/definitions.rb +3 -0
- data/lib/jazz_model/key.rb +22 -25
- data/lib/jazz_model/key_context.rb +31 -22
- data/lib/jazz_model/mode.rb +30 -35
- data/lib/jazz_model/mode_context.rb +22 -26
- data/lib/jazz_model/mode_sequence.rb +13 -12
- data/lib/jazz_model/notes_collection.rb +64 -71
- data/lib/jazz_model/scale.rb +63 -85
- data/lib/jazz_model/scale_tone.rb +14 -15
- data/lib/jazz_model/tone.rb +47 -48
- data/lib/jazz_model/tone_sequence.rb +56 -58
- data/lib/jazz_model/version.rb +2 -2
- data/lib/jazz_model/voicing.rb +6 -7
- data/lib/jazz_model/voicing_tone.rb +5 -6
- data/lib/jazz_model.rb +7 -10
- metadata +104 -141
- data/CHANGELOG.rdoc +0 -14
- data/Gemfile +0 -4
- data/README.rdoc +0 -186
- data/Rakefile +0 -14
- data/lib/jazz_model/note_sequence.rb +0 -15
- data/spec/chord_collection_spec.rb +0 -21
- data/spec/chord_quality_spec.rb +0 -27
- data/spec/chord_scale_spec.rb +0 -7
- data/spec/chord_spec.rb +0 -29
- data/spec/chord_symbol_spec.rb +0 -6
- data/spec/chord_tone_spec.rb +0 -6
- data/spec/definition_spec.rb +0 -35
- data/spec/key_context_spec.rb +0 -27
- data/spec/key_spec.rb +0 -15
- data/spec/mode_spec.rb +0 -9
- data/spec/notes_collection_spec.rb +0 -25
- data/spec/scale_spec.rb +0 -42
- data/spec/scale_tone_spec.rb +0 -6
- data/spec/spec_helper.rb +0 -12
- data/spec/tone_sequence_spec.rb +0 -17
- data/spec/tone_spec.rb +0 -4
- data/spec/voicing_spec.rb +0 -6
- 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
|
+
[](https://github.com/eigenben/jazz_model/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/jazz_model)
|
|
5
|
+
[](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
|
data/lib/jazz_model/base.rb
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
module JazzModel
|
|
2
2
|
class Base < ActiveRecord::Base
|
|
3
3
|
self.abstract_class = true
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
data/lib/jazz_model/chord.rb
CHANGED
|
@@ -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, :
|
|
89
|
-
has_one :primary_symbol, :
|
|
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, :
|
|
93
|
-
has_many :tones, :
|
|
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, :
|
|
97
|
-
|
|
96
|
+
|
|
97
|
+
delegate :notes, to: :tones
|
|
98
|
+
|
|
98
99
|
def symbols_list
|
|
99
|
-
|
|
100
|
+
symbols.map(&:name).join(", ")
|
|
100
101
|
end
|
|
101
|
-
|
|
102
|
+
|
|
102
103
|
class << self
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
#
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
map { |c| "#{c.key&.name} #{c.name}".strip }
|
|
28
27
|
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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, :
|
|
21
|
-
delegate :key, :
|
|
22
|
-
|
|
19
|
+
|
|
20
|
+
delegate :scale, to: :mode
|
|
21
|
+
delegate :key, to: :chord
|
|
22
|
+
|
|
23
23
|
def self.specify(mode, strength = 1)
|
|
24
|
-
chord_scale =
|
|
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
|