lydown 0.6.1 → 0.6.2
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.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/lib/lydown/core_ext.rb +22 -0
- data/lib/lydown/parsing/lydown.treetop +4 -1
- data/lib/lydown/parsing/nodes.rb +11 -1
- data/lib/lydown/rendering/music.rb +23 -225
- data/lib/lydown/rendering/notes.rb +273 -0
- data/lib/lydown/rendering.rb +1 -0
- data/lib/lydown/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b8c837a068f50dd59adac76af318ea85b9e27c0
|
4
|
+
data.tar.gz: a2c3297624f4682fda2c3484334955488a7911ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 105a386ac25d493b6621877cc983508c41a7e00ee6fa6c5a2d3dc61ceef5358ffce2d0a54ae3f06d71076c884a347b341ee9614b0d67315e2995756b4dfce4af
|
7
|
+
data.tar.gz: 441484711848e46815d6cbbba3478dfc54b37d4bcfab1bb1e5171bed91100f6393da6845f1daca92b005c9baf303c88f66c52a0ea9ee461fe3157486b7969368
|
data/README.md
CHANGED
@@ -94,6 +94,10 @@ Notes can be repeated using the <code>@</code> placeholder:
|
|
94
94
|
|
95
95
|
(The repeating note placeholder is useful when entering repeated notes with accidentals).
|
96
96
|
|
97
|
+
Chords are written using angled brackets, like in lilypond:
|
98
|
+
|
99
|
+
(2<fd>4<ge>) => <f d>2( <g e>4)
|
100
|
+
|
97
101
|
### Rests
|
98
102
|
|
99
103
|
Normal rests are written [like in lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/writing-rests#rests):
|
@@ -284,6 +288,10 @@ Multiple arguments can be given, separated by colons. Arguments need to be quote
|
|
284
288
|
|
285
289
|
\!override:AccidentalSuggestion:"#'avoid-slur = #'outside"
|
286
290
|
|
291
|
+
Some lilypond command arguments are expected to be quoted. Quotes can be escaped by prefixing them with a backslash:
|
292
|
+
|
293
|
+
\footnote:"#'(-1 . 1)":"\"slurred?\""
|
294
|
+
|
287
295
|
### Inline lyrics
|
288
296
|
|
289
297
|
Lyrics for vocal parts can be entered on separate lines prefixed by a > symbol:
|
data/lib/lydown/core_ext.rb
CHANGED
@@ -108,6 +108,28 @@ class String
|
|
108
108
|
def camelize
|
109
109
|
split('_').collect(&:capitalize).join
|
110
110
|
end
|
111
|
+
|
112
|
+
# String unescaping code from here: http://stackoverflow.com/a/20131717
|
113
|
+
|
114
|
+
UNESCAPES = {
|
115
|
+
'a' => "\x07", 'b' => "\x08", 't' => "\x09",
|
116
|
+
'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
|
117
|
+
'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
|
118
|
+
"\"" => "\x22", "'" => "\x27"
|
119
|
+
}
|
120
|
+
|
121
|
+
def unescape
|
122
|
+
# Escape all the things
|
123
|
+
gsub(/\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) {
|
124
|
+
if $1
|
125
|
+
if $1 == '\\' then '\\' else UNESCAPES[$1] end
|
126
|
+
elsif $2 # escape \u0000 unicode
|
127
|
+
["#$2".hex].pack('U*')
|
128
|
+
elsif $3 # escape \0xff or \xff
|
129
|
+
[$3].pack('H2')
|
130
|
+
end
|
131
|
+
}
|
132
|
+
end
|
111
133
|
end
|
112
134
|
|
113
135
|
class Fixnum
|
@@ -45,7 +45,7 @@ grammar Lydown
|
|
45
45
|
end
|
46
46
|
rule event
|
47
47
|
(inline_command / inline_lyrics / voice_selector / barline / duration /
|
48
|
-
note / standalone_figures / rest / silence / phrasing / tie) white_space*
|
48
|
+
chord / note / standalone_figures / rest / silence / phrasing / tie) white_space*
|
49
49
|
end
|
50
50
|
rule barline
|
51
51
|
('?|' / ':|][|:' / '[|:' / ':|]' / [\|\.\:]+) <Barline>
|
@@ -88,6 +88,9 @@ grammar Lydown
|
|
88
88
|
rule note
|
89
89
|
note_head octave* accidental_flag? figures? expression* <Note>
|
90
90
|
end
|
91
|
+
rule chord
|
92
|
+
'<' note white_space* (note white_space*)* '>' expression* <Chord>
|
93
|
+
end
|
91
94
|
rule expression
|
92
95
|
(expression_shorthand / expression_longhand / string) <Note::Expression>
|
93
96
|
end
|
data/lib/lydown/parsing/nodes.rb
CHANGED
@@ -114,6 +114,16 @@ module Lydown::Parsing
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
117
|
+
|
118
|
+
module Chord
|
119
|
+
include Root
|
120
|
+
|
121
|
+
def to_stream(stream)
|
122
|
+
chord = {type: :chord, notes: []}
|
123
|
+
_to_stream(self, chord[:notes])
|
124
|
+
stream << chord
|
125
|
+
end
|
126
|
+
end
|
117
127
|
|
118
128
|
module FiguresComponent
|
119
129
|
def to_stream(note)
|
@@ -271,7 +281,7 @@ module Lydown::Parsing
|
|
271
281
|
module Argument
|
272
282
|
def to_stream(cmd)
|
273
283
|
if text_value =~ /^"(.+)"$/
|
274
|
-
value = $1
|
284
|
+
value = $1.unescape
|
275
285
|
else
|
276
286
|
value = text_value
|
277
287
|
end
|
@@ -1,231 +1,7 @@
|
|
1
1
|
require 'lydown/rendering/figures'
|
2
|
+
require 'lydown/rendering/notes'
|
2
3
|
|
3
4
|
module Lydown::Rendering
|
4
|
-
module Accidentals
|
5
|
-
KEY_CYCLE = %w{c- g- d- a- e- b- f c g d a e b f+ c+ g+ d+ a+ e+ b+}
|
6
|
-
C_IDX = KEY_CYCLE.index('c'); A_IDX = KEY_CYCLE.index('a')
|
7
|
-
SHARPS_IDX = KEY_CYCLE.index('f+'); FLATS_IDX = KEY_CYCLE.index('b-')
|
8
|
-
KEY_ACCIDENTALS = {}
|
9
|
-
|
10
|
-
def self.accidentals_for_key_signature(signature)
|
11
|
-
KEY_ACCIDENTALS[signature] ||= calc_accidentals_for_key_signature(signature)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.calc_accidentals_for_key_signature(signature)
|
15
|
-
unless signature =~ /^([a-g][\+\-]*) (major|minor)$/
|
16
|
-
raise "Invalid key signature #{signature.inspect}"
|
17
|
-
end
|
18
|
-
|
19
|
-
key = $1; mode = $2
|
20
|
-
|
21
|
-
# calculate offset from c major / a minor
|
22
|
-
base_idx = (mode == 'major') ? C_IDX : A_IDX
|
23
|
-
offset = KEY_CYCLE.index(key) - base_idx
|
24
|
-
|
25
|
-
if offset >= 0
|
26
|
-
calc_accidentals_map(KEY_CYCLE[SHARPS_IDX, offset])
|
27
|
-
else
|
28
|
-
calc_accidentals_map(KEY_CYCLE[FLATS_IDX + offset + 1, -offset])
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.calc_accidentals_map(accidentals)
|
33
|
-
accidentals.inject({}) { |h, a| h[a[0]] = (a[1] == '+') ? 1 : -1; h}
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.lilypond_note_name(note, key_signature = 'c major')
|
37
|
-
value = 0
|
38
|
-
# accidental value from note
|
39
|
-
note = note.gsub(/[\-\+]/) { |c| value += (c == '+') ? 1 : -1; '' }
|
40
|
-
# add key signature value
|
41
|
-
value += accidentals_for_key_signature(key_signature)[note] || 0
|
42
|
-
|
43
|
-
note + (value >= 0 ? 'is' * value : 'es' * -value)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Takes into account the accidentals mode
|
47
|
-
def self.translate_note_name(work, note)
|
48
|
-
if work[:accidentals] == 'manual'
|
49
|
-
key = 'c major'
|
50
|
-
else
|
51
|
-
key = work[:key]
|
52
|
-
end
|
53
|
-
lilypond_note_name(note, key)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
module Notes
|
58
|
-
include Lydown::Rendering::Figures
|
59
|
-
|
60
|
-
def add_note(event)
|
61
|
-
return add_macro_note(event) if @work['process/duration_macro']
|
62
|
-
|
63
|
-
if @event[:head] == '@'
|
64
|
-
# replace repeating note head
|
65
|
-
@event[:head] = @work['process/last_note_head']
|
66
|
-
else
|
67
|
-
@work['process/last_note_head'] = event[:head]
|
68
|
-
end
|
69
|
-
|
70
|
-
value = @work['process/duration_values'].first
|
71
|
-
@work['process/duration_values'].rotate!
|
72
|
-
|
73
|
-
add_figures(event[:figures], value) if event[:figures]
|
74
|
-
|
75
|
-
# push value into running values accumulator. This is used to synthesize
|
76
|
-
# the bass figures durations.
|
77
|
-
unless event[:figures]
|
78
|
-
@work['process/running_values'] ||= []
|
79
|
-
if event[:rest_value]
|
80
|
-
@work['process/running_values'] << event[:rest_value]
|
81
|
-
else
|
82
|
-
@work['process/running_values'] << value
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# only add the value if different than the last used
|
87
|
-
if value == @work['process/last_value']
|
88
|
-
value = ''
|
89
|
-
else
|
90
|
-
@work['process/last_value'] = value
|
91
|
-
end
|
92
|
-
|
93
|
-
@work.emit(event[:stream] || :music, lilypond_note(event, value: value))
|
94
|
-
end
|
95
|
-
|
96
|
-
FICTA_CODE = <<EOF
|
97
|
-
\\once \\override AccidentalSuggestion #'avoid-slur = #'outside
|
98
|
-
\\once \\set suggestAccidentals = ##t
|
99
|
-
EOF
|
100
|
-
|
101
|
-
def lilypond_note(event, options = {})
|
102
|
-
head = Accidentals.translate_note_name(@work, event[:head])
|
103
|
-
if options[:head_only]
|
104
|
-
head
|
105
|
-
else
|
106
|
-
if event[:accidental_flag] == '^'
|
107
|
-
accidental_flag = ''
|
108
|
-
prefix = FICTA_CODE
|
109
|
-
else
|
110
|
-
accidental_flag = event[:accidental_flag]
|
111
|
-
prefix = ''
|
112
|
-
end
|
113
|
-
|
114
|
-
[
|
115
|
-
prefix,
|
116
|
-
head,
|
117
|
-
event[:octave],
|
118
|
-
accidental_flag,
|
119
|
-
options[:value],
|
120
|
-
lilypond_phrasing(event),
|
121
|
-
event[:expressions] ? event[:expressions].join : '',
|
122
|
-
' '
|
123
|
-
].join
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def lilypond_phrasing(event)
|
128
|
-
phrasing = ''
|
129
|
-
if @work['process/open_beam']
|
130
|
-
phrasing << '['
|
131
|
-
@work['process/open_beam'] = nil
|
132
|
-
end
|
133
|
-
if @work['process/open_slur']
|
134
|
-
phrasing << '('
|
135
|
-
@work['process/open_slur'] = nil
|
136
|
-
end
|
137
|
-
phrasing << ']' if event[:beam_close]
|
138
|
-
phrasing << ')' if event[:slur_close]
|
139
|
-
phrasing
|
140
|
-
end
|
141
|
-
|
142
|
-
def lydown_phrasing_open(event)
|
143
|
-
phrasing = ''
|
144
|
-
if @work['process/open_beam']
|
145
|
-
phrasing << '['
|
146
|
-
@work['process/open_beam'] = nil
|
147
|
-
end
|
148
|
-
if @work['process/open_slur']
|
149
|
-
phrasing << '('
|
150
|
-
@work['process/open_slur'] = nil
|
151
|
-
end
|
152
|
-
phrasing
|
153
|
-
end
|
154
|
-
|
155
|
-
def lydown_phrasing_close(event)
|
156
|
-
phrasing = ''
|
157
|
-
phrasing << ']' if event[:beam_close]
|
158
|
-
phrasing << ')' if event[:slur_close]
|
159
|
-
phrasing
|
160
|
-
end
|
161
|
-
|
162
|
-
def add_macro_note(event)
|
163
|
-
@work['process/macro_group'] ||= @work['process/duration_macro'].clone
|
164
|
-
underscore_count = 0
|
165
|
-
|
166
|
-
lydown_note = "%s%s%s%s%s%s%s" % [
|
167
|
-
lydown_phrasing_open(event),
|
168
|
-
event[:head], event[:octave], event[:accidental_flag],
|
169
|
-
lydown_phrasing_close(event),
|
170
|
-
event[:figures] ? "<#{event[:figures].join}>" : '',
|
171
|
-
event[:expressions] ? event[:expressions].join : ''
|
172
|
-
]
|
173
|
-
|
174
|
-
# replace place holder and repeaters in macro group with actual note
|
175
|
-
@work['process/macro_group'].gsub!(/[_∞]/) do |match|
|
176
|
-
case match
|
177
|
-
when '_'
|
178
|
-
underscore_count += 1
|
179
|
-
underscore_count == 1 ? lydown_note : match
|
180
|
-
when '∞'
|
181
|
-
underscore_count < 2 ? event[:head] : match
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# if group is complete, compile it just like regular code
|
186
|
-
unless @work['process/macro_group'].include?('_')
|
187
|
-
# stash macro, in order to compile macro group
|
188
|
-
macro = @work['process/duration_macro']
|
189
|
-
@work['process/duration_macro'] = nil
|
190
|
-
|
191
|
-
code = LydownParser.parse(@work['process/macro_group'])
|
192
|
-
@work.process(code, no_reset: true)
|
193
|
-
|
194
|
-
# restore macro
|
195
|
-
@work['process/duration_macro'] = macro
|
196
|
-
@work['process/macro_group'] = nil
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
LILYPOND_EXPRESSIONS = {
|
201
|
-
'_' => '--',
|
202
|
-
'.' => '-.',
|
203
|
-
'`' => '-!'
|
204
|
-
}
|
205
|
-
|
206
|
-
def translate_expressions
|
207
|
-
return unless @event[:expressions]
|
208
|
-
|
209
|
-
@event[:expressions] = @event[:expressions].map do |expr|
|
210
|
-
if expr =~ /^(?:\\(_?))?"(.+)"$/
|
211
|
-
placement = ($1 == '_') ? '_' : '^'
|
212
|
-
"#{placement}\\markup { #{translate_string_expression($2)} }"
|
213
|
-
elsif expr =~ /^\\/
|
214
|
-
expr
|
215
|
-
elsif LILYPOND_EXPRESSIONS[expr]
|
216
|
-
LILYPOND_EXPRESSIONS[expr]
|
217
|
-
else
|
218
|
-
raise LydownError, "Invalid expression #{expr.inspect}"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def translate_string_expression(expr)
|
224
|
-
expr.gsub(/__([^_]+)__/) {|m| "\\bold { #{$1} }" }.
|
225
|
-
gsub(/_([^_]+)_/) {|m| "\\italic { #{$1} }" }
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
5
|
LILYPOND_DURATIONS = {
|
230
6
|
'6' => '16',
|
231
7
|
'3' => '32'
|
@@ -294,6 +70,27 @@ EOF
|
|
294
70
|
add_note(@event)
|
295
71
|
end
|
296
72
|
end
|
73
|
+
|
74
|
+
class Chord < Base
|
75
|
+
include Notes
|
76
|
+
|
77
|
+
def translate
|
78
|
+
look_ahead_idx = @idx + 1
|
79
|
+
while event = @stream[look_ahead_idx]
|
80
|
+
case event[:type]
|
81
|
+
when :beam_close
|
82
|
+
@event[:beam_close] = true
|
83
|
+
when :slur_close
|
84
|
+
@event[:slur_close] = true
|
85
|
+
else
|
86
|
+
break
|
87
|
+
end
|
88
|
+
look_ahead_idx += 1
|
89
|
+
end
|
90
|
+
|
91
|
+
add_chord(@event)
|
92
|
+
end
|
93
|
+
end
|
297
94
|
|
298
95
|
class StandAloneFigures < Base
|
299
96
|
include Notes
|
@@ -359,6 +156,7 @@ EOF
|
|
359
156
|
|
360
157
|
if @event[:multiplier]
|
361
158
|
value = full_bar_value(@work[:time])
|
159
|
+
@work['process/duration_macro'] = nil unless @work['process/macro_group']
|
362
160
|
if value
|
363
161
|
@event[:rest_value] = "#{value}*#{@event[:multiplier]}"
|
364
162
|
@event[:head] = "#{@event[:head]}#{@event[:rest_value]}"
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'lydown/rendering/figures'
|
2
|
+
|
3
|
+
module Lydown::Rendering
|
4
|
+
module Accidentals
|
5
|
+
KEY_CYCLE = %w{c- g- d- a- e- b- f c g d a e b f+ c+ g+ d+ a+ e+ b+}
|
6
|
+
C_IDX = KEY_CYCLE.index('c'); A_IDX = KEY_CYCLE.index('a')
|
7
|
+
SHARPS_IDX = KEY_CYCLE.index('f+'); FLATS_IDX = KEY_CYCLE.index('b-')
|
8
|
+
KEY_ACCIDENTALS = {}
|
9
|
+
|
10
|
+
def self.accidentals_for_key_signature(signature)
|
11
|
+
KEY_ACCIDENTALS[signature] ||= calc_accidentals_for_key_signature(signature)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.calc_accidentals_for_key_signature(signature)
|
15
|
+
unless signature =~ /^([a-g][\+\-]*) (major|minor)$/
|
16
|
+
raise "Invalid key signature #{signature.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
key = $1; mode = $2
|
20
|
+
|
21
|
+
# calculate offset from c major / a minor
|
22
|
+
base_idx = (mode == 'major') ? C_IDX : A_IDX
|
23
|
+
offset = KEY_CYCLE.index(key) - base_idx
|
24
|
+
|
25
|
+
if offset >= 0
|
26
|
+
calc_accidentals_map(KEY_CYCLE[SHARPS_IDX, offset])
|
27
|
+
else
|
28
|
+
calc_accidentals_map(KEY_CYCLE[FLATS_IDX + offset + 1, -offset])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.calc_accidentals_map(accidentals)
|
33
|
+
accidentals.inject({}) { |h, a| h[a[0]] = (a[1] == '+') ? 1 : -1; h}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.lilypond_note_name(note, key_signature = 'c major')
|
37
|
+
value = 0
|
38
|
+
# accidental value from note
|
39
|
+
note = note.gsub(/[\-\+]/) { |c| value += (c == '+') ? 1 : -1; '' }
|
40
|
+
# add key signature value
|
41
|
+
value += accidentals_for_key_signature(key_signature)[note] || 0
|
42
|
+
|
43
|
+
note + (value >= 0 ? 'is' * value : 'es' * -value)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Takes into account the accidentals mode
|
47
|
+
def self.translate_note_name(work, note)
|
48
|
+
if work[:accidentals] == 'manual'
|
49
|
+
key = 'c major'
|
50
|
+
else
|
51
|
+
key = work[:key]
|
52
|
+
end
|
53
|
+
lilypond_note_name(note, key)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Notes
|
58
|
+
include Lydown::Rendering::Figures
|
59
|
+
|
60
|
+
def add_note(event, options = {})
|
61
|
+
return add_macro_note(event) if @work['process/duration_macro']
|
62
|
+
|
63
|
+
if @event[:head] == '@'
|
64
|
+
# replace repeating note head
|
65
|
+
@event[:head] = @work['process/last_note_head']
|
66
|
+
else
|
67
|
+
@work['process/last_note_head'] = event[:head]
|
68
|
+
end
|
69
|
+
|
70
|
+
value = @work['process/duration_values'].first
|
71
|
+
@work['process/duration_values'].rotate!
|
72
|
+
|
73
|
+
add_figures(event[:figures], value) if event[:figures]
|
74
|
+
|
75
|
+
# push value into running values accumulator. This is used to synthesize
|
76
|
+
# the bass figures durations.
|
77
|
+
unless event[:figures]
|
78
|
+
@work['process/running_values'] ||= []
|
79
|
+
if event[:rest_value]
|
80
|
+
@work['process/running_values'] << event[:rest_value]
|
81
|
+
else
|
82
|
+
@work['process/running_values'] << value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# only add the value if different than the last used
|
87
|
+
if options[:no_value] || (value == @work['process/last_value'])
|
88
|
+
value = ''
|
89
|
+
else
|
90
|
+
@work['process/last_value'] = value
|
91
|
+
end
|
92
|
+
|
93
|
+
code = lilypond_note(event, options.merge(value: value))
|
94
|
+
@work.emit(event[:stream] || :music, code)
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_chord(event, options = {})
|
98
|
+
value = @work['process/duration_values'].first
|
99
|
+
@work['process/duration_values'].rotate!
|
100
|
+
|
101
|
+
add_figures(event[:figures], value) if event[:figures]
|
102
|
+
|
103
|
+
# push value into running values accumulator. This is used to synthesize
|
104
|
+
# the bass figures durations.
|
105
|
+
unless event[:figures]
|
106
|
+
@work['process/running_values'] ||= []
|
107
|
+
if event[:rest_value]
|
108
|
+
@work['process/running_values'] << event[:rest_value]
|
109
|
+
else
|
110
|
+
@work['process/running_values'] << value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# only add the value if different than the last used
|
115
|
+
if value == @work['process/last_value']
|
116
|
+
value = ''
|
117
|
+
else
|
118
|
+
@work['process/last_value'] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
notes = event[:notes].map do |note|
|
122
|
+
lilypond_note(note)
|
123
|
+
end
|
124
|
+
|
125
|
+
options = options.merge(value: value)
|
126
|
+
@work.emit(event[:stream] || :music, lilypond_chord(event, notes, options))
|
127
|
+
end
|
128
|
+
|
129
|
+
FICTA_CODE = <<EOF
|
130
|
+
\\once \\override AccidentalSuggestion #'avoid-slur = #'outside
|
131
|
+
\\once \\set suggestAccidentals = ##t
|
132
|
+
EOF
|
133
|
+
|
134
|
+
def lilypond_note(event, options = {})
|
135
|
+
head = Accidentals.translate_note_name(@work, event[:head])
|
136
|
+
if options[:head_only]
|
137
|
+
head
|
138
|
+
else
|
139
|
+
if event[:accidental_flag] == '^'
|
140
|
+
accidental_flag = ''
|
141
|
+
prefix = FICTA_CODE
|
142
|
+
else
|
143
|
+
accidental_flag = event[:accidental_flag]
|
144
|
+
prefix = ''
|
145
|
+
end
|
146
|
+
|
147
|
+
[
|
148
|
+
prefix,
|
149
|
+
head,
|
150
|
+
event[:octave],
|
151
|
+
accidental_flag,
|
152
|
+
options[:value],
|
153
|
+
lilypond_phrasing(event),
|
154
|
+
event[:expressions] ? event[:expressions].join : '',
|
155
|
+
options[:no_whitespace] ? '' : ' '
|
156
|
+
].join
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def lilypond_chord(event, notes, options = {})
|
161
|
+
[
|
162
|
+
'<',
|
163
|
+
notes.join(' ').strip, # strip trailing whitespace
|
164
|
+
'>',
|
165
|
+
options[:value],
|
166
|
+
lilypond_phrasing(event),
|
167
|
+
event[:expressions] ? event[:expressions].join : '',
|
168
|
+
].join
|
169
|
+
end
|
170
|
+
|
171
|
+
def lilypond_phrasing(event)
|
172
|
+
phrasing = ''
|
173
|
+
if @work['process/open_beam']
|
174
|
+
phrasing << '['
|
175
|
+
@work['process/open_beam'] = nil
|
176
|
+
end
|
177
|
+
if @work['process/open_slur']
|
178
|
+
phrasing << '('
|
179
|
+
@work['process/open_slur'] = nil
|
180
|
+
end
|
181
|
+
phrasing << ']' if event[:beam_close]
|
182
|
+
phrasing << ')' if event[:slur_close]
|
183
|
+
phrasing
|
184
|
+
end
|
185
|
+
|
186
|
+
def lydown_phrasing_open(event)
|
187
|
+
phrasing = ''
|
188
|
+
if @work['process/open_beam']
|
189
|
+
phrasing << '['
|
190
|
+
@work['process/open_beam'] = nil
|
191
|
+
end
|
192
|
+
if @work['process/open_slur']
|
193
|
+
phrasing << '('
|
194
|
+
@work['process/open_slur'] = nil
|
195
|
+
end
|
196
|
+
phrasing
|
197
|
+
end
|
198
|
+
|
199
|
+
def lydown_phrasing_close(event)
|
200
|
+
phrasing = ''
|
201
|
+
phrasing << ']' if event[:beam_close]
|
202
|
+
phrasing << ')' if event[:slur_close]
|
203
|
+
phrasing
|
204
|
+
end
|
205
|
+
|
206
|
+
def add_macro_note(event)
|
207
|
+
@work['process/macro_group'] ||= @work['process/duration_macro'].clone
|
208
|
+
underscore_count = 0
|
209
|
+
|
210
|
+
lydown_note = "%s%s%s%s%s%s%s" % [
|
211
|
+
lydown_phrasing_open(event),
|
212
|
+
event[:head], event[:octave], event[:accidental_flag],
|
213
|
+
lydown_phrasing_close(event),
|
214
|
+
event[:figures] ? "<#{event[:figures].join}>" : '',
|
215
|
+
event[:expressions] ? event[:expressions].join : ''
|
216
|
+
]
|
217
|
+
|
218
|
+
# replace place holder and repeaters in macro group with actual note
|
219
|
+
@work['process/macro_group'].gsub!(/[_∞]/) do |match|
|
220
|
+
case match
|
221
|
+
when '_'
|
222
|
+
underscore_count += 1
|
223
|
+
underscore_count == 1 ? lydown_note : match
|
224
|
+
when '∞'
|
225
|
+
underscore_count < 2 ? event[:head] : match
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# if group is complete, compile it just like regular code
|
230
|
+
unless @work['process/macro_group'].include?('_')
|
231
|
+
# stash macro, in order to compile macro group
|
232
|
+
macro = @work['process/duration_macro']
|
233
|
+
@work['process/duration_macro'] = nil
|
234
|
+
|
235
|
+
code = LydownParser.parse(@work['process/macro_group'])
|
236
|
+
@work.process(code, no_reset: true)
|
237
|
+
|
238
|
+
# restore macro
|
239
|
+
@work['process/duration_macro'] = macro
|
240
|
+
@work['process/macro_group'] = nil
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
LILYPOND_EXPRESSIONS = {
|
245
|
+
'_' => '--',
|
246
|
+
'.' => '-.',
|
247
|
+
'`' => '-!'
|
248
|
+
}
|
249
|
+
|
250
|
+
def translate_expressions
|
251
|
+
return unless @event[:expressions]
|
252
|
+
|
253
|
+
@event[:expressions] = @event[:expressions].map do |expr|
|
254
|
+
if expr =~ /^(?:\\(_?))?"(.+)"$/
|
255
|
+
placement = ($1 == '_') ? '_' : '^'
|
256
|
+
"#{placement}\\markup { #{translate_string_expression($2)} }"
|
257
|
+
elsif expr =~ /^\\/
|
258
|
+
expr
|
259
|
+
elsif LILYPOND_EXPRESSIONS[expr]
|
260
|
+
LILYPOND_EXPRESSIONS[expr]
|
261
|
+
else
|
262
|
+
raise LydownError, "Invalid expression #{expr.inspect}"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def translate_string_expression(expr)
|
268
|
+
expr.unescape.
|
269
|
+
gsub(/__([^_]+)__/) {|m| "\\bold { #{$1} }" }.
|
270
|
+
gsub(/_([^_]+)_/) {|m| "\\italic { #{$1} }" }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
data/lib/lydown/rendering.rb
CHANGED
@@ -3,6 +3,7 @@ require 'lydown/work'
|
|
3
3
|
require 'lydown/rendering/base'
|
4
4
|
require 'lydown/rendering/comments'
|
5
5
|
require 'lydown/rendering/lyrics'
|
6
|
+
require 'lydown/rendering/notes'
|
6
7
|
require 'lydown/rendering/music'
|
7
8
|
require 'lydown/rendering/settings'
|
8
9
|
require 'lydown/rendering/staff'
|
data/lib/lydown/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lydown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: treetop
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- lib/lydown/rendering/lyrics.rb
|
51
51
|
- lib/lydown/rendering/movement.rb
|
52
52
|
- lib/lydown/rendering/music.rb
|
53
|
+
- lib/lydown/rendering/notes.rb
|
53
54
|
- lib/lydown/rendering/settings.rb
|
54
55
|
- lib/lydown/rendering/staff.rb
|
55
56
|
- lib/lydown/rendering/voices.rb
|