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
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,14 @@
1
+ == 0.1.0 [2010-10-20] Modernization
2
+ * Upgraded to RSpec 2.0
3
+ * Ditched Controllers/Rails Application
4
+ * Reworked all models to use in-memory ActiveModel instead of ActiveRecord
5
+ * Internal Refactoring
6
+ * Released as a Gem
7
+
8
+ == 2008-08-24:
9
+ * Upgraded to Rails 2.1
10
+ * Refactored models to use new Rails 2.1 features.
11
+
12
+ == Old:
13
+ * Implemented REST Interface
14
+ * Added RSpec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "activemodel", "~> 3.0.0"
4
+ gem "activesupport", "~> 3.0.0"
data/README.rdoc ADDED
@@ -0,0 +1,186 @@
1
+ = Jazz Model
2
+
3
+ Jazz Model is full ActiveRecord model of concepts in Jazz theory, establishing relationships between chords and scales, and much more. Aside from representing jazz theory relationships in the database, jazz model can do key conversions and other operations on these jazz "objects". By default it uses an in-memory sqlite3 database, but it could be persisted elsewhere.
4
+
5
+ == Architecture Overview
6
+
7
+ The core of Jazz Toolbox is a full Ruby object model representing concepts of Jazz theory,
8
+ distilled to their most basic concepts and architected in a very abstract manner. The system
9
+ is data-centric and all "rules" (for example, the tones in a C7 chord) in theory are
10
+ self-contained in the database.
11
+
12
+ All chord/scale/mode/etc. definitions are stored as a mathematical system (sequences of numbers)
13
+ which are then used to perform calculations. For example, putting some chord in a different key
14
+ is a matter of adding a semitone delta and doing modulo 12.
15
+
16
+ While there are currently many chord calculators in existence, to my knowledge this project is the
17
+ first one that attempts to fully represent the entirety of Jazz theory as a mathematical/computational
18
+ system exposed through an elegant object model.
19
+
20
+ Note: the current database consists entirely of my own personal knowledge of Jazz theory. I
21
+ haven't yet scoured through the Jazz theory literature to formalize and expand the current database
22
+ of chords, scales, and chord-scales. There's still a lot of work to do and I have lots of ideas for how to expand this.
23
+
24
+ == Core Features
25
+
26
+ * Scale & Mode Enumeration
27
+ * Handles Variety of Notations
28
+ * ChordTone Enumeration
29
+ * Traversing ScaleChord Relationships with Strength Metric
30
+ * Full Understanding of Theoretic Tones (vs. only Pitches)
31
+
32
+ == Installation
33
+
34
+ Simply include the gem in your Gemfile:
35
+
36
+ gem "jazz_model"
37
+
38
+ From here you can begin using the classes under +JazzModel+ directly, but it won't be much use until you load the definitions:
39
+
40
+ JazzModel::Base.load_definitions
41
+
42
+ == Examples using Default Definitions
43
+
44
+ Everything is under the JazzModel namespace, so first do this if you want to use these classes directly:
45
+
46
+ include JazzModel
47
+
48
+ * Getting a Chord object:
49
+
50
+ Chord['maj7']
51
+ Chord['Bbmaj7'] # <- With Key Context
52
+ Chord['Abmaj7#11']
53
+ ...
54
+
55
+ * Getting a Scale object:
56
+
57
+ Scale['Major']
58
+ Scale['Melodic Minor']
59
+ Scale['Diminished']
60
+ ...
61
+
62
+ * Getting a particular mode of a scale:
63
+
64
+ Scale['Major'].modes['Dorian'] # By Mode Name
65
+ Scale['Major'].modes[2] # By Mode Index
66
+
67
+ # Or directly index the scale object (same as above):
68
+ Scale['Major']['Dorian']
69
+ Scale['Major'][2]
70
+
71
+ * Enumerate notes of a Chord:
72
+
73
+ Chord['maj'].notes # Defaults to C without specified key context
74
+ # => ['C', 'E', 'G']
75
+
76
+ Chord['Ebmaj7'].notes
77
+ # => ['Eb', 'G', 'Bb', 'D']
78
+
79
+ # Or specify key context with chained methods like this...
80
+ Chord['maj7'].in_key_of('Eb').notes
81
+
82
+ Chord['Bmaj7#11'].notes
83
+ # => ['B', 'D#', 'F#', 'A#', 'E#']
84
+ # Note E# - Correct theoretic value for this chord, not F
85
+
86
+ Chord['Falt'].notes
87
+ Chord['F7b9#9'].notes
88
+ # => ['F', 'A', 'Eb', 'Gb', 'G#', 'C#']
89
+
90
+ Chord['Gbmaj7'].notes
91
+ # => ['Gb', 'Bb', 'Db', 'F']
92
+
93
+ # But...
94
+
95
+ Chord['F#maj7'].notes
96
+ # => ['F#', 'A#', 'C#', 'E#']
97
+
98
+
99
+ * Enumerate notes of a Scale:
100
+
101
+ Scale['Major'].notes # Defaults to C without specified key context
102
+ # => ['C', 'D', 'E', 'F', 'G', 'A', 'B']
103
+
104
+ Scale['Eb Major'].notes
105
+ # => ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'D']
106
+
107
+ # Or specify key context with chained methods like this:
108
+ Scale['Major'].in_key_of('Eb').notes
109
+
110
+ Scale['Whole Tone'].notes
111
+ # => ['C', 'D', 'E', 'F#', 'G#', 'Bb']
112
+
113
+ Scale['Bebop'].notes
114
+ # => ['C', 'D', 'E', 'F', 'G', 'A', 'Bb', 'B']
115
+
116
+
117
+ * Enumerate notes from a Scale Mode:
118
+
119
+ Scale['Major'].in_key_of('Eb').modes['Dorian'].notes
120
+ # => ['F', 'G', 'Ab', 'Bb', 'C', 'D', 'Eb']
121
+
122
+ Scale['Melodic Minor']['Lydian Dominant'].notes
123
+ # => ['F', 'G', 'A', 'B', 'C', 'D', 'Eb']
124
+
125
+
126
+ * Enumerate scale modes associated with a chord:
127
+
128
+ Chord['min7'].modes.names # .names == .map(&:name)
129
+ # => ['Dorian']
130
+ Chord['min7'].modes[0].scale.name
131
+ # => "Major"
132
+
133
+ Chord['Amin7'].modes.names
134
+ # => ['A Dorian']
135
+
136
+
137
+ * Enumerate chords associated with a scale mode:
138
+
139
+ Scale['Major']['Dorian'].chords.symbols
140
+ # => ['min7', 'min6']
141
+
142
+ Scale['Major'][4].chords.symbols
143
+ # => ['maj7#11']
144
+
145
+
146
+ * Ruby Example Problem:
147
+ Find all chords associated with the Major (Ionian) scale and print
148
+ each on a new line with the chord tones.
149
+
150
+ Scale['Major'].chords.map {|c| c.name + ': ' + c.notes.join(', ')} * "\n"
151
+ # => Major 7: C, E, G, B
152
+ Major 6: C, E, G, A
153
+ Dominant 6/9: C, E, G, Bb, D, A
154
+
155
+ These examples should show that with the power of Ruby and the elegant nature of
156
+ this API, extracting Jazz data from the system is a breeze (even fun!).
157
+
158
+ == Definitions Available
159
+
160
+ The default definition is just named "default" and is loaded when you run JazzModel::Base.load_definitions without argument, though you can give a custom argument to this to load a different definition:
161
+
162
+ JazzModel::Base.load_definitions(:my_custom_set)
163
+
164
+ Currently there are two definitions available:
165
+ * Keys - Creates the basic 12 keys. This definition should always be loaded so long as you are dealing with Western harmony.
166
+ * Default - The default set of data for jazz models, including many scales and chords. Any other definitions will probably want to build upon this instead of start from scratch.
167
+
168
+ == Creating Definitions
169
+
170
+ The gem has a distinction between classes such as +Chord+ and the actual definitions such as "C Major Chord". Definitions are simply packaged set of instructions for initializing the objects with data (which get put in the database). Since by default jazz model uses an in-memory sqlite3 database, definitions need to be loaded when your application loads.
171
+
172
+ To create a definition, simply do this:
173
+
174
+ JazzModel::Definition.define :my_definition => [:keys] do
175
+
176
+ end
177
+
178
+ Within the block you'll want to create whatever data is necessary to comprise your definitions. For examples of definitions, see lib/jazz_model/definitions in the project. The argument to define acts like rake tasks - use the hash value to define which dependencies your definition has. In the above case defining "my_definition" will first ensure the "keys" definition is already defined.
179
+
180
+ == Anticipated Future Features
181
+
182
+ * Chord Progression Analysis
183
+ * MIDI Integration
184
+ * User Comments & Contributions (such as Chord-Scale recommendations)
185
+ * Melodic Components & Licks
186
+ * Voicings Associated with Chords
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+
5
+ desc "Generate documentation for the plugin."
6
+ Rake::RDocTask.new(:rdoc) do |rdoc|
7
+ rdoc.rdoc_dir = "rdoc"
8
+ rdoc.title = "message_block"
9
+ rdoc.options << "--line-numbers" << "--inline-source"
10
+ rdoc.rdoc_files.include('README')
11
+ rdoc.rdoc_files.include('lib/**/*.rb')
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }
data/lib/jazz_model.rb ADDED
@@ -0,0 +1,34 @@
1
+ module JazzModel
2
+ require "rubygems"
3
+ require "active_support"
4
+ require "active_record"
5
+ require "acts_as_tree"
6
+ require "acts_as_list"
7
+
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :Base
11
+ autoload :Chord
12
+ autoload :ChordCollection
13
+ autoload :ChordQuality
14
+ autoload :ChordScale
15
+ autoload :ChordSymbol
16
+ autoload :ChordSymbolCollection
17
+ autoload :ChordTone
18
+ autoload :Key
19
+ autoload :KeyContext
20
+ autoload :Mode
21
+ autoload :ModeContext
22
+ autoload :ModeSequence
23
+ autoload :NoteSequence
24
+ autoload :NotesCollection
25
+ autoload :Scale
26
+ autoload :ScaleTone
27
+ autoload :Tone
28
+ autoload :ToneSequence
29
+ autoload :Voicing
30
+ autoload :VoicingTone
31
+
32
+ autoload :Definition
33
+
34
+ end
@@ -0,0 +1,15 @@
1
+ module JazzModel
2
+ class Base < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ establish_connection :adapter => "sqlite3", :database => ":memory:"
6
+ load File.join(File.dirname(__FILE__), "../../db/schema.rb")
7
+
8
+ def self.load_definitions(definition_name = :default)
9
+ definition = JazzModel::Definition[definition_name]
10
+ raise ArgumentError, "Definition #{definition_name} not found." unless definition
11
+
12
+ definition.load
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,141 @@
1
+ module JazzModel
2
+ # A chord object represents a chord defined as an unordered collection of tones (non-octaval).
3
+ # Mixes in the +KeyContext+ module to provide optional key context on the chord.
4
+ #
5
+ # == Creating a Chord
6
+ #
7
+ # Chords should be created by indexing the +Chord+ class like an array, like so:
8
+ #
9
+ # Chord[symbol]
10
+ #
11
+ # An alternative method of resolving a chord is to use the +resolve+ method,
12
+ # though this is likely to become deprecated in the future when chord progression
13
+ # support/representation is added:
14
+ #
15
+ # Chord.resolve(symbol)
16
+ #
17
+ # This resolves a specified chord symbol into a new chord object using
18
+ # data regarding chord symbol-to-chord relationships.
19
+ # +symbol+ can be any standard jazz chord symbol representable as
20
+ # plain ASCII (unicode support for other symbols such as full-diminished circle
21
+ # in the works). Can be prefixed with a key.
22
+ #
23
+ # == Object Relationships
24
+ #
25
+ # A chord object has the following associations, exposed by methods:
26
+ #
27
+ # * +symbols+ - Associated chord symbols.
28
+ # * +primary_symbol+ - Primary chord symbol.
29
+ # * +chord_scales+ - Chord-scale relationship objects. See ChordScale.
30
+ # * +modes+ - Direct access to associated scale modes through +chord_scales+.
31
+ # * +tones+ - Sequence of tones associated with the chord. See +ToneSequence+.
32
+ # * +voicings+ - Voicings associated with this chord.#
33
+ #
34
+ # == Example Usage
35
+ #
36
+ # === Creating Chords Without Key Context
37
+ #
38
+ # Chord['maj']
39
+ # Chord['min7'
40
+ # Chord['maj7#11']
41
+ # Chord['7#9']
42
+ #
43
+ # === Creating Chords With Key Context
44
+ #
45
+ # Chord['C7']
46
+ # Chord['Bbalt']
47
+ # Chord['Gbmaj7']
48
+ #
49
+ # === Getting Chord Notes
50
+ # Use +notes+ to retrieve a collection of notes, which actually delegates to +tones.notes+:
51
+ #
52
+ # Chord['C7'].notes
53
+ # # => ['C', 'E', 'G', 'Bb']
54
+ #
55
+ # Chord['Cmaj7#11'].notes
56
+ # # => ["C", "E", "G", "B", "F#"]
57
+ #
58
+ # === Correctly Interpets Theoretical Keys (not just pitches)
59
+ # Note the #11 is correctly interpreted as E# and not enharmonic F here:
60
+ #
61
+ # Chord['Bmaj7#11'].notes
62
+ # # => ["B", "D#", "F#", "A#", "E#"]
63
+ #
64
+ # Also correctly interpet tones off of enharmonic base keys:
65
+ #
66
+ # Chord['Gbmaj7'].notes
67
+ # # => ["Gb", "Bb", "Db", "F"]
68
+ # Chord['F#maj7'].notes
69
+ # # => ["F#", "A#", "C#", "E#"]
70
+ #
71
+ # === See Related Scales
72
+ #
73
+ # Chord['min7'].modes.names # .names == .map(&:name)
74
+ # # => ['Dorian']
75
+ # Chord['min7'].modes[0].scale.name
76
+ # # => "Major"
77
+ #
78
+ # Chord['Amin7'].modes.names
79
+ # # => ['A Dorian']
80
+ #
81
+ class Chord < JazzModel::Base
82
+ include KeyContext
83
+
84
+ acts_as_tree
85
+
86
+ 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
+
91
+ has_many :chord_scales
92
+ has_many :modes, :through => :chord_scales
93
+ has_many :tones, :class_name => 'ChordTone', :extend => ToneSequence
94
+ has_many :voicings
95
+
96
+ delegate :notes, :to => :tones
97
+
98
+ def symbols_list
99
+ self.symbols.map {|s| s.name }.join(', ')
100
+ end
101
+
102
+ 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.
107
+ def resolve(symbol)
108
+ in_key = nil
109
+
110
+ 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]
127
+ 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
135
+ end
136
+ alias_method :[], :resolve
137
+
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,68 @@
1
+ module JazzModel
2
+ # Intended to be used as a dynamic object extension of an Array representing
3
+ # a collection of chords for some added convenience.
4
+ #
5
+ # For example, even though the base class is an Array, we can write:
6
+ #
7
+ # * +scale.chords.symbols+ - For an array of symbols.
8
+ # * +scale.chords.names+ - For an array of full names.
9
+ #
10
+ module ChordCollection
11
+
12
+ # Formats chord collection
13
+ def to_s(format = :symbols)
14
+ case format
15
+ when :symbols then symbols.join(', ')
16
+ when :names then names.join(', ')
17
+ end
18
+ end
19
+
20
+ # Returns array of chord symbols only
21
+ def symbols
22
+ self.map {|c| "#{c.key.name if c.key}#{c.primary_symbol.name}" }
23
+ end
24
+
25
+ # Returns array of chord names only
26
+ def names
27
+ self.map {|c| "#{c.key.name if c.key} #{c.name}".strip }
28
+ 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.
37
+ def resolve(symbol)
38
+ in_key = nil
39
+
40
+ 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
48
+ 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
64
+ end
65
+ alias_method :[], :resolve
66
+
67
+ end
68
+ end