LMMS
Loading...
Searching...
No Matches
SFZReader.cpp
Go to the documentation of this file.
1/*************************************************************************************
2 * Original code copyright (C) 2012 Steve Folta
3 * Converted to Juce module (C) 2016 Leo Olivers
4 * Forked from https://github.com/stevefolta/SFZero
5 * For license info please see the LICENSE file distributed with this source code
6 *************************************************************************************/
7#include "SFZReader.h"
8#include "SFZRegion.h"
9#include "SFZSound.h"
10
12
13namespace sfzero
14{
15
16Reader::Reader(Sound *soundIn) : sound_(soundIn), line_(1) {}
17
19
21{
22 water::MemoryBlock contents;
23 bool ok = file.loadFileAsData(contents);
24
25 if (!ok)
26 {
27 sound_->addError("Couldn't read \"" + file.getFullPathName() + "\"");
28 return;
29 }
30
31 read(static_cast<const char *>(contents.getData()), static_cast<int>(contents.getSize()));
32}
33
34void Reader::read(const char *text, unsigned int length)
35{
36 const char *p = text;
37 const char *end = text + length;
38 char c = 0;
39
40 Region curGlobal;
41 Region curGroup;
42 Region curRegion;
43 Region *buildingRegion = nullptr;
44 bool inComment = false;
45 bool inControl = false;
46 bool inGroup = false;
47 water::String defaultPath;
48
49 while (p < end)
50 {
51 // We're at the start of a line; skip any whitespace.
52 while (p < end)
53 {
54 c = *p;
55 if ((c != ' ') && (c != '\t'))
56 {
57 break;
58 }
59 p += 1;
60 }
61 if (p >= end)
62 {
63 break;
64 }
65
66 // Check if it's a comment line.
67 if (c == '/')
68 {
69 // Skip to end of line or c-style comment.
70 do
71 {
72 c = *++p;
73 if (inComment)
74 {
75 if (c != '*')
76 continue;
77 c = *++p;
78 if (c != '/')
79 continue;
80 inComment = false;
81 }
82 if (c == '*')
83 {
84 inComment = true;
85 continue;
86 }
87 if ((c == '\n') || (c == '\r'))
88 {
89 break;
90 }
91 }
92 while (p < end);
93 inComment = false;
94 p = handleLineEnd(p);
95 continue;
96 }
97
98 // Check if it's a blank line.
99 if ((c == '\r') || (c == '\n'))
100 {
101 p = handleLineEnd(p);
102 continue;
103 }
104
105 // Handle elements on the line.
106 while (p < end)
107 {
108 c = *p;
109
110 // Tag.
111 if (c == '<')
112 {
113 p += 1;
114 const char *tagStart = p;
115 while (p < end)
116 {
117 c = *p++;
118 if ((c == '\n') || (c == '\r'))
119 {
120 error("Unterminated tag");
121 goto fatalError;
122 }
123 else if (c == '>')
124 {
125 break;
126 }
127 }
128 if (p >= end)
129 {
130 error("Unterminated tag");
131 goto fatalError;
132 }
133 StringSlice tag(tagStart, p - 1);
134 if (tag == "global")
135 {
136 curGlobal.clear();
137 buildingRegion = &curGlobal;
138 inControl = false;
139 inGroup = false;
140 }
141 else if (tag == "region")
142 {
143 if (buildingRegion && (buildingRegion == &curRegion))
144 {
145 finishRegion(&curRegion);
146 }
147 curRegion = curGroup;
148 buildingRegion = &curRegion;
149 inControl = false;
150 inGroup = false;
151 }
152 else if (tag == "group")
153 {
154 if (buildingRegion && (buildingRegion == &curRegion))
155 {
156 finishRegion(&curRegion);
157 }
158 if (! inGroup)
159 {
160 curGroup = curGlobal;
161 buildingRegion = &curGroup;
162 inControl = false;
163 inGroup = true;
164 }
165 }
166 else if (tag == "control")
167 {
168 if (buildingRegion && (buildingRegion == &curRegion))
169 {
170 finishRegion(&curRegion);
171 }
172 curGroup.clear();
173 buildingRegion = nullptr;
174 inControl = true;
175 }
176 else
177 {
178 error("Illegal tag");
179 }
180 }
181 // Comment.
182 else if (c == '/')
183 {
184 // Skip to end of line or c-style comment.
185 do
186 {
187 c = *++p;
188 if (inComment)
189 {
190 if (c != '*')
191 continue;
192 c = *++p;
193 if (c != '/')
194 continue;
195 inComment = false;
196 }
197 if (c == '*')
198 {
199 inComment = true;
200 continue;
201 }
202 if ((c == '\r') || (c == '\n'))
203 {
204 break;
205 }
206 }
207 while (p < end);
208 inComment = false;
209 }
210 // Parameter.
211 else
212 {
213 // Get the parameter name.
214 const char *parameterStart = p;
215 while (p < end)
216 {
217 c = *p++;
218 if ((c == '=') || (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n'))
219 {
220 break;
221 }
222 }
223 if ((p >= end) || (c != '='))
224 {
225 error("Malformed parameter");
226 goto nextElement;
227 }
228 StringSlice opcode(parameterStart, p - 1);
229 if (inControl)
230 {
231 if (opcode == "default_path")
232 {
233 p = readPathInto(&defaultPath, p, end);
234 }
235 else
236 {
237 const char *valueStart = p;
238 while (p < end)
239 {
240 c = *p;
241 if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))
242 {
243 break;
244 }
245 p++;
246 }
247 water::String value(valueStart, (water::uint64)(p - valueStart));
248 water::String fauxOpcode = water::String(opcode.getStart(), opcode.length()) + " (in <control>)";
249 sound_->addUnsupportedOpcode(fauxOpcode);
250 }
251 }
252 else if (opcode == "sample")
253 {
254 water::String path;
255 p = readPathInto(&path, p, end);
256 if (!path.isEmpty())
257 {
258 if (buildingRegion)
259 {
260 buildingRegion->sample = sound_->addSample(path, defaultPath);
261 }
262 else
263 {
264 error("Adding sample outside a group or region");
265 }
266 }
267 else
268 {
269 error("Empty sample path");
270 }
271 }
272 else
273 {
274 const char *valueStart = p;
275 while (p < end)
276 {
277 c = *p;
278 if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))
279 {
280 break;
281 }
282 p++;
283 }
284 water::String value(valueStart, (water::uint64)(p - valueStart));
285 if (buildingRegion == nullptr)
286 {
287 error("Setting a parameter outside a region or group");
288 }
289 else if (opcode == "lokey")
290 {
291 buildingRegion->lokey = keyValue(value);
292 }
293 else if (opcode == "hikey")
294 {
295 buildingRegion->hikey = keyValue(value);
296 }
297 else if (opcode == "key")
298 {
299 buildingRegion->hikey = buildingRegion->lokey = buildingRegion->pitch_keycenter = keyValue(value);
300 }
301 else if (opcode == "lovel")
302 {
303 buildingRegion->lovel = value.getIntValue();
304 }
305 else if (opcode == "hivel")
306 {
307 buildingRegion->hivel = value.getIntValue();
308 }
309 else if (opcode == "trigger")
310 {
311 buildingRegion->trigger = static_cast<Region::Trigger>(triggerValue(value));
312 }
313 else if (opcode == "group")
314 {
315 buildingRegion->group = static_cast<int>(value.getLargeIntValue());
316 }
317 else if (opcode == "off_by" || opcode == "offby")
318 {
319 buildingRegion->off_by = value.getLargeIntValue();
320 }
321 else if (opcode == "offset")
322 {
323 buildingRegion->offset = value.getLargeIntValue();
324 }
325 else if (opcode == "end")
326 {
327 water::int64 end2 = value.getLargeIntValue();
328 if (end2 < 0)
329 {
330 buildingRegion->negative_end = true;
331 }
332 else
333 {
334 buildingRegion->end = end2;
335 }
336 }
337 else if (opcode == "loop_mode" || opcode == "loopmode")
338 {
339 bool modeIsSupported = value == "no_loop" || value == "one_shot" || value == "loop_continuous";
340 if (modeIsSupported)
341 {
342 buildingRegion->loop_mode = static_cast<Region::LoopMode>(loopModeValue(value));
343 }
344 else
345 {
346 water::String fauxOpcode = water::String(opcode.getStart(), opcode.length()) + "=" + value;
347 sound_->addUnsupportedOpcode(fauxOpcode);
348 }
349 }
350 else if (opcode == "loop_start" || opcode == "loopstart")
351 {
352 buildingRegion->loop_start = value.getLargeIntValue();
353 }
354 else if (opcode == "loop_end" || opcode == "loopend")
355 {
356 buildingRegion->loop_end = value.getLargeIntValue();
357 }
358 else if (opcode == "transpose")
359 {
360 buildingRegion->transpose = value.getIntValue();
361 }
362 else if (opcode == "tune")
363 {
364 buildingRegion->tune = value.getIntValue();
365 }
366 else if (opcode == "pitch_keycenter")
367 {
368 buildingRegion->pitch_keycenter = keyValue(value);
369 }
370 else if (opcode == "pitch_keytrack")
371 {
372 buildingRegion->pitch_keytrack = value.getIntValue();
373 }
374 else if (opcode == "bend_up" || opcode == "bendup")
375 {
376 buildingRegion->bend_up = value.getIntValue();
377 }
378 else if (opcode == "bend_down" || opcode == "benddown")
379 {
380 buildingRegion->bend_down = value.getIntValue();
381 }
382 else if (opcode == "volume")
383 {
384 buildingRegion->volume = value.getFloatValue();
385 }
386 else if (opcode == "pan")
387 {
388 buildingRegion->pan = value.getFloatValue();
389 }
390 else if (opcode == "amp_veltrack")
391 {
392 buildingRegion->amp_veltrack = value.getFloatValue();
393 }
394 else if (opcode == "ampeg_delay")
395 {
396 buildingRegion->ampeg.delay = value.getFloatValue();
397 }
398 else if (opcode == "ampeg_start")
399 {
400 buildingRegion->ampeg.start = value.getFloatValue();
401 }
402 else if (opcode == "ampeg_attack")
403 {
404 buildingRegion->ampeg.attack = value.getFloatValue();
405 }
406 else if (opcode == "ampeg_hold")
407 {
408 buildingRegion->ampeg.hold = value.getFloatValue();
409 }
410 else if (opcode == "ampeg_decay")
411 {
412 buildingRegion->ampeg.decay = value.getFloatValue();
413 }
414 else if (opcode == "ampeg_sustain")
415 {
416 buildingRegion->ampeg.sustain = value.getFloatValue();
417 }
418 else if (opcode == "ampeg_release")
419 {
420 buildingRegion->ampeg.release = value.getFloatValue();
421 }
422 else if (opcode == "ampeg_vel2delay")
423 {
424 buildingRegion->ampeg_veltrack.delay = value.getFloatValue();
425 }
426 else if (opcode == "ampeg_vel2attack")
427 {
428 buildingRegion->ampeg_veltrack.attack = value.getFloatValue();
429 }
430 else if (opcode == "ampeg_vel2hold")
431 {
432 buildingRegion->ampeg_veltrack.hold = value.getFloatValue();
433 }
434 else if (opcode == "ampeg_vel2decay")
435 {
436 buildingRegion->ampeg_veltrack.decay = value.getFloatValue();
437 }
438 else if (opcode == "ampeg_vel2sustain")
439 {
440 buildingRegion->ampeg_veltrack.sustain = value.getFloatValue();
441 }
442 else if (opcode == "ampeg_vel2release")
443 {
444 buildingRegion->ampeg_veltrack.release = value.getFloatValue();
445 }
446 else if (opcode == "default_path")
447 {
448 error("\"default_path\" outside of <control> tag");
449 }
450 else
451 {
452 sound_->addUnsupportedOpcode(water::String(opcode.getStart(), opcode.length()));
453 }
454 }
455 }
456
457 // Skip to next element.
458 nextElement:
459 c = 0;
460 while (p < end)
461 {
462 c = *p;
463 if ((c != ' ') && (c != '\t'))
464 {
465 break;
466 }
467 p += 1;
468 }
469 if ((c == '\r') || (c == '\n'))
470 {
471 p = handleLineEnd(p);
472 break;
473 }
474 }
475 }
476
477fatalError:
478 if (buildingRegion && (buildingRegion == &curRegion))
479 {
480 finishRegion(buildingRegion);
481 }
482}
483
484const char *Reader::handleLineEnd(const char *p)
485{
486 // Check for DOS-style line ending.
487 char lineEndChar = *p++;
488
489 if ((lineEndChar == '\r') && (*p == '\n'))
490 {
491 p += 1;
492 }
493 line_ += 1;
494 return p;
495}
496
497const char *Reader::readPathInto(water::String *pathOut, const char *pIn, const char *endIn)
498{
499 // Paths are kind of funny to parse because they can contain whitespace.
500 const char *p = pIn;
501 const char *end = endIn;
502 const char *pathStart = p;
503 const char *potentialEnd = nullptr;
504
505 while (p < end)
506 {
507 char c = *p;
508 if (c == ' ')
509 {
510 // Is this space part of the path? Or the start of the next opcode? We
511 // don't know yet.
512 potentialEnd = p;
513 p += 1;
514 // Skip any more spaces.
515 while (p < end && *p == ' ')
516 {
517 p += 1;
518 }
519 }
520 else if ((c == '\n') || (c == '\r') || (c == '\t'))
521 {
522 break;
523 }
524 else if (c == '=')
525 {
526 // We've been looking at an opcode; we need to rewind to
527 // potentialEnd.
528 p = potentialEnd;
529 break;
530 }
531 p += 1;
532 }
533 if (p > pathStart)
534 {
535 // Can't do this:
536 // water::String path(CharPointer_UTF8(pathStart), CharPointer_UTF8(p));
537 // It won't compile for some unfathomable reason.
539 water::String path(water::CharPointer_UTF8(pathStart), end2);
540 *pathOut = path;
541 }
542 else
543 {
544 *pathOut = water::String();
545 }
546 return p;
547}
548
550{
551 const char* const chars = str.toRawUTF8();
552
553 char c = chars[0];
554
555 if ((c >= '0') && (c <= '9'))
556 {
557 return str.getIntValue();
558 }
559
560 int note = 0;
561 static const int notes[] = {
562 12 + 0, 12 + 2, 3, 5, 7, 8, 10,
563 };
564 if ((c >= 'A') && (c <= 'G'))
565 {
566 note = notes[c - 'A'];
567 }
568 else if ((c >= 'a') && (c <= 'g'))
569 {
570 note = notes[c - 'a'];
571 }
572 int octaveStart = 1;
573
574 c = chars[1];
575 if ((c == 'b') || (c == '#'))
576 {
577 octaveStart += 1;
578 if (c == 'b')
579 {
580 note -= 1;
581 }
582 else
583 {
584 note += 1;
585 }
586 }
587
588 int octave = str.substring(octaveStart).getIntValue();
589 // A3 == 57.
590 int result = octave * 12 + note + (57 - 4 * 12);
591 return result;
592}
593
595{
596 if (str == "release")
597 {
598 return Region::release;
599 }
600 if (str == "first")
601 {
602 return Region::first;
603 }
604 if (str == "legato")
605 {
606 return Region::legato;
607 }
608 return Region::attack;
609}
610
612{
613 if (str == "no_loop")
614 {
615 return Region::no_loop;
616 }
617 if (str == "one_shot")
618 {
619 return Region::one_shot;
620 }
621 if (str == "loop_continuous")
622 {
624 }
625 if (str == "loop_sustain")
626 {
628 }
629 return Region::sample_loop;
630}
631
633{
634 Region *newRegion = new Region();
635
636 *newRegion = *region;
637 sound_->addRegion(newRegion);
638}
639
641{
642 water::String fullMessage = message;
643
644 fullMessage += " (line " + water::String(line_) + ").";
645 sound_->addError(fullMessage);
646}
647
648}
opcode
Definition Spc_Cpu.h:173
static void message(int level, const char *fmt,...)
Definition adplugdb.cpp:120
void finishRegion(Region *region)
Definition SFZReader.cpp:632
int keyValue(const water::String &str)
Definition SFZReader.cpp:549
const char * handleLineEnd(const char *p)
Definition SFZReader.cpp:484
void read(const water::File &file)
Definition SFZReader.cpp:20
int line_
Definition SFZReader.h:39
int triggerValue(const water::String &str)
Definition SFZReader.cpp:594
void error(const water::String &message)
Definition SFZReader.cpp:640
~Reader()
Definition SFZReader.cpp:18
int loopModeValue(const water::String &str)
Definition SFZReader.cpp:611
Reader(Sound *sound)
Definition SFZReader.cpp:16
Sound * sound_
Definition SFZReader.h:38
const char * readPathInto(water::String *pathOut, const char *p, const char *end)
Definition SFZReader.cpp:497
Definition SFZSound.h:22
Definition SFZReader.h:45
Definition CharPointer_UTF8.h:45
Definition File.h:50
Definition MemoryBlock.h:39
size_t getSize() const noexcept
Definition MemoryBlock.h:102
void * getData() const noexcept
Definition MemoryBlock.h:91
Definition String.h:48
String substring(int startIndex, int endIndex) const
Definition String.cpp:1373
const char * toRawUTF8() const
Definition String.cpp:1925
int getIntValue() const noexcept
Definition String.cpp:1781
bool isEmpty() const noexcept
Definition String.h:238
static PuglViewHint int value
Definition pugl.h:1708
Definition SFZDebug.cpp:11
unsigned long long uint64
Definition water.h:102
long long int64
Definition water.h:100
png_uint_32 length
Definition png.c:2247
float release
Definition SFZRegion.h:23
float hold
Definition SFZRegion.h:23
float delay
Definition SFZRegion.h:23
float sustain
Definition SFZRegion.h:23
float attack
Definition SFZRegion.h:23
float decay
Definition SFZRegion.h:23
float start
Definition SFZRegion.h:23
Definition SFZRegion.h:30
float amp_veltrack
Definition SFZRegion.h:83
int lokey
Definition SFZRegion.h:65
Sample * sample
Definition SFZRegion.h:64
int group
Definition SFZRegion.h:68
EGParameters ampeg_veltrack
Definition SFZRegion.h:85
Trigger trigger
Definition SFZRegion.h:67
EGParameters ampeg
Definition SFZRegion.h:85
water::int64 offset
Definition SFZRegion.h:72
float pan
Definition SFZRegion.h:82
int tune
Definition SFZRegion.h:78
int pitch_keycenter
Definition SFZRegion.h:79
bool negative_end
Definition SFZRegion.h:74
water::int64 off_by
Definition SFZRegion.h:69
water::int64 end
Definition SFZRegion.h:73
int bend_down
Definition SFZRegion.h:80
void clear()
Definition SFZRegion.cpp:33
int pitch_keytrack
Definition SFZRegion.h:79
int bend_up
Definition SFZRegion.h:80
water::int64 loop_end
Definition SFZRegion.h:76
int hivel
Definition SFZRegion.h:66
int hikey
Definition SFZRegion.h:65
LoopMode loop_mode
Definition SFZRegion.h:75
Trigger
Definition SFZRegion.h:32
@ legato
Definition SFZRegion.h:36
@ release
Definition SFZRegion.h:34
@ first
Definition SFZRegion.h:35
@ attack
Definition SFZRegion.h:33
LoopMode
Definition SFZRegion.h:40
@ no_loop
Definition SFZRegion.h:42
@ sample_loop
Definition SFZRegion.h:41
@ loop_sustain
Definition SFZRegion.h:45
@ one_shot
Definition SFZRegion.h:43
@ loop_continuous
Definition SFZRegion.h:44
int transpose
Definition SFZRegion.h:77
float volume
Definition SFZRegion.h:82
water::int64 loop_start
Definition SFZRegion.h:76
int lovel
Definition SFZRegion.h:66
const char * text
Definition swell-functions.h:167
uch * p
Definition crypt.c:594
return c
Definition crypt.c:175
int error
Definition extract.c:1038
int result
Definition process.c:1455
struct zdirent * file
Definition win32.c:1500