head_music 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c74af27a5ca661511c85f7cb4bc516a8b9dac3ad
4
- data.tar.gz: aa5a6603255d9a06c2233acc03584e6765641d24
3
+ metadata.gz: 56a211e11bb35239af47cdf2e79e2032b6b97f7b
4
+ data.tar.gz: 293be36552913da8cd64f946b4b1ece210114991
5
5
  SHA512:
6
- metadata.gz: fef4a15562ef45e51da6ed0b132cfe7d3da2db38717b164dd638a0a89c61ad5d67bfdf42b8ca5d91cd37b4e7b097200399ab5e6feb0fcfe611053b4b1fbf9b98
7
- data.tar.gz: 5373d9a300e8fbcd3ba92002655a1fc5d6d70839947b2b76830421f319a39019d7858fa35d11ee7ef1e77563af375e17903242f54cb0cf608bf33d8d20083a7f
6
+ metadata.gz: d809adbad72a56eca2910a2c234d6497028d3c0a9723bfd02a214cf62f22e44777c38ff08a20707beff1d65e59cd9b5e8ddacacce82d2d64b9b826c48a5c3329
7
+ data.tar.gz: a2aa89d2b462c9a8dd938ddef98376a78a4a1a2ba7dd721e2f4e39913f5074c53d201465ae1ba0e59e11d6706e4905cb74434bb596b892b903dc298604e2ba06
@@ -1,9 +1,10 @@
1
1
  class HeadMusic::Composition
2
- attr_reader :name, :key_signature, :meter, :measures
2
+ attr_reader :name, :key_signature, :meter, :measures, :voices
3
3
 
4
4
  def initialize(name:, key_signature: nil, meter: nil)
5
5
  ensure_attributes(name, key_signature, meter)
6
6
  add_measure
7
+ add_voice
7
8
  end
8
9
 
9
10
  def add_measure
@@ -17,6 +18,11 @@ class HeadMusic::Composition
17
18
  end
18
19
  end
19
20
 
21
+ def add_voice
22
+ @voices ||= []
23
+ @voices << HeadMusic::Voice.new(self)
24
+ end
25
+
20
26
  private
21
27
 
22
28
  def ensure_attributes(name, key_signature, meter)
@@ -1,6 +1,7 @@
1
1
  class HeadMusic::KeySignature
2
2
  attr_reader :tonic_spelling
3
- attr_reader :quality_name
3
+ attr_reader :scale_type
4
+ attr_reader :scale
4
5
 
5
6
  SHARPS = %w{F# C# G# D# A# E# B#}
6
7
  FLATS = %w{Bb Eb Ab Db Gb Cb Fb}
@@ -12,65 +13,56 @@ class HeadMusic::KeySignature
12
13
  def self.get(identifier)
13
14
  return identifier if identifier.is_a?(HeadMusic::KeySignature)
14
15
  @key_signatures ||= {}
15
- tonic_spelling, quality_name = identifier.split(/\s/)
16
+ tonic_spelling, scale_type_name = identifier.strip.split(/\s/)
16
17
  hash_key = HeadMusic::Utilities::HashKey.for(identifier)
17
- @key_signatures[hash_key] ||= new(tonic_spelling, quality_name)
18
+ @key_signatures[hash_key] ||= new(tonic_spelling, scale_type_name)
18
19
  end
19
20
 
20
21
  delegate :pitch_class, to: :tonic_spelling, prefix: :tonic
21
22
  delegate :to_s, to: :name
22
-
23
- def initialize(tonic_spelling, quality_name = nil)
24
- @tonic_spelling = tonic_spelling
25
- @quality_name = quality_name || :major
23
+ delegate :pitches, to: :scale
24
+
25
+ def initialize(tonic_spelling, scale_type = nil)
26
+ @tonic_spelling = HeadMusic::Spelling.get(tonic_spelling)
27
+ @scale_type = HeadMusic::ScaleType.get(scale_type) if scale_type
28
+ @scale_type ||= HeadMusic::ScaleType.default
29
+ @scale_type = @scale_type.parent || @scale_type
30
+ @scale = HeadMusic::Scale.get(@tonic_spelling, @scale_type)
26
31
  end
27
32
 
28
33
  def sharps
29
- SHARPS.first(num_sharps)
34
+ pitches.map(&:spelling).uniq.select { |spelling|
35
+ spelling.sharp?
36
+ }.map(&:to_s).sort_by { |sharp|
37
+ SHARPS.index(sharp)
38
+ }
30
39
  end
31
40
 
32
41
  def flats
33
- FLATS.first(num_flats)
42
+ pitches.map(&:spelling).uniq.select { |spelling|
43
+ spelling.flat?
44
+ }.map(&:to_s).sort_by { |flat|
45
+ FLATS.index(flat)
46
+ }
34
47
  end
35
48
 
36
49
  def num_sharps
37
- (HeadMusic::Circle.of_fifths.index(tonic_pitch_class) - quality_name_adjustment) % 12
50
+ sharps.length
38
51
  end
39
52
 
40
53
  def num_flats
41
- (HeadMusic::Circle.of_fourths.index(tonic_pitch_class) + quality_name_adjustment) % 12
54
+ flats.length
42
55
  end
43
56
 
44
57
  def sharps_or_flats
45
- return sharps if @tonic_spelling.to_s =~ /#/
46
- return flats if @tonic_spelling.to_s =~ /b/
47
- num_sharps <= num_flats ? sharps : flats
58
+ flats.length > 0 ? flats : sharps
48
59
  end
49
60
 
50
61
  def name
51
- [tonic_spelling.to_s, quality_name.to_s].join(' ')
62
+ [tonic_spelling.to_s, scale_type.to_s].join(' ')
52
63
  end
53
64
 
54
65
  def ==(other)
55
- self.to_s == other.to_s
56
- end
57
-
58
- private
59
-
60
- def quality_name_adjustment
61
- quality_name == :minor ? 3 : 0
62
- end
63
-
64
- def major?
65
- @quality_name.to_sym == :major
66
- end
67
-
68
- def minor?
69
- @quality_name.to_sym == :minor
70
- end
71
-
72
- def relative_major_pitch_class
73
- return tonic_pitch_class if major?
74
- return (tonic_pitch_class.to_i + 3) % 12 if minor?
66
+ self.sharps_or_flats == self.class.get(other).sharps_or_flats
75
67
  end
76
68
  end
@@ -0,0 +1,39 @@
1
+ class HeadMusic::Placement
2
+ include Comparable
3
+
4
+ attr_reader :voice, :position, :rhythmic_value, :pitch
5
+ delegate :composition, to: :voice
6
+
7
+ def initialize(voice, position, rhythmic_value, pitch)
8
+ ensure_attributes(voice, position, rhythmic_value, pitch)
9
+ end
10
+
11
+ def note?
12
+ pitch
13
+ end
14
+
15
+ def rest?
16
+ !note?
17
+ end
18
+
19
+ def <=>(other)
20
+ self.position <=> other.position
21
+ end
22
+
23
+ private
24
+
25
+ def ensure_attributes(voice, position, rhythmic_value, pitch)
26
+ @voice = voice
27
+ ensure_position(position)
28
+ @rhythmic_value = rhythmic_value
29
+ @pitch = HeadMusic::Pitch.get(pitch)
30
+ end
31
+
32
+ def ensure_position(position)
33
+ if position.is_a?(HeadMusic::Position)
34
+ @position = position
35
+ else
36
+ @position = HeadMusic::Position.new(composition, position)
37
+ end
38
+ end
39
+ end
@@ -1,4 +1,6 @@
1
1
  class HeadMusic::Position
2
+ include Comparable
3
+
2
4
  attr_reader :composition, :measure_number, :count, :tick
3
5
  delegate :to_s, to: :code
4
6
  delegate :meter, to: :composition
@@ -12,15 +14,31 @@ class HeadMusic::Position
12
14
  end
13
15
 
14
16
  def code
15
- [measure_number, count, tick].join(':')
17
+ values.join(':')
16
18
  end
17
19
 
18
20
  def state
19
21
  [composition.name, code].join(' ')
20
22
  end
21
23
 
22
- def ==(other)
23
- self.state == other.state
24
+ def values
25
+ [measure_number, count, tick]
26
+ end
27
+
28
+ def <=>(other)
29
+ self.values <=> other.values
30
+ end
31
+
32
+ def strength
33
+ meter.beat_strength(count, tick: tick)
34
+ end
35
+
36
+ def strong?
37
+ strength >= 80
38
+ end
39
+
40
+ def weak?
41
+ !strong?
24
42
  end
25
43
 
26
44
  private
@@ -0,0 +1,3 @@
1
+ module HeadMusic::Rhythm
2
+ PPQN = PULSES_PER_QUARTER_NOTE = 960
3
+ end
@@ -5,8 +5,6 @@ class HeadMusic::RhythmicUnit
5
5
  BRITISH_MULTIPLE_NAMES = %w[semibreve breve longa maxima]
6
6
  BRITISH_DIVISION_NAMES = %w[semibreve minim crotchet quaver semiquaver demisemiquaver hemidemisemiquaver semihemidemisemiquaver demisemihemidemisemiquaver]
7
7
 
8
- PPQN = PULSES_PER_QUARTER_NOTE = 960
9
-
10
8
  def self.get(name)
11
9
  @rhythmic_units ||= {}
12
10
  hash_key = HeadMusic::Utilities::HashKey.for(name)
@@ -32,7 +30,7 @@ class HeadMusic::RhythmicUnit
32
30
  end
33
31
 
34
32
  def ticks
35
- PPQN * 4 * relative_value
33
+ HeadMusic::Rhythm::PPQN * 4 * relative_value
36
34
  end
37
35
 
38
36
  def notehead
@@ -27,7 +27,7 @@ class HeadMusic::RhythmicValue
27
27
  end
28
28
 
29
29
  def ticks
30
- HeadMusic::RhythmicUnit::PPQN * 4 * total_value
30
+ HeadMusic::Rhythm::PPQN * 4 * total_value
31
31
  end
32
32
 
33
33
  def per_whole
@@ -1,10 +1,16 @@
1
1
  class HeadMusic::Scale
2
- def self.get(root_pitch, scale_type_name = nil)
2
+ SCALE_REGEX = /^[A-G][#b]?\s+\w+$/
3
+
4
+ def self.get(root_pitch, scale_type = nil)
5
+ if root_pitch.is_a?(String) && scale_type =~ SCALE_REGEX
6
+ root_pitch, scale_type = root_pitch.split(/\s+/)
7
+ end
3
8
  root_pitch = HeadMusic::Pitch.get(root_pitch)
4
- scale_type ||= HeadMusic::ScaleType.get(scale_type_name || :major)
9
+ scale_type = HeadMusic::ScaleType.get(scale_type || :major)
5
10
  @scales ||= {}
6
- @scales[root_pitch.to_s] ||= {}
7
- @scales[root_pitch.to_s][scale_type.name] ||= new(root_pitch, scale_type)
11
+ name = [root_pitch.to_s, scale_type].join(' ')
12
+ hash_key = HeadMusic::Utilities::HashKey.for(name)
13
+ @scales[hash_key] ||= new(root_pitch, scale_type)
8
14
  end
9
15
 
10
16
  attr_reader :root_pitch, :scale_type
@@ -66,6 +66,10 @@ class HeadMusic::ScaleType
66
66
  end
67
67
  singleton_class.send(:alias_method, :[], :get)
68
68
 
69
+ def self.default
70
+ get(:major)
71
+ end
72
+
69
73
  attr_reader :name, :ascending_intervals, :descending_intervals, :parent_name
70
74
  alias_method :intervals, :ascending_intervals
71
75
 
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -0,0 +1,23 @@
1
+ class HeadMusic::Voice
2
+ include Comparable
3
+
4
+ attr_reader :composition, :placements
5
+
6
+ def initialize(composition = nil)
7
+ @composition = composition || Composition.new(name: "Unnamed")
8
+ @placements = []
9
+ end
10
+
11
+ def place(position, rhythmic_value, pitch = nil)
12
+ HeadMusic::Placement.new(self, position, rhythmic_value, pitch).tap { |placement|
13
+ insert_into_placements(placement)
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def insert_into_placements(placement)
20
+ @placements << placement
21
+ @placements = @placements.sort
22
+ end
23
+ end
data/lib/head_music.rb CHANGED
@@ -21,14 +21,17 @@ require 'head_music/note'
21
21
  require 'head_music/octave'
22
22
  require 'head_music/pitch_class'
23
23
  require 'head_music/pitch'
24
+ require 'head_music/placement'
24
25
  require 'head_music/position'
25
26
  require 'head_music/quality'
27
+ require 'head_music/rhythm'
26
28
  require 'head_music/rhythmic_unit'
27
29
  require 'head_music/rhythmic_value'
28
30
  require 'head_music/scale'
29
31
  require 'head_music/scale_type'
30
32
  require 'head_music/spelling'
31
33
  require 'head_music/staff'
34
+ require 'head_music/voice'
32
35
  require 'head_music/utilities/hash_key'
33
36
 
34
37
  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.7.0
4
+ version: 0.8.0
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-03-03 00:00:00.000000000 Z
11
+ date: 2017-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -133,8 +133,10 @@ files:
133
133
  - lib/head_music/octave.rb
134
134
  - lib/head_music/pitch.rb
135
135
  - lib/head_music/pitch_class.rb
136
+ - lib/head_music/placement.rb
136
137
  - lib/head_music/position.rb
137
138
  - lib/head_music/quality.rb
139
+ - lib/head_music/rhythm.rb
138
140
  - lib/head_music/rhythmic_unit.rb
139
141
  - lib/head_music/rhythmic_value.rb
140
142
  - lib/head_music/scale.rb
@@ -143,6 +145,7 @@ files:
143
145
  - lib/head_music/staff.rb
144
146
  - lib/head_music/utilities/hash_key.rb
145
147
  - lib/head_music/version.rb
148
+ - lib/head_music/voice.rb
146
149
  homepage: https://github.com/roberthead/head_music
147
150
  licenses:
148
151
  - MIT