head_music 0.1.1 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 161df8f1795f4e70adcdc8d7298cdbf1dd2af984
4
- data.tar.gz: 621aac81ec102840a879186df55eb9aeed040b8b
3
+ metadata.gz: 8379f9a907ea8dd73018b1cd9a8da6a055510735
4
+ data.tar.gz: f3de473d170a910e26a5b4318232a5a9d7eb4258
5
5
  SHA512:
6
- metadata.gz: 947631876b0c2c74a807515783a22783836838d75d2a21eab2f66e8863a5b25d5849e92874885007a99dd0596acacb5c795ad0ffc153d47cac616322ec566fef
7
- data.tar.gz: 9ff5a2e01209ccd7f515d9d152f49159a0f18518f7bfb17e289609cb519395b96e7ae4399dd321346b3b971f05ae970d2095fb4556ca1bb666b36d268f46fe2c
6
+ metadata.gz: 05c161185607fd4f4ac48b5ad9389ee6d15fda24deaed1bf1a8f4e88b7140615ef60cdf695266c6e6a714cf66dc9db3a5f47e056c7c6096a9c0fde6e0d5fb0fa
7
+ data.tar.gz: f7142b9a29669c3a885c6ee9edf68330c9912ff8e71622efb1d95f7c3f314dea05dc2443c5f1c092988ceb236ba7a7768ac28a0ba43651c5954085595e57b295
@@ -10,15 +10,15 @@ class HeadMusic::Accidental
10
10
 
11
11
  def self.get(identifier)
12
12
  @accidentals ||= {}
13
- @accidentals[identifier] ||= for_symbol(identifier) || for_interval(identifier)
13
+ for_symbol(identifier) || for_interval(identifier)
14
14
  end
15
15
 
16
16
  def self.for_symbol(identifier)
17
- new(identifier) if ACCIDENTAL_SEMITONES.keys.include?(identifier)
17
+ @accidentals[identifier.to_s] ||= new(identifier.to_s) if ACCIDENTAL_SEMITONES.keys.include?(identifier.to_s)
18
18
  end
19
19
 
20
20
  def self.for_interval(semitones)
21
- ACCIDENTAL_SEMITONES.key(semitones.to_i)
21
+ @accidentals[semitones.to_i] ||= ACCIDENTAL_SEMITONES.key(semitones.to_i)
22
22
  end
23
23
 
24
24
  def initialize(string)
@@ -9,7 +9,7 @@ class HeadMusic::Interval
9
9
 
10
10
  def self.get(semitones)
11
11
  @intervals_memo ||= {}
12
- @intervals_memo[semitones] ||= new(semitones)
12
+ @intervals_memo[semitones.to_i] ||= new(semitones.to_i)
13
13
  end
14
14
 
15
15
  def self.named(name)
@@ -7,9 +7,9 @@ class HeadMusic::KeySignature
7
7
 
8
8
  delegate :pitch_class, to: :tonic_spelling, prefix: :tonic
9
9
 
10
- def initialize(tonic_spelling, scale_type = :major)
10
+ def initialize(tonic_spelling, scale_type = nil)
11
11
  @tonic_spelling = tonic_spelling
12
- @scale_type = scale_type
12
+ @scale_type = scale_type || :major
13
13
  end
14
14
 
15
15
  def sharps
@@ -19,12 +19,12 @@ class HeadMusic::Letter
19
19
 
20
20
  def self.get(identifier)
21
21
  @letters ||= {}
22
- @letters[identifier] ||= from_name(identifier) || from_pitch_class(identifier)
22
+ from_name(identifier) || from_pitch_class(identifier)
23
23
  end
24
24
 
25
25
  def self.from_name(name)
26
26
  name = name.to_s.first.upcase
27
- new(name) if NAMES.include?(name)
27
+ @letters[name] ||= new(name) if NAMES.include?(name)
28
28
  end
29
29
 
30
30
  def self.from_pitch_class(pitch_class)
@@ -33,7 +33,7 @@ class HeadMusic::Letter
33
33
  pitch_class = pitch_class.to_i % 12
34
34
  name = NAMES.detect { |name| pitch_class == NATURAL_PITCH_CLASS_NUMBERS[name] }
35
35
  name ||= HeadMusic::PitchClass::PREFERRED_SPELLINGS[pitch_class].first
36
- return new(name) if NAMES.include?(name)
36
+ @letters[name] ||= new(name) if NAMES.include?(name)
37
37
  end
38
38
 
39
39
  attr_reader :name
@@ -0,0 +1,42 @@
1
+ class HeadMusic::Octave
2
+ include Comparable
3
+
4
+ DEFAULT = 4
5
+
6
+ MATCHER = /(-?\d+)$/
7
+
8
+ def self.get(identifier)
9
+ @octaves ||= {}
10
+ from_number(identifier) || from_name(identifier) || default
11
+ end
12
+
13
+ def self.from_number(identifier)
14
+ return nil unless identifier.to_s == identifier.to_i.to_s
15
+ return nil unless (-2..12).include?(identifier.to_i)
16
+ @octaves[identifier.to_i] ||= new(identifier.to_i)
17
+ end
18
+
19
+ def self.from_name(string)
20
+ if string.to_s.match(HeadMusic::Spelling::MATCHER)
21
+ _letter, _accidental, octave_string = string.to_s.match(HeadMusic::Spelling::MATCHER).captures
22
+ @octaves[octave_string.to_i] ||= new(octave_string.to_i) if octave_string
23
+ end
24
+ end
25
+
26
+ def self.default
27
+ @octaves[DEFAULT] ||= new(DEFAULT)
28
+ end
29
+
30
+ attr_reader :number
31
+ delegate :to_i, :to_s, to: :number
32
+
33
+ def initialize(number)
34
+ @number ||= number
35
+ end
36
+
37
+ def <=>(other)
38
+ self.to_i <=> other.to_i
39
+ end
40
+
41
+ private_class_method :new
42
+ end
@@ -13,16 +13,21 @@ class HeadMusic::Pitch
13
13
 
14
14
  def self.from_name(name)
15
15
  return nil unless name == name.to_s
16
- spelling = HeadMusic::Spelling.get(name)
17
- octave = name.scan(/-?\d+$/).first.to_i
18
- new(spelling, octave) if spelling && (-1..9).include?(octave)
16
+ fetch_or_create(HeadMusic::Spelling.get(name), HeadMusic::Octave.get(name).to_i)
19
17
  end
20
18
 
21
19
  def self.from_number(number)
22
20
  return nil unless number == number.to_i
23
21
  spelling = HeadMusic::Spelling.from_number(number)
24
22
  octave = (number.to_i / 12) - 1
25
- new(spelling, octave) if spelling && (-1..9).include?(octave)
23
+ fetch_or_create(spelling, octave)
24
+ end
25
+
26
+ def self.fetch_or_create(spelling, octave)
27
+ if spelling && (-1..9).include?(octave)
28
+ key = [spelling, octave].join
29
+ @pitches[key] ||= new(spelling, octave)
30
+ end
26
31
  end
27
32
 
28
33
  def initialize(spelling, octave)
@@ -38,6 +43,8 @@ class HeadMusic::Pitch
38
43
  (octave + 1) * 12 + pitch_class.to_i
39
44
  end
40
45
 
46
+ alias_method :midi, :midi_note_number
47
+
41
48
  def to_s
42
49
  name
43
50
  end
@@ -3,9 +3,10 @@ class HeadMusic::PitchClass
3
3
 
4
4
  PREFERRED_SPELLINGS = %w[C C# D Eb E F F# G Ab A Bb B]
5
5
 
6
- def self.get(number)
6
+ def self.get(identifier)
7
7
  @pitch_classes ||= {}
8
- number = number.to_i % 12
8
+ number = Spelling.get(identifier).pitch_class.to_i if Spelling.match(identifier)
9
+ number ||= identifier.to_i % 12
9
10
  @pitch_classes[number] ||= new(number)
10
11
  end
11
12
 
@@ -14,7 +15,7 @@ class HeadMusic::PitchClass
14
15
  end
15
16
 
16
17
  def initialize(pitch_class_or_midi_number)
17
- @number = pitch_class_or_midi_number % 12
18
+ @number = pitch_class_or_midi_number.to_i % 12
18
19
  end
19
20
 
20
21
  def to_i
@@ -0,0 +1,22 @@
1
+ class HeadMusic::Quality
2
+ QUALITY_NAMES = %w[perfect major minor diminished augmented].map(&:to_sym)
3
+
4
+ def self.get(identifier)
5
+ @qualities ||= {}
6
+ identifier = identifier.to_s.to_sym
7
+ @qualities[identifier] ||= new(identifier) if QUALITY_NAMES.include?(identifier)
8
+ end
9
+
10
+ attr_reader :name
11
+ delegate :to_s, to: :name
12
+
13
+ def initialize(name)
14
+ @name = name
15
+ end
16
+
17
+ def ==(other)
18
+ self.to_s == other.to_s
19
+ end
20
+
21
+ private_class_method :new
22
+ end
@@ -0,0 +1,83 @@
1
+ class HeadMusic::ScaleType
2
+ H = 1 # whole step
3
+ W = 2 # half step
4
+ WH = W + H # augmented second
5
+
6
+ # Modal
7
+ I = [W, W, H, W, W, W, H]
8
+ II = I.rotate
9
+ III = I.rotate(2)
10
+ IV = I.rotate(3)
11
+ V = I.rotate(4)
12
+ VI = I.rotate(5)
13
+ VII = I.rotate(6)
14
+
15
+ # Tonal
16
+ HARMONIC_MINOR = [W, H, W, W, H, WH, H]
17
+ MELODIC_MINOR_ASCENDING = [W, H, W, W, W, W, H]
18
+
19
+ CHROMATIC = [H, H, H, H, H, H, H, H, H, H, H, H]
20
+ MINOR_PENTATONIC = [3, 2, 2, 3, 2]
21
+ MAJOR_PENTATONIC = MINOR_PENTATONIC.rotate
22
+
23
+ MODE_NAMES = {
24
+ i: [:ionian, :major, :maj],
25
+ ii: [:dorian],
26
+ iii: [:phrygian],
27
+ iv: [:lydian],
28
+ v: [:mixolydian],
29
+ vi: [:aeolian, :minor, :natural_minor, :min],
30
+ vii: [:locrian],
31
+ }
32
+ SCALE_TYPES = {}
33
+ MODE_NAMES.each do |roman_numeral, aliases|
34
+ intervals = { ascending: const_get(roman_numeral.upcase) }
35
+ SCALE_TYPES[roman_numeral] = intervals
36
+ aliases.each do |name|
37
+ SCALE_TYPES[name.to_sym] = intervals
38
+ end
39
+ end
40
+ SCALE_TYPES[:harmonic_minor] = { ascending: HARMONIC_MINOR }
41
+ SCALE_TYPES[:melodic_minor] = { ascending: MELODIC_MINOR_ASCENDING, descending: VI.reverse }
42
+
43
+ SCALE_TYPES[:chromatic] = { ascending: CHROMATIC }
44
+
45
+ SCALE_TYPES[:minor_pentatonic] = { ascending: MINOR_PENTATONIC }
46
+ SCALE_TYPES[:major_pentatonic] = { ascending: MAJOR_PENTATONIC }
47
+
48
+ SCALE_TYPES[:octatonic] = { ascending: [W, H, W, H, W, H, W, H] }
49
+ SCALE_TYPES[:whole_tone] = { ascending: [W, W, W, W, W, W] }
50
+ SCALE_TYPES[:monotonic] = { ascending: [12] }
51
+
52
+ class << self
53
+ SCALE_TYPES.keys.each do |name|
54
+ define_method(name) do
55
+ self.get(name)
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.get(name)
61
+ @scale_types ||= {}
62
+ name = name.to_s.to_sym
63
+ intervals = SCALE_TYPES[name]
64
+ @scale_types[name] ||= new(name, intervals[:ascending], intervals[:descending])
65
+ end
66
+
67
+ attr_reader :name, :ascending_intervals, :descending_intervals
68
+ delegate :to_s, to: :name
69
+
70
+ def initialize(name, ascending_intervals, descending_intervals = nil)
71
+ @name = name
72
+ @ascending_intervals = ascending_intervals
73
+ @descending_intervals = descending_intervals || ascending_intervals.reverse
74
+ end
75
+
76
+ def ==(other)
77
+ self.state == other.state
78
+ end
79
+
80
+ def state
81
+ [ascending_intervals, descending_intervals]
82
+ end
83
+ end
@@ -1,24 +1,30 @@
1
- # A Spelling is a pitch class with a letter and an accidental
1
+ # A Spelling is a pitch class with a letter and possibly an accidental
2
2
 
3
3
  class HeadMusic::Spelling
4
+ MATCHER = /^\s*([A-G])([b#]*)(\-?\d+)?\s*$/
5
+
6
+ attr_reader :pitch_class
4
7
  attr_reader :letter
5
8
  attr_reader :accidental
6
- attr_reader :pitch_class
7
9
 
8
- SPELLING_MATCHER = /^\s*([A-G])([b#]*)(\-?\d+)?\s*$/
10
+ delegate :number, to: :pitch_class, prefix: true
9
11
 
10
12
  def self.get(identifier)
11
13
  @spellings ||= {}
12
- @spellings[identifier] ||= from_name(identifier) || from_number(identifier)
14
+ from_name(identifier) || from_number(identifier)
15
+ end
16
+
17
+ def self.match(string)
18
+ string.to_s.match(MATCHER)
13
19
  end
14
20
 
15
21
  def self.from_name(name)
16
- return nil unless name == name.to_s
17
- match = name.to_s.match(SPELLING_MATCHER)
18
- if match
19
- letter_name, accidental_string, _octave = match.captures
22
+ if match(name)
23
+ letter_name, accidental_string, _octave = match(name).captures
20
24
  letter = HeadMusic::Letter.get(letter_name)
21
- new(letter, HeadMusic::Accidental.get(accidental_string)) if letter
25
+ return nil unless letter
26
+ accidental = HeadMusic::Accidental.get(accidental_string)
27
+ fetch_or_create(letter, accidental)
22
28
  end
23
29
  end
24
30
 
@@ -26,10 +32,13 @@ class HeadMusic::Spelling
26
32
  return nil unless number == number.to_i
27
33
  pitch_class_number = number.to_i % 12
28
34
  letter = HeadMusic::Letter.from_pitch_class(pitch_class_number)
29
- if letter.pitch_class != pitch_class_number
30
- accidental = HeadMusic::Accidental.for_interval(pitch_class_number - letter.pitch_class.to_i)
31
- end
32
- new(letter, accidental)
35
+ accidental = HeadMusic::Accidental.for_interval(pitch_class_number - letter.pitch_class.to_i)
36
+ fetch_or_create(letter, accidental)
37
+ end
38
+
39
+ def self.fetch_or_create(letter, accidental)
40
+ key = [letter, accidental].join
41
+ @spellings[key] ||= new(letter, accidental)
33
42
  end
34
43
 
35
44
  def initialize(letter, accidental = nil)
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.5"
3
3
  end
data/lib/head_music.rb CHANGED
@@ -8,9 +8,12 @@ require "head_music/circle"
8
8
  require "head_music/interval"
9
9
  require "head_music/key_signature"
10
10
  require "head_music/letter"
11
+ require "head_music/octave"
11
12
  require "head_music/pitch_class"
12
13
  require "head_music/pitch"
14
+ require "head_music/quality"
13
15
  require "head_music/scale"
16
+ require "head_music/scale_type"
14
17
  require "head_music/spelling"
15
18
 
16
19
  module HeadMusic
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-17 00:00:00.000000000 Z
11
+ date: 2017-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -105,9 +105,12 @@ files:
105
105
  - lib/head_music/interval.rb
106
106
  - lib/head_music/key_signature.rb
107
107
  - lib/head_music/letter.rb
108
+ - lib/head_music/octave.rb
108
109
  - lib/head_music/pitch.rb
109
110
  - lib/head_music/pitch_class.rb
111
+ - lib/head_music/quality.rb
110
112
  - lib/head_music/scale.rb
113
+ - lib/head_music/scale_type.rb
111
114
  - lib/head_music/spelling.rb
112
115
  - lib/head_music/version.rb
113
116
  homepage: https://github.com/roberthead/head_music