lydown 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|