Class: Lydown::WorkContext

Inherits:
Object
  • Object
show all
Includes:
TemplateBinding
Defined in:
lib/lydown/work_context.rb

Overview

A WorkContext instance holds the entire state of a work being processed. This includes both the document settings and processing state, and the resulting lilypond streams and associated data.

Constant Summary collapse

DEFAULT_SOURCE_STREAMS =
%w{music}
DEFAULT_RENDER_MODES =
[:part, :score]

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from TemplateBinding

#template_binding

Constructor Details

#initialize(opts = {}, context = nil) ⇒ WorkContext

Returns a new instance of WorkContext.



10
11
12
13
14
15
16
17
18
# File 'lib/lydown/work_context.rb', line 10

def initialize(opts = {}, context = nil)
  if context
    @context = context
  else
    @context = {}.deep!
    reset(:work)
    @context[:options] = opts.deep_clone
  end
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



6
7
8
# File 'lib/lydown/work_context.rb', line 6

def context
  @context
end

Instance Method Details

#[](key) ⇒ Object



194
195
196
# File 'lib/lydown/work_context.rb', line 194

def [](key)
  @context[key]
end

#[]=(key, value) ⇒ Object



198
199
200
# File 'lib/lydown/work_context.rb', line 198

def []=(key, value)
  @context[key] = value
end

#add_part_includes(movement_name, m) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/lydown/work_context.rb', line 171

def add_part_includes(movement_name, m)
  # check for part includes
  if part = self['options/parts']
    part = part[0] if part.is_a?(Array)
    includes = get_setting(:include_parts, 
      movement: movement_name, part: part)
      
    return unless includes
    
    includes = includes.split(',').map(&:strip)
    
    includes.each do |included_part|
      # If the included part does not exist, try to find its source
      part_hash = self["movements/#{movement_name}/parts/#{included_part}"]
      unless part_hash
        source = part_source(movement_name, included_part)
        part_hash = self["movements/#{movement_name}/parts/#{source}"]
      end
      m["parts/#{included_part}"] ||= part_hash
    end
  end
end

#clone_for_translationObject



66
67
68
69
# File 'lib/lydown/work_context.rb', line 66

def clone_for_translation
  new_context = @context.deep_merge({'movements' => nil})
  WorkContext.new(nil, new_context)
end

#colla_parte_map(movement_name) ⇒ Object



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/lydown/work_context.rb', line 328

def colla_parte_map(movement_name)
  parts_settings = get_merged_setting_tree(:parts, movement: movement_name)
  colla_parte = get_merged_setting_tree(:colla_parte, movement: movement_name)
  
  map = Hash.new {|h, k| h[k] = []}
  parts_settings.each do |name, settings|
    if source = settings['source']
      map[source] << name
    end
  end
  
  colla_parte.each do |source, parts|
    parts.split(',').map(&:strip).inject(map[source]) do |m, p|
      m << p unless m.include?(p); m
    end
  end
  
  map
end

#current_setting_optsObject



312
313
314
# File 'lib/lydown/work_context.rb', line 312

def current_setting_opts
  {movement: @context[:movement], part: @context[:part]}
end

#current_stream(subpath) ⇒ Object



212
213
214
215
216
217
218
219
220
221
# File 'lib/lydown/work_context.rb', line 212

def current_stream(subpath)
  if @context['process/voice_selector']
    path = "process/voices/#{@context['process/voice_selector']}/#{subpath}"
  else
    movement = @context[:movement]
    part = @context[:part]
    path = "movements/#{movement}/parts/#{part}/#{subpath}"
  end
  @context[path] ||= (subpath == :settings) ? {} : ''
end

#emit(path, *content) ⇒ Object



202
203
204
205
206
207
208
209
210
# File 'lib/lydown/work_context.rb', line 202

def emit(path, *content)
  if self['process/mode']
    return unless self['process/mode'] == render_mode
  end
  
  stream = current_stream(path)

  content.each {|c| stream << c}
end

#filter(opts = {}) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/lydown/work_context.rb', line 80

def filter(opts = {})
  filtered = @context.deep_clone

  if filtered[:movements].nil? || filtered[:movements].size == 0
    # no movements found, so no music
    raise LydownError, "No music found"
  elsif filtered[:movements].size > 1
    # delete default movement if other movements are present
    filtered[:movements].delete('')
  end

  if filter = opts[:movements]
    filter = [filter] unless filter.is_a?(Array)
    filtered[:movements].select! {|name, m| filter.include?(name.to_s)}
  end

  if filter = opts[:parts]
    filter = [filter] unless filter.is_a?(Array)
    filter += opts[:include_parts] if opts[:include_parts]
  end
  
  filtered[:movements].each do |movement_name, m|
    # delete default part if other parts are present
    if m[:parts].size > 1
      m[:parts].delete('')
    end

    filter_movement_parts(movement_name, m, filter)
  end

  WorkContext.new(nil, filtered)
end

#filter_movement_parts(movement_name, m, filter) ⇒ Object

Parts are filtered as follows:

  • Parts for which the render_modes do not include the current mode are rejected.

  • If a filter is provided, only the specified parts are selected, and any colla parte parts are added if found.

  • Part includes are checked and added as well



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/lydown/work_context.rb', line 122

def filter_movement_parts(movement_name, m, filter)
  mode = self['options/mode']
  if DEFAULT_RENDER_MODES.include?(mode)
    m[:parts].select!  do |part_name|
      part_render_modes(movement_name, part_name).include?(mode)
    end
  end
  
  select_filter_parts(movement_name, m, filter) if filter
  add_part_includes(movement_name, m)
end

#get_current_setting(path) ⇒ Object

Get setting while code is being translated



308
309
310
# File 'lib/lydown/work_context.rb', line 308

def get_current_setting(path)
  get_setting(path, current_setting_opts)
end

#get_merged_setting_tree(path, opts) ⇒ Object

Returns a merged tree of the settings from different levels



317
318
319
320
321
322
323
324
325
326
# File 'lib/lydown/work_context.rb', line 317

def get_merged_setting_tree(path, opts)
  tree = (DEFAULTS[path] || {}).deep_merge(
          query_setting_tree(nil, nil, path))

  if opts[:movement]
    tree.deep_merge! query_setting_tree(opts[:movement], nil, path)
  else
    tree.deep!
  end
end

#get_setting(path, opts = {}) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/lydown/work_context.rb', line 276

def get_setting(path, opts = {})
  # In order to allow false values for settings, we create
  # a temporary instance variable, and use it to store the
  # setting value once it's found. That way we can use the
  # || operator to stop searching once we've found it.
  @temp_setting_value = nil
  
  if opts[:part]
    parts_section_path = "parts/#{opts[:part]}/#{path}"
    
    query_setting(opts[:movement], opts[:part], path) ||
    query_setting(nil, opts[:part], path) ||
    
    # search in parts section
    query_setting(opts[:movement], nil, parts_section_path) ||
    query_setting(opts[:movement], nil, path) ||

    query_setting(nil, nil, parts_section_path) ||

    query_setting(nil, nil, path) ||
    
    query_defaults("parts/#{opts[:part]}/#{path}") ||
    query_defaults(path)
  else
    query_setting(opts[:movement], nil, path) || 
    query_setting(nil, nil, path) || 
    query_defaults(path)
  end
  @temp_setting_value
end

#merge_movements(ctx) ⇒ Object



71
72
73
74
75
76
77
78
# File 'lib/lydown/work_context.rb', line 71

def merge_movements(ctx)
  return unless ctx['movements']
  if @context['movements']
    @context['movements'].deep_merge! ctx['movements']
  else
    @context['movements'] = ctx['movements']
  end
end

#part_list_for_extraction(opts) ⇒ Object

Returns a list of parts to extract for the specified opts. Only parts that should be extracted (based on the render_modes setting) are included.



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/lydown/work_context.rb', line 379

def part_list_for_extraction(opts)
  parts = []
  return parts unless @context[:movements]
  
  @context[:movements].each do |mname, m|
    if m[:parts]
      m[:parts].each do |pname, p|
        # Add only parts that render in part mode
        if part_render_modes(mname, pname).include?(:part)
          parts << pname unless (pname == '') || parts.include?(pname)
        end
      end
    end
  end
  parts
end

#part_render_modes(movement_name, part_name) ⇒ Object



136
137
138
139
140
141
142
143
# File 'lib/lydown/work_context.rb', line 136

def part_render_modes(movement_name, part_name)
  modes = get_setting(:render_modes, part: part_name, movement: movement_name)
  if modes
    modes.split(',').map {|m| m.strip.to_sym}
  else
    DEFAULT_RENDER_MODES
  end
end

#part_source(movement_name, part_name) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/lydown/work_context.rb', line 348

def part_source(movement_name, part_name)
  parts_settings = get_merged_setting_tree(:parts, movement: movement_name)
  if source = parts_settings["#{part_name}/source"]
    return source
  else
    colla_parte = get_merged_setting_tree(:colla_parte, movement: movement_name)
    colla_parte.each do |source, parts|
      if parts.split(',').map(&:strip).include?(part_name)
        return source
      end
    end
  end 
  nil
end

#part_source_streams(movement_name, part_name) ⇒ Object



363
364
365
366
# File 'lib/lydown/work_context.rb', line 363

def part_source_streams(movement_name, part_name)
  parts_settings = get_merged_setting_tree(:parts, movement: movement_name)
  parts_settings["#{part_name}/source_streams"]
end

#query_defaults(path) ⇒ Object



261
262
263
264
265
266
267
268
269
# File 'lib/lydown/work_context.rb', line 261

def query_defaults(path)
  value = DEFAULTS[path]
  unless value.nil?
    @temp_setting_value = value
    true
  else
    false
  end
end

#query_setting(movement, part, path) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
# File 'lib/lydown/work_context.rb', line 245

def query_setting(movement, part, path)
  path = "#{settings_path(movement, part)}/#{path}"
  value = @context[path]

  unless value.nil?
    @temp_setting_value = value
    true
  else
    false
  end
end

#query_setting_tree(movement, part, path) ⇒ Object



271
272
273
274
# File 'lib/lydown/work_context.rb', line 271

def query_setting_tree(movement, part, path)
  path = "#{settings_path(movement, part)}/#{path}"
  @context[path] || {}
end

#render_modeObject



373
374
375
# File 'lib/lydown/work_context.rb', line 373

def render_mode
  self['options/mode'] || self['render_opts/mode']
end

#rendered_editionObject



257
258
259
# File 'lib/lydown/work_context.rb', line 257

def rendered_edition
  @rendered_edition = @context['render_opts/edition']
end

#reset(mode) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/lydown/work_context.rb', line 32

def reset(mode)
  case mode
  when :work
    @context[:part] = nil
    set_setting(:time, '4/4')
    set_setting(:tempo, nil)
    @context[:cadenza_mode] = nil
    set_setting(:key, 'c major')
    set_setting(:pickup, nil)
    set_setting(:beaming, nil)
    set_setting(:end_barline, nil)
  when :movement
    @context[:part] = nil
  end
  if @context['process/tuplet_mode']
    Lydown::Rendering::TupletDuration.emit_tuplet_end(self)
  end
  if @context['process/grace_mode']
    Lydown::Rendering::Grace.emit_grace_end(self)
  end
  
  if @context['process/voice_selector']
    Lydown::Rendering::VoiceSelect.render_voices(self)
  end
  
  Lydown::Rendering::Notes.cleanup_duration_macro(self)

  # reset processing variables
  @context['process'] = {
    'duration_values' => ['4'],
    'running_values' => []
  }
end

#select_filter_parts(movement_name, m, filter) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/lydown/work_context.rb', line 145

def select_filter_parts(movement_name, m, filter)
  m[:parts].select! {|part_name, p| filter.include?(part_name)}

  # go over filter and check for colla parte
  filter.each do |part_name|
    unless m[:parts].keys.include?(part_name)
      if source = part_source(movement_name, part_name)
        part_path = "parts/#{part_name}"
        source_path = "movements/#{movement_name}/parts/#{source}"
      
        source_streams = ['settings']
        if stream_list = part_source_streams(movement_name, part_name)
          source_streams += stream_list.split(',').map(&:strip)
        else
          source_streams += DEFAULT_SOURCE_STREAMS
        end
      
        m[part_path] = source_streams.inject({}) do |hash, stream|
          hash[stream] = self["#{source_path}/#{stream}"]
          hash
        end.deep!
      end
    end
  end
end

#set_part_context(part) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
# File 'lib/lydown/work_context.rb', line 223

def set_part_context(part)
  movement = @context[:movement]
  path = "movements/#{movement}/parts/#{part}/settings"

  settings = {}.deep!
  settings[:pickup] = @context[:pickup]
  settings[:key] = @context[:key]
  settings[:tempo] = @context[:tempo]
  
  @context[path] = settings
end

#set_setting(path, value) ⇒ Object



368
369
370
371
# File 'lib/lydown/work_context.rb', line 368

def set_setting(path, value)
  path = "#{settings_path(@context[:movement], @context[:part])}/#{path}"
  @context[path] = value
end

#settings_path(movement, part) ⇒ Object



235
236
237
238
239
240
241
242
243
# File 'lib/lydown/work_context.rb', line 235

def settings_path(movement, part)
  if part
    "movements/#{movement}/parts/#{part}/settings"
  elsif movement && !movement.empty?
    "movements/#{movement}/settings"
  else
    "global/settings"
  end
end

#translate(stream, opts = {}) ⇒ Object

process lydown stream by translating into self



21
22
23
24
25
26
27
28
29
30
# File 'lib/lydown/work_context.rb', line 21

def translate(stream, opts = {})
  stream.each_with_index do |e, idx|
    if e[:type]
      Lydown::Rendering.translate(self, e, stream, idx)
    else
      raise LydownError, "Invalid lydown stream event: #{e.inspect}"
    end
  end
  reset(:part) unless opts[:macro_group]
end