musicality 0.8.0 → 0.9.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.
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