lydown 0.9.0 → 0.10.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -2
  3. data/lib/lydown.rb +8 -2
  4. data/lib/lydown/cache.rb +54 -0
  5. data/lib/lydown/cli.rb +1 -0
  6. data/lib/lydown/cli/commands.rb +27 -9
  7. data/lib/lydown/cli/compiler.rb +218 -54
  8. data/lib/lydown/cli/diff.rb +1 -1
  9. data/lib/lydown/cli/proofing.rb +3 -3
  10. data/lib/lydown/cli/signals.rb +23 -0
  11. data/lib/lydown/cli/support.rb +23 -1
  12. data/lib/lydown/core_ext.rb +41 -5
  13. data/lib/lydown/{rendering/defaults.yml → defaults.yml} +3 -3
  14. data/lib/lydown/errors.rb +3 -0
  15. data/lib/lydown/lilypond.rb +73 -31
  16. data/lib/lydown/ly_lib/lib.ly +297 -0
  17. data/lib/lydown/parsing.rb +1 -2
  18. data/lib/lydown/parsing/lydown.treetop +16 -10
  19. data/lib/lydown/parsing/nodes.rb +29 -5
  20. data/lib/lydown/rendering.rb +32 -6
  21. data/lib/lydown/rendering/command.rb +79 -2
  22. data/lib/lydown/rendering/figures.rb +29 -8
  23. data/lib/lydown/rendering/literal.rb +7 -0
  24. data/lib/lydown/rendering/movement.rb +61 -0
  25. data/lib/lydown/rendering/music.rb +37 -5
  26. data/lib/lydown/rendering/notes.rb +26 -8
  27. data/lib/lydown/rendering/settings.rb +41 -13
  28. data/lib/lydown/rendering/skipping.rb +43 -10
  29. data/lib/lydown/rendering/staff.rb +72 -16
  30. data/lib/lydown/templates.rb +8 -2
  31. data/lib/lydown/templates/lilypond_doc.erb +10 -1
  32. data/lib/lydown/templates/movement.erb +87 -34
  33. data/lib/lydown/templates/multi_voice.erb +1 -1
  34. data/lib/lydown/templates/part.erb +83 -55
  35. data/lib/lydown/templates/variables.erb +38 -0
  36. data/lib/lydown/version.rb +1 -1
  37. data/lib/lydown/work.rb +39 -26
  38. data/lib/lydown/work_context.rb +252 -14
  39. metadata +138 -8
  40. data/lib/lydown/rendering/lib.ly +0 -88
@@ -8,7 +8,9 @@ module Lydown
8
8
 
9
9
  def self.render(name, ctx, locals = {})
10
10
  _binding = ctx.respond_to?(:template_binding) ? ctx.template_binding(locals) : binding
11
- template(name).result(_binding)
11
+
12
+ # remove trailing white space and superfluous new lines
13
+ template(name).result(_binding).gsub(/^\s+$/, '').gsub(/\n+/m, "\n")
12
14
  end
13
15
 
14
16
  def self.template(name)
@@ -16,7 +18,11 @@ module Lydown
16
18
  end
17
19
 
18
20
  def self.load_template(name)
19
- ERB.new IO.read(File.join(TEMPLATES_DIR, "#{name}.erb")), 0, '<>'
21
+ fn = name.is_a?(Symbol) ?
22
+ File.join(TEMPLATES_DIR, "#{name}.erb") :
23
+ name
24
+
25
+ ERB.new IO.read(fn), 0, '<>'
20
26
  end
21
27
  end
22
28
 
@@ -1,20 +1,29 @@
1
1
  \version "2.18.2"
2
2
 
3
- <% unless self['render_opts']['no_lib'] %>
3
+ <% unless self['render_opts/no_lib'] %>
4
4
  \include "<%= File.join(LY_LIB_DIR, 'lib.ly') %>"
5
5
  <% end %>
6
6
 
7
+ <% if self.render_mode == :proof %>
8
+ <%= Lydown::Rendering::PROOFING_LY_SETTINGS %>
9
+ <% end %>
10
+
7
11
  <% if self['layout'] %>
8
12
  \layout {
9
13
  }
10
14
  <% end %>
11
15
 
16
+ <%= Lydown::Templates.render(:variables, self) %>
17
+
12
18
  \book {
13
19
  \header {
14
20
  }
15
21
 
22
+ \bookpart {
16
23
  <% self['movements'].each do |n, m| %>
17
24
  <%= Lydown::Templates.render(:movement, self, name: n, movement: m)
18
25
  %>
19
26
  <% end %>
27
+
28
+ }
20
29
  }
@@ -1,7 +1,7 @@
1
1
  <%
2
- score_mode = self['render_opts/mode'] == :score
2
+ render_mode = self.render_mode
3
3
  staff_groups = Lydown::Rendering::Staff.staff_groups(
4
- self, movement, movement['parts'].keys)
4
+ self, {movement: name}, movement['parts'].keys)
5
5
  parts_in_order = staff_groups.flatten
6
6
  staff_hierarchy = Lydown::Rendering::Staff.staff_hierarchy(staff_groups)
7
7
 
@@ -9,25 +9,62 @@
9
9
  m[p] = movement['parts'][p]
10
10
  m
11
11
  end
12
+
13
+ score_mode = (render_mode == :score) ||
14
+ (render_mode == :proof) || (parts.size > 1)
12
15
 
16
+ tacet = Lydown::Rendering::Movement.tacet?(self, name)
13
17
  movement_title = Lydown::Rendering::Movement.movement_title(self, name)
18
+ movement_source = self.get_setting(:movement_source , movement: name)
19
+
20
+ format = self['options/format'] || self['render_opts/format'] || :pdf
21
+ midi_mode = (format == :midi) || (format == :mp3)
22
+
23
+ empty_staves = self.get_setting(:empty_staves, movement: name)
24
+
25
+ page_breaks = Lydown::Rendering::Movement.page_breaks(self, movement: name)
26
+
27
+ includes = Lydown::Rendering::Movement.include_files(self, movement: name)
28
+
29
+ hide_bar_numbers = Lydown::Rendering::Movement.hide_bar_numbers?(
30
+ self, movement: name
31
+ )
14
32
  %>
15
33
 
16
- \bookpart {
17
- <% if movement_title %>
18
- \header {
19
- piece = \markup {
20
- \column {
21
- \fill-line {\bold \large "<%= movement_title %>"}
22
-
34
+ <% if page_breaks[:blank_page_before] %>
35
+ } \bookpart {
36
+ \markup \column {
37
+ \null \null \null \null \null \null
38
+ \null \null \null \null \null \null
39
+ \null \null \null \null \null \null
40
+ \null \null \null \null \null \null
41
+ \fill-line { "(this page has been left blank to facilitate page turning)" }
42
+ }
43
+ } \bookpart {
44
+ \pageBreak
45
+ <% elsif page_breaks[:before] %>
46
+ } \bookpart {
47
+ <% end %>
48
+
49
+ <% includes.each do |i| %>
50
+ <%= i %>
51
+ <% end %>
52
+
53
+ <% unless tacet %>
54
+ \score {
55
+ <% if movement_title && render_mode != :proof %>
56
+ \header {
57
+ piece = \markup {
58
+ \bold \large { <%= movement_title %> }
59
+ <% if movement_source %>
60
+ \hspace #1 \italic { <%= movement_source %> }
61
+ <% end %>
23
62
  }
24
63
  }
25
- }
26
- <% end %>
64
+ <% end %>
27
65
 
28
- <% if score_mode %>
29
- \score {
30
- <% if self['empty_staves'] == 'hide' %>
66
+ <% if score_mode %>
67
+ <% if empty_staves == 'hide' %>
31
68
  \layout {
32
69
  \context {
33
70
  \RemoveEmptyStaffContext
@@ -35,31 +72,47 @@
35
72
  }
36
73
  }
37
74
  <% end %>
38
-
75
+
39
76
  \new StaffGroup <<
40
77
  \set StaffGroup.systemStartDelimiterHierarchy = <%= staff_hierarchy %>
41
- <% end %>
78
+ <% end %>
42
79
 
43
- <% if n = movement['bar_number'] %>
44
- \set Score.currentBarNumber = #<%= n %>
45
- \set Score.barNumberVisibility = #all-bar-numbers-visible
46
- \bar ""
47
- <% end %>
80
+ <% if n = movement['bar_number'] %>
81
+ \set Score.currentBarNumber = #<%= n %>
82
+ \set Score.barNumberVisibility = #all-bar-numbers-visible
83
+ \bar ""
84
+ <% end %>
48
85
 
49
- <% parts.each do |n, p| %>
50
- <%= Lydown::Templates.render(:part, self,
51
- name: n, part: p, movement: movement) %>
52
- <% end %>
86
+ <% parts.each do |n, p| %>
87
+ <%= Lydown::Templates.render(:part, self,
88
+ name: n, part: p, movement: movement, movement_name: name) %>
89
+ <% end %>
53
90
 
54
- <% if score_mode %>
91
+ <% if score_mode %>
55
92
  >>
56
- <% if self['render_opts']['format'] == 'midi' %>
57
- \midi {
58
- <% if tempo = movement['settings/midi_tempo'] %>
59
- \tempo <%= tempo %>
60
- <% end %>
93
+ <% end %>
94
+ <% if midi_mode %>
95
+ \midi {
96
+ <% if tempo = self.get_setting(:midi_tempo, movement: name) %>
97
+ \tempo <%= tempo %>
98
+ <% end %>
99
+ }
100
+ <% end %>
101
+
102
+ <% if hide_bar_numbers %>
103
+ \layout {
104
+ \context {
105
+ \Score
106
+ \omit BarNumber
61
107
  }
62
- <% end %>
63
108
  }
64
- <% end %>
65
- }
109
+ <% end %>
110
+ }
111
+ <% else # tacet %>
112
+ \markup {
113
+ \line { \bold \large { <%= movement_title %> } }
114
+ \line { \pad-markup #3 " " }
115
+
116
+ }
117
+ <% end %>
118
+ <% if page_breaks[:after] %>\pageBreak <% end %>
@@ -1,5 +1,5 @@
1
1
  <<
2
- <% beaming_mode = Lydown::Rendering::Staff.beaming_mode(part) %>
2
+ <% beaming_mode = Lydown::Rendering::Staff.beaming_mode(self, self.current_setting_opts) %>
3
3
  <% voice_prefix = part.nil? || (part == "") ? nil : "#{part}_" %>
4
4
 
5
5
  <% self['process/voices'].each do |voice, stream| %>
@@ -1,90 +1,118 @@
1
1
  <%
2
- title = nil
2
+ render_mode = self.render_mode
3
+
4
+ setting_opts = {movement: movement_name, part: name}
5
+
6
+ inline_part_title = nil
3
7
  if name && (name != '')
4
- title = Lydown::Rendering.part_title(name)
8
+ part_title = Lydown::Rendering::Staff.part_title(self, setting_opts)
5
9
  voice_prefix = "#{name}_"
6
10
  else
7
- title = nil
11
+ part_title = "#\"\""
8
12
  voice_prefix = nil
9
13
  end
10
- staff_id = "#{title && title.gsub(' ', '')}Staff"
11
-
14
+ staff_id = Lydown::Rendering::Staff.staff_id(name)
12
15
 
13
- score_mode = self['render_opts/mode'] == :score
16
+ score_mode = render_mode == :score
14
17
 
15
- part['settings'] ||= {
16
- 'pickup' => self[:pickup],
17
- 'key' => self[:key],
18
- 'tempo' => self[:tempo]
19
- }.deep!
20
-
21
- clef = Lydown::Rendering::Staff.clef(name)
18
+ clef = Lydown::Rendering::Staff.clef(self, setting_opts)
19
+ prevent_remove_empty = Lydown::Rendering::Staff.prevent_remove_empty(self, setting_opts)
22
20
 
23
- midi_mode = self['render_opts']['format'] == 'midi'
21
+ midi_mode = (self['render_opts/format'] == 'midi') ||
22
+ (self['render_opts/format'] == 'mp3')
24
23
  midi_instrument = midi_mode && Lydown::Rendering::Staff.midi_instrument(name)
25
24
 
26
- beaming_mode = Lydown::Rendering::Staff.beaming_mode(name)
27
- partial = part['settings'][:pickup] ? "\\partial #{part['settings'][:pickup]}" : ""
25
+ beaming_mode = Lydown::Rendering::Staff.beaming_mode(self, setting_opts)
26
+ partial = self.get_setting(:pickup, setting_opts)
27
+ partial = partial ? "\\partial #{partial}" : ""
28
28
 
29
- end_barline = Lydown::Rendering::Staff.end_barline(self, movement)
29
+ end_barline = Lydown::Rendering::Staff.end_barline(self, setting_opts)
30
30
 
31
- cadenza = part['settings'][:time] == 'unmetered'
31
+ time = self.get_setting(:time, setting_opts)
32
+ cadenza = time == 'unmetered'
33
+
34
+ skip_bars = (render_mode == :proof) || (render_mode == :part)
35
+
36
+ instrument_names = self.get_setting(:instrument_names, setting_opts)
37
+ hide_instrument_names =
38
+ (instrument_names == 'hide') ||
39
+ (instrument_names =~ /^inline\-?(.+)?$/)
40
+
41
+ if instrument_names =~ /^inline\-?(.+)?$/
42
+ alignment = $1 && "\\#{$1}"
43
+ inline_part_title = Lydown::Rendering::Staff.inline_part_title(
44
+ self, setting_opts.merge(alignment: alignment)
45
+ )
46
+ end
32
47
  %>
33
48
 
34
49
  <<
35
50
 
36
51
  \new Staff = <%= staff_id %> \with {
52
+ <% if prevent_remove_empty %>
53
+ \override VerticalAxisGroup.remove-empty = ##f
54
+ <% end %>
55
+ <% if size = self.get_setting(:staff_size, setting_opts) %>
56
+ fontSize = #<%= size %>
57
+ \override StaffSymbol.staff-space = #(magstep <%= size %>)
58
+ <% end %>
37
59
  }
38
60
 
39
61
  \context Staff = <%= staff_id %> {
40
- <% if part['settings'][:tempo] %>
41
- \tempo "<%= part['settings'][:tempo] %>"
62
+ <% if skip_bars %>
63
+ \set Score.skipBars = ##t
64
+ <% end %>
65
+
66
+
67
+ <% if tempo = self.get_setting(:tempo, setting_opts) %>
68
+ \tempo "<%= tempo %>"
42
69
  <% end %>
43
70
 
44
- <% if score_mode %>\set Staff.instrumentName = #"<%= title %>"<% end %>
71
+ <% if score_mode && !hide_instrument_names %>
72
+ \set Staff.instrumentName = <%= part_title %>
73
+ <% end %>
45
74
 
46
75
  <% if midi_instrument %>
47
76
  \set Staff.midiInstrument = #"<%= midi_instrument %>"
48
77
  <% end %>
49
78
 
50
- \relative c {
51
- <% if clef %>
52
- \clef "<%= clef %>"
53
- <% end %>
54
- <%= partial %>
55
-
56
- <<
57
- \new Voice = "<%= voice_prefix %>voice1" {
58
- <%= beaming_mode %>
59
- <%= part['music'] %>
60
- }
61
- >>
62
- <% if end_barline %>
63
- \bar "<%= end_barline %>"
64
- <% end %>
65
- }
79
+ <% if clef %>
80
+ \clef "<%= clef %>"
81
+ <% end %>
82
+ <%= partial %>
83
+
84
+ <% if score_mode && inline_part_title %>
85
+ <%= inline_part_title %>
86
+ <% end %>
87
+
88
+ <%= beaming_mode %>
89
+ \<%= Lydown::Rendering.variable_name(setting_opts.merge(stream: :music)) %>
90
+
91
+ <% if end_barline %>
92
+ \bar "<%= end_barline %>"
93
+ <% end %>
66
94
  }
67
95
 
68
- <% if part['lyrics'] %>
69
- <% multi_voice = part['lyrics'].size > 1 %>
70
- <% part['lyrics'].each_key do |voice| %>
71
- <% above_staff = multi_voice && ['voice1', 'voice3'].include?(voice) %>
72
- <% part['lyrics'][voice].keys.sort.each do |idx| %>
73
- \new Lyrics
74
- <% if above_staff %>
75
- \with { alignAboveContext = "<%= staff_id %>" }
76
- <% end %>
77
- {
78
- \lyricsto "<%= voice_prefix %><%= voice %>" {
79
- <%= part['lyrics'][voice][idx] %>
80
- }
81
- }
96
+ <% if part['lyrics'] %>
97
+ <% multi_voice = part['lyrics'].size > 1 %>
98
+ <% part['lyrics'].each_key do |voice| %>
99
+ <% above_staff = multi_voice && ['voice1', 'voice3'].include?(voice) %>
100
+ <% part['lyrics'][voice].keys.sort.each do |idx| %>
101
+ \new Lyrics
102
+ <% if above_staff %>
103
+ \with { alignAboveContext = "<%= staff_id %>" }
82
104
  <% end %>
105
+ {
106
+ \lyricsto "<%= voice_prefix %><%= voice %>" {
107
+ \<%= Lydown::Rendering.variable_name(setting_opts.merge(stream: :lyrics, voice: voice, idx: idx)) %>
108
+ }
109
+ }
83
110
  <% end %>
84
-
85
111
  <% end %>
86
112
 
87
- <% if part['figures'] %>
88
- \figures { <%= part['figures'] %> }
89
- <% end %>
113
+ <% end %>
114
+
115
+ <% if part['figures'] %>
116
+ \new FiguredBass { \<%= Lydown::Rendering.variable_name(setting_opts.merge(stream: :figures)) %> }
117
+ <% end %>
90
118
  >>
@@ -0,0 +1,38 @@
1
+ <% var_name = lambda {|opts| Lydown::Rendering.variable_name(opts)} %>
2
+ <% self['movements'].each do |mvt_name, mvt|
3
+ mvt['parts'].each do |part_name, part|
4
+ var_opts = {movement: mvt_name, part: part_name}
5
+ if part_name && !part_name.empty?
6
+ voice_prefix = "#{part_name}_"
7
+ else
8
+ voice_prefix = nil
9
+ end
10
+ %>
11
+ <%= var_name[var_opts.merge(stream: :music)] %> = \relative c {
12
+ <<
13
+ \new Voice = "<%= voice_prefix %>voice1" {
14
+ <%= part['music'] %>
15
+ }
16
+ >>
17
+ }
18
+
19
+ <% if part['lyrics']
20
+ part['lyrics'].each do |voice, voice_lyrics|
21
+ voice_lyrics.each do |idx, lyrics|
22
+ %>
23
+ <%= var_name[var_opts.merge(stream: :lyrics, voice: voice, idx: idx)]%> = \lyricmode { <%= lyrics %> }
24
+ <%
25
+ end
26
+ end
27
+ end
28
+ %>
29
+ <% if part['figures']
30
+ %>
31
+ <%= var_name[var_opts.merge(stream: :figures)]%> = \figuremode { <%= part['figures'] %> }
32
+ <%
33
+ end
34
+ %>
35
+ <%
36
+ end
37
+ end
38
+ %>
@@ -1,3 +1,3 @@
1
1
  module Lydown
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/lydown/work.rb CHANGED
@@ -25,8 +25,8 @@ module Lydown
25
25
  end
26
26
 
27
27
  def to_lilypond(opts = {})
28
- @context['render_opts'] = opts
29
- ly_code = ''
28
+ @context[:render_opts] = opts.stringify_keys
29
+ @context[:variables] = {}
30
30
 
31
31
  if opts[:stream_path]
32
32
  unless @context[opts[:stream_path]]
@@ -34,14 +34,13 @@ module Lydown
34
34
  end
35
35
  @context[opts[:stream_path]].strip
36
36
  else
37
- @original_context = @context
38
- begin
39
- filtered = @context.filter(opts)
40
- filtered.extend(TemplateBinding)
41
- Lydown::Templates.render(:lilypond_doc, filtered)
42
- ensure
43
- @context = @original_context
44
- end
37
+ filtered = @context.filter(opts)
38
+
39
+ # the filtered context is to the template's self
40
+ filtered.extend(TemplateBinding)
41
+
42
+ # Remove empty lines from the rendered code
43
+ Lydown::Templates.render(:lilypond_doc, filtered)
45
44
  end
46
45
  end
47
46
 
@@ -77,11 +76,20 @@ module Lydown
77
76
  DEFAULT_BASENAMES = %w{work movement}
78
77
 
79
78
  def process_directory(path)
79
+ parts_filter = @context[:options][:parts]
80
+ if @context[:options] && @context[:options][:include_parts]
81
+ if parts_filter
82
+ parts_filter += @context[:options][:include_parts]
83
+ else
84
+ parts_filter = @context[:options][:include_parts]
85
+ end
86
+ end
87
+
80
88
  state = {
81
89
  streams: {},
82
90
  movements: Hash.new {|h, k| h[k] = {}},
83
91
  current_movement: nil,
84
- part_filter: @context[:options][:parts],
92
+ part_filter: parts_filter,
85
93
  mvmt_filter: @context[:options][:movements]
86
94
  }
87
95
 
@@ -110,7 +118,9 @@ module Lydown
110
118
  state[:movements][state[:current_movement]][part] = entry
111
119
  end
112
120
  elsif File.directory?(entry) && recursive
121
+ # handle movement subdirectory
113
122
  movement = File.basename(entry)
123
+ state[:movements][movement] ||= {}.deep!
114
124
  unless skip_movement?(movement, state)
115
125
  state[:current_movement] = movement
116
126
  read_directory(entry, false, state)
@@ -119,8 +129,7 @@ module Lydown
119
129
  end
120
130
 
121
131
  def skip_part?(part, state)
122
- DEFAULT_BASENAMES.include?(part) ||
123
- state[:part_filter] && !state[:part_filter].include?(part)
132
+ DEFAULT_BASENAMES.include?(part)
124
133
  end
125
134
 
126
135
  def skip_movement?(mvmt, state)
@@ -136,7 +145,7 @@ module Lydown
136
145
 
137
146
  PARALLEL_PROCESS_OPTIONS = {
138
147
  progress: {
139
- title: 'Process',
148
+ title: 'Render',
140
149
  format: Lydown::CLI::PROGRESS_FORMAT
141
150
  }
142
151
  }
@@ -145,15 +154,17 @@ module Lydown
145
154
  streams = state[:streams]
146
155
  proof_mode = @context['options/proof_mode']
147
156
  paths = streams.keys
148
-
149
- processed_streams = Parallel.map(paths, PARALLEL_PARSE_OPTIONS) do |path|
157
+
158
+ processed_streams = Parallel.map(paths, PARALLEL_PARSE_OPTIONS.clone) do |path|
150
159
  content = IO.read(path)
151
- LydownParser.parse(content, {
152
- filename: File.expand_path(path),
153
- source: content,
154
- proof_mode: proof_mode,
155
- no_progress: true
156
- })
160
+ Cache.hit(content) do
161
+ LydownParser.parse(content, {
162
+ filename: File.expand_path(path),
163
+ source: content,
164
+ proof_mode: proof_mode,
165
+ no_progress: true
166
+ })
167
+ end
157
168
  end
158
169
  processed_streams.each_with_index {|s, idx| streams[paths[idx]] = s}
159
170
  end
@@ -169,12 +180,14 @@ module Lydown
169
180
 
170
181
  def translate_movement_files(state)
171
182
  stream_entries = prepare_work_stream_array(state)
172
- processed_contexts = Parallel.map(stream_entries, PARALLEL_PROCESS_OPTIONS) do |entry|
183
+ processed_contexts = Parallel.map(stream_entries, PARALLEL_PROCESS_OPTIONS.clone) do |entry|
173
184
  mvmt_stream, stream = *entry
174
185
  ctx = @context.clone_for_translation
175
- ctx.translate(mvmt_stream)
176
- ctx.translate(stream)
177
- ctx
186
+ Cache.hit(ctx, mvmt_stream, stream) do
187
+ ctx.translate(mvmt_stream)
188
+ ctx.translate(stream)
189
+ ctx
190
+ end
178
191
  end
179
192
 
180
193
  processed_contexts.each {|ctx| @context.merge_movements(ctx)}