lydown 0.7.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -14
  3. data/bin/lydown +1 -122
  4. data/lib/lydown.rb +3 -3
  5. data/lib/lydown/cli.rb +4 -0
  6. data/lib/lydown/cli/commands.rb +98 -0
  7. data/lib/lydown/cli/compiler.rb +11 -14
  8. data/lib/lydown/cli/diff.rb +3 -3
  9. data/lib/lydown/cli/output.rb +18 -0
  10. data/lib/lydown/cli/proofing.rb +8 -6
  11. data/lib/lydown/cli/support.rb +34 -0
  12. data/lib/lydown/cli/translation.rb +73 -0
  13. data/lib/lydown/core_ext.rb +1 -1
  14. data/lib/lydown/lilypond.rb +49 -11
  15. data/lib/lydown/parsing.rb +37 -6
  16. data/lib/lydown/parsing/nodes.rb +57 -56
  17. data/lib/lydown/rendering.rb +0 -9
  18. data/lib/lydown/rendering/base.rb +2 -2
  19. data/lib/lydown/rendering/command.rb +2 -2
  20. data/lib/lydown/rendering/comments.rb +1 -1
  21. data/lib/lydown/rendering/figures.rb +14 -14
  22. data/lib/lydown/rendering/lib.ly +22 -0
  23. data/lib/lydown/rendering/lyrics.rb +2 -2
  24. data/lib/lydown/rendering/movement.rb +2 -2
  25. data/lib/lydown/rendering/music.rb +46 -46
  26. data/lib/lydown/rendering/notes.rb +149 -70
  27. data/lib/lydown/rendering/settings.rb +21 -16
  28. data/lib/lydown/rendering/skipping.rb +1 -1
  29. data/lib/lydown/rendering/source_ref.rb +4 -4
  30. data/lib/lydown/rendering/staff.rb +8 -4
  31. data/lib/lydown/rendering/voices.rb +9 -9
  32. data/lib/lydown/templates.rb +9 -0
  33. data/lib/lydown/templates/lilypond_doc.erb +1 -1
  34. data/lib/lydown/templates/movement.erb +9 -1
  35. data/lib/lydown/templates/part.erb +12 -3
  36. data/lib/lydown/translation.rb +17 -0
  37. data/lib/lydown/translation/ripple.rb +30 -0
  38. data/lib/lydown/translation/ripple/nodes.rb +191 -0
  39. data/lib/lydown/translation/ripple/ripple.treetop +100 -0
  40. data/lib/lydown/version.rb +1 -1
  41. data/lib/lydown/work.rb +144 -198
  42. data/lib/lydown/work_context.rb +152 -0
  43. metadata +11 -2
@@ -4,7 +4,8 @@ module Lydown::Rendering
4
4
 
5
5
  SETTING_KEYS = [
6
6
  'key', 'time', 'pickup', 'clef', 'part', 'movement', 'tempo',
7
- 'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves'
7
+ 'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves',
8
+ 'midi_tempo'
8
9
  ]
9
10
 
10
11
  RENDERABLE_SETTING_KEYS = [
@@ -19,34 +20,38 @@ module Lydown::Rendering
19
20
 
20
21
  def translate
21
22
  # if setting while doing a macro, insert it into the current macro group
22
- if @work['process/duration_macro'] && @event[:raw]
23
+ if @context['process/duration_macro'] && @event[:raw]
23
24
  return add_macro_event(@event[:raw])
24
25
  end
25
26
 
26
27
  key = @event[:key]
27
28
  value = @event[:value]
28
29
  level = @event[:level] || 0
29
-
30
+
30
31
  unless (level > 0) || SETTING_KEYS.include?(key)
31
32
  raise LydownError, "Invalid setting (#{key})"
32
33
  end
33
34
 
34
35
  if level == 0
35
36
  value = check_setting_value(key, value)
36
- @work[key] = value
37
+ @context[key] = value
37
38
  case key
38
39
  when 'part'
39
- @work.set_part_context(value)
40
+ @context.set_part_context(value)
40
41
 
41
42
  # when changing parts we repeat the last set time and key signature
42
- render_setting('time', @work[:time]) unless @work[:time] == '4/4'
43
+ render_setting('time', @context[:time]) unless @context[:time] == '4/4'
43
44
 
44
- key = @work[:key]
45
+ key = @context[:key]
45
46
  render_setting('key', key) unless key == 'c major'
46
47
 
47
- @work.reset_context(:part)
48
+ @context.reset(:part)
48
49
  when 'movement'
49
- @work.reset_context(:movement)
50
+ @context.reset(:movement)
51
+ when 'midi_tempo'
52
+ movement = @context[:movement]
53
+ path = "movements/#{movement}/settings/midi_tempo"
54
+ @context[path] = value
50
55
  end
51
56
 
52
57
  if RENDERABLE_SETTING_KEYS.include?(key)
@@ -56,14 +61,14 @@ module Lydown::Rendering
56
61
  # nested settings
57
62
  l, path = 0, ''
58
63
  while l < level
59
- path << "#{@work['process/setting_levels'][l]}/"; l += 1
64
+ path << "#{@context['process/setting_levels'][l]}/"; l += 1
60
65
  end
61
66
  path << key
62
- @work[path] = value
67
+ @context[path] = value
63
68
  end
64
69
 
65
- @work['process/setting_levels'] ||= {}
66
- @work['process/setting_levels'][level] = key
70
+ @context['process/setting_levels'] ||= {}
71
+ @context['process/setting_levels'][level] = key
67
72
  end
68
73
 
69
74
  def check_setting_value(key, value)
@@ -85,9 +90,9 @@ module Lydown::Rendering
85
90
  setting = ""
86
91
  case key
87
92
  when 'time'
88
- cadenza_mode = @work[:cadenza_mode]
93
+ cadenza_mode = @context[:cadenza_mode]
89
94
  should_cadence = value == 'unmetered'
90
- @work[:cadenza_mode] = should_cadence
95
+ @context[:cadenza_mode] = should_cadence
91
96
 
92
97
  if should_cadence && !cadenza_mode
93
98
  setting = "\\cadenzaOn "
@@ -119,7 +124,7 @@ module Lydown::Rendering
119
124
  setting = "\\#{key} #{value} "
120
125
  end
121
126
 
122
- @work.emit(:music, setting)
127
+ @context.emit(:music, setting)
123
128
  end
124
129
  end
125
130
  end
@@ -37,7 +37,7 @@ module Lydown::Rendering
37
37
  # returns index of first event at or after specified line
38
38
  def find_line_idx(stream, line, last = false)
39
39
  if last
40
- stream.reverse_each do |e|
40
+ stream.reverse_each do |e|
41
41
  return stream.index(e) if e[:line] && e[:line] <= line
42
42
  end
43
43
  nil
@@ -3,12 +3,12 @@ module Lydown::Rendering
3
3
  # for notes in a macro group
4
4
  class SourceRef < Base
5
5
  def translate
6
- return # unless @work['options/proof_mode']
6
+ return # unless @context['options/proof_mode']
7
7
 
8
8
  fn = @event[:filename]
9
- if fn && fn != @work['process/last_filename']
10
- @work['process/last_filename'] = fn
11
- @work.emit(@event[:stream] || :music, "%{::#{File.expand_path(fn)}%} ")
9
+ if fn && fn != @context['process/last_filename']
10
+ @context['process/last_filename'] = fn
11
+ @context.emit(@event[:stream] || :music, "%{::#{File.expand_path(fn)}%} ")
12
12
  end
13
13
  end
14
14
  end
@@ -1,7 +1,7 @@
1
1
  module Lydown::Rendering
2
2
  module Staff
3
- def self.staff_groups(work, movement, parts)
4
- model = work['score/order'] || movement['score/order'] || DEFAULTS['score/order']
3
+ def self.staff_groups(context, movement, parts)
4
+ model = context['score/order'] || movement['score/order'] || DEFAULTS['score/order']
5
5
  parts_copy = parts.clone
6
6
 
7
7
  groups = []
@@ -59,6 +59,10 @@ module Lydown::Rendering
59
59
  DEFAULTS["parts/#{part}/clef"]
60
60
  end
61
61
 
62
+ def self.midi_instrument(part)
63
+ DEFAULTS["parts/#{part}/midi_instrument"]
64
+ end
65
+
62
66
  def self.beaming_mode(part)
63
67
  beaming = DEFAULTS["parts/#{part}/beaming"]
64
68
  return nil if beaming.nil?
@@ -75,8 +79,8 @@ module Lydown::Rendering
75
79
 
76
80
  DEFAULT_END_BARLINE = '|.'
77
81
 
78
- def self.end_barline(work, movement)
79
- barline = movement['end_barline'] || work['end_barline'] || DEFAULT_END_BARLINE
82
+ def self.end_barline(context, movement)
83
+ barline = movement['end_barline'] || context['end_barline'] || DEFAULT_END_BARLINE
80
84
  barline == 'none' ? nil : barline
81
85
  end
82
86
  end
@@ -2,30 +2,30 @@ module Lydown::Rendering
2
2
  class VoiceSelect < Base
3
3
  def translate
4
4
  if @event[:voice]
5
- @work['process/voice_selector'] = "voice#{@event[:voice]}"
5
+ @context['process/voice_selector'] = "voice#{@event[:voice]}"
6
6
  else
7
- self.class.render_voices(@work)
7
+ self.class.render_voices(@context)
8
8
  end
9
9
  end
10
10
 
11
- def self.render_voices(work)
12
- work['process/voice_selector'] = nil
11
+ def self.render_voices(context)
12
+ context['process/voice_selector'] = nil
13
13
 
14
- music = Lydown::Templates.render(:multi_voice, work, part: work[:part])
14
+ music = Lydown::Templates.render(:multi_voice, context, part: context[:part])
15
15
 
16
- work.emit(:music, music)
16
+ context.emit(:music, music)
17
17
 
18
- work['process/voices'].each_value do |stream|
18
+ context['process/voices'].each_value do |stream|
19
19
  if stream['lyrics']
20
20
  stream['lyrics'].each do |voice, lyrics_stream|
21
21
  lyrics_stream.each do |idx, content|
22
- work.emit("lyrics/#{voice}/#{idx}", content)
22
+ context.emit("lyrics/#{voice}/#{idx}", content)
23
23
  end
24
24
  end
25
25
  end
26
26
  end
27
27
 
28
- work['process/voices'] = nil
28
+ context['process/voices'] = nil
29
29
  end
30
30
 
31
31
  VOICE_COMMANDS = {
@@ -19,4 +19,13 @@ module Lydown
19
19
  ERB.new IO.read(File.join(TEMPLATES_DIR, "#{name}.erb")), 0, '<>'
20
20
  end
21
21
  end
22
+
23
+ module TemplateBinding
24
+ # Used to bind to instance when rendering templates
25
+ def template_binding(locals = {})
26
+ b = binding
27
+ locals.each {|k, v| b.local_variable_set(k.to_sym, v)}
28
+ b
29
+ end
30
+ end
22
31
  end
@@ -17,4 +17,4 @@
17
17
  <%= Lydown::Templates.render(:movement, self, name: n, movement: m)
18
18
  %>
19
19
  <% end %>
20
- }
20
+ }
@@ -52,6 +52,14 @@
52
52
  <% end %>
53
53
 
54
54
  <% if score_mode %>
55
- >> }
55
+ >>
56
+ <% if self['render_opts']['format'] == 'midi' %>
57
+ \midi {
58
+ <% if tempo = movement['settings/midi_tempo'] %>
59
+ \tempo <%= tempo %>
60
+ <% end %>
61
+ }
62
+ <% end %>
63
+ }
56
64
  <% end %>
57
65
  }
@@ -13,12 +13,16 @@
13
13
  score_mode = self['render_opts/mode'] == :score
14
14
 
15
15
  part['settings'] ||= {
16
- 'pickup' => @context[:pickup],
17
- 'key' => @context[:key],
18
- 'tempo' => @context[:tempo]
16
+ 'pickup' => self[:pickup],
17
+ 'key' => self[:key],
18
+ 'tempo' => self[:tempo]
19
19
  }.deep!
20
20
 
21
21
  clef = Lydown::Rendering::Staff.clef(name)
22
+
23
+ midi_mode = self['render_opts']['format'] == 'midi'
24
+ midi_instrument = midi_mode && Lydown::Rendering::Staff.midi_instrument(name)
25
+
22
26
  beaming_mode = Lydown::Rendering::Staff.beaming_mode(name)
23
27
  partial = part['settings'][:pickup] ? "\\partial #{part['settings'][:pickup]}" : ""
24
28
 
@@ -38,6 +42,11 @@
38
42
  <% end %>
39
43
 
40
44
  <% if score_mode %>\set Staff.instrumentName = #"<%= title %>"<% end %>
45
+
46
+ <% if midi_instrument %>
47
+ \set Staff.midiInstrument = #"<%= midi_instrument %>"
48
+ <% end %>
49
+
41
50
  \relative c {
42
51
  <% if clef %>
43
52
  \clef "<%= clef %>"
@@ -0,0 +1,17 @@
1
+ module Lydown
2
+ module Translation
3
+ def self.process(source)
4
+ output = ''
5
+ if source[:ripple]
6
+ output << RippleParser.translate(source[:ripple], source)
7
+ end
8
+ if source[:lyrics]
9
+ output << "\n=lyrics\n#{source[:lyrics]}"
10
+ end
11
+ output
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'lydown/translation/ripple'
17
+
@@ -0,0 +1,30 @@
1
+ require 'polyglot'
2
+ require 'treetop'
3
+
4
+ require 'lydown/translation/ripple/nodes'
5
+ require 'lydown/translation/ripple/ripple.treetop'
6
+
7
+ class RippleParser
8
+ def self.parse(source, opts = {})
9
+ parser = self.new
10
+ ast = parser.parse(source)
11
+ unless ast
12
+ error_msg = format_parser_error(source, parser, opts)
13
+ $stderr.puts error_msg
14
+ raise LydownError, error_msg
15
+ end
16
+ ast
17
+ end
18
+
19
+ def self.translate(source, opts)
20
+ ast = parse(source)
21
+ stream = ''
22
+ ast.translate(stream, opts.merge(source: source))
23
+ end
24
+
25
+ def self.format_parser_error(source, parser, opts)
26
+ msg = "#{parser.failure_reason} at #{parser.failure_line}:#{parser.failure_column}\n"
27
+ msg << " #{source.lines[parser.failure_line - 1].chomp}\n #{' ' * parser.failure_column}^"
28
+ msg
29
+ end
30
+ end
@@ -0,0 +1,191 @@
1
+ module Lydown::Translation::Ripple
2
+ class Root < Treetop::Runtime::SyntaxNode
3
+ def _translate(element, stream, opts)
4
+ if element.elements
5
+ element.elements.each do |e|
6
+ e.respond_to?(:translate) ?
7
+ e.translate(stream, opts) :
8
+ _translate(e, stream, opts)
9
+ end
10
+ end
11
+ stream
12
+ end
13
+
14
+ def translate(stream = [], opts = {})
15
+ _translate(self, stream, opts)
16
+ stream
17
+ end
18
+
19
+ def source_line(opts)
20
+ if opts[:source]
21
+ line, column = opts[:source].find_line_and_column(interval.first)
22
+ line
23
+ else
24
+ nil
25
+ end
26
+ end
27
+
28
+ def check_line_break(stream, opts)
29
+ line = source_line(opts)
30
+ if line != opts[:last_line]
31
+ unless stream.empty? || (stream[-1] == "\n")
32
+ # remove whitespace at end of line
33
+ stream.gsub!(/\s+$/, '')
34
+ stream << "\n"
35
+ end
36
+ opts[:last_line] = line
37
+ end
38
+ end
39
+ end
40
+
41
+ class RelativeCommand < Root
42
+ def translate(stream, opts)
43
+ opts[:relative_start_octave] = text_value
44
+ end
45
+ end
46
+
47
+ class Note < Root
48
+ def translate(stream, opts)
49
+ check_line_break(stream, opts)
50
+ note = {expressions: []}
51
+ _translate(self, note, opts)
52
+ expressions = note[:expressions].join
53
+ expressions << ' ' unless expressions.empty?
54
+
55
+ unless opts[:first_note] || note[:head] =~ /^[rs]/
56
+ octave = Lydown::Rendering::Octaves.absolute_octave(
57
+ note[:head], opts[:relative_start_octave] || 'c'
58
+ )
59
+ if note[:head] =~ /[',]+$/
60
+ note[:head].gsub!(/[',]+/, octave)
61
+ else
62
+ note[:head] << octave
63
+ end
64
+ opts[:first_note] = note[:head]
65
+ end
66
+
67
+ if opts[:post_macro_value]
68
+ note[:duration] ||= opts[:post_macro_value]
69
+ opts[:post_macro_value] = nil
70
+ end
71
+
72
+ stream << "%s%s%s" % [
73
+ note[:duration],
74
+ note[:head],
75
+ expressions
76
+ ]
77
+ end
78
+
79
+ class Head < Root
80
+ def translate(note, opts)
81
+ head = text_value.dup
82
+ head.gsub!(/(?<=.{1})b/, '-')
83
+ head.gsub!(/(?<=.{1})es/, '-')
84
+ head.gsub!(/(?<=.{1})s/, '+')
85
+ head.sub!(/([a-g][\+\-]*)/) do |m|
86
+ Lydown::Rendering::Accidentals.chromatic_to_diatonic(
87
+ m, opts[:key] || 'c major'
88
+ )
89
+ end
90
+ note[:head] = head
91
+ end
92
+ end
93
+
94
+ class Duration < Root
95
+ def translate(note, opts)
96
+ note[:duration] = text_value
97
+ end
98
+ end
99
+
100
+ class Expression < Root
101
+ def translate(note, opts)
102
+ note[:expressions] << text_value
103
+ end
104
+ end
105
+ end
106
+
107
+ class KeySignature < Root
108
+ def translate(stream, opts)
109
+ signature = {}
110
+ _translate(self, signature, opts)
111
+ opts[:key] = "#{signature[:head]} #{signature[:mode]}"
112
+ stream << "- key: #{opts[:key]}\n"
113
+ end
114
+
115
+ class Mode < Root
116
+ def translate(signature, opts)
117
+ if text_value =~ /(major|minor)/
118
+ signature[:mode] = $1
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ class TimeSignature < Root
125
+ def translate(stream, opts)
126
+ opts[:time] = text_value
127
+ stream << "- time: #{text_value}\n"
128
+ end
129
+ end
130
+
131
+ class FullRest < Root
132
+ def translate(stream, opts)
133
+ check_line_break(stream, opts)
134
+ if text_value =~ /R[\d]+\*(\d+)/
135
+ stream << "R*#{$1} "
136
+ else
137
+ stream << "R "
138
+ end
139
+ end
140
+ end
141
+
142
+ class Phrasing < Root
143
+ def translate(stream, opts)
144
+ check_line_break(stream, opts)
145
+ stream << text_value
146
+ end
147
+ end
148
+
149
+ class NamedMacro < Root
150
+ def translate(stream, opts)
151
+ check_line_break(stream, opts)
152
+ macro_name = text_value.sub('$', '')
153
+ if opts[:macros][macro_name]
154
+ macro = translate_macro(opts[:macros][macro_name])
155
+ stream << "{#{macro}}"
156
+ opts[:current_macro] = macro
157
+ opts[:post_macro_value] = nil
158
+ else
159
+ raise LydownError, "Could not find named macro #{macro_name}"
160
+ end
161
+ end
162
+
163
+ PLACE_HOLDERS = {
164
+ '#' => '_',
165
+ '@' => '@',
166
+ 'r' => 'r'
167
+ }
168
+
169
+ def translate_macro(macro)
170
+ macro.gsub(/([r#@])([0-9\.]*)/) {|m| "#{$2}#{PLACE_HOLDERS[$1]}"}.
171
+ gsub(' ', '')
172
+ end
173
+
174
+ class Stop < Root
175
+ def translate(stream, opts)
176
+ if opts[:current_macro]
177
+ if opts[:current_macro] =~ /([0-9]+\.*)[^0-9]*$/
178
+ opts[:post_macro_value] = $1
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ class Command < Root
186
+ def translate(stream, opts)
187
+ cmd = text_value.gsub(' ', ':')
188
+ stream << " #{cmd} "
189
+ end
190
+ end
191
+ end