musicality 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +27 -1
  3. data/README.md +153 -10
  4. data/bin/collidify +102 -0
  5. data/bin/lilify +57 -29
  6. data/bin/midify +64 -24
  7. data/bin/musicality +39 -0
  8. data/examples/composition/auto_counterpoint.rb +4 -5
  9. data/examples/composition/part_generator.rb +8 -2
  10. data/examples/composition/scale_exercise.rb +1 -1
  11. data/examples/notation/notes.rb +27 -0
  12. data/examples/notation/parts.rb +51 -0
  13. data/examples/notation/scores.rb +38 -0
  14. data/examples/notation/twinkle.rb +34 -0
  15. data/examples/notation/twinkle.score +33 -0
  16. data/lib/musicality.rb +46 -11
  17. data/lib/musicality/composition/dsl/score_dsl.rb +2 -2
  18. data/lib/musicality/composition/dsl/score_methods.rb +10 -7
  19. data/lib/musicality/notation/conversion/change_conversion.rb +1 -1
  20. data/lib/musicality/notation/conversion/note_time_converter.rb +6 -23
  21. data/lib/musicality/notation/conversion/score_conversion.rb +15 -15
  22. data/lib/musicality/notation/conversion/score_converter.rb +50 -67
  23. data/lib/musicality/notation/model/articulations.rb +3 -2
  24. data/lib/musicality/notation/model/change.rb +15 -6
  25. data/lib/musicality/notation/model/dynamics.rb +11 -8
  26. data/lib/musicality/notation/model/instrument.rb +61 -0
  27. data/lib/musicality/notation/model/instruments.rb +111 -0
  28. data/lib/musicality/notation/model/key.rb +137 -0
  29. data/lib/musicality/notation/model/keys.rb +37 -0
  30. data/lib/musicality/notation/model/link.rb +6 -19
  31. data/lib/musicality/notation/model/mark.rb +43 -0
  32. data/lib/musicality/notation/model/marks.rb +11 -0
  33. data/lib/musicality/notation/model/meter.rb +4 -0
  34. data/lib/musicality/notation/model/note.rb +42 -28
  35. data/lib/musicality/notation/model/part.rb +18 -5
  36. data/lib/musicality/notation/model/pitch.rb +13 -4
  37. data/lib/musicality/notation/model/score.rb +104 -66
  38. data/lib/musicality/notation/model/symbols.rb +16 -11
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +38 -38
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +14 -14
  41. data/lib/musicality/notation/parsing/link_parsing.rb +6 -6
  42. data/lib/musicality/notation/parsing/link_parsing.treetop +3 -3
  43. data/lib/musicality/notation/parsing/mark_parsing.rb +138 -0
  44. data/lib/musicality/notation/parsing/mark_parsing.treetop +31 -0
  45. data/lib/musicality/notation/parsing/note_node.rb +19 -12
  46. data/lib/musicality/notation/parsing/note_parsing.rb +218 -87
  47. data/lib/musicality/notation/parsing/note_parsing.treetop +9 -5
  48. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +7 -2
  49. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +1 -1
  50. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +6 -4
  51. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +1 -1
  52. data/lib/musicality/notation/util/function.rb +41 -18
  53. data/lib/musicality/packable.rb +156 -0
  54. data/lib/musicality/performance/conversion/glissando_converter.rb +2 -2
  55. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +223 -70
  56. data/lib/musicality/performance/conversion/portamento_converter.rb +5 -2
  57. data/lib/musicality/performance/conversion/score_collator.rb +70 -64
  58. data/lib/musicality/performance/midi/midi_events.rb +3 -3
  59. data/lib/musicality/performance/midi/midi_settings.rb +127 -0
  60. data/lib/musicality/performance/midi/midi_util.rb +8 -2
  61. data/lib/musicality/performance/midi/part_sequencer.rb +19 -18
  62. data/lib/musicality/performance/midi/score_sequencer.rb +13 -9
  63. data/lib/musicality/performance/midi/score_sequencing.rb +5 -5
  64. data/lib/musicality/performance/model/attack.rb +8 -0
  65. data/lib/musicality/performance/model/duration_functions.rb +23 -0
  66. data/lib/musicality/performance/model/note_sequence.rb +52 -95
  67. data/lib/musicality/performance/model/separation.rb +10 -0
  68. data/lib/musicality/performance/supercollider/add_actions.rb +13 -0
  69. data/lib/musicality/performance/supercollider/bundle.rb +18 -0
  70. data/lib/musicality/performance/supercollider/conductor.rb +125 -0
  71. data/lib/musicality/performance/supercollider/group.rb +71 -0
  72. data/lib/musicality/performance/supercollider/message.rb +26 -0
  73. data/lib/musicality/performance/supercollider/node.rb +122 -0
  74. data/lib/musicality/performance/supercollider/performer.rb +123 -0
  75. data/lib/musicality/performance/supercollider/score_conducting.rb +17 -0
  76. data/lib/musicality/performance/supercollider/server.rb +8 -0
  77. data/lib/musicality/performance/supercollider/synth.rb +43 -0
  78. data/lib/musicality/performance/supercollider/synthdef.rb +57 -0
  79. data/lib/musicality/performance/supercollider/synthdef_settings.rb +23 -0
  80. data/lib/musicality/performance/supercollider/synthdefs.rb +1654 -0
  81. data/lib/musicality/{composition/model/pitch_class.rb → pitch_class.rb} +1 -1
  82. data/lib/musicality/{composition/model/pitch_classes.rb → pitch_classes.rb} +9 -9
  83. data/lib/musicality/printing/lilypond/clef.rb +12 -0
  84. data/lib/musicality/printing/lilypond/key_engraving.rb +9 -0
  85. data/lib/musicality/printing/lilypond/lilypond_settings.rb +105 -0
  86. data/lib/musicality/printing/lilypond/meter_engraving.rb +1 -1
  87. data/lib/musicality/printing/lilypond/note_engraving.rb +112 -30
  88. data/lib/musicality/printing/lilypond/part_engraver.rb +114 -3
  89. data/lib/musicality/printing/lilypond/pitch_class_engraving.rb +22 -0
  90. data/lib/musicality/printing/lilypond/pitch_engraving.rb +2 -15
  91. data/lib/musicality/printing/lilypond/score_engraver.rb +44 -73
  92. data/lib/musicality/printing/lilypond/score_engraving.rb +3 -3
  93. data/lib/musicality/project/create_tasks.rb +31 -0
  94. data/lib/musicality/project/file_cleaner.rb +19 -0
  95. data/lib/musicality/project/file_raker.rb +107 -0
  96. data/lib/musicality/project/load_config.rb +43 -0
  97. data/lib/musicality/project/project.rb +64 -0
  98. data/lib/musicality/version.rb +1 -1
  99. data/musicality.gemspec +3 -0
  100. data/spec/composition/util/random_sampler_spec.rb +1 -1
  101. data/spec/notation/conversion/measure_note_map_spec.rb +1 -1
  102. data/spec/notation/conversion/note_time_converter_spec.rb +5 -85
  103. data/spec/notation/conversion/score_conversion_spec.rb +6 -41
  104. data/spec/notation/conversion/score_converter_spec.rb +19 -137
  105. data/spec/notation/model/change_spec.rb +55 -0
  106. data/spec/notation/model/key_spec.rb +171 -0
  107. data/spec/notation/model/link_spec.rb +34 -5
  108. data/spec/notation/model/meter_spec.rb +15 -0
  109. data/spec/notation/model/note_spec.rb +33 -27
  110. data/spec/notation/model/part_spec.rb +53 -4
  111. data/spec/notation/model/pitch_spec.rb +15 -0
  112. data/spec/notation/model/score_spec.rb +64 -72
  113. data/spec/notation/parsing/link_nodes_spec.rb +3 -3
  114. data/spec/notation/parsing/link_parsing_spec.rb +6 -6
  115. data/spec/notation/parsing/note_node_spec.rb +34 -9
  116. data/spec/notation/parsing/note_parsing_spec.rb +11 -9
  117. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +4 -0
  118. data/spec/notation/parsing/pitch_node_spec.rb +0 -1
  119. data/spec/notation/util/value_computer_spec.rb +2 -2
  120. data/spec/performance/conversion/glissando_converter_spec.rb +9 -9
  121. data/spec/performance/conversion/note_sequence_extractor_spec.rb +48 -53
  122. data/spec/performance/conversion/portamento_converter_spec.rb +11 -9
  123. data/spec/performance/conversion/score_collator_spec.rb +59 -63
  124. data/spec/performance/midi/midi_util_spec.rb +22 -8
  125. data/spec/performance/midi/part_sequencer_spec.rb +2 -2
  126. data/spec/performance/midi/score_sequencer_spec.rb +12 -10
  127. data/spec/performance/midi/score_sequencing_spec.rb +2 -2
  128. data/spec/performance/model/note_sequence_spec.rb +41 -134
  129. data/spec/printing/note_engraving_spec.rb +204 -0
  130. data/spec/printing/score_engraver_spec.rb +40 -0
  131. data/spec/spec_helper.rb +1 -0
  132. metadata +69 -23
  133. data/examples/notation/hip.rb +0 -32
  134. data/examples/notation/missed_connection.rb +0 -26
  135. data/examples/notation/song1.rb +0 -33
  136. data/examples/notation/song2.rb +0 -32
  137. data/lib/musicality/notation/model/links.rb +0 -11
  138. data/lib/musicality/notation/packing/change_packing.rb +0 -56
  139. data/lib/musicality/notation/packing/part_packing.rb +0 -31
  140. data/lib/musicality/notation/packing/score_packing.rb +0 -123
  141. data/lib/musicality/performance/model/note_attacks.rb +0 -19
  142. data/lib/musicality/performance/util/note_linker.rb +0 -28
  143. data/spec/notation/packing/change_packing_spec.rb +0 -304
  144. data/spec/notation/packing/part_packing_spec.rb +0 -66
  145. data/spec/notation/packing/score_packing_spec.rb +0 -255
  146. data/spec/performance/util/note_linker_spec.rb +0 -68
@@ -6,14 +6,22 @@ grammar Note
6
6
  include Articulation
7
7
  include Link
8
8
  include Duration
9
+ include Mark
9
10
 
10
11
  rule note
12
+ begin_marks:(
13
+ (first:begin_triplet second:begin_slur?) /
14
+ (first:begin_slur second:begin_triplet?)
15
+ )?
11
16
  duration
12
17
  more:(
13
18
  first_pl:pitch_link
14
19
  more_pl:("," pl:pitch_link)*
15
20
  art:articulation?
16
- acc:accent?
21
+ )?
22
+ end_marks:(
23
+ (first:end_triplet second:end_slur?) /
24
+ (first:end_slur second:end_triplet?)
17
25
  )?
18
26
  <NoteNode>
19
27
  end
@@ -21,10 +29,6 @@ grammar Note
21
29
  rule pitch_link
22
30
  pitch the_link:link?
23
31
  end
24
-
25
- rule accent
26
- "!"
27
- end
28
32
  end
29
33
 
30
34
  end
@@ -45,8 +45,13 @@ module NonnegativeInteger
45
45
  break
46
46
  end
47
47
  end
48
- r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
49
- r0.extend(NonnegativeInteger0)
48
+ if s0.empty?
49
+ @index = i0
50
+ r0 = nil
51
+ else
52
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
53
+ r0.extend(NonnegativeInteger0)
54
+ end
50
55
 
51
56
  node_cache[:nonnegative_integer][start_index] = r0
52
57
 
@@ -3,7 +3,7 @@ module Parsing
3
3
 
4
4
  grammar NonnegativeInteger
5
5
  rule nonnegative_integer
6
- [0-9]* {
6
+ [0-9]+ {
7
7
  def to_i
8
8
  text_value.to_i
9
9
  end
@@ -14,9 +14,6 @@ module PositiveInteger
14
14
  include NonnegativeInteger
15
15
 
16
16
  module PositiveInteger0
17
- def nonnegative_integer
18
- elements[2]
19
- end
20
17
  end
21
18
 
22
19
  module PositiveInteger1
@@ -66,7 +63,12 @@ module PositiveInteger
66
63
  end
67
64
  s0 << r3
68
65
  if r3
69
- r4 = _nt_nonnegative_integer
66
+ r5 = _nt_nonnegative_integer
67
+ if r5
68
+ r4 = r5
69
+ else
70
+ r4 = instantiate_node(SyntaxNode,input, index...index)
71
+ end
70
72
  s0 << r4
71
73
  end
72
74
  end
@@ -5,7 +5,7 @@ grammar PositiveInteger
5
5
  include NonnegativeInteger
6
6
 
7
7
  rule positive_integer
8
- [0]* [1-9] nonnegative_integer {
8
+ [0]* [1-9] nonnegative_integer? {
9
9
  def to_i
10
10
  text_value.to_i
11
11
  end
@@ -13,15 +13,41 @@ class Function
13
13
  return perc * (end_domain.last - end_domain.first) + end_domain.first
14
14
  end
15
15
 
16
+ attr_reader :domain
17
+ def initialize domain = (DOMAIN_MIN...DOMAIN_MAX), memoize: true, &at_block
18
+ raise ArgumentError unless domain.last > domain.first
19
+ @domain = domain
20
+ raise ArgumentError unless block_given?
21
+ raise ArgumentError unless at_block.arity == 1
22
+ @at_block = at_block
23
+
24
+ @memoize = memoize
25
+ @memoized = {}
26
+ end
27
+
28
+ def at(x)
29
+ raise DomainError unless @domain.include?(x)
30
+ if @memoize
31
+ if @memoized.has_key? x
32
+ @memoized[x]
33
+ else
34
+ @memoized[x] = @at_block.call(x)
35
+ end
36
+ else
37
+ @at_block.call(x)
38
+ end
39
+ end
40
+
41
+ def ==(other)
42
+ @domain == other.domain
43
+ end
44
+
16
45
  class Constant < Function
17
46
  attr_reader :value
18
47
 
19
48
  def initialize value
20
49
  @value = value
21
- end
22
-
23
- def at(x)
24
- @value
50
+ super() {|x| @value }
25
51
  end
26
52
 
27
53
  def ==(other)
@@ -35,10 +61,8 @@ class Function
35
61
  def initialize p1,p2
36
62
  @slope = (p2[1] - p1[1])/(p2[0] - p1[0]).to_f
37
63
  @intercept = p1[1] - @slope * p1[0]
38
- end
39
-
40
- def at(x)
41
- x * @slope + @intercept
64
+
65
+ super() {|x| x * @slope + @intercept }
42
66
  end
43
67
 
44
68
  def ==(other)
@@ -59,24 +83,23 @@ class Function
59
83
  SIGM_RANGE = Sigmoid.sigm(SIGM_DOMAIN.first)..Sigmoid.sigm(SIGM_DOMAIN.last)
60
84
  SIGM_SPAN = SIGM_RANGE.last - SIGM_RANGE.first
61
85
 
62
- attr_reader :y0, :dy, :domain
86
+ attr_reader :y0, :dy
63
87
  def initialize p0, p1
64
88
  @y0, y1 = p0[1], p1[1]
65
89
  @dy = y1 - @y0
66
- @domain = p0[0]..p1[0]
67
- end
68
-
69
- def at(x)
70
- x_ = Function.transform_domains(@domain, SIGM_DOMAIN, x)
71
- y_ = (Sigmoid.sigm(x_) - SIGM_RANGE.first) / SIGM_SPAN
72
- y = @y0 + y_ * @dy
73
- return y
90
+ @external_domain = p0[0]..p1[0]
91
+
92
+ super() do |x|
93
+ x_ = Function.transform_domains(@external_domain, SIGM_DOMAIN, x)
94
+ y_ = (Sigmoid.sigm(x_) - SIGM_RANGE.first) / SIGM_SPAN
95
+ @y0 + y_ * @dy
96
+ end
74
97
  end
75
98
 
76
99
  #def from(y)
77
100
  # y2 = (y - @y0) / @dy
78
101
  # x2 = Sigmoid.inv_sigm(y2 * SIGM_SPAN + SIGM_RANGE.first)
79
- # x = Function.transform_domains(SIGM_DOMAIN, @domain, x2)
102
+ # x = Function.transform_domains(SIGM_DOMAIN, @external_domain, x2)
80
103
  # return x
81
104
  #end
82
105
 
@@ -0,0 +1,156 @@
1
+ # Requires that an including class can be instantiated entirely by keyword
2
+ # args that map these symbols to values
3
+ module Packable
4
+ PACKED_CLASS_KEY = :packed_class
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ base.class_variable_set(:@@special_packing, {})
9
+ base.class_variable_set(:@@special_unpacking, {})
10
+ end
11
+
12
+ module ClassMethods
13
+ def unpack packing
14
+ args = []
15
+ kwargs = {}
16
+
17
+ init_params.each do |name,type|
18
+ if (type == :req || type == :keyreq) && !packing.has_key?(name)
19
+ raise ArgumentError, "Packing does not have required key #{name}"
20
+ end
21
+
22
+ val = packing[name]
23
+ if type == :keyrest
24
+ raise "Expected this to be a Hash" unless val.is_a? Hash
25
+ end
26
+
27
+ val2 = if unpacks_specially?(name)
28
+ unpack_specially(name,val)
29
+ else
30
+ Packable.unpack_val(val)
31
+ end
32
+
33
+ case type
34
+ when :req, :opt
35
+ args.push val2
36
+ when :key, :keyreq
37
+ kwargs[name] = val2
38
+ when :keyrest
39
+ kwargs.merge!(val2)
40
+ end
41
+ end
42
+
43
+ if args.any?
44
+ if kwargs.any?
45
+ obj = new(*args,**kwargs)
46
+ else
47
+ obj = new(*args)
48
+ end
49
+ elsif kwargs.any?
50
+ obj = new(**kwargs)
51
+ else
52
+ obj = new
53
+ end
54
+
55
+ block_given? ? yield(obj) : obj
56
+ end
57
+
58
+ def init_params
59
+ params = instance_method(:initialize).parameters
60
+ Hash[ params.map {|pair| pair.reverse } ]
61
+ end
62
+
63
+ def special_packing sym, &block
64
+ class_variable_get(:@@special_packing)[sym] = block
65
+ end
66
+
67
+ def special_unpacking sym, &block
68
+ class_variable_get(:@@special_unpacking)[sym] = block
69
+ end
70
+
71
+ def packs_specially? sym
72
+ class_variable_get(:@@special_packing).has_key?(sym)
73
+ end
74
+
75
+ def unpacks_specially? sym
76
+ class_variable_get(:@@special_unpacking).has_key?(sym)
77
+ end
78
+
79
+ def pack_specially sym, val
80
+ class_variable_get(:@@special_packing)[sym].call(val)
81
+ end
82
+
83
+ def unpack_specially sym, val
84
+ class_variable_get(:@@special_unpacking)[sym].call(val)
85
+ end
86
+ end
87
+
88
+ def init_params
89
+ self.class.init_params
90
+ end
91
+
92
+ def class_str
93
+ self.class.to_s
94
+ end
95
+
96
+ def pack
97
+ packing = { PACKED_CLASS_KEY => class_str }
98
+ init_params.keys.each do |name|
99
+ val = self.send(name)
100
+ val2 = if self.class.packs_specially?(name)
101
+ self.class.pack_specially(name,val)
102
+ else
103
+ Packable.pack_val(val)
104
+ end
105
+ packing[name] = val2
106
+ end
107
+ packing
108
+ end
109
+
110
+ def recover_class klass_str
111
+ Kernel.const_get(klass_str)
112
+ end
113
+ module_function :recover_class
114
+
115
+ private
116
+
117
+ def unpack_val val
118
+ if val.is_a? Array
119
+ val.map {|v| Packable.unpack_val v}
120
+ elsif val.is_a? Hash
121
+ if val.packed_class?
122
+ val.unpack
123
+ else
124
+ Hash[ val.map {|k,v| [ Packable.unpack_val(k), Packable.unpack_val(v) ]} ]
125
+ end
126
+ else
127
+ val
128
+ end
129
+ end
130
+ module_function :unpack_val
131
+
132
+ def pack_val val
133
+ if val.is_a?(Packable)
134
+ val.pack
135
+ elsif val.is_a? Array
136
+ val.map {|v| pack_val v}
137
+ elsif val.is_a? Hash
138
+ Hash[ val.map {|k,v| [ Packable.pack_val(k), Packable.pack_val(v) ]} ]
139
+ else
140
+ val
141
+ end
142
+ end
143
+ module_function :pack_val
144
+ end
145
+
146
+ class Hash
147
+ def packed_class?
148
+ has_key?(Packable::PACKED_CLASS_KEY) &&
149
+ Packable.recover_class(fetch(Packable::PACKED_CLASS_KEY)).included_modules.include?(Packable)
150
+ end
151
+
152
+ def unpack
153
+ raise "Not a packed class" unless packed_class?
154
+ Packable.recover_class(fetch(Packable::PACKED_CLASS_KEY)).unpack self
155
+ end
156
+ end
@@ -22,11 +22,11 @@ class GlissandoConverter
22
22
  end
23
23
  end
24
24
 
25
- def self.glissando_elements(start_pitch, target_pitch, duration, accented)
25
+ def self.glissando_elements(start_pitch, target_pitch, duration, attack)
26
26
  pitches = glissando_pitches(start_pitch, target_pitch)
27
27
  subdur = Rational(duration, pitches.size)
28
28
  pitches.map do |pitch|
29
- LegatoElement.new(subdur, pitch, accented)
29
+ NoteSequence::Element.new(subdur, pitch, attack)
30
30
  end
31
31
  end
32
32
  end
@@ -1,97 +1,250 @@
1
1
  module Musicality
2
2
 
3
3
  class NoteSequenceExtractor
4
- # For all link types:
5
- # - Remove link where source pitch is not in current note
6
- # For tie:
7
- # - Remove any bad tie (where the tying pitch does not exist in the next note).
8
- # - Replace any good tie with a slur.
9
- # For slur/legato:
10
- # - Remove any bad link (where the target pitch does not exist in the next note).
11
- # TODO: what to do about multiple links that target the same pitch?
12
- def self.fixup_links note, next_note
13
- note.links.each do |pitch, link|
14
- if !note.pitches.include?(pitch)
15
- note.links.delete pitch
16
- elsif link.is_a? Link::Tie
17
- if next_note.pitches.include? pitch
18
- note.links[pitch] = Link::Slur.new(pitch)
4
+ attr_reader :notes
5
+ def initialize notes
6
+ prepare_notes(notes)
7
+ mark_slurring
8
+ remove_bad_links
9
+ calculate_offsets
10
+ establish_maps
11
+ # now, ready to extract sequences!
12
+ end
13
+
14
+ def extract_sequences cents_per_step = 10
15
+ return [] if @notes.empty?
16
+
17
+ next_seqs = seqs_from_note(@notes.size-1, cents_per_step)
18
+ return next_seqs if @notes.one?
19
+
20
+ complete_seqs = []
21
+ (@notes.size-2).downto(0) do |i|
22
+ cur_seqs = seqs_from_note(i, cents_per_step)
23
+ map = @maps[i]
24
+
25
+ next_seqs.each do |next_seq|
26
+ p1 = nil
27
+ p2 = next_seq.elements.first.pitch
28
+ if p1 = map[:ties].key(p2)
29
+ cur_seq = cur_seqs.find {|x| x.elements.first.pitch == p1 }
30
+ NoteSequenceExtractor.tie_seqs(cur_seq, next_seq)
31
+ elsif p1 = map[:slurs].key(p2)
32
+ cur_seq = cur_seqs.find {|x| x.elements.first.pitch == p1 }
33
+ NoteSequenceExtractor.slur_seqs(cur_seq, next_seq)
34
+ elsif p1 = map[:full_glissandos].key(p2)
35
+ cur_seq = cur_seqs.find {|x| x.elements.first.pitch == p1 }
36
+ NoteSequenceExtractor.glissando_seqs(cur_seq, next_seq)
37
+ elsif p1 = map[:full_portamentos].key(p2)
38
+ cur_seq = cur_seqs.find {|x| x.elements.first.pitch == p1 }
39
+ NoteSequenceExtractor.portamento_seqs(cur_seq, next_seq, cents_per_step)
19
40
  else
20
- note.links.delete pitch
21
- end
22
- elsif (link.is_a?(Link::Slur) ||
23
- link.is_a?(Link::Legato))
24
- unless next_note.pitches.include? link.target_pitch
25
- note.links.delete pitch
41
+ complete_seqs.push next_seq
26
42
  end
43
+ end
44
+ next_seqs = cur_seqs
45
+ end
46
+ complete_seqs += next_seqs
47
+ return complete_seqs
48
+ end
49
+
50
+ private
51
+
52
+ def self.tie_seqs(cur_seq, next_seq)
53
+ cur_seq.elements.last.duration += next_seq.elements.first.duration
54
+ cur_seq.elements += next_seq.elements[1..-1]
55
+ end
56
+
57
+ def self.slur_seqs(cur_seq, next_seq)
58
+ if next_seq.elements.first.attack == Attack::NORMAL
59
+ next_seq.elements.first.attack = Attack::NONE
60
+ end
61
+ cur_seq.separation = next_seq.separation
62
+ cur_seq.elements += next_seq.elements
63
+ end
64
+
65
+ def self.glissando_seqs(cur_seq, next_seq)
66
+ cur_seq.elements = GlissandoConverter.glissando_elements(cur_seq.last_pitch,
67
+ next_seq.first_pitch, cur_seq.full_duration, cur_seq.last_attack)
68
+ cur_seq.separation = next_seq.separation
69
+ cur_seq.elements += next_seq.elements
70
+ end
71
+
72
+ def self.portamento_seqs(cur_seq, next_seq, cents_per_step)
73
+ cur_seq.elements = PortamentoConverter.portamento_elements(cur_seq.last_pitch,
74
+ next_seq.first_pitch, cents_per_step, cur_seq.full_duration, cur_seq.last_attack)
75
+ cur_seq.separation = Separation::NONE
76
+ cur_seq.elements += next_seq.elements
77
+ end
78
+
79
+ def seqs_from_note(idx, cents_per_step)
80
+ map = @maps[idx]
81
+ note = @notes[idx]
82
+ attack = NoteSequenceExtractor.note_attack(note.articulation)
83
+ separation = NoteSequenceExtractor.note_separation(note.articulation, @slurring_flags[idx])
84
+ offset = @offsets[idx]
85
+ note.pitches.map do |p|
86
+ if map[:half_glissandos].has_key?(p)
87
+ NoteSequence.new(offset, separation,
88
+ GlissandoConverter.glissando_elements(
89
+ p, map[:half_glissandos][p], note.duration, attack))
90
+ elsif map[:half_portamentos].has_key?(p)
91
+ NoteSequence.new(offset, Separation::NONE,
92
+ PortamentoConverter.portamento_elements(
93
+ p, map[:half_portamentos][p], cents_per_step, note.duration, attack))
94
+ else
95
+ NoteSequence.new(offset, separation,
96
+ [NoteSequence::Element.new(note.duration, p, attack)])
27
97
  end
28
98
  end
29
99
  end
30
100
 
31
- def self.replace_articulation note, next_note
32
- case note.articulation
33
- when Articulations::SLUR
34
- NoteLinker.fully_link(note, next_note, Link::Slur)
35
- note.articulation = Articulations::NORMAL
36
- when Articulations::LEGATO
37
- NoteLinker.fully_link(note, next_note, Link::Legato)
38
- note.articulation = Articulations::NORMAL
101
+ def self.note_attack articulation
102
+ case articulation
103
+ when Articulations::NORMAL then Attack::NORMAL
104
+ when Articulations::TENUTO then Attack::TENUTO
105
+ when Articulations::ACCENT then Attack::ACCENT
106
+ when Articulations::MARCATO then Attack::ACCENT
107
+ when Articulations::PORTATO then Attack::NORMAL
108
+ when Articulations::STACCATO then Attack::NORMAL
109
+ when Articulations::STACCATISSIMO then Attack::NORMAL
39
110
  end
40
111
  end
41
112
 
42
- attr_reader :notes
43
- def initialize notes, cents_per_step = 10
44
- @cents_per_step = cents_per_step
45
- @notes = notes.map {|n| n.clone }
113
+ def self.note_separation articulation, under_slur
114
+ if under_slur
115
+ case articulation
116
+ when Articulations::NORMAL then Separation::NONE
117
+ when Articulations::TENUTO then Separation::NONE
118
+ when Articulations::ACCENT then Separation::NONE
119
+ when Articulations::MARCATO then Separation::PORTATO
120
+ when Articulations::PORTATO then Separation::NORMAL
121
+ when Articulations::STACCATO then Separation::PORTATO
122
+ when Articulations::STACCATISSIMO then Separation::STACCATO
123
+ end
124
+ else
125
+ case articulation
126
+ when Articulations::NORMAL then Separation::NORMAL
127
+ when Articulations::TENUTO then Separation::TENUTO
128
+ when Articulations::ACCENT then Separation::NORMAL
129
+ when Articulations::MARCATO then Separation::NORMAL
130
+ when Articulations::PORTATO then Separation::PORTATO
131
+ when Articulations::STACCATO then Separation::STACCATO
132
+ when Articulations::STACCATISSIMO then Separation::STACCATISSIMO
133
+ end
134
+ end
135
+ end
136
+
137
+ def prepare_notes notes
138
+ in_triplet = false
139
+ @notes = Array.new(notes.size) do |i|
140
+ note = notes[i]
141
+
142
+ if note.begins_triplet?
143
+ in_triplet = true
144
+ end
145
+
146
+ new_note = in_triplet ? note.resize(note.duration * Rational(2,3)) : note.clone
147
+
148
+ if note.ends_triplet?
149
+ in_triplet = false
150
+ end
46
151
 
47
- @notes.push Note.quarter
48
- (@notes.size-1).times do |i|
49
- NoteSequenceExtractor.fixup_links(@notes[i], @notes[i+1])
50
- NoteSequenceExtractor.replace_articulation(@notes[i], @notes[i+1])
152
+ new_note
51
153
  end
52
- @notes.pop
53
154
  end
54
155
 
55
- def extract_sequences
56
- sequences = []
57
- offset = 0
156
+ def mark_slurring
157
+ @slurring_flags = []
158
+ under_slur = false
159
+
160
+ @slurring_flags = Array.new(@notes.size) do |i|
161
+ note = @notes[i]
58
162
 
59
- @notes.each_index do |i|
60
- while @notes[i].pitches.any?
61
- elements = []
163
+ if note.begins_slur? && !note.ends_slur?
164
+ under_slur = true
165
+ end
166
+ flag = under_slur
62
167
 
63
- j = i
64
- loop do
65
- note = @notes[j]
66
- pitch = note.pitches.pop
67
- dur = note.duration
68
- accented = note.accented
69
- link = note.links[pitch]
168
+ if note.ends_slur? && !note.begins_slur?
169
+ under_slur = false
170
+ end
171
+ flag
172
+ end
173
+ end
70
174
 
71
- case link
72
- when Link::Slur
73
- elements.push(SlurredElement.new(dur, pitch, accented))
74
- when Link::Legato
75
- elements.push(LegatoElement.new(dur, pitch, accented))
76
- when Link::Glissando
77
- elements += GlissandoConverter.glissando_elements(pitch, link.target_pitch, dur, accented)
78
- when Link::Portamento
79
- elements += PortamentoConverter.portamento_elements(pitch, link.target_pitch, @cents_per_step, dur, accented)
175
+ def remove_bad_links
176
+ @notes.each_with_index do |n,i|
177
+ # create a dummy note (with no pitches) for checking links from the last note
178
+ n2 = (i == (@notes.size-1)) ? Note.quarter : @notes[i+1]
179
+ n.links.delete_if do |p,l|
180
+ !n.pitches.include?(p) || (l.is_a?(Link::Tie) && !n2.pitches.include?(p))
181
+ end
182
+ end
183
+ end
184
+
185
+ def calculate_offsets
186
+ offset = 0.to_r
187
+ @offsets = Array.new(@notes.size) do |i|
188
+ cur_offset = offset
189
+ offset += @notes[i].duration
190
+ cur_offset
191
+ end
192
+ end
193
+
194
+ def self.no_separation?(articulation, under_slur)
195
+ under_slur && (
196
+ articulation == Articulations::NORMAL ||
197
+ articulation == Articulations::TENUTO ||
198
+ articulation == Articulations::ACCENT
199
+ )
200
+ end
201
+
202
+ def establish_maps
203
+ @maps = []
204
+
205
+ @notes.each_index do |i|
206
+ map = { :ties => {}, :slurs => {}, :full_glissandos => {},
207
+ :full_portamentos => {}, :half_glissandos => {},
208
+ :half_portamentos => {}}
209
+ note = @notes[i]
210
+
211
+ # Create a dummy note (with no pitches) for the last note to "link" to.
212
+ # This will allow half glissandos and half portamentos from the last note
213
+ next_note = (i == (@notes.size-1)) ? Note.quarter : @notes[i+1]
214
+
215
+ no_separation = NoteSequenceExtractor.no_separation?(note.articulation, @slurring_flags[i])
216
+
217
+ linked = note.pitches & note.links.keys
218
+ linked.each do |p|
219
+ l = note.links[p]
220
+ if l.is_a?(Link::Tie)
221
+ map[:ties][p] = p
222
+ elsif l.is_a?(Link::Glissando)
223
+ if next_note.pitches.include?(l.target_pitch)
224
+ map[:full_glissandos][p] = l.target_pitch
80
225
  else
81
- elements.push(FinalElement.new(dur, pitch, accented, note.articulation))
82
- break
226
+ map[:half_glissandos][p] = l.target_pitch
227
+ end
228
+ elsif l.is_a?(Link::Portamento)
229
+ if next_note.pitches.include?(l.target_pitch)
230
+ map[:full_portamentos][p] = l.target_pitch
231
+ else
232
+ map[:half_portamentos][p] = l.target_pitch
83
233
  end
84
-
85
- j += 1
86
- break if j >= @notes.size || !@notes[j].pitches.include?(link.target_pitch)
87
234
  end
88
-
89
- sequences.push(NoteSequence.from_elements(offset,elements))
90
235
  end
91
- offset += @notes[i].duration
236
+
237
+ if(no_separation)
238
+ unlinked = note.pitches - linked
239
+ target_pitches = note.links.map {|p,l| l.is_a?(Link::Tie) ? p : l.target_pitch }
240
+ untargeted = next_note.pitches - target_pitches
241
+ Optimization.linking(unlinked, untargeted).each do |pitch,tgt_pitch|
242
+ map[:slurs][pitch] = tgt_pitch
243
+ end
244
+ end
245
+
246
+ @maps.push map
92
247
  end
93
-
94
- return sequences
95
248
  end
96
249
  end
97
250