coltrane 2.2.1 → 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +16 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +25 -2
- data/README.md +3 -0
- data/bin/console +6 -2
- data/coltrane.gemspec +2 -1
- data/exe/coltrane +14 -224
- data/lib/coltrane.rb +3 -50
- data/lib/coltrane/commands.rb +14 -0
- data/lib/coltrane/commands/chords.rb +77 -0
- data/lib/coltrane/commands/command.rb +45 -0
- data/lib/coltrane/commands/common_chords.rb +33 -0
- data/lib/coltrane/commands/errors.rb +44 -0
- data/lib/coltrane/commands/find_progression.rb +28 -0
- data/lib/coltrane/commands/find_scale.rb +39 -0
- data/lib/coltrane/commands/notes.rb +44 -0
- data/lib/coltrane/commands/progression.rb +27 -0
- data/lib/{cli → coltrane/commands}/representation.rb +0 -0
- data/lib/coltrane/commands/scale.rb +46 -0
- data/lib/coltrane/renderers.rb +6 -0
- data/lib/coltrane/renderers/renderer.rb +42 -0
- data/lib/coltrane/renderers/text_renderer.rb +23 -0
- data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
- data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
- data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
- data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
- data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
- data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
- data/lib/coltrane/representation.rb +12 -0
- data/lib/coltrane/representation/guitar.rb +34 -0
- data/lib/coltrane/representation/guitar/chord.rb +180 -0
- data/lib/coltrane/representation/guitar/note.rb +26 -0
- data/lib/coltrane/representation/guitar/note_set.rb +35 -0
- data/lib/coltrane/representation/guitar/string.rb +31 -0
- data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
- data/lib/coltrane/representation/piano.rb +36 -0
- data/lib/coltrane/representation/piano/note_set.rb +22 -0
- data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
- data/lib/coltrane/theory.rb +54 -0
- data/lib/coltrane/theory/cadence.rb +9 -0
- data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
- data/lib/coltrane/theory/chord.rb +101 -0
- data/lib/coltrane/theory/chord_quality.rb +113 -0
- data/lib/coltrane/theory/chord_substitutions.rb +11 -0
- data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
- data/lib/coltrane/theory/classic_scales.rb +113 -0
- data/lib/coltrane/theory/diatonic_scale.rb +38 -0
- data/lib/coltrane/theory/errors.rb +97 -0
- data/lib/coltrane/theory/frequency.rb +52 -0
- data/lib/coltrane/theory/frequency_interval.rb +83 -0
- data/lib/coltrane/theory/interval.rb +209 -0
- data/lib/coltrane/theory/interval_class.rb +212 -0
- data/lib/coltrane/theory/interval_sequence.rb +157 -0
- data/lib/coltrane/theory/key.rb +18 -0
- data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
- data/lib/coltrane/theory/notable_progressions.rb +30 -0
- data/lib/coltrane/theory/note.rb +104 -0
- data/lib/coltrane/theory/note_set.rb +101 -0
- data/lib/coltrane/theory/pitch.rb +94 -0
- data/lib/coltrane/theory/pitch_class.rb +154 -0
- data/lib/coltrane/theory/progression.rb +81 -0
- data/lib/coltrane/theory/progression_set.rb +18 -0
- data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
- data/lib/coltrane/theory/roman_chord.rb +114 -0
- data/lib/coltrane/theory/scale.rb +161 -0
- data/lib/coltrane/theory/scale_search_result.rb +6 -0
- data/lib/coltrane/theory/scale_set.rb +40 -0
- data/lib/coltrane/theory/voicing.rb +41 -0
- data/lib/coltrane/version.rb +1 -1
- metadata +88 -63
- data/bin/rails +0 -17
- data/data/qualities.yml +0 -83
- data/db/cache.sqlite3 +0 -0
- data/db/cache_test.sqlite3 +0 -0
- data/db/config.yml +0 -11
- data/lib/cli.rb +0 -24
- data/lib/cli/bass_guitar.rb +0 -12
- data/lib/cli/chord.rb +0 -39
- data/lib/cli/config.rb +0 -39
- data/lib/cli/errors.rb +0 -46
- data/lib/cli/guitar.rb +0 -67
- data/lib/cli/guitar_chords.rb +0 -122
- data/lib/cli/notes.rb +0 -20
- data/lib/cli/piano.rb +0 -57
- data/lib/cli/scale.rb +0 -53
- data/lib/cli/text.rb +0 -16
- data/lib/cli/ukulele.rb +0 -14
- data/lib/coltrane/cache.rb +0 -43
- data/lib/coltrane/cadence.rb +0 -7
- data/lib/coltrane/chord.rb +0 -89
- data/lib/coltrane/chord_quality.rb +0 -111
- data/lib/coltrane/chord_substitutions.rb +0 -9
- data/lib/coltrane/circle_of_fifths.rb +0 -31
- data/lib/coltrane/classic_scales.rb +0 -94
- data/lib/coltrane/diatonic_scale.rb +0 -36
- data/lib/coltrane/errors.rb +0 -95
- data/lib/coltrane/frequency.rb +0 -50
- data/lib/coltrane/frequency_interval.rb +0 -81
- data/lib/coltrane/interval.rb +0 -208
- data/lib/coltrane/interval_class.rb +0 -210
- data/lib/coltrane/interval_sequence.rb +0 -155
- data/lib/coltrane/key.rb +0 -16
- data/lib/coltrane/notable_progressions.rb +0 -28
- data/lib/coltrane/note.rb +0 -98
- data/lib/coltrane/note_set.rb +0 -89
- data/lib/coltrane/pitch.rb +0 -92
- data/lib/coltrane/pitch_class.rb +0 -148
- data/lib/coltrane/progression.rb +0 -74
- data/lib/coltrane/roman_chord.rb +0 -112
- data/lib/coltrane/scale.rb +0 -154
- data/lib/coltrane/unordered_interval_class.rb +0 -7
- data/lib/coltrane/voicing.rb +0 -39
- data/lib/coltrane_game/question.rb +0 -7
- data/lib/coltrane_instruments.rb +0 -7
- data/lib/coltrane_instruments/guitar.rb +0 -10
- data/lib/coltrane_instruments/guitar/base.rb +0 -29
- data/lib/coltrane_instruments/guitar/chord.rb +0 -170
- data/lib/coltrane_instruments/guitar/note.rb +0 -24
- data/lib/coltrane_instruments/guitar/string.rb +0 -29
- data/lib/os.rb +0 -21
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# It describes a sequence of intervals
|
6
|
+
class IntervalSequence
|
7
|
+
extend Forwardable
|
8
|
+
attr_reader :intervals
|
9
|
+
|
10
|
+
def_delegators :@intervals, :map, :each, :[], :size,
|
11
|
+
:reduce, :delete, :reject!, :delete_if, :detect, :to_a
|
12
|
+
|
13
|
+
def initialize(*intervals, notes: nil, relative_intervals: nil)
|
14
|
+
if intervals.any?
|
15
|
+
@intervals = if intervals.first.is_a?(Interval)
|
16
|
+
intervals
|
17
|
+
else
|
18
|
+
intervals.map { |i| Interval[i] }
|
19
|
+
end
|
20
|
+
|
21
|
+
elsif notes
|
22
|
+
notes = NoteSet[*notes] if notes.is_a?(Array)
|
23
|
+
@intervals = intervals_from_notes(notes)
|
24
|
+
elsif relative_intervals
|
25
|
+
@relative_intervals = relative_intervals
|
26
|
+
@intervals = intervals_from_relative_intervals(relative_intervals)
|
27
|
+
else
|
28
|
+
raise WrongKeywordsError,
|
29
|
+
'Provide: [notes:] || [intervals:] || [relative_intervals:]'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Interval.all_including_compound_and_altered.each do |interval|
|
34
|
+
# Creates methods such as major_third, returning it if it finds
|
35
|
+
define_method(interval.full_name.underscore.to_s) { find(interval) }
|
36
|
+
# Creates methods such as has_major_third?, returning a boolean
|
37
|
+
define_method("has_#{interval.full_name.underscore}?") { has?(interval) }
|
38
|
+
end
|
39
|
+
|
40
|
+
Interval.distances_names.map(&:underscore).each_with_index do |distance, i|
|
41
|
+
# Creates methods such as has_third?, returning a boolean
|
42
|
+
define_method("has_#{distance}?") { !!find_by_distance(i + 1) }
|
43
|
+
# Creates methods such third, returning any third it finds
|
44
|
+
define_method(distance.to_s) { find_by_distance(i + 1) }
|
45
|
+
# Creates methods such third!, returning thirds that arent aug or dim
|
46
|
+
define_method("#{distance}!") { find_by_distance(i + 1, false) }
|
47
|
+
end
|
48
|
+
|
49
|
+
instance_eval { alias [] new }
|
50
|
+
|
51
|
+
def relative_intervals
|
52
|
+
intervals_semitones[1..-1].each_with_index.map do |n, i|
|
53
|
+
if i.zero?
|
54
|
+
n
|
55
|
+
elsif i < intervals_semitones.size
|
56
|
+
n - intervals_semitones[i]
|
57
|
+
end
|
58
|
+
end + [12 - intervals_semitones.last]
|
59
|
+
end
|
60
|
+
|
61
|
+
def names
|
62
|
+
intervals.map(&:name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def find(interval)
|
66
|
+
interval.clone if detect { |i| interval == i }
|
67
|
+
end
|
68
|
+
|
69
|
+
def has?(interval)
|
70
|
+
!!find(interval)
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_by_distance(n, accept_altered = true)
|
74
|
+
strategy = (accept_altered ? :as : :as!)
|
75
|
+
map { |interval| interval.send(strategy, n) }
|
76
|
+
.compact
|
77
|
+
.sort_by { |i| i.alteration.abs }
|
78
|
+
.first
|
79
|
+
end
|
80
|
+
|
81
|
+
alias interval_names names
|
82
|
+
|
83
|
+
def all
|
84
|
+
intervals
|
85
|
+
end
|
86
|
+
|
87
|
+
def [](x)
|
88
|
+
intervals[x]
|
89
|
+
end
|
90
|
+
|
91
|
+
def shift(ammount)
|
92
|
+
self.class.new(*intervals.map do |i|
|
93
|
+
(i.semitones + ammount) % 12
|
94
|
+
end)
|
95
|
+
end
|
96
|
+
|
97
|
+
def zero_it
|
98
|
+
shift(-intervals.first.semitones)
|
99
|
+
end
|
100
|
+
|
101
|
+
def inversion(index)
|
102
|
+
self.class.new(*intervals.rotate(index)).zero_it
|
103
|
+
end
|
104
|
+
|
105
|
+
def next_inversion
|
106
|
+
inversion(index + 1)
|
107
|
+
end
|
108
|
+
|
109
|
+
def previous_inversion
|
110
|
+
inversion(index - 1)
|
111
|
+
end
|
112
|
+
|
113
|
+
def inversions
|
114
|
+
Array.new(intervals.length) { |i| inversion(i) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def intervals_semitones
|
118
|
+
map(&:semitones)
|
119
|
+
end
|
120
|
+
|
121
|
+
def names
|
122
|
+
map(&:name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def full_names
|
126
|
+
map(&:full_name)
|
127
|
+
end
|
128
|
+
|
129
|
+
def notes_for(root_note)
|
130
|
+
NoteSet[
|
131
|
+
*intervals.reduce([]) do |memo, interval|
|
132
|
+
memo + [root_note + interval]
|
133
|
+
end
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
def &(other)
|
138
|
+
case other
|
139
|
+
when Array then intervals & other
|
140
|
+
when IntervalSequence then intervals & other.semitones
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def intervals_from_relative_intervals(relative_intervals)
|
147
|
+
relative_intervals[0..-2].reduce([Interval[0]]) do |memo, d|
|
148
|
+
memo + [memo.last + d]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def intervals_from_notes(notes)
|
153
|
+
notes.map { |n| notes.root - n }.sort_by(&:semitones)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
class Key < DiatonicScale
|
6
|
+
KEY_REGEX = /([A-G][#b]?)([mM]?)/
|
7
|
+
|
8
|
+
def initialize(notation)
|
9
|
+
_, note, s = *notation.match(KEY_REGEX)
|
10
|
+
super(note, major: s != 'm')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.[](notation)
|
14
|
+
new(notation)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# This module takes care of adding to progressions knowledge that is more
|
6
|
+
# based on common standards and practices.
|
7
|
+
module NotableProgressions
|
8
|
+
PROGRESSIONS = {
|
9
|
+
'Pop' => ['I-V-vi-IV', :major],
|
10
|
+
'Jazzy Pop' => ['IM7-V7-vi7-IVM7', :major],
|
11
|
+
'Jazz' => ['ii7-V7-I7', :major],
|
12
|
+
'Jazz Minor' => ['ii7-V7-i7', :major],
|
13
|
+
'Blues' => ['IM7-IV7-I7-V7-IV7-I7', :major],
|
14
|
+
'Jazz Blues' => ['I7-IV7-I7-V7-IV7-I7', :major],
|
15
|
+
'Fifties' => ['I-vi-IV-V', :major],
|
16
|
+
'Circle' => ['vi-ii-V-I', :major],
|
17
|
+
'Tune Up' => ['ii7-V7-IM7-i7-IV7-IVM7-VIIM7', :minor]
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
PROGRESSIONS.each do |name, values|
|
21
|
+
notation, scale_name = values
|
22
|
+
define_method name.underscore do |note|
|
23
|
+
note = note.is_a?(Note) ? note : Note[note]
|
24
|
+
scale = Scale.public_send(scale_name, note)
|
25
|
+
new(notation, scale: scale)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# Notes are different ways of calling pitch classes. In the context of equal
|
6
|
+
# tempered scales, they're more like a conceptual subject for
|
7
|
+
# matters of convention than an actual thing.
|
8
|
+
#
|
9
|
+
# Take for example A# and Bb. Those are different notes. Nevertheless, in the
|
10
|
+
# context of equal tempered scales they represent pretty much the same
|
11
|
+
# frequency.
|
12
|
+
#
|
13
|
+
# The theory of notes have changed too much in the course of time, which
|
14
|
+
# lead us with a lot of conventions and strategies when dealing with music.
|
15
|
+
# That's what this class is for.
|
16
|
+
class Note < PitchClass
|
17
|
+
attr_reader :alteration
|
18
|
+
|
19
|
+
ALTERATIONS = {
|
20
|
+
'b' => -1,
|
21
|
+
'#' => 1
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(arg)
|
25
|
+
note_name = case arg
|
26
|
+
when String then arg
|
27
|
+
when PitchClass then arg.true_notation
|
28
|
+
when Numeric, Frequency then PitchClass.new(arg).true_notation
|
29
|
+
else raise(WrongArgumentsError, arg)
|
30
|
+
end
|
31
|
+
|
32
|
+
chars = note_name.chars
|
33
|
+
letter = chars.shift
|
34
|
+
raise InvalidNoteLetterError, arg unless ('A'..'G').cover?(letter)
|
35
|
+
@alteration = chars.reduce(0) do |alt, symbol|
|
36
|
+
raise InvalidNoteLetterError, arg unless ALTERATIONS.include?(symbol)
|
37
|
+
alt + ALTERATIONS[symbol]
|
38
|
+
end
|
39
|
+
super((PitchClass[letter].integer + @alteration) % PitchClass.size)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.[](arg)
|
43
|
+
new(arg)
|
44
|
+
end
|
45
|
+
|
46
|
+
def name
|
47
|
+
"#{base_pitch_class}#{accidents}".gsub(/#b|b#/, '')
|
48
|
+
end
|
49
|
+
|
50
|
+
def base_pitch_class
|
51
|
+
PitchClass[integer - alteration]
|
52
|
+
end
|
53
|
+
|
54
|
+
def pitch_class
|
55
|
+
PitchClass.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def alteration=(a)
|
59
|
+
@alteration = a unless PitchClass[integer - a].accidental?
|
60
|
+
end
|
61
|
+
|
62
|
+
def alter(x)
|
63
|
+
Note.new(name).tap { |n| n.alteration = x }
|
64
|
+
end
|
65
|
+
|
66
|
+
def sharp?
|
67
|
+
alteration == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
def double_sharp?
|
71
|
+
alteration == 2
|
72
|
+
end
|
73
|
+
|
74
|
+
def flat?
|
75
|
+
alteration == -1
|
76
|
+
end
|
77
|
+
|
78
|
+
def double_flat?
|
79
|
+
alteration == -2
|
80
|
+
end
|
81
|
+
|
82
|
+
def natural?
|
83
|
+
alteration == 0
|
84
|
+
end
|
85
|
+
|
86
|
+
def accidents
|
87
|
+
(@alteration > 0 ? '#' : 'b') * alteration.abs
|
88
|
+
end
|
89
|
+
|
90
|
+
def -(other)
|
91
|
+
super(other).yield_self { |r| r.is_a?(Note) ? r.alter(alteration) : r }
|
92
|
+
end
|
93
|
+
|
94
|
+
def +(other)
|
95
|
+
super(other).yield_self { |r| r.is_a?(Note) ? r.alter(alteration) : r }
|
96
|
+
end
|
97
|
+
|
98
|
+
def as(letter)
|
99
|
+
a = (Note[letter] - self)
|
100
|
+
alter([a.semitones, -(-a).semitones].min_by(&:abs))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# It describes a set of notes
|
6
|
+
class NoteSet
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@notes, :first, :each, :size, :map, :reduce, :index,
|
10
|
+
:[], :index, :empty?, :permutation, :include?, :<<, :any?,
|
11
|
+
:count, :rotate
|
12
|
+
|
13
|
+
attr_reader :notes
|
14
|
+
|
15
|
+
alias root first
|
16
|
+
alias all notes
|
17
|
+
|
18
|
+
def self.[](*notes)
|
19
|
+
new(notes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(arg)
|
23
|
+
@notes =
|
24
|
+
case arg
|
25
|
+
when NoteSet then arg.notes
|
26
|
+
when Array then arg.compact.map { |n| n.is_a?(PitchClass) ? n : Note[n] }.uniq
|
27
|
+
else raise InvalidNotesError, arg
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
(self & other).size == self.size
|
33
|
+
end
|
34
|
+
|
35
|
+
alias eql? ==
|
36
|
+
|
37
|
+
def &(other)
|
38
|
+
NoteSet[*(notes & other.notes)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def degree(note)
|
42
|
+
index(note) + 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def +(other)
|
46
|
+
case other
|
47
|
+
when Note then NoteSet[*(notes + [other])]
|
48
|
+
when NoteSet then NoteSet[*notes, *other.notes]
|
49
|
+
when Interval then NoteSet[*notes.map { |n| n + other }]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def -(other)
|
54
|
+
case other
|
55
|
+
when NoteSet then NoteSet[*(notes - other.notes)]
|
56
|
+
when Interval then NoteSet[*notes.map { |n| n - other }]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def pretty_names
|
61
|
+
map(&:pretty_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def names
|
65
|
+
map(&:name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def hash
|
69
|
+
integers.join.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
def integers
|
73
|
+
map(&:integer)
|
74
|
+
end
|
75
|
+
|
76
|
+
def accidentals
|
77
|
+
count(&:accidental?)
|
78
|
+
end
|
79
|
+
|
80
|
+
def sharps
|
81
|
+
count(&:sharp?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def flats
|
85
|
+
count(&:flat?)
|
86
|
+
end
|
87
|
+
|
88
|
+
def alter(alteration)
|
89
|
+
NoteSet[*map { |n| n.alter(alteration) }]
|
90
|
+
end
|
91
|
+
|
92
|
+
def alter_accidentals(alteration)
|
93
|
+
NoteSet[*map { |n| n.alter(alteration) if n.accidental? }]
|
94
|
+
end
|
95
|
+
|
96
|
+
def interval_sequence
|
97
|
+
IntervalSequence.new(notes: self)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# It describes a pitch, like E4 or Bb5. It's like a note, but it has an octave
|
6
|
+
class Pitch
|
7
|
+
attr_reader :integer
|
8
|
+
|
9
|
+
def initialize(notation_arg = nil,
|
10
|
+
note: nil,
|
11
|
+
octave: nil,
|
12
|
+
notation: nil,
|
13
|
+
frequency: nil)
|
14
|
+
|
15
|
+
@integer = begin
|
16
|
+
if (n = notation_arg || notation)
|
17
|
+
n.is_a?(Integer) ? n : integer_from_notation(n)
|
18
|
+
elsif note && octave
|
19
|
+
integer_from_note_and_octave(Note[note], octave)
|
20
|
+
elsif frequency
|
21
|
+
integer_from_frequency(frequency)
|
22
|
+
else
|
23
|
+
raise(InvalidArgumentsError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.[](*args)
|
29
|
+
new *args
|
30
|
+
end
|
31
|
+
|
32
|
+
def scientific_notation
|
33
|
+
"#{pitch_class}#{octave}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def pitch_class
|
37
|
+
PitchClass[integer]
|
38
|
+
end
|
39
|
+
|
40
|
+
def octave
|
41
|
+
(integer / 12) - 1
|
42
|
+
end
|
43
|
+
|
44
|
+
alias notation scientific_notation
|
45
|
+
alias name scientific_notation
|
46
|
+
alias to_s scientific_notation
|
47
|
+
|
48
|
+
alias hash integer
|
49
|
+
alias midi integer
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
integer == other.integer
|
53
|
+
end
|
54
|
+
|
55
|
+
def frequency
|
56
|
+
pitch_class.frequency.octave(octave)
|
57
|
+
end
|
58
|
+
|
59
|
+
alias eql? ==
|
60
|
+
alias eq ==
|
61
|
+
|
62
|
+
def +(other)
|
63
|
+
case other
|
64
|
+
when Integer then Pitch[integer + other]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def -(other)
|
69
|
+
case other
|
70
|
+
when Integer then Pitch[integer - other]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def integer_from_notation(the_notation)
|
77
|
+
_, n, o = *the_notation.match(/(\D+)(\d+)/)
|
78
|
+
integer_from_note_and_octave(Note[n], o)
|
79
|
+
end
|
80
|
+
|
81
|
+
def integer_from_note_and_octave(p, o)
|
82
|
+
12 * (o.to_i + 1) + p.integer
|
83
|
+
end
|
84
|
+
|
85
|
+
def integer_from_frequency(f)
|
86
|
+
octave_from_frequency(f) * 12 + PitchClass.new(frequency: f).integer
|
87
|
+
end
|
88
|
+
|
89
|
+
def octave_from_frequency(f)
|
90
|
+
Math.log(f.to_f / PitchClass['C'].frequency.to_f, 2).ceil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|