lydown 0.7.2 → 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 (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