|
- /*************************************************************************************
- * Original code copyright (C) 2012 Steve Folta
- * Converted to Juce module (C) 2016 Leo Olivers
- * Forked from https://github.com/stevefolta/SFZero
- * For license info please see the LICENSE file distributed with this source code
- *************************************************************************************/
- #include "SFZReader.h"
- #include "SFZRegion.h"
- #include "SFZSound.h"
-
- #include "water/memory/MemoryBlock.h"
-
- namespace sfzero
- {
-
- Reader::Reader(Sound *soundIn) : sound_(soundIn), line_(1) {}
-
- Reader::~Reader() {}
-
- void Reader::read(const water::File &file)
- {
- water::MemoryBlock contents;
- bool ok = file.loadFileAsData(contents);
-
- if (!ok)
- {
- sound_->addError("Couldn't read \"" + file.getFullPathName() + "\"");
- return;
- }
-
- read(static_cast<const char *>(contents.getData()), static_cast<int>(contents.getSize()));
- }
-
- void Reader::read(const char *text, unsigned int length)
- {
- const char *p = text;
- const char *end = text + length;
- char c = 0;
-
- Region curGlobal;
- Region curGroup;
- Region curRegion;
- Region *buildingRegion = nullptr;
- bool inComment = false;
- bool inControl = false;
- bool inGroup = false;
- water::String defaultPath;
-
- while (p < end)
- {
- // We're at the start of a line; skip any whitespace.
- while (p < end)
- {
- c = *p;
- if ((c != ' ') && (c != '\t'))
- {
- break;
- }
- p += 1;
- }
- if (p >= end)
- {
- break;
- }
-
- // Check if it's a comment line.
- if (c == '/')
- {
- // Skip to end of line or c-style comment.
- do
- {
- c = *++p;
- if (inComment)
- {
- if (c != '*')
- continue;
- c = *++p;
- if (c != '/')
- continue;
- inComment = false;
- }
- if (c == '*')
- {
- inComment = true;
- continue;
- }
- if ((c == '\n') || (c == '\r'))
- {
- break;
- }
- }
- while (p < end);
- inComment = false;
- p = handleLineEnd(p);
- continue;
- }
-
- // Check if it's a blank line.
- if ((c == '\r') || (c == '\n'))
- {
- p = handleLineEnd(p);
- continue;
- }
-
- // Handle elements on the line.
- while (p < end)
- {
- c = *p;
-
- // Tag.
- if (c == '<')
- {
- p += 1;
- const char *tagStart = p;
- while (p < end)
- {
- c = *p++;
- if ((c == '\n') || (c == '\r'))
- {
- error("Unterminated tag");
- goto fatalError;
- }
- else if (c == '>')
- {
- break;
- }
- }
- if (p >= end)
- {
- error("Unterminated tag");
- goto fatalError;
- }
- StringSlice tag(tagStart, p - 1);
- if (tag == "global")
- {
- curGlobal.clear();
- buildingRegion = &curGlobal;
- inControl = false;
- inGroup = false;
- }
- else if (tag == "region")
- {
- if (buildingRegion && (buildingRegion == &curRegion))
- {
- finishRegion(&curRegion);
- }
- curRegion = curGroup;
- buildingRegion = &curRegion;
- inControl = false;
- inGroup = false;
- }
- else if (tag == "group")
- {
- if (buildingRegion && (buildingRegion == &curRegion))
- {
- finishRegion(&curRegion);
- }
- if (! inGroup)
- {
- curGroup = curGlobal;
- buildingRegion = &curGroup;
- inControl = false;
- inGroup = true;
- }
- }
- else if (tag == "control")
- {
- if (buildingRegion && (buildingRegion == &curRegion))
- {
- finishRegion(&curRegion);
- }
- curGroup.clear();
- buildingRegion = nullptr;
- inControl = true;
- }
- else
- {
- error("Illegal tag");
- }
- }
- // Comment.
- else if (c == '/')
- {
- // Skip to end of line or c-style comment.
- do
- {
- c = *++p;
- if (inComment)
- {
- if (c != '*')
- continue;
- c = *++p;
- if (c != '/')
- continue;
- inComment = false;
- }
- if (c == '*')
- {
- inComment = true;
- continue;
- }
- if ((c == '\r') || (c == '\n'))
- {
- break;
- }
- }
- while (p < end);
- inComment = false;
- }
- // Parameter.
- else
- {
- // Get the parameter name.
- const char *parameterStart = p;
- while (p < end)
- {
- c = *p++;
- if ((c == '=') || (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n'))
- {
- break;
- }
- }
- if ((p >= end) || (c != '='))
- {
- error("Malformed parameter");
- goto nextElement;
- }
- StringSlice opcode(parameterStart, p - 1);
- if (inControl)
- {
- if (opcode == "default_path")
- {
- p = readPathInto(&defaultPath, p, end);
- }
- else
- {
- const char *valueStart = p;
- while (p < end)
- {
- c = *p;
- if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))
- {
- break;
- }
- p++;
- }
- water::String value(valueStart, (water::uint64)(p - valueStart));
- water::String fauxOpcode = water::String(opcode.getStart(), opcode.length()) + " (in <control>)";
- sound_->addUnsupportedOpcode(fauxOpcode);
- }
- }
- else if (opcode == "sample")
- {
- water::String path;
- p = readPathInto(&path, p, end);
- if (!path.isEmpty())
- {
- if (buildingRegion)
- {
- buildingRegion->sample = sound_->addSample(path, defaultPath);
- }
- else
- {
- error("Adding sample outside a group or region");
- }
- }
- else
- {
- error("Empty sample path");
- }
- }
- else
- {
- const char *valueStart = p;
- while (p < end)
- {
- c = *p;
- if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))
- {
- break;
- }
- p++;
- }
- water::String value(valueStart, (water::uint64)(p - valueStart));
- if (buildingRegion == nullptr)
- {
- error("Setting a parameter outside a region or group");
- }
- else if (opcode == "lokey")
- {
- buildingRegion->lokey = keyValue(value);
- }
- else if (opcode == "hikey")
- {
- buildingRegion->hikey = keyValue(value);
- }
- else if (opcode == "key")
- {
- buildingRegion->hikey = buildingRegion->lokey = buildingRegion->pitch_keycenter = keyValue(value);
- }
- else if (opcode == "lovel")
- {
- buildingRegion->lovel = value.getIntValue();
- }
- else if (opcode == "hivel")
- {
- buildingRegion->hivel = value.getIntValue();
- }
- else if (opcode == "trigger")
- {
- buildingRegion->trigger = static_cast<Region::Trigger>(triggerValue(value));
- }
- else if (opcode == "group")
- {
- buildingRegion->group = static_cast<int>(value.getLargeIntValue());
- }
- else if (opcode == "off_by" || opcode == "offby")
- {
- buildingRegion->off_by = value.getLargeIntValue();
- }
- else if (opcode == "offset")
- {
- buildingRegion->offset = value.getLargeIntValue();
- }
- else if (opcode == "end")
- {
- water::int64 end2 = value.getLargeIntValue();
- if (end2 < 0)
- {
- buildingRegion->negative_end = true;
- }
- else
- {
- buildingRegion->end = end2;
- }
- }
- else if (opcode == "loop_mode" || opcode == "loopmode")
- {
- bool modeIsSupported = value == "no_loop" || value == "one_shot" || value == "loop_continuous";
- if (modeIsSupported)
- {
- buildingRegion->loop_mode = static_cast<Region::LoopMode>(loopModeValue(value));
- }
- else
- {
- water::String fauxOpcode = water::String(opcode.getStart(), opcode.length()) + "=" + value;
- sound_->addUnsupportedOpcode(fauxOpcode);
- }
- }
- else if (opcode == "loop_start" || opcode == "loopstart")
- {
- buildingRegion->loop_start = value.getLargeIntValue();
- }
- else if (opcode == "loop_end" || opcode == "loopend")
- {
- buildingRegion->loop_end = value.getLargeIntValue();
- }
- else if (opcode == "transpose")
- {
- buildingRegion->transpose = value.getIntValue();
- }
- else if (opcode == "tune")
- {
- buildingRegion->tune = value.getIntValue();
- }
- else if (opcode == "pitch_keycenter")
- {
- buildingRegion->pitch_keycenter = keyValue(value);
- }
- else if (opcode == "pitch_keytrack")
- {
- buildingRegion->pitch_keytrack = value.getIntValue();
- }
- else if (opcode == "bend_up" || opcode == "bendup")
- {
- buildingRegion->bend_up = value.getIntValue();
- }
- else if (opcode == "bend_down" || opcode == "benddown")
- {
- buildingRegion->bend_down = value.getIntValue();
- }
- else if (opcode == "volume")
- {
- buildingRegion->volume = value.getFloatValue();
- }
- else if (opcode == "pan")
- {
- buildingRegion->pan = value.getFloatValue();
- }
- else if (opcode == "amp_veltrack")
- {
- buildingRegion->amp_veltrack = value.getFloatValue();
- }
- else if (opcode == "ampeg_delay")
- {
- buildingRegion->ampeg.delay = value.getFloatValue();
- }
- else if (opcode == "ampeg_start")
- {
- buildingRegion->ampeg.start = value.getFloatValue();
- }
- else if (opcode == "ampeg_attack")
- {
- buildingRegion->ampeg.attack = value.getFloatValue();
- }
- else if (opcode == "ampeg_hold")
- {
- buildingRegion->ampeg.hold = value.getFloatValue();
- }
- else if (opcode == "ampeg_decay")
- {
- buildingRegion->ampeg.decay = value.getFloatValue();
- }
- else if (opcode == "ampeg_sustain")
- {
- buildingRegion->ampeg.sustain = value.getFloatValue();
- }
- else if (opcode == "ampeg_release")
- {
- buildingRegion->ampeg.release = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2delay")
- {
- buildingRegion->ampeg_veltrack.delay = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2attack")
- {
- buildingRegion->ampeg_veltrack.attack = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2hold")
- {
- buildingRegion->ampeg_veltrack.hold = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2decay")
- {
- buildingRegion->ampeg_veltrack.decay = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2sustain")
- {
- buildingRegion->ampeg_veltrack.sustain = value.getFloatValue();
- }
- else if (opcode == "ampeg_vel2release")
- {
- buildingRegion->ampeg_veltrack.release = value.getFloatValue();
- }
- else if (opcode == "default_path")
- {
- error("\"default_path\" outside of <control> tag");
- }
- else
- {
- sound_->addUnsupportedOpcode(water::String(opcode.getStart(), opcode.length()));
- }
- }
- }
-
- // Skip to next element.
- nextElement:
- c = 0;
- while (p < end)
- {
- c = *p;
- if ((c != ' ') && (c != '\t'))
- {
- break;
- }
- p += 1;
- }
- if ((c == '\r') || (c == '\n'))
- {
- p = handleLineEnd(p);
- break;
- }
- }
- }
-
- fatalError:
- if (buildingRegion && (buildingRegion == &curRegion))
- {
- finishRegion(buildingRegion);
- }
- }
-
- const char *Reader::handleLineEnd(const char *p)
- {
- // Check for DOS-style line ending.
- char lineEndChar = *p++;
-
- if ((lineEndChar == '\r') && (*p == '\n'))
- {
- p += 1;
- }
- line_ += 1;
- return p;
- }
-
- const char *Reader::readPathInto(water::String *pathOut, const char *pIn, const char *endIn)
- {
- // Paths are kind of funny to parse because they can contain whitespace.
- const char *p = pIn;
- const char *end = endIn;
- const char *pathStart = p;
- const char *potentialEnd = nullptr;
-
- while (p < end)
- {
- char c = *p;
- if (c == ' ')
- {
- // Is this space part of the path? Or the start of the next opcode? We
- // don't know yet.
- potentialEnd = p;
- p += 1;
- // Skip any more spaces.
- while (p < end && *p == ' ')
- {
- p += 1;
- }
- }
- else if ((c == '\n') || (c == '\r') || (c == '\t'))
- {
- break;
- }
- else if (c == '=')
- {
- // We've been looking at an opcode; we need to rewind to
- // potentialEnd.
- p = potentialEnd;
- break;
- }
- p += 1;
- }
- if (p > pathStart)
- {
- // Can't do this:
- // water::String path(CharPointer_UTF8(pathStart), CharPointer_UTF8(p));
- // It won't compile for some unfathomable reason.
- water::CharPointer_UTF8 end2(p);
- water::String path(water::CharPointer_UTF8(pathStart), end2);
- *pathOut = path;
- }
- else
- {
- *pathOut = water::String();
- }
- return p;
- }
-
- int Reader::keyValue(const water::String &str)
- {
- const char* const chars = str.toRawUTF8();
-
- char c = chars[0];
-
- if ((c >= '0') && (c <= '9'))
- {
- return str.getIntValue();
- }
-
- int note = 0;
- static const int notes[] = {
- 12 + 0, 12 + 2, 3, 5, 7, 8, 10,
- };
- if ((c >= 'A') && (c <= 'G'))
- {
- note = notes[c - 'A'];
- }
- else if ((c >= 'a') && (c <= 'g'))
- {
- note = notes[c - 'a'];
- }
- int octaveStart = 1;
-
- c = chars[1];
- if ((c == 'b') || (c == '#'))
- {
- octaveStart += 1;
- if (c == 'b')
- {
- note -= 1;
- }
- else
- {
- note += 1;
- }
- }
-
- int octave = str.substring(octaveStart).getIntValue();
- // A3 == 57.
- int result = octave * 12 + note + (57 - 4 * 12);
- return result;
- }
-
- int Reader::triggerValue(const water::String &str)
- {
- if (str == "release")
- {
- return Region::release;
- }
- if (str == "first")
- {
- return Region::first;
- }
- if (str == "legato")
- {
- return Region::legato;
- }
- return Region::attack;
- }
-
- int Reader::loopModeValue(const water::String &str)
- {
- if (str == "no_loop")
- {
- return Region::no_loop;
- }
- if (str == "one_shot")
- {
- return Region::one_shot;
- }
- if (str == "loop_continuous")
- {
- return Region::loop_continuous;
- }
- if (str == "loop_sustain")
- {
- return Region::loop_sustain;
- }
- return Region::sample_loop;
- }
-
- void Reader::finishRegion(Region *region)
- {
- Region *newRegion = new Region();
-
- *newRegion = *region;
- sound_->addRegion(newRegion);
- }
-
- void Reader::error(const water::String &message)
- {
- water::String fullMessage = message;
-
- fullMessage += " (line " + water::String(line_) + ").";
- sound_->addError(fullMessage);
- }
-
- }
|