coltrane 2.0.0 → 2.1.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 +4 -4
- data/CHANGELOG.md +27 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +21 -3
- data/README.md +0 -7
- data/Rakefile +5 -0
- data/bin/console +2 -0
- data/bin/opal +29 -0
- data/bin/opal-build +29 -0
- data/bin/opal-mspec +29 -0
- data/bin/opal-repl +29 -0
- data/bin/rackup +12 -0
- data/bin/sprockets +12 -0
- data/bin/tilt +12 -0
- data/coltrane.gemspec +1 -1
- data/config.ru +10 -0
- data/exe/coltrane +44 -13
- data/lib/cli.rb +7 -1
- data/lib/cli/bass_guitar.rb +1 -1
- data/lib/cli/chord.rb +11 -4
- data/lib/cli/config.rb +38 -0
- data/lib/cli/guitar.rb +8 -10
- data/lib/cli/guitar_chords.rb +122 -0
- data/lib/cli/notes.rb +3 -4
- data/lib/cli/piano.rb +1 -1
- data/lib/cli/representation.rb +6 -12
- data/lib/cli/scale.rb +4 -4
- data/lib/cli/text.rb +1 -1
- data/lib/cli/ukulele.rb +1 -1
- data/lib/coltrane.rb +35 -32
- data/lib/coltrane/chord_quality.rb +4 -3
- data/lib/coltrane/circle_of_fifths.rb +29 -0
- data/lib/coltrane/classic_scales.rb +36 -43
- data/lib/coltrane/diatonic_scale.rb +34 -0
- data/lib/coltrane/frequency.rb +1 -1
- data/lib/coltrane/interval.rb +14 -0
- data/lib/coltrane/interval_class.rb +6 -0
- data/lib/coltrane/interval_sequence.rb +0 -1
- data/lib/coltrane/key.rb +14 -0
- data/lib/coltrane/note.rb +37 -2
- data/lib/coltrane/note_set.rb +23 -2
- data/lib/coltrane/pitch.rb +65 -31
- data/lib/coltrane/pitch_class.rb +25 -12
- data/lib/coltrane/progression.rb +2 -2
- data/lib/coltrane/qualities.rb +257 -0
- data/lib/coltrane/roman_chord.rb +10 -10
- data/lib/coltrane/scale.rb +9 -3
- data/lib/coltrane/version.rb +1 -1
- data/lib/coltrane/voicing.rb +38 -0
- data/lib/coltrane_game/question.rb +6 -0
- data/lib/coltrane_instruments.rb +3 -0
- data/lib/coltrane_instruments/guitar.rb +5 -2
- data/lib/coltrane_instruments/guitar/base.rb +18 -3
- data/lib/coltrane_instruments/guitar/chord.rb +139 -12
- data/lib/coltrane_instruments/guitar/note.rb +16 -0
- data/lib/coltrane_instruments/guitar/string.rb +21 -0
- data/lib/coltrane_synth.rb +7 -0
- data/lib/coltrane_synth/base.rb +47 -0
- data/lib/coltrane_synth/synth.rb +24 -0
- data/lib/core_ext.rb +25 -7
- data/lib/os.rb +19 -0
- metadata +24 -9
- data/dist/coltrane +0 -0
- data/lib/coltrane/piano_representation.rb +0 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module Coltrane
|
2
|
+
|
3
|
+
# This class describes an actual implementation of a Chord, being aware
|
4
|
+
# of exact octaves of each pitch and even repeating pitches across octaves.
|
5
|
+
class Voicing
|
6
|
+
attr_reader :pitches
|
7
|
+
|
8
|
+
def initialize(*pitch_strings, pitches: nil)
|
9
|
+
@pitches = if pitch_strings.any?
|
10
|
+
pitch_strings.map { |s| Pitch[s] }
|
11
|
+
elsif pitches
|
12
|
+
pitches
|
13
|
+
else
|
14
|
+
raise WrongArgumentsError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.[](*args)
|
19
|
+
new(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def pitch_classes
|
23
|
+
NoteSet[*pitches.map(&:pitch_class).uniq]
|
24
|
+
end
|
25
|
+
|
26
|
+
alias notes pitch_classes
|
27
|
+
|
28
|
+
def chord
|
29
|
+
@chord ||= Chord.new(notes: notes)
|
30
|
+
rescue ChordNotFoundError
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
|
34
|
+
def frequencies
|
35
|
+
pitches.map(&:frequency)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/coltrane_instruments.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ColtraneInstruments
|
4
|
-
# This module represents a guitar
|
5
4
|
module Guitar
|
5
|
+
autoload :Base, 'coltrane_instruments/guitar/base'
|
6
|
+
autoload :Chord, 'coltrane_instruments/guitar/chord'
|
7
|
+
autoload :Note, 'coltrane_instruments/guitar/note'
|
8
|
+
autoload :String, 'coltrane_instruments/guitar/string'
|
6
9
|
end
|
7
|
-
end
|
10
|
+
end
|
@@ -2,12 +2,27 @@
|
|
2
2
|
|
3
3
|
module ColtraneInstruments
|
4
4
|
module Guitar
|
5
|
+
DEFAULT_TUNING = %w[E2 A2 D3 G3 B3 E4]
|
6
|
+
DEFAULT_FRETS = 23
|
5
7
|
# A base class for operations involving Guitars
|
6
8
|
class Base
|
7
|
-
|
9
|
+
attr_reader :strings, :frets
|
8
10
|
|
9
|
-
def
|
10
|
-
|
11
|
+
def self.find_chords(target_chord)
|
12
|
+
unless target_chord.is_a?(Coltrane::Chord)
|
13
|
+
target_chord = Coltrane::Chord.new(name: target_chord)
|
14
|
+
end
|
15
|
+
|
16
|
+
ColtraneInstruments::Guitar::Chord.new(target_chord, guitar: new)
|
17
|
+
.fetch_descendant_chords
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(tuning = DEFAULT_TUNING, frets = DEFAULT_FRETS)
|
21
|
+
@strings = tuning.map do |p|
|
22
|
+
String.new(Coltrane::Pitch[p], guitar: self)
|
23
|
+
end
|
24
|
+
|
25
|
+
@frets = frets
|
11
26
|
end
|
12
27
|
end
|
13
28
|
end
|
@@ -4,14 +4,62 @@ module ColtraneInstruments
|
|
4
4
|
module Guitar
|
5
5
|
# This class represents a group of guitar notes, strummed at the same time
|
6
6
|
class Chord
|
7
|
-
|
7
|
+
include Comparable
|
8
|
+
attr_reader :guitar_notes, :guitar, :free_fingers, :target_chord, :barre
|
9
|
+
|
10
|
+
MAX_FRET_SPAN = 3
|
11
|
+
|
12
|
+
def self.find(chord)
|
13
|
+
new(chord).fetch_descendant_chords
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(target_chord,
|
17
|
+
guitar_notes: [],
|
8
18
|
free_fingers: 4,
|
9
19
|
barre: nil,
|
10
20
|
guitar:)
|
21
|
+
|
11
22
|
@target_chord = target_chord
|
12
23
|
@guitar_notes = guitar_notes
|
13
24
|
@free_fingers = free_fingers
|
14
|
-
@
|
25
|
+
@guitar = guitar
|
26
|
+
@barre = barre
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(other)
|
30
|
+
rank <=> other.rank
|
31
|
+
end
|
32
|
+
|
33
|
+
def rank
|
34
|
+
+completeness * 10000 +
|
35
|
+
+fullness * 1000 +
|
36
|
+
-spreadness * 100 +
|
37
|
+
+easyness * 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def analysis
|
41
|
+
{
|
42
|
+
completeness: completeness,
|
43
|
+
fullness: fullness,
|
44
|
+
easyness: easyness,
|
45
|
+
spreadness: spreadness
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def spreadness
|
50
|
+
fret_range.size.to_f / MAX_FRET_SPAN
|
51
|
+
end
|
52
|
+
|
53
|
+
def completeness
|
54
|
+
(target_chord.notes.size.to_f - notes_left.size) / target_chord.notes.size
|
55
|
+
end
|
56
|
+
|
57
|
+
def easyness
|
58
|
+
frets.count(0).to_f / guitar_notes.size
|
59
|
+
end
|
60
|
+
|
61
|
+
def fullness
|
62
|
+
(guitar.strings.size.to_f - frets.count(nil)) / guitar.strings.size
|
15
63
|
end
|
16
64
|
|
17
65
|
def barre?
|
@@ -19,23 +67,102 @@ module ColtraneInstruments
|
|
19
67
|
end
|
20
68
|
|
21
69
|
def fetch_descendant_chords
|
22
|
-
return self if guitar_notes.size
|
23
|
-
possible_new_notes.reduce([]) do |memo, n|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
70
|
+
return [self] if guitar_notes.size >= guitar.strings.size
|
71
|
+
possible_new_notes(notes_available.positive?).reduce([]) do |memo, n|
|
72
|
+
barre = n.fret if guitar_notes.last == n.fret
|
73
|
+
fingers_change = (n.fret == barre || n.fret.zero?) ? 0 : 1
|
74
|
+
next memo if (free_fingers - fingers_change).negative?
|
75
|
+
Chord.new(target_chord, guitar_notes: guitar_notes + [n],
|
76
|
+
free_fingers: free_fingers - fingers_change,
|
77
|
+
guitar: guitar,
|
78
|
+
barre: barre)
|
79
|
+
.fetch_descendant_chords + memo
|
31
80
|
end
|
32
81
|
end
|
33
82
|
|
34
|
-
def
|
83
|
+
def notes_available
|
84
|
+
strings_available - notes_left.size
|
85
|
+
end
|
86
|
+
|
87
|
+
def strings_available
|
88
|
+
guitar.strings.size - guitar_notes.size
|
89
|
+
end
|
90
|
+
|
91
|
+
def notes_left
|
92
|
+
@notes_left ||= (target_chord.notes - Coltrane::NoteSet[
|
93
|
+
*guitar_notes.map do |n|
|
94
|
+
next if n.pitch.nil?
|
95
|
+
n.pitch.pitch_class
|
96
|
+
end
|
97
|
+
])
|
98
|
+
end
|
99
|
+
|
100
|
+
def target_notes
|
101
|
+
notes_left.any? ? notes_left : target_chord.notes
|
102
|
+
end
|
103
|
+
|
104
|
+
def notes
|
105
|
+
guitar_notes.map(&:pitch)
|
106
|
+
end
|
107
|
+
|
108
|
+
def frets
|
109
|
+
@frets ||= guitar_notes.map(&:fret)
|
110
|
+
end
|
111
|
+
|
112
|
+
def non_zero_frets
|
113
|
+
frets.reject {|f| f.nil? || f.zero? }
|
114
|
+
end
|
115
|
+
|
116
|
+
def lowest_fret
|
117
|
+
non_zero_frets.any? ? non_zero_frets.min : 0
|
118
|
+
end
|
119
|
+
|
120
|
+
def highest_fret
|
121
|
+
non_zero_frets.max || 0
|
122
|
+
end
|
123
|
+
|
124
|
+
def fret_range
|
125
|
+
(lowest_fret..highest_fret)
|
126
|
+
end
|
127
|
+
|
128
|
+
def lowest_possible_fret
|
129
|
+
lowest_fret.zero? ? 0 : [(lowest_fret - possible_span), 0].max
|
130
|
+
end
|
131
|
+
|
132
|
+
def highest_possible_fret
|
133
|
+
[(possible_span + (highest_fret == 0 ? guitar.frets : highest_fret)), guitar.frets].min
|
134
|
+
end
|
135
|
+
|
136
|
+
def possible_span
|
137
|
+
MAX_FRET_SPAN - fret_range.size
|
138
|
+
end
|
139
|
+
|
140
|
+
def fret_expansion_range
|
141
|
+
(lowest_possible_fret..highest_possible_fret).to_a +
|
142
|
+
[(0 unless barre?)].compact
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_s
|
146
|
+
guitar_notes.map { |n| n.fret.nil? ? 'x' : n.fret }.join('-')
|
147
|
+
end
|
148
|
+
|
149
|
+
def voicing
|
150
|
+
Coltrane::Voicing.new(pitches: guitar_notes.map(&:pitch).compact)
|
151
|
+
end
|
35
152
|
|
36
153
|
private
|
37
154
|
|
38
155
|
attr_writer :barre
|
156
|
+
|
157
|
+
def next_string
|
158
|
+
guitar.strings[guitar_notes.size]
|
159
|
+
end
|
160
|
+
|
161
|
+
def possible_new_notes(include_mute_note = false)
|
162
|
+
target_notes.notes.map do |note|
|
163
|
+
next_string.find(note, possible_frets: fret_expansion_range)
|
164
|
+
end.flatten + [(Guitar::Note.new(next_string, nil) if include_mute_note)].compact
|
165
|
+
end
|
39
166
|
end
|
40
167
|
end
|
41
168
|
end
|
@@ -3,6 +3,22 @@
|
|
3
3
|
module ColtraneInstruments
|
4
4
|
module Guitar
|
5
5
|
class Note
|
6
|
+
attr_reader :string, :fret
|
7
|
+
|
8
|
+
def initialize(string, fret = nil)
|
9
|
+
@string = string
|
10
|
+
@fret = fret
|
11
|
+
end
|
12
|
+
|
13
|
+
def pitch
|
14
|
+
string + fret unless fret.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def pitch_class
|
18
|
+
pitch.pitch_class unless fret.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
alias note pitch_class
|
6
22
|
end
|
7
23
|
end
|
8
24
|
end
|
@@ -3,6 +3,27 @@
|
|
3
3
|
module ColtraneInstruments
|
4
4
|
module Guitar
|
5
5
|
class String
|
6
|
+
attr_reader :pitch, :guitar
|
7
|
+
|
8
|
+
def initialize(pitch, guitar:)
|
9
|
+
@guitar = guitar
|
10
|
+
@pitch = pitch
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(pitch_class, possible_frets: (0..guitar.frets).to_a)
|
14
|
+
output = []
|
15
|
+
n = 0
|
16
|
+
loop do
|
17
|
+
f = (pitch_class.integer - pitch.integer) % 12 + 12 * n
|
18
|
+
possible_frets.include?(f) ? output << Note.new(self, f) : break
|
19
|
+
n += 1
|
20
|
+
end
|
21
|
+
output
|
22
|
+
end
|
23
|
+
|
24
|
+
def +(fret)
|
25
|
+
pitch + fret
|
26
|
+
end
|
6
27
|
end
|
7
28
|
end
|
8
29
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ColtraneSynth
|
2
|
+
class Base
|
3
|
+
attr_reader :device, :buffer
|
4
|
+
|
5
|
+
SAMPLE_RATE = 44000
|
6
|
+
|
7
|
+
def self.play(something, duration=1.0)
|
8
|
+
new.play(*extract_frequencies(something), duration: duration)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@device = CoreAudio.default_output_device
|
13
|
+
end
|
14
|
+
|
15
|
+
def play(*freqs, duration: 1.0)
|
16
|
+
@device = CoreAudio.default_output_device
|
17
|
+
buffer = @device.output_loop(SAMPLE_RATE)
|
18
|
+
|
19
|
+
SAMPLE_RATE.times do |i|
|
20
|
+
sound = freqs.reduce(0) { |sum, freq| Math.sin(phase(freq)*i) + sum }
|
21
|
+
buffer[i] = (sound * (0x6AFF/freqs.size)).round
|
22
|
+
end
|
23
|
+
|
24
|
+
buffer.start
|
25
|
+
sleep duration
|
26
|
+
buffer.stop
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def self.extract_frequencies(arg)
|
32
|
+
case arg
|
33
|
+
when Coltrane::Chord then arg.notes.map {|n| n.frequency.octave(4).to_f }
|
34
|
+
when Coltrane::PitchClass then arg.frequency.octave(4).to_f
|
35
|
+
when Coltrane::Voicing then arg.frequencies.map(&:to_f)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sine_wave(freq, i)
|
40
|
+
Math.sin(phase(freq)*i)
|
41
|
+
end
|
42
|
+
|
43
|
+
def phase(freq)
|
44
|
+
(Math::PI * 2.0 * freq) / SAMPLE_RATE.to_f
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ColtraneSynth
|
2
|
+
class Synth
|
3
|
+
attr_reader :freq, :nominal_rate
|
4
|
+
|
5
|
+
def initialize(buffer, freq, nominal_rate)
|
6
|
+
@freq = freq.to_f
|
7
|
+
@nominal_rate = nominal_rate
|
8
|
+
|
9
|
+
i = -1
|
10
|
+
wav = NArray.sint(1024)
|
11
|
+
|
12
|
+
while i += 1
|
13
|
+
1024.times do |j|
|
14
|
+
wav[j] = (0.4 * Math.sin(phase(freq) * (i * 1024 + j)) * 0x7FFF).round
|
15
|
+
end
|
16
|
+
buffer << wav
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def phase(freq)
|
21
|
+
Math::PI * 2.0 * freq / nominal_rate
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/core_ext.rb
CHANGED
@@ -2,15 +2,24 @@
|
|
2
2
|
|
3
3
|
# Stolen from Active Support and changed a bit
|
4
4
|
# may in the future be substituted by facets version
|
5
|
+
class Regexp
|
6
|
+
def match?(*args)
|
7
|
+
!!match(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
5
11
|
class String
|
6
12
|
def underscore
|
7
13
|
return self unless /[A-Z-]|::/.match?(self)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
to_s.gsub('::', '/')
|
15
|
+
.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
16
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
17
|
+
.tr('- ', '_')
|
18
|
+
.downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def match?(*args)
|
22
|
+
!!match(*args)
|
14
23
|
end
|
15
24
|
|
16
25
|
def interval_quality
|
@@ -22,10 +31,14 @@ class String
|
|
22
31
|
'd' => 'Diminished'
|
23
32
|
}[self]
|
24
33
|
end
|
34
|
+
|
35
|
+
def symbolize
|
36
|
+
self.underscore.to_sym
|
37
|
+
end
|
25
38
|
end
|
26
39
|
|
27
40
|
# Here we add some syntax sugar to make the code more understandable later
|
28
|
-
class
|
41
|
+
class Numeric
|
29
42
|
def interval_name
|
30
43
|
{
|
31
44
|
1 => 'Unison',
|
@@ -49,11 +62,16 @@ end
|
|
49
62
|
|
50
63
|
# Here we add some methods better work with Tries
|
51
64
|
class Hash
|
65
|
+
def dig(*args)
|
66
|
+
args.size > 1 ? self[args.shift].dig(*args) : self[args[0]]
|
67
|
+
end
|
68
|
+
|
52
69
|
def clone_values(from_keys: nil,
|
53
70
|
to_keys: nil,
|
54
71
|
suffix: nil,
|
55
72
|
branch_a: nil,
|
56
73
|
branch_b: nil)
|
74
|
+
|
57
75
|
branch_a ||= dig(*from_keys)
|
58
76
|
if branch_b.nil?
|
59
77
|
create_branch!(*to_keys)
|