coltrane 2.2.1 → 3.0.0.pre
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 +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
|