diff --git a/CMakeLists.txt b/CMakeLists.txt index b81e422..964852a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,14 @@ -# CMakeList.txt : convert の CMake プロジェクト。ソースを含めて、次を定義します: +# CMakeList.txt : rlib-MML の CMake プロジェクト。ソースを含めて、次を定義します: # プロジェクト専用ロジックはこちらです。 # cmake_minimum_required (VERSION 3.8) +# サポートされている場合は、MSVC コンパイラのホット リロードを有効にします。 +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + # デフォルトは Release ビルド if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING @@ -29,6 +35,7 @@ TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_program_options) TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_regex) + # TODO: テストを追加し、必要な場合は、ターゲットをインストールします。 project("smftomml") @@ -41,5 +48,6 @@ add_executable( ${CMAKE_PROJECT_NAME} # ライブラリ TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} stdc++fs) -TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_program_options) -TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_regex) +TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_program_options boost_regex) +TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} boost_locale) +#TARGET_LINK_LIBRARIES( ${CMAKE_PROJECT_NAME} icui18n icuuc) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f4bc98b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,101 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "macos-debug", + "displayName": "macOS Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + } + ] +} diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index 9509ae6..0000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "configurations": [ - { - "name": "WSL-GCC-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeExecutable": "cmake", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_x64" ], - "wslPath": "${defaultWSLPath}", - "addressSanitizerRuntimeFlags": "detect_leaks=0", - "variables": [] - }, - { - "name": "WSL-GCC-Release", - "generator": "Ninja", - "configurationType": "Release", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeExecutable": "cmake", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_x64" ], - "wslPath": "${defaultWSLPath}", - "addressSanitizerRuntimeFlags": "detect_leaks=0", - "variables": [] - } - ] -} \ No newline at end of file diff --git a/README.ja.md b/README.ja.md index dd4387b..df008e0 100644 --- a/README.ja.md +++ b/README.ja.md @@ -65,7 +65,10 @@ C++17 環境でコンパイルできます。ビルドには boost が必要で |PitchBend([ベンド値])| ピッチベンドです。
値は -8192(2音下)~8191(2音上)で、中央は0です。| "PitchBend(-4096) cde Pan(0) cde"
→ 半音下げたドレミと通常のドレミです| |CC([コントロール番号],[値])
別名 ContorlChange| コントロールチェンジです。
第1引数がコントロール番号、第1引数が値です| "CC(0,10)CC(32,130)@2" バンクセレクトしたプログラムチェンジです。| |CreateSequence(
 name:[シーケンス名],
 mml:[MML],
)|シーケンス(サブシーケンス)を定義します。
楽曲(MML)を部品として定義し、以降のMMLの中で呼び出す(張り付ける)ことができます。| // ドラムパターンを定義します
CreateSequence(name:drum, mml:"
 CreatePort(name:kick, channel:10) l8 o1 c^^c ^c^^
 CreatePort(name:snare, channel:10) l8 o1 ^^d^ ^^d^
")| -|Seq([シーケンス名],length:[長さ(省略可)])
別名 Sequence|定義済のシーケンス(サブシーケンス)を呼び出します。| // 定義したドラムパターンを3回繰り返します。
// 3周目のシ-ケンスは2分音符分のみ採用します
Seq(drum) Seq(drum) Seq(drum,length:2)| +|Seq(
 [シーケンス名],
 length:[長さ(省略可)]
)
別名 Sequence|定義済のシーケンス(サブシーケンス)を呼び出します。| // 定義したドラムパターンを3回繰り返します。
// 3周目のシ-ケンスは2分音符分のみ採用します
Seq(drum) Seq(drum) Seq(drum,length:2)| +|Meta(
 type:[イベントタイプ],
 [データ]...
)|メタイベントです。
typeでイベントタイプを指定します。
名前ナシの可変長引数でデータを指定します。データ長を記述する必要はありません。| // title 情報です
Meta(type:0x1,"The Lost King's Scepter")
// SMPTE オフセットです。
Meta(type:0x54,96,0,0,0,0)| +|DefinePresetFM(
 no:[プログラムナンバー],
 name:[音色名],
 [音色データ]...
)|rlib-MML でFM音源音色を定義するシーケンサー固有のメタイベントです。|DefinePresetFM(no:4,name:"piano",
// AR DR SR RR SL TL KS ML DT
29, 8, 0, 8, 3, 31, 2, 1, 3,
31, 3, 1, 6, 10, 0, 0, 2, 7,
29, 20, 0, 9, 2, 44, 0, 4, 2,
31, 7, 2, 6, 6, 0, 0, 1, 5,
// AL FB
4, 7,
)| + ## 文字列 @@ -74,7 +77,7 @@ C++17 環境でコンパイルできます。ビルドには boost が必要で | 表記 | 説明 |例| | ---- | ---- | ---- | | ○○○ | 英数字のみの場合でこの指定が可能です。| abcdefg | -| "○○○" | " から " までが文字列です。空白や記号を以外を使う場合にも使えます。 | "drum part" +| "○○○" | " から " までが文字列です。空白や記号を使う場合にも使えます。 | "drum part" | R"\*\*(○○○)\*\*" | 空白や記号や " を使う場合にも使えます。
シーケンスの中にシーケンスを定義するような場合は、** を一意の文字列にすることで文字列定義の中に文字列定義があるようなケースに対応できます | R"(drum)"

R"ch1( mml:R"(drum)" )ch1" diff --git a/README.md b/README.md index e3e8431..0d500b2 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,16 @@ Experimental WebAssembly support is also available. | notation | description |example| | ---- | ---- | ---- | -|createPort(
 name:[port name],
 instrument:[instrument name]\(optional),
 channel:[channel number],
)| Define (declare) the port
Channel numbers are 1-16.|"createPort(name:Piano, channel:3)"
→ Declare the port of MIDI channel 3 with the name "Piano".
instrument specifies the name of the instrument. If omitted, the default instrument is used.| -|port([port name])| It is port switching|"port(Piano) cde" → CDE at Port "Piano"| +|CreatePort(
 name:[port name],
 instrument:[instrument name]\(optional),
 channel:[channel number],
)| Define (declare) the port
Channel numbers are 1-16.|"createPort(name:Piano, channel:3)"
→ Declare the port of MIDI channel 3 with the name "Piano".
instrument specifies the name of the instrument. If omitted, the default instrument is used.| +|Port([port name])| It is port switching|"port(Piano) cde" → CDE at Port "Piano"| |V([volume value])
ailias: volume|The volume. Values ​​are 0-127.| "V(120) cde" → CDE at Volume 120.| -|pan([pan value])
ailias: panpot|It is pan (panpot).
Values ​​range from 0 (far left) to 127 (far right), with 64 in the center.| "pan(0) cde" → CDE at Pan 0 (far left).| +|Pan([pan value])
ailias: panpot|It is pan (panpot).
Values ​​range from 0 (far left) to 127 (far right), with 64 in the center.| "pan(0) cde" → CDE at Pan 0 (far left).| |PitchBend([value])| Pitch bend.
Values range from -8192 (two notes down) to 8191 (two notes up), with the centre at 0.| "PitchBend(-4096) cde Pan(0) cde"
→ CDE lowered by a semitone and normal CDE.| |CC([contorl change no],[value])
ailias: ContorlChange| Contorl change.
The first argument is the control number and the first argument is the value| "CC(0,10)CC(32,130)@2" Bank-selected programme change.| |CreateSequence(
 name:[sequence name],
 mml:[MML],
)|Defined Sequence(sub Sequence).
Define songs (MML) as parts and call them in subsequent MMLs.| // Defined rhythm pattern
CreateSequence(name:drum, mml:"
 CreatePort(name:kick, channel:10) l8 o1 c^^c ^c^^
 CreatePort(name:snare, channel:10) l8 o1 ^^d^ ^^d^
")| -|Seq([sequence name],length:[length(optional)])
ailias Sequence|Calls a predefined sequence (sub-sequence).| // Defined rhythmic pattern is repeated three times.
// Only half-note minutes are used for the third round of the sequence.
Seq(drum) Seq(drum) Seq(drum,length:2)| - +|Seq(
 [sequence name],
 length:[length(optional)]
)
ailias Sequence|Calls a predefined sequence (sub-sequence).| // Defined rhythmic pattern is repeated three times.
// Only half-note minutes are used for the third round of the sequence.
Seq(drum) Seq(drum) Seq(drum,length:2)| +|Meta(
 type:[event type],
 [data]...
)|Meta Event.
type specifies the event type.
Specify data with a variable-length argument with no name. It is not necessary to describe the data length.| // title info
Meta(type:0x1,"The Lost King's Scepter")
// SMPTE offset
Meta(type:0x54,96,0,0,0,0)| +|DefinePresetFM(
 no:[program no],
 name:[name],
 [data]...
)|Sequencer specific meta event that defines FM sound tone in rlib-MML.|DefinePresetFM(no:4,name:"piano",
// AR DR SR RR SL TL KS ML DT
29, 8, 0, 8, 3, 31, 2, 1, 3,
31, 3, 1, 6, 10, 0, 0, 2, 7,
29, 20, 0, 9, 2, 44, 0, 4, 2,
31, 7, 2, 6, 6, 0, 0, 1, 5,
// AL FB
4, 7,
)| ## string Where a string is specified, the following formats can be used. diff --git a/json/Json.h b/json/Json.h new file mode 100644 index 0000000..cf0d831 --- /dev/null +++ b/json/Json.h @@ -0,0 +1,957 @@ +// rlib-Json https://github.com/tr-takatsuka/rlib-Json +// This software is released under the CC0. +/* +JSON パーサーです。C++11での実装です。 + ++ JSON 仕様に沿ったデータ構造クラスに、パース(parse)と出力(stringify)機能を付加したものです。 ++ 1つのヘッダーファイルで動作します。boost などの外部ライブラリには依存していません。 ++ JSON5 の一部仕様を実装しています。(オプションで無効に出来ます) + + コメント付きJSONをパース可能です。 + + 末尾のカンマ(最後のカンマ)を許可します。 ++ JSON Pointer を実装しています。 ++ 初期化子リストでの構築が可能です。 ++ 参照や編集等で例外は発生しない設計です。( at() 関数を除く) + + 範囲外の読み込みはデフォルト値が取得され、書き込みの場合は要素を作成します。 ++ javascript と違い、数値は浮動小数点数(double)と整数(std::intmax_t)に区別しています。 ++ 入出力は std::string(UTF-8)のみ対応です。 + + ストリーム入力のパース処理には非対応です。 + +・使い方例 + try { + using Json = rlib::Json; + const Json j = Json::parse( // JSON 文字列から構築 + u8R"({ // allows comments (JSON5) + "n" : -123.456e+2, + "list":[ + 32, + "ABC", // allows Trailing comma (JSON5) + ], + "b": true, + "c": null + })"); + double d0 = j["n"].get(); // -123.456e+2 を取得 + double da = j.at("n").get(); // at() で参照する記述です。(範囲外の場合に例外が発生します) + double d1 = j["e"].get(); // 0.0 を取得 (存在しない位置を指定したのでデフォルト値が取れる) + std::intmax_t n1 = j["n"].get(); // -12346 を取得 (double値を四捨五入した整数値が取れます) + std::string s0 = j["list"][1].get(); // "ABC" を取得 + std::string sa = j.at(Json::Pointer("/list/1")).get(); // JSON Pointerで指定する記述です。 + std::string s1 = j["ary"][9].get(); // 空文字を取得 (存在しない位置を指定したのでデフォルト値が取れる) + Json list = j["list"]; // "list"以下をコピー(複製) + list[10]["add"] = 123; // [10]の位置に {"add":123} を 追加 ( 配列[2~9]の位置は null で埋められる) + bool compare = list == j["list"]; // 比較です。false が返ります。 + std::string json = list.stringify(); // JSON 文字列を取得 + list[10].erase("add"); // [10]の位置の連想配列の要素({"add":123})を削除 + list.erase(9); // [9]の位置の要素(null)を削除 + const Json j1 = Json::Map{ // 初期化子リストでの構築です + {"a", 123}, // { "a": 123, + {"b", Json::Array{456, "ABC", 0.5}}, // "b": [456, "ABC", 0.5], + {"c", Json::Map{ // "c": { + {"d", true}, // "d": true, + {"e", nullptr}, // "e": null + }}, // } + }; // } + std::map map{ {j,0},{j1,1} }; // std::map, set などのキーにすることが可能です + const Json& c = j1.at("f"); // at() で参照すると範囲外の場合に例外が発生します + } catch (rlib::Json::ParseException& e) { // パース 失敗 + std::cerr << e.what() << std::endl; + } catch (std::out_of_range& e) { // 範囲外参照 + std::cerr << e.what() << std::endl; + } +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rlib +{ + template class JsonT + { + public: + // version (major, minor, patch) + static std::tuple version() { + return std::make_tuple(1, 0, 1); // 1.0.1 + } + enum class Type { + Null, // null (デフォルト) + Bool, // bool + Float, // double + Int, // std::intmax_t + String, // std::string + Array, // 配列 + Map, // 連想配列(オブジェクト) + }; + typedef std::vector Array; + typedef std::map Map; + private: + Type m_type; + union { + bool m_bool; + double m_float; + std::intmax_t m_int; + std::string m_string; + Array m_array; + Map m_map; + }; + static const JsonT m_emptyJson; + static const std::string m_emptyString; + static const Array m_emptyArray; + static const Map m_emptyMap; + public: + JsonT() + :m_type(Type::Null) + {} + JsonT(std::nullptr_t) + :m_type(Type::Null) + {} + template JsonT(U n, typename std::enable_if::value>::type* = nullptr) + : m_type(Type::Bool), m_bool(n) + {} + template JsonT(U s, typename std::enable_if::value>::type* = nullptr) + : m_type(Type::Float), m_float(s) + {} + template JsonT(U n, typename std::enable_if::value && !std::is_same::value>::type* = nullptr) + : m_type(Type::Int), m_int(n) + {} + template JsonT(U s, typename std::enable_if::type, std::string>::value || std::is_same::value>::type* = nullptr) + : m_type(Type::String), m_string(std::move(s)) + {} + JsonT(const Array& s) + :m_type(Type::Array), m_array(s) + {} + JsonT(Array&& s) + :m_type(Type::Array), m_array(std::move(s)) + {} + template JsonT(const std::initializer_list& s) + : m_type(Type::Array), m_array(s.begin(), s.end()) + {} + JsonT(const Map& s) + :m_type(Type::Map), m_map(s) + {} + JsonT(Map&& s) + :m_type(Type::Map), m_map(std::move(s)) + {} + JsonT(const JsonT& s) + :m_type(Type::Null) + { + *this = s; + } + JsonT(JsonT&& s) + :m_type(s.m_type) + { + switch (m_type) { + case Type::Null: break; + case Type::Bool: m_bool = s.m_bool; break; + case Type::Float: m_float = s.m_float; break; + case Type::Int: m_int = s.m_int; break; + case Type::String: new(&m_string) decltype(m_string)(std::move(s.m_string)); break; + case Type::Array: new(&m_array) decltype(m_array)(std::move(s.m_array)); break; + case Type::Map: new(&m_map) decltype(m_map)(std::move(s.m_map)); break; + default: assert(false); + } + } + + ~JsonT() { + clear(); + } + + bool operator==(const JsonT& s) const { + if (m_type == s.m_type) { + switch (m_type) { + case Type::Null: return true; + case Type::Bool: return m_bool == s.m_bool; + case Type::Float: return m_float == s.m_float; + case Type::Int: return m_int == s.m_int; + case Type::String: return m_string == s.m_string; + case Type::Array: return m_array == s.m_array; + case Type::Map: return m_map == s.m_map; + } + assert(false); + } + return false; + } + bool operator!=(const JsonT& s) const { + return !(*this == s); + } + + bool operator<(const JsonT& s) const { + if (m_type == s.m_type) { + switch (m_type) { + case Type::Null: return false; + case Type::Bool: return m_bool < s.m_bool; + case Type::Float: return m_float < s.m_float; + case Type::Int: return m_int < s.m_int; + case Type::String: return m_string < s.m_string; + case Type::Array: return m_array < s.m_array; + case Type::Map: return m_map < s.m_map; + } + assert(false); + } + return m_type < s.m_type; + } + bool operator<=(const JsonT& s) const { + return !(s < *this); + } + bool operator>(const JsonT& s) const { + return s < *this; + } + bool operator>=(const JsonT& s) const { + return !(*this < s); + } + + template JsonT& operator=(const S& s) { + *this = JsonT(s); + return *this; + } + + JsonT& operator=(const JsonT& s) { + if (this != &s) { + clear(); + switch (s.m_type) { + case Type::Null: new(this) JsonT(nullptr); break; + case Type::Bool: new(this) JsonT(s.m_bool); break; + case Type::Float: new(this) JsonT(s.m_float); break; + case Type::Int: new(this) JsonT(s.m_int); break; + case Type::String: new(this) JsonT(s.m_string); break; + case Type::Array: new(this) JsonT(s.m_array); break; + case Type::Map: new(this) JsonT(s.m_map); break; + default: assert(false); + } + } + return *this; + } + JsonT& operator=(JsonT&& s) { + if (this != &s) { + clear(); + new(this) JsonT(std::move(s)); + } + return *this; + } + + void clear() { + typedef std::string TypeString; + switch (m_type) { + case Type::String: m_string.~TypeString(); break; + case Type::Array: m_array.~Array(); break; + case Type::Map: m_map.~Map(); break; + default: break; + } + m_type = Type::Null; + } + + // 連想配列を取得 (typeがMapではない場合、Mapに変更した上で返す。連想配列(map)を直接操作したい場合に使うべし) + Map& ensureMap() { + if (m_type != Type::Map) { + clear(); + m_type = Type::Map; + new(&m_map) decltype(m_map)(); + } + return m_map; + } + + // 連想配列から要素を取得 (存在しないキーを指定されたら空実体を返す。例外発生しない) + const JsonT& operator[](const std::string& key) const { + const auto& m = get(); + const auto i = m.find(key); + return i != m.end() ? i->second : m_emptyJson; + } + + // 連想配列から要素(参照)を取得 (取得出来るようキーを追加する) + JsonT& operator[](const std::string& key) { + return ensureMap()[key]; + } + + // 連想配列から要素を取得 (存在しないキーを指定されたら throw std::out_of_range) + const JsonT& at(const std::string& key) const noexcept(false) { + return const_cast::type>::type*>(this)->at(key); + } + JsonT& at(const std::string& key) noexcept(false) { + if (m_type != Type::Map) throw std::out_of_range("not map:" + key); + const auto i = m_map.find(key); + if (i == m_map.end()) throw std::out_of_range("invalid key:" + key); + return i->second; + } + + // 連想配列から指定のキーを削除 (存在しないキーを指定されたら何もしないでfalseを返す) + // 戻り値 true:削除した false:対象のキーが存在しなかった + bool erase(const std::string& key) { + return m_type == Type::Map ? m_map.erase(key) > 0 : false; + } + + // 配列を取得 (typeがArrayではない場合、Arrayに変更した上で返す。配列(vector)を直接操作したい場合に使うべし) + Array& ensureArray() { + if (m_type != Type::Array) { + clear(); + m_type = Type::Array; + new(&m_array) decltype(m_array)(); + } + return m_array; + } + + // 配列から要素を取得 (範囲外指定は空実体を返す。例外発生しない) + const JsonT& operator[](size_t index) const { + const auto& a = get(); + return index < a.size() ? a[index] : m_emptyJson; + } + + // 配列から要素(参照)を取得 (取得出来るよう必要に応じて配列を拡張する) + JsonT& operator[](size_t index) { + auto& a = ensureArray(); + if (index >= a.size()) a.resize(index + 1); // 足りないなら作る + return a[index]; + } + + // 配列から要素を取得 (範囲外指定は throw std::out_of_range) + const JsonT& at(size_t index) const noexcept(false) { + return const_cast::type>::type*>(this)->at(index); + } + JsonT& at(size_t index) noexcept(false) { + if (m_type != Type::Array) throw std::out_of_range("not array"); + if (index >= m_array.size()) throw std::out_of_range("invalid index"); + return m_array[index]; + } + + // 配列から要素を削除 (存在しないindexを指定されたら何もしないでfalseを返す) + // 戻り値 true:削除した false:対象のindexが存在しなかった + bool erase(size_t index) { + if (m_type != Type::Array || index >= m_array.size()) return false; + m_array.erase(m_array.begin() + index); + return true; + } + + Type type() const noexcept { + return m_type; + } + bool isType(Type t) const noexcept { + return type() == t; + } + bool isNull() const noexcept { + return isType(Type::Null); + } + + // bool + template U get(typename std::enable_if::value>::type* = nullptr) const noexcept { + switch (m_type) { + case Type::Bool: return m_bool; + //case Type::Int: return static_cast(m_bool); + default: break; + } + return decltype(m_bool)(); + } + // 浮動小数点数 + template U get(typename std::enable_if::value>::type* = nullptr) const noexcept { + switch (m_type) { + case Type::Float: return static_cast(m_float); + case Type::Int: return static_cast(m_int); + default: break; + } + return decltype(m_float)(); + } + // 整数 + template U get(typename std::enable_if::value && !std::is_same::value >::type* = nullptr) const noexcept { + switch (m_type) { + case Type::Bool: return m_bool; + case Type::Float: return static_cast(std::llroundl(m_float)); + case Type::Int: return static_cast(m_int); + default: break; + } + return decltype(m_int)(); + } + // 文字列 + template const U& get(typename std::enable_if::value >::type* = nullptr) const noexcept { + return m_type == Type::String ? m_string : m_emptyString; + } + // 配列 + template const U& get(typename std::enable_if::value >::type* = nullptr) const noexcept { + return m_type == Type::Array ? m_array : m_emptyArray; + } + // 連想配列(オブジェクト) + template const U& get(typename std::enable_if::value >::type* = nullptr) const noexcept { + return m_type == Type::Map ? m_map : m_emptyMap; + } + + // JSON Pointer + struct Pointer { + const std::string& text; // 使わないが参照用に残しておく + const std::pair> tokens; + Pointer(const std::string& s) + : text(s) + , tokens( + [&s]()->decltype(tokens) { + using std::string; + try { + if (s.empty()) return { true,{} }; + const auto tokens = [&] { // "/" で分割したトークン配列 + static const std::regex re("/"); + const auto t = s + "/"; // 末尾の情報も必要なのでセパレータ追加 + return std::vector{ std::sregex_token_iterator(t.cbegin(), t.cend(), re, -1), std::sregex_token_iterator() }; + }(); + auto j = parse( // jsonパース処理を使う + [&tokens] { + string json; + for (auto& s : tokens) { + string r; + for (auto it = s.cbegin(); it != s.cend();) { + std::smatch m; + static const std::regex re(R"(\~.?)"); + if (!regex_search(it, s.cend(), m, re)) { // "~" escape + r += string(it, s.cend()); + break; + } + static const std::map mapReplace{ + { R"(~0)", R"(~)"}, + { R"(~1)", R"(\/)"}, + }; + const auto i = mapReplace.find(m[0].str()); + if (i == mapReplace.end()) throw std::out_of_range("invalid key"); + r += string(it, m[0].first) + i->second; + it = m[0].second; + } + json += "\"" + r + "\","; + } + return "[" + json + "]"; + }()); + if (!j.isType(Type::Array) || j.template get().size() <= 0) throw std::out_of_range("invalid key"); // failsae + const auto& a = j.template get(); + if (!a[0].template get().empty()) throw std::out_of_range("invalid key"); // 先頭の/の前に文字がある場合はNG + std::vector v; + for (size_t i = 1; i < a.size(); i++) { + v.push_back(a[i].template get()); + } + return { true,v }; + } catch (...) { + } + return { false,{} }; + }()) + {} + }; + + // 連想配列から要素を取得 (存在しないキーを指定されたら空実体を返す。例外発生しない) + const JsonT& operator[](const Pointer& pointer) const { + try { + return at(pointer); + } catch (std::out_of_range&) { + } + return m_emptyJson; + } + //// 連想配列から要素(参照)を取得 (取得出来るようキーを追加する) + // JsonT& operator[](const Pointer& pointer); JsonPointer 版の実装はナシ(例外ナシを担保出来ない) + + // 連想配列から要素を取得 (存在しないキーを指定されたら throw std::out_of_range) + const JsonT& at(const Pointer& pointer) const noexcept(false) { + return const_cast::type>::type*>(this)->at(pointer); + } + JsonT& at(const Pointer& pointer) noexcept(false) { + if (!pointer.tokens.first) throw std::out_of_range("invalid key"); + auto* p = this; + try { + for (const auto& s : pointer.tokens.second) { + switch (p->type()) { + case Type::Array: + { + std::smatch m; + static const std::regex re("^[0-9]+$"); + if (!regex_search(s, m, re)) throw std::out_of_range("invalid key"); // 整数以外はthrow + const auto n = std::stoull(s); + if (n > (std::numeric_limits::max)()) throw std::out_of_range("invalid key"); + p = &(p->at(static_cast(n))); + } + break; + case Type::Map: + p = &(p->at(s)); + break; + default: + throw std::out_of_range("invalid key"); + } + } + } catch (std::out_of_range&) { + throw; + } catch (...) { + throw std::out_of_range("invalid key"); + } + return *p; + } + + // parse error + struct ParseException : public std::runtime_error { + const size_t position; + ParseException(const std::string& msg, const std::string& json, const std::string::const_iterator& it) + : std::runtime_error(msg + std::string(it, json.cend()).substr(0, 16)) + , position(it - json.cbegin()) + {} + }; + + // parse 設定 + struct ParseOptions { + bool comment; // true:コメント有効 + bool trailingComma; // true:末尾のカンマを許可 + ParseOptions() + : comment(true) + , trailingComma(true) + {} + }; + + // JSON文字列をパース + static JsonT parse(const std::string& sJson, const ParseOptions& opt = ParseOptions()) noexcept(false) { + using std::string; + struct State { + const string& json; + const ParseOptions& opt; + JsonT result; + union { // 次トークン情報 + struct { + uint16_t bFinish : 1; // 文末 + uint16_t bBegin : 1; // 開始(オブジェクトあるいは配列の開始) + uint16_t end : 3; // 終了 1:オブジェクト終了 2:オブジェクト終了あるいはカンマ 3:配列終了 4:配列終了あるいはカンマ + uint16_t objectKey : 2; // オブジェクトのキー 1:キー(文字列) 2:キーの後のコロン + uint16_t value : 2; // 値 1:オブジェクトの中の値 2:配列の中の値 3:それ以外の値 + }; + uint16_t all = 0; + }flags; + std::vector parents; + string::const_iterator it; + string::const_iterator itLineEnd; + + State(const string& s, const ParseOptions& o) + :json(s), opt(o), parents{ &result }, it(json.cbegin()) + { + flags.bBegin = true; + flags.value = 3; + } + + // 値セット共通処理 + void setValue(const JsonT& value) { + auto& parent = **parents.rbegin(); + switch (flags.value) { // 値 + case 1: // オブジェクトの中の値 + assert(parent.isNull()); + parent = value; + parents.pop_back(); + flags.all = 0; // 次トークン + flags.end = 2; // オブジェクト終了あるいはカンマ + return; + case 2: // 配列の値 + if (!parent.isType(Type::Array)) throw ParseException("", json, it); + parent[parent.m_array.size()] = value; + flags.all = 0; // 次トークン + flags.end = 4; // 配列終了あるいはカンマ + return; + case 3: // それ以外の値 + parent = value; + flags.all = 0; // 次トークン + flags.bFinish = true; // 文末 + return; + } + throw ParseException("", json, it); + }; + + // 終了共通処理 + void setEnd() { + if (parents.size() <= 1) { + flags.all = 0; // 次トークン + flags.bFinish = true; // 文末 + } else { + parents.pop_back(); + switch ((*parents.rbegin())->type()) { + case Type::Map: + flags.all = 0; // 次トークン + flags.end = 2; // オブジェクト終了あるいはカンマ + break; + case Type::Array: + flags.all = 0; // 次トークン + flags.end = 4; // 配列終了あるいはカンマ + break; + default: + throw ParseException("", json, it); + } + } + }; + }state(sJson, opt); + + try { + while (true) { + if (state.flags.all == 0) throw ParseException("", state.json, state.it); + if (state.parents.empty()) throw ParseException("", state.json, state.it); + + // 終了? + if (state.it == state.json.cend()) { + if (!state.flags.bFinish) throw ParseException("", state.json, state.it); + break; + } + + std::smatch m; + // 先頭の行末を取得 (VisualC++ の regex は ^ が各行の先頭にヒットしてしまうので1行単位で処理する) + state.itLineEnd = regex_search(state.it, state.json.cend(), m, std::regex("\n")) ? m[0].second : state.json.cend(); + // 空行なら次へ + if (!regex_search(state.it, state.itLineEnd, m, std::regex(R"([^\s])"))) { + state.it = state.itLineEnd; + continue; + } + + // トークン取得 + const string sToken = [&] { + auto& f = state.flags; + string s; + if (state.opt.comment) s += R"((\/\/)|(\/\*))" "|";// コメント + if (f.bFinish) s += "\\S" "|"; // 文末 + if (f.bBegin) s += "\\{|\\[" "|"; // オブジェクトあるいは配列の開始 + switch (f.end) { + case 0: break; + case 1: s += "\\}" "|"; break; // オブジェクト終了 + case 2: s += "\\}|\\," "|"; break; // オブジェクト終了あるいはカンマ + case 3: s += "\\]" "|"; break; // 配列終了 + case 4: s += "\\]|\\," "|"; break; // 配列終了あるいはカンマ + default: assert(false); + } + switch (f.objectKey) { + case 0: break; + case 1: s += "\\\"" "|"; break; // オブジェクトのキー(の最初の " ) + case 2: s += ":" "|"; break; // オブジェクトのキーの後のコロン + default: assert(false); + } + if (f.value) s += + "\\\"" "|" // 値 文字列(の最初の " ) + "true|false|null" "|" // 値 true|false|null + "(-?[0-9]+(\\.[0-9]*)?([eE][+-]?[0-9]+)?)" "|";// 値 浮動小数点表記 (頭に"+"が付く数値はエラー扱い) + s.pop_back(); // 末尾の "|" を取る + const std::regex re("^\\s*(" + s + ")"); + if (regex_search(state.it, state.itLineEnd, m, re)) { + state.it = m[0].second; // 次の位置 + return m[1].str(); + } + return string(); + }(); + + static const std::map> mapToken{ + + {"//", [](State& state) { // コメント + std::smatch m; + if (!regex_search(state.it, state.json.cend(), m, std::regex(".*(\n|$)"))) { // 改行まで + throw ParseException("", state.json, state.it); + } + state.it = m[0].second; + }}, + + {"/*", [](State& state) { // コメント + std::smatch m; + if (!regex_search(state.it, state.json.cend(), m, std::regex(R"(\*\/)"))) { // 閉じるまで + throw ParseException("", state.json, state.it); + } + state.it = m[0].second; + }}, + + {"{", [](State& state) { // オブジェクト開始 + auto& parent = **state.parents.rbegin(); + if (parent.isType(Type::Array)) { // 配列の中の要素なら + auto& v = parent[parent.m_array.size()]; // 要素を追加 + v.ensureMap(); // オブジェクトに設定 + state.parents.push_back(&v); // 親リストに自身を追加 + } else { + if (!parent.isNull()) throw ParseException("", state.json, state.it); + parent.ensureMap(); // オブジェクトに設定 + } + state.flags.all = 0; + state.flags.objectKey = 1; // 次トークン(オブジェクトのキー) + state.flags.end = 1; // 次トークン(オブジェクト終了) + }}, + + {":", [](State& state) { // オブジェクトのキーの後のコロン + state.flags.all = 0; // 次トークン + state.flags.bBegin = true; // オブジェクトあるいは配列の開始 + state.flags.value = 1; // オブジェクトの中の値 + }}, + + {"}", [](State& state) { // オブジェクト終了 + auto& parent = **state.parents.rbegin(); + if (!parent.isType(Type::Map)) throw ParseException("", state.json, state.it); + state.setEnd(); + }}, + + {"[", [](State& state) { // 配列開始 + auto& parent = **state.parents.rbegin(); + if (parent.isType(Type::Array)) { // 配列の中の要素なら + auto& v = parent[parent.m_array.size()]; // 要素を追加 + v.ensureArray(); // 配列に設定 + state.parents.push_back(&v); + } else { + if (!parent.isNull()) throw ParseException("", state.json, state.it); + parent.ensureArray(); // 配列に設定 + } + state.flags.all = 0; // 次トークン + state.flags.bBegin = true; // オブジェクトあるいは配列の開始 + state.flags.value = 2; // 配列の中の値 + state.flags.end = 3; // 配列終了 + }}, + + {"]", [](State& state) { // 配列終了 + auto& parent = **state.parents.rbegin(); + if (!parent.isType(Type::Array)) throw ParseException("", state.json, state.it); + state.setEnd(); + } }, + + {",", [](State& state) { // カンマ + switch (state.flags.end) { + case 2: // オブジェクト終了あるいはカンマ + state.flags.all = 0; // 次トークン + state.flags.objectKey = 1; // オブジェクトのキー + if (state.opt.trailingComma) state.flags.end = 1; // オブジェクト終了 + return; + case 4: // 配列終了あるいはカンマ + state.flags.all = 0; // 次トークン + state.flags.bBegin = true; // オブジェクトあるいは配列の開始 + state.flags.value = 2; // 配列の中の値 + if (state.opt.trailingComma) state.flags.end = 3; // 配列終了 + return; + } + throw ParseException("", state.json, state.it); + }}, + + {"true", [](State& state) { // true + state.setValue(JsonT(true)); + }}, + + {"false", [](State& state) { // false + state.setValue(JsonT(false)); + }}, + + {"null", [](State& state) { // null + state.setValue(JsonT(nullptr)); + }}, + + {"\"", [](State& state) { // 文字列 + const string sText = [&]() { // 文字列デコード + string result; + static const std::regex re("^(.*?)" "(" // (1) (2) + R"(\\u([dD][89abAB][0-9a-fA-F]{2})\\u([dD][c-fC-F][0-9a-fA-F]{2}))" "|" // UTF-16 文字コード サロゲートペア (3)(4) + R"(\\u([0-9a-fA-F]{4}))" "|" // UTF-16 文字コード (5) + R"((\\.))" "|" // エスケープ (6) + R"(")" ")"); + while (true) { + std::smatch m; + if (!regex_search(state.it, state.itLineEnd, m, re)) throw ParseException("", state.json, state.it); + state.it = m[0].second; + result += m[1].str(); + const string sToken = m[2].str(); + if (sToken == "\"") break; // 文字列終了? + + if (m[6].length() > 0) { // エスケープ文字 + static const std::map mapReplace{ + { R"(\")", "\""}, + { R"(\\)", "\\"}, + { R"(\/)", "/" }, + { R"(\b)", "\b"}, + { R"(\f)", "\f"}, + { R"(\n)", "\n"}, + { R"(\r)", "\r"}, + { R"(\t)", "\t"}, + }; + const auto i = mapReplace.find(m[6].str()); + if (i == mapReplace.end()) throw ParseException("", state.json, state.it); + result += i->second; + continue; + } + + // "\uxxxx" UTF-16 文字コード + const string u16a = m[3].str(); // 文字コード(サロゲートペア上位) + const string u16b = m[4].str(); // 文字コード(サロゲートペア下位) + const string u16 = m[5].str(); // 文字コード(非サロゲートペア) + const std::vector c = u16.empty() ? + std::vector{static_cast(stoi(u16a, nullptr, 16)), static_cast(stoi(u16b, nullptr, 16))} : + std::vector{ static_cast(stoi(u16,nullptr,16)) }; + const auto su16 = std::u16string(&c[0], c.size()); + const string su8 = // UTF16 → UTF8 +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) + std::wstring_convert, uint16_t>().to_bytes(reinterpret_cast(su16.c_str())); +#pragma warning(pop) +#else + std::wstring_convert, char16_t>().to_bytes(su16.c_str()); +#endif + result += su8; + } + return result; + }(); + + if (state.flags.objectKey == 1) { // 処理したのはオブジェクトのキーなら + auto& parent = **state.parents.rbegin(); + assert(parent.isType(Type::Map)); + auto& v = parent[sText]; + v.clear(); // 2度目以降の登場は後勝ち(JSONの仕様) + state.parents.push_back(&v); + state.flags.all = 0; // 次トークン + state.flags.objectKey = 2; // オブジェクトのキーの後のコロン + return; + } + + state.setValue(JsonT(sText)); + }}, + + }; + const auto i = mapToken.find(sToken); + if (i != mapToken.end()) { + i->second(state); + continue; + } + + // それ以外は数値 + state.setValue( + [&] { + std::smatch m; + static const std::regex r("^-?[0-9]+$"); + if (regex_search(sToken, m, r)) { // 整数なら + try { + return JsonT(static_cast(stoll(sToken))); + } catch (const std::exception&) { + } + } + return JsonT(stod(sToken)); // doubleで処理 + }()); + + }//while(true) + } catch (const std::exception& e) { + throw ParseException(e.what(), state.json, state.it); + } catch (...) { + throw; + } + return state.result; + } + + // JSON文字列を出力 + struct Stringify { + struct Format { + std::string lf; + std::string indent; + std::string colon = ":"; + + Format() {} + Format(const std::string& lf_, const std::string& indent_, const std::string& colon_) :lf(lf_), indent(indent_), colon(colon_) {} + }; + static const Format standard; + + const JsonT& json; + const Format& format; + Stringify(const JsonT& j, const Format& f = Stringify::standard) + :json(j), format(f) + {} + }; + std::string stringify() const { + return stringify(typename Stringify::Format()); + } + std::string stringify(const typename Stringify::Format& sf) const { + std::stringstream ss; + ss << Stringify(*this, sf); + return ss.str(); + } + friend std::ostream& operator<<(std::ostream& os, const JsonT& json) { + os << Stringify(json, typename Stringify::Format()); + return os; + } + friend std::ostream& operator<<(std::ostream& os, const Stringify& t) { + using std::string; + struct F { + static std::pair> getReplaceStringRegex(const std::vector>& t) { + assert(!t.empty()); + std::vector v; + string re; + for (auto& i : t) { + re += "(" + i.first + ")|"; + v.push_back(i.second); + } + re.pop_back(); // 末尾の "|" を削除 + return { std::regex(re) ,v }; + }; + + static string replaceString(const string& s, const std::pair>& rr) { + string r; + for (auto it = s.cbegin(); it != s.cend();) { + std::smatch m; + if (!regex_search(it, s.cend(), m, rr.first)) { + r += string(it, s.cend()); + break; + } + for (size_t i = 1; i < m.size(); i++) { + auto& submatch = m[i]; + if (!submatch.matched) continue; + r += string(it, m[i].first) + rr.second[i - 1]; + it = m[i].second; + break; + } + } + return r; + }; + + static string escape(const string& s) { // 文字列エスケープ処理 + static const auto rr = getReplaceStringRegex({ + {"\\\"", R"(\")" }, + {"\\\\", R"(\\)" }, + {"\\/", R"(\/)" }, + {"\\\b", R"(\b)" }, + {"\\\f", R"(\f)" }, + {"\\\n", R"(\n)" }, + {"\\\r", R"(\r)" }, + {"\\\t", R"(\t)" }, + }); + return replaceString(s, rr); + }; + + static void f(std::ostream& os, const JsonT& j, const typename Stringify::Format& f, const std::string& depth) { + switch (j.type()) { + case Type::Null: + os << "null"; + break; + case Type::Bool: + os << std::boolalpha << j.m_bool; + break; + case Type::Float: + os << j.m_float; + break; + case Type::Int: + os << j.m_int; + break; + case Type::String: + os << "\"" + escape(j.m_string) + "\""; + break; + case Type::Array: + os << "[" << f.lf; + for (auto it = j.m_array.cbegin(); it != j.m_array.cend(); it++) { + if (it != j.m_array.cbegin()) os << "," << f.lf; + os << depth << f.indent; + F::f(os, *it, f, depth + f.indent); + } + os << f.lf << depth << "]"; + break; + case Type::Map: + os << "{" << f.lf; + for (auto it = j.m_map.cbegin(); it != j.m_map.cend(); it++) { + if (it != j.m_map.cbegin()) os << "," << f.lf; + os << depth << f.indent; + os << "\"" + escape(it->first) + "\"" + f.colon; + F::f(os, it->second, f, depth + f.indent); + } + os << f.lf << depth << "}"; + break; + default: + assert(false); + } + } + }; + F::f(os, t.json, t.format, ""); + return os; + } + + }; + template const JsonT JsonT::m_emptyJson; + template const std::string JsonT::m_emptyString; + template const typename JsonT::Array JsonT::m_emptyArray; + template const typename JsonT::Map JsonT::m_emptyMap; + template const typename JsonT::Stringify::Format JsonT::Stringify::standard("\n", " ", ": "); + typedef JsonT<> Json; +} diff --git a/mmltosmf.cpp b/mmltosmf.cpp index bb7f4f7..cc7173f 100644 --- a/mmltosmf.cpp +++ b/mmltosmf.cpp @@ -29,7 +29,7 @@ int main(const int argc, const char* const argv[]) po::store(po::command_line_parser(argc, argv).options(desc).positional(pd).run(), vm); if (vm.count("version")) { - std::cout << "rlib-MML mmltosmf version 1.2.1" << std::endl; + std::cout << "mmltosmf version 1.2.2" << std::endl; return 0; } diff --git a/sequencer/MmlCompiler.cpp b/sequencer/MmlCompiler.cpp index 1c322be..c18722c 100644 --- a/sequencer/MmlCompiler.cpp +++ b/sequencer/MmlCompiler.cpp @@ -6,12 +6,15 @@ #include #include #include +#include #include #include +#include "../json/Json.h" + #include "./MmlCompiler.h" -#include "../stringformat/StringFormat.h" +#include "./MidiEvent.h" using namespace rlib; using namespace rlib::sequencer; @@ -56,6 +59,11 @@ std::string MmlCompiler::Exception::getMessage(Code code) { {Code::createSequenceNameError, u8R"(CreateSequence コマンドの名前指定に誤りがあります)" }, {Code::sequenceError, u8R"(Sequence コマンドに誤りがあります)" }, {Code::sequenceNameError, u8R"(Sequence コマンドの名前指定に誤りがあります)" }, + {Code::metaError, u8R"(Meta コマンドに誤りがあります)" }, + {Code::metaTypeError, u8R"(Meta コマンドの type の指定に誤りがあります)" }, + {Code::definePresetFMError, u8R"(DefinePresetFM コマンドに誤りがあります)" }, + {Code::definePresetFMNoError, u8R"(DefinePresetFM コマンドのプログラムナンバー指定に誤りがあります)" }, + {Code::definePresetFMRangeError, u8R"(DefinePresetFM コマンドの値が範囲外です)" }, {Code::unknownError, u8R"(解析出来ない書式です)"}, {Code::stdEexceptionError, u8R"(std::excption エラーです)"}, }; @@ -84,15 +92,15 @@ std::optional regexSearch(const std::string::const_iterator& iBeg using PairIterator = std::pair; // std::string::const_iteratorをポインタに変換 (TODO: std::to_address に差し替えたい) -const std::pair toAddress(const std::string::const_iterator& iBegin, const std::string::const_iterator& iEnd) { - const size_t size = iEnd - iBegin; +template const std::pair toAddress(const T& iBegin, const T& iEnd) { + const size_t size = std::distance(iBegin, iEnd);//iEnd - iBegin; if (size == 0) return { nullptr,nullptr }; auto p = &(*iBegin); return { p,p + size }; } -// 文字列の先頭を比較 -std::optional isStartsWith(const std::string::const_iterator& iBegin, const std::string::const_iterator& iEnd, const std::string& s) { +// 文字列の前方一致比較 +template std::optional isStartsWith(const T& iBegin, const T& iEnd, const std::string& s) { if (static_cast(iEnd - iBegin) >= s.size() && std::equal(s.cbegin(), s.cend(), iBegin)) { return iBegin + s.size(); } @@ -105,7 +113,7 @@ auto parseString(const std::string::const_iterator& iBegin, const std::string::c std::string::const_iterator next; // 次の位置 std::optional parsedString; // パースした文字列(nullの場合はパースエラー) }; - if (iBegin == iEnd) std::optional(); + if (iBegin == iEnd) return std::optional(); std::optional result({ iBegin }); if (*iBegin == '"') { // "・・・" 形式の文字列 auto i = std::find(iBegin + 1, iEnd, '"'); // 終端を検索 @@ -169,19 +177,44 @@ auto parseInt(const std::string::const_iterator& iBegin, const std::string::cons std::string::const_iterator next; std::variant value; // 正数:uintmax_t 負数:intmax_t +○○:intmax_t }result; - if (iBegin == iEnd) return std::optional(); - const auto [begin, end] = toAddress(iBegin, iEnd); - bool const plus = *iBegin == '+'; - const auto begin2 = begin + (plus ? 1 : 0); - intmax_t value; - const auto [ptr, ec] = std::from_chars(begin2, end, value); + std::string::const_iterator it = iBegin; + + // 16進数 + if (auto r = isStartsWith(it, iEnd, "0x")) { + it = *r; + const auto [pBegin, pEnd] = toAddress(it, iEnd); + uintmax_t value; + const auto [ptr, ec] = std::from_chars(pBegin, pEnd, value, 16); + if (ec != std::errc{}) return std::optional(); + result.value = value; + result.next = iBegin + (ptr - &(*iBegin)); + return std::optional(result); + } + + if (it == iEnd) return std::optional(); + const int sign = [&] { + if (*it == '-') { + it++; + return -1; + } + if (*it == '+') { + it++; + return +1; + } + return 0; + }(); + const auto [pBegin, pEnd] = toAddress(it, iEnd); + uintmax_t value; + const auto [ptr, ec] = std::from_chars(pBegin, pEnd, value, 10); if (ec != std::errc{}) return std::optional(); - if (value < 0 || plus) { + if (sign < 0) { + result.value = -static_cast(value); + } else if (sign > 0) { result.value = static_cast(value); } else { result.value = static_cast(value); } - result.next = iBegin + (ptr - begin); + result.next = iBegin + (ptr - &(*iBegin)); return std::optional(result); } @@ -211,32 +244,49 @@ auto parseDouble(const std::string::const_iterator& iBegin, const std::string::c // 関数解析 struct ParseFunctionResult { - PairIterator functionName; // 関数名 - std::string::const_iterator next; // 次の位置 - std::map args; // 引数リスト + PairIterator functionName; // 関数名 + std::string::const_iterator next; // 次の位置 + std::vector argsList; // 引数リスト(名前ナシ連番) + std::map argsName; // 引数リスト(名前アリ) template std::optional findArg(const std::string& name)const { - if (auto i = args.find(name); i != args.end()) { - if (std::holds_alternative(i->second)) { - return std::get(i->second); - } + if (auto i = argsName.find(name); i != argsName.end() && std::holds_alternative(i->second)) { + return std::get(i->second); + } + return std::nullopt; + } + template std::optional findArg(size_t index)const { + if (argsList.size() > index && std::holds_alternative(argsList[index])) { + return std::get(argsList[index]); } return std::nullopt; } - std::optional findArgString(const std::string& name)const { + template std::optional findArgString(const T& name)const { if (auto r = findArg(name)) { return std::string(r->first, r->second); } return std::nullopt; } + + template std::optional findArgInt(const T& name)const { + if (auto r = findArg(name)) { + return r; + } + if (auto r = findArg(name)) { + return r; + } + return std::nullopt; + } + }; auto parseFunction( const std::string::const_iterator& iBegin, const std::string::const_iterator& iEnd, - const std::vector& functionNames, - const std::set& argNames // 引数名(ココにない引数名はエラー) + const std::vector& functionNames, // 関数名 + const std::set& argNames, // 名前付き引数名(ココにない引数名はエラー) + const size_t argCount = (std::numeric_limits::max)() // 名前ナシ引数数(ココを超える数の引数はエラー) ) { std::optional result = ParseFunctionResult(); @@ -258,7 +308,6 @@ auto parseFunction( }(); if (!func) return decltype(result)(); // 該当しなかった - std::vector nonameArgs; // 名前ナシ引数 std::string currentArgName; union { // 次トークン情報 struct { @@ -279,9 +328,6 @@ auto parseFunction( if (it == iEnd) break; if (flags.rightParen && *it == ')') { // ) - for (size_t i = 0; i < nonameArgs.size(); i++) { - result->args[boost::lexical_cast(i)] = nonameArgs[i]; - } result->next = it + 1; // 次の位置 return result; // 正常終了 } @@ -320,9 +366,12 @@ auto parseFunction( } it = r->next; if (currentArgName.empty()) { - nonameArgs.emplace_back(std::move(*r->word)); + result->argsList.emplace_back(std::move(*r->word)); + if (result->argsList.size() > argCount) { + throw MmlCompiler::Exception(MmlCompiler::Exception::Code::argumentError, it, iEnd); // 引数が多すぎる + } } else { - result->args[currentArgName] = std::move(*r->word); + result->argsName[currentArgName] = std::move(*r->word); currentArgName.clear(); } flags.all = 0; @@ -561,7 +610,7 @@ class MmlCompiler::Inner const auto parseSequence = [&state, &sequences](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "Seq","Sequence" }, { "length" })) { - const auto name = (*r).findArgString("0").value_or(""); + const auto name = (*r).findArgString(0).value_or(""); if (name.empty()) { throw Exception(Exception::Code::sequenceNameError, begin, end); } @@ -633,7 +682,7 @@ class MmlCompiler::Inner const auto parsePort = [&state](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "Port","port" }, {})) { // Port - const auto name = (*r).findArgString("0").value_or(""); + const auto name = (*r).findArgString(0).value_or(""); if (name.empty()) { throw Exception(Exception::Code::portNameError, begin, end); } @@ -649,10 +698,10 @@ class MmlCompiler::Inner const auto parseVolume = [&state](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "V","Volume","volume" }, {})) { // Volume auto& port = *state.currentPort; - if (const auto v = r->findArg("0")) { // 符号付なら相対指定 + if (const auto v = r->findArg(0)) { // 符号付なら相対指定 const intmax_t n = port.volume + *v; port.volume = static_cast(std::min(std::max(n, 0), 127)); - } else if (const auto v = r->findArg("0")) { // 符号ナシなら絶対指定 + } else if (const auto v = r->findArg(0)) { // 符号ナシなら絶対指定 if (*v < 0 || *v > 127) { throw Exception(Exception::Code::volumeRangeError, begin, end); } @@ -672,10 +721,10 @@ class MmlCompiler::Inner const auto parsePan = [&state](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "Pan","pan" }, {})) { // Pan auto& port = *state.currentPort; - if (const auto v = r->findArg("0")) { // 符号付なら相対指定 + if (const auto v = r->findArg(0)) { // 符号付なら相対指定 const intmax_t n = port.pan + *v; port.pan = static_cast(std::min(std::max(n, 0), 127)); - } else if (const auto v = r->findArg("0")) { // 符号ナシなら絶対指定 + } else if (const auto v = r->findArg(0)) { // 符号ナシなら絶対指定 if (*v < 0 || *v > 127) { throw Exception(Exception::Code::panRangeError, begin, end); } @@ -694,18 +743,15 @@ class MmlCompiler::Inner const auto parsePitchBend = [&state](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "PitchBend","pitchBend" }, {})) { // PitchBend - const auto v = [&]()->intmax_t { - if (const auto v = r->findArg("0")) return *v; - if (const auto v = r->findArg("0")) return *v; - throw Exception(Exception::Code::pitchBendError, begin, end); - }(); + const auto v = r->findArgInt(0); + if (!v) throw Exception(Exception::Code::pitchBendError, begin, end); auto& port = *state.currentPort; - if (v < -8192 || v > 8191) { + if (*v < -8192 || *v > 8191) { throw Exception(Exception::Code::pitchBendRangeError, begin, end); } auto e = std::make_shared(); e->position = port.position; - e->pitchBend = static_castpitchBend)>(v); + e->pitchBend = static_castpitchBend)>(*v); port.port.eventList.insert(e); return r->next; } @@ -715,12 +761,12 @@ class MmlCompiler::Inner const auto parseControlChange = [&state](const iterator& begin, const iterator& end)->std::optional { if (auto r = parseFunction(begin, end, { "CC","ControlChange","controlChange" }, { "no","value" })) { // ControlChange const auto no = [&] { - if (auto v = r->findArg("0")) return *v; + if (auto v = r->findArg(0)) return *v; if (auto v = r->findArg("no")) return *v; throw Exception(Exception::Code::controlChangeError, begin, end); }(); const auto val = [&] { - if (auto v = r->findArg("1")) return *v; + if (auto v = r->findArg(1)) return *v; if (auto v = r->findArg("value")) return *v; throw Exception(Exception::Code::controlChangeError, begin, end); }(); @@ -861,6 +907,95 @@ class MmlCompiler::Inner return std::nullopt; }; + // メタイベント + const auto parseMeta = [&state](const iterator& begin, const iterator& end)->std::optional { + if (auto r = parseFunction(begin, end, { "Meta" }, { "type" })) { + const auto type = (*r).findArgInt("type"); + if (!type || *type < 0 || *type > std::numeric_limits::max()) { + throw Exception(Exception::Code::metaTypeError, begin, end); + } + auto& port = *state.currentPort; + + auto e = std::make_shared(); + e->type = static_casttype)>(*type); + e->position = port.position; + + for (auto& arg : r->argsList) { + if (std::holds_alternative(arg)) { + auto n = std::get(arg); + if (n > std::numeric_limits::max()) { + throw Exception(Exception::Code::metaTypeError, begin, end); + } + e->data.push_back(static_cast(n)); + }else if (std::holds_alternative(arg)) { + auto n = std::get(arg); + if (n ::min() || n > std::numeric_limits::max()) { + throw Exception(Exception::Code::metaTypeError, begin, end); + } + e->data.push_back(static_cast(n)); + } else if (std::holds_alternative(arg)) { + auto n = std::get(arg); + e->data.append(n.first, n.second); + } else { + throw Exception(Exception::Code::metaTypeError, begin, end); // failsafe + } + } + + port.port.eventList.insert(e); + return r->next; + } + return std::nullopt; + }; + + + // FM音色定義 (rlib-MML 固有メタイベント) + const auto parseDefinePresetFM = [&state](const iterator& begin, const iterator& end)->std::optional { + if (auto r = parseFunction(begin, end, { "DefinePresetFM" }, {"no","name" }, 38)) { + const auto no = (*r).findArgInt("no"); + if (!no || *no < 0 || *no>127) { + throw Exception(Exception::Code::definePresetFMNoError, begin, end); + } + const auto name = (*r).findArgString("name").value_or(""); + + static constexpr std::array maxTable = { + // AR DR SR RR SL TL KS ML DT + 31, 31, 31, 15, 15,127, 3, 15, 7, + 31, 31, 31, 15, 15,127, 3, 15, 7, + 31, 31, 31, 15, 15,127, 3, 15, 7, + 31, 31, 31, 15, 15,127, 3, 15, 7, + 7, 7, // AL FB + }; + Json::Array parameter; + for (size_t i = 0; i < maxTable.size(); i++) { + const auto n = (*r).findArgInt(i); + if (!n) throw Exception(Exception::Code::definePresetFMError, begin, end); + if (*n<0 || *n> maxTable[i] ) throw Exception(Exception::Code::definePresetFMRangeError, begin, end); + parameter.push_back(*n); + } + + const Json j = Json::Map{ + {"rlib-MML", Json::Map{ + {"fm4op", Json::Map{ + {std::to_string(*no), Json::Map{ + {"name", name}, + {"reg", parameter}, + }}, + }}, + }}, + }; + + auto& port = *state.currentPort; + auto e = std::make_shared(); + e->type = static_casttype)>(midi::EventMeta::Type::sequencerLocal); + e->position = port.position; + e->data = j.stringify(); + auto it = port.port.eventList.insert(e); + + return r->next; + } + return std::nullopt; + }; + const std::initializer_list< std::pair(const iterator&, const iterator&)>, Exception::Code> > funcs = { @@ -882,6 +1017,8 @@ class MmlCompiler::Inner {parseVelocity, Exception::Code::vCommandError}, // v? ベロシティ {parseCreateSequence, Exception::Code::createSequenceError}, // CreateSequence {parseSequence, Exception::Code::sequenceError}, // Sequence + {parseMeta, Exception::Code::metaError}, // Meta + {parseDefinePresetFM, Exception::Code::definePresetFMError} // DefinePresetFM }; for (auto it = iBegin; true;) { @@ -1033,8 +1170,42 @@ void MmlCompiler::unitTest() { std::string s("1.5e5 is pi"); auto a = parseDouble(s.begin(),s.end()); - std::string s1("+1.1e5 is pi"); - auto a1 = parseInt(s1.begin(), s1.end()); + {// parseInt + std::cout << "parseInt" << std::endl; + struct { + std::string text; + std::pair < size_t, std::variant> result; + }static const tbl[] = { + {"", {0,static_cast(0)}}, + {"+", {0,static_cast(0)}}, + {"-", {0,static_cast(0)}}, + {"0", {1,static_cast(0)}}, + {"-0.", {2,static_cast(0)}}, + {"1.2", {1,static_cast(1)}}, + {"2e1.2", {1,static_cast(2)}}, + {"+3e1.2", {2,static_cast(3)}}, + {"-4e1.2", {2,static_cast(-4)}}, + {"0x3a1.e1.2", {5,static_cast(0x3a1)}}, + {"+0x3e1.2", {2,static_cast(0)}}, + {"0x", {0,static_cast(0)}}, + {"+x", {0,static_cast(0)}}, + {"++0", {0,static_cast(0)}}, + {"--1", {0,static_cast(0)}}, + {"+-1", {0,static_cast(0)}}, + {"-.", {0,static_cast(0)}}, + }; + for (auto &t : tbl) { + std::cout << t.text << std::endl; + auto r = parseInt(t.text.begin(), t.text.end()); + if (t.result.first == 0) { + assert(!r); + } else { + assert(r->value == t.result.second); + assert(t.result.first == std::distance(t.text.begin(), r->next)); + } + } + } + { static const std::initializer_list> list = { diff --git a/sequencer/MmlCompiler.h b/sequencer/MmlCompiler.h index d83e358..6900e8c 100644 --- a/sequencer/MmlCompiler.h +++ b/sequencer/MmlCompiler.h @@ -21,6 +21,14 @@ namespace rlib::sequencer { } }; + struct EventMeta : public Event { + uint8_t type = 0; + std::string data; + virtual std::shared_ptr clone()const { + return std::make_shared(*this); + } + }; + struct EventProgramChange : public Event { uint8_t programNo = 0; virtual std::shared_ptr clone()const { @@ -120,6 +128,11 @@ namespace rlib::sequencer { createSequenceNameError, // CreateSequence コマンドの名前指定に誤りがあります sequenceError, // Sequence コマンドに誤りがあります sequenceNameError, // Sequence コマンドの名前指定に誤りがあります + metaError, // Meta コマンドに誤りがあります + metaTypeError, // Meta コマンドの type の指定に誤りがあります + definePresetFMError, // DefinePresetFM コマンドに誤りがあります + definePresetFMNoError, // DefinePresetFM コマンドのプログラムナンバー指定に誤りがあります + definePresetFMRangeError, // DefinePresetFM コマンドの値が範囲外です unknownError, // 解析出来ない書式です stdEexceptionError, // std::excption エラーです }; diff --git a/sequencer/MmlToSmf.h b/sequencer/MmlToSmf.h index 735cfaa..2569f9e 100644 --- a/sequencer/MmlToSmf.h +++ b/sequencer/MmlToSmf.h @@ -51,6 +51,10 @@ namespace rlib::sequencer { auto& e = static_cast(event); track.events.insert(Smf::Event(e.position, std::make_shared(midi::EventMeta::createTempo(e.tempo)))); }}, + {typeid(MmlCompiler::EventMeta), [](Smf::Track& track,const MmlCompiler::Port& port,const MmlCompiler::Event& event) { + auto& e = static_cast(event); + track.events.insert(Smf::Event(e.position, std::make_shared(midi::EventMeta::createText(static_cast(e.type), e.data)))); + }}, }; const auto &ev = *event; if (auto i = map.find(typeid(ev)); i != map.end()) { diff --git a/sequencer/Smf.cpp b/sequencer/Smf.cpp index 50a6d97..2c59c0b 100644 --- a/sequencer/Smf.cpp +++ b/sequencer/Smf.cpp @@ -3,6 +3,7 @@ //#include //#endif +#include #include #include #include @@ -148,120 +149,144 @@ Smf Smf::fromStream(std::istream& is) smf.timeBase = headerChunk.division; // トラックチャンク - for (size_t i = 0; i < headerChunk.trackCount; i++) { - const auto trackChunk = [&is]() { - Inner::TrackChunk c = Inner::read(is); - if (c.MTrk != Inner::TrackChunk().MTrk) { - throw std::runtime_error("MTrk chunk error"); - } - Inner::changeEndian(c.dataLength); - return c; - }(); - - Smf::Track track; - - std::uint64_t currentPosition = 0; // 現在位置 - uint8_t beforeStatus = 0; - for (const auto beginPosition = is.tellg(); is.tellg() - beginPosition < trackChunk.dataLength; ) { - auto readVariableValue = [&is] { // 可変長数値を取得 - return inner::readVariableValue([&] {return Inner::read(is); }); - }; - - currentPosition += readVariableValue(); // 現在位置 += デルタタイム - - // status 読み込み - const auto status = [&] { - const uint8_t status = Inner::read(is); - if (!(status & 0x80)) { // status 省略なら直前値を採用 - is.seekg(-1, std::ios::cur); // 位置を戻す - return beforeStatus; + std::exception_ptr ep; // 例外保持 + try { + for (size_t i = 0; i < headerChunk.trackCount; i++) { + const auto trackChunk = [&is]() { + Inner::TrackChunk c = Inner::read(is); + if (c.MTrk != Inner::TrackChunk().MTrk) { + throw std::runtime_error("MTrk chunk error."); } - beforeStatus = status; - return status; + Inner::changeEndian(c.dataLength); + return c; }(); - switch (status & 0xf0) { - case EventNoteOff::statusByte: { - const std::array a = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); - break; - } - case EventNoteOn::statusByte: { - const std::array a = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); - break; - } - case EventPolyphonicKeyPressure::statusByte: { - const std::array a = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); - break; - } - case EventControlChange::statusByte: { - const std::array a = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); - break; - } - case EventProgramChange::statusByte: { - const uint8_t n = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, n & 0x7f)); - break; - } - case EventPitchBend::statusByte: { - const std::array a = Inner::read(is); - const auto n = ((a[0] & 0x7f) + (a[1] & 0x7f) * 0x80) - 8192; - track.events.emplace(currentPosition, std::make_shared(status & 0xf, n)); - break; - } - case EventChannelPressure::statusByte: { - const uint8_t n = Inner::read(is); - track.events.emplace(currentPosition, std::make_shared(status & 0xf, n & 0x7f)); - break; - } - default: - switch (status) { - case EventExclusive::statusByte: { - const auto size = readVariableValue(); // データ長 - std::vector data; - for (auto i = 0; i < size; i++) { + Smf::Track track; + try{ + std::uint64_t currentPosition = 0; // 現在位置 + uint8_t beforeStatus = 0; + for (const auto beginPosition = is.tellg(); is.tellg() - beginPosition < trackChunk.dataLength; ) { + auto readVariableValue = [&is] { // 可変長数値を取得 + return inner::readVariableValue([&] {return Inner::read(is); }); + }; + + currentPosition += readVariableValue(); // 現在位置 += デルタタイム + + // status 読み込み + const auto status = [&] { + const uint8_t status = Inner::read(is); + if (!(status & 0x80)) { // status 省略なら直前値を採用 + is.seekg(-1, std::ios::cur); // 位置を戻す + return beforeStatus; + } + beforeStatus = status; + return status; + }(); + + switch (status & 0xf0) { + case EventNoteOff::statusByte: { + const std::array a = Inner::read(is); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); + break; + } + case EventNoteOn::statusByte: { + const std::array a = Inner::read(is); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); + break; + } + case EventPolyphonicKeyPressure::statusByte: { + const std::array a = Inner::read(is); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); + break; + } + case EventControlChange::statusByte: { + const std::array a = Inner::read(is); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, a[0] & 0x7f, a[1] & 0x7f)); + break; + } + case EventProgramChange::statusByte: { + const uint8_t n = Inner::read(is); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, n & 0x7f)); + break; + } + case EventPitchBend::statusByte: { + const std::array a = Inner::read(is); + const auto n = ((a[0] & 0x7f) + (a[1] & 0x7f) * 0x80) - 8192; + track.events.emplace(currentPosition, std::make_shared(status & 0xf, n)); + break; + } + case EventChannelPressure::statusByte: { const uint8_t n = Inner::read(is); - if (n == 0xf7) { // EOX(終了コード) - if(i != size - 1){ // ヘンなところで終わった? - throw std::runtime_error("exclusive size error"); + track.events.emplace(currentPosition, std::make_shared(status & 0xf, n & 0x7f)); + break; + } + default: + switch (status) { + case EventExclusive::statusByte: { + const auto size = readVariableValue(); // データ長 + std::vector data; + for (auto i = 0; i < size; i++) { + const uint8_t n = Inner::read(is); + if (n == 0xf7) { // EOX(終了コード) + if (i != size - 1) { + // ヘンなところで終わった(が、エラーにはせず続行) + std::clog << "[warning] exclusive size error. EOX(0xf7) is failed position." << std::endl; // throw std::runtime_error("exclusive size error"); + break; + } + break; + } + if ((n & 0x80) != 0) { + // あるべきハズのEOX(0xf7)が無い(が、エラーにはせず続行) + std::clog << "[warning] exclusive data error. EOX(0xf7) is missing." << std::endl; + break; + } + data.push_back(n); } + track.events.emplace(currentPosition, std::make_shared(std::move(data))); break; } - if ((n & 0x80) != 0) { - // あるべきハズのEOX(0xf7)が無い(が、エラーにはせず続行) - std::clog << "[warning] exclusive data error. EOX(0xf7) is missing." << std::endl; + case EventMeta::statusByte: { + const uint8_t type = Inner::read(is); // イベントタイプ + const auto len = readVariableValue(); // データ長 + std::vector data(len); + if (const auto readed = is.read(reinterpret_cast(data.data()), data.size()).gcount(); readed < static_cast(data.size())) { + data.resize(readed); + // データが足りない(が、エラーにはせず続行) + std::clog << "[warning] meta data size error." << std::endl; + } + track.events.emplace(currentPosition, std::make_shared(static_cast(type), std::move(data))); + break; + } + default: + assert(false); break; } - data.push_back(n); - } - track.events.emplace(currentPosition, std::make_shared(std::move(data))); - break; - } - case EventMeta::statusByte: { - const uint8_t type = Inner::read(is); // イベントタイプ - const auto len = readVariableValue(); // データ長 - std::vector data(len); - if (is.read(reinterpret_cast(data.data()), data.size()).gcount() < static_cast(data.size())) { - throw std::runtime_error("size error"); } - track.events.emplace(currentPosition, std::make_shared(static_cast(type), std::move(data))); - break; } - default: - assert(false); - break; + } catch (...) { + if (track.events.size() > 0) { + smf.tracks.emplace_back(std::move(track)); } + throw; } - + smf.tracks.emplace_back(std::move(track)); + } + } catch (...) { + ep = std::current_exception(); + } + if (ep) { + try { + std::rethrow_exception(ep); + } catch (const std::exception& e) { + std::clog << "[warning] exception: " << e.what() << std::endl; + } catch (...) { + std::clog << "[warning] exception unknwon" << std::endl; + } + if (smf.tracks.size() <= 0) { // トラックがまったくパースできてない状態なら + std::rethrow_exception(ep); // throw で終了 } - smf.tracks.emplace_back(std::move(track)); - } return smf; - } Smf Smf::convertTimebase(const Smf& smf, int timeBase) { diff --git a/sequencer/SmfToMml.h b/sequencer/SmfToMml.h index 49a45ae..59dd9c2 100644 --- a/sequencer/SmfToMml.h +++ b/sequencer/SmfToMml.h @@ -1,18 +1,86 @@ #pragma once +#ifndef __EMSCRIPTEN__ +//#include +#include +//#include +#endif + #include "MidiEvent.h" #include "Smf.h" #include "TempoList.h" #include "MmlCompiler.h" #include "../stringformat/StringFormat.h" +#include "../json/Json.h" + + + namespace rlib::sequencer { inline std::string smfToMml(const midi::Smf& smf) { using Smf = midi::Smf; - const auto makeMml = [](const std::string& name, const std::string& instrument, int channel, const Smf::Events& events) { + // 文字列を必要に応じて " " や R"( )" で括る + const auto safeText = [](const std::string& text) { + const auto isValidName = [](const std::string& s) { + auto r = MmlCompiler::Util::parseWord(s.begin(), s.end()); + if (r && r->word) { + if (std::holds_alternative>(*r->word)) { + return r->next == s.end(); + } + } + return false; + }; + if (isValidName(text)) return text; + if (auto t = string::format(u8R"("%s")", text); isValidName(t)) { // ダメなら " " で括る + return t; + } + if (auto t = string::format(u8R"_(R"(%s)")_", text); isValidName(t)) { // ダメなら R"( )" で括る + return t; + } + for (int i = 0; i < 1000; i++) { + if (auto t = string::format(u8R"(R"t%d(%s)t%d")", i, text, i); isValidName(t)) { // それでもダメなら R"t?(○○)t?" で括る + return t; + } + } + return string::format(u8R"(R"t_(%s)t_")", text); // 最後は R"t_(○○)t_" で諦める + }; + + const auto decodeText = [](const std::string& bin)->std::optional { + try { + const auto hasControlCode = [](const std::string& s) { + static const std::regex controlCode(R"([\x00-\x08\x0B-\x1F\x7F])"); // 改行(\x0A)とタブ(\x09)は許可し、他の制御文字を検出 + return std::regex_search(s, controlCode); + }; +#ifndef __EMSCRIPTEN__ + const auto sjisToUtf8 = [](const std::string& sjisStr)->std::optional { + try { + return boost::locale::conv::to_utf(sjisStr.c_str(), "Shift-JIS"); + } catch (...) { + } + return std::nullopt; + }; + + // SJIS + if (const auto s = sjisToUtf8(bin)) { + if (!hasControlCode(*s)) { + return s; + } + } +#endif + + // そのまま + if (!hasControlCode(bin)) { + return bin; + } + } catch (...) { + } + return std::nullopt; + }; + + const auto makeMml = [&](const std::string& name, const std::string& instrument, int channel, const Smf::Events& events) { struct { int note = -1; @@ -162,6 +230,9 @@ namespace rlib::sequencer { const bool overlap = [&] { auto i = it; i++; + if (i == events.end()) { + return false; + } if (isNoteOff(i->event) || i->position >= it->position + len) { // ない? return false; } @@ -226,10 +297,59 @@ namespace rlib::sequencer { switch (e.type) { case midi::EventMeta::Type::tempo: *mmls.rbegin() += string::format(u8"t%s", e.getTempo()); + return; + case midi::EventMeta::Type::sequencerLocal: + try { + // rlib-MML 固有情報なら展開する + if (const auto json = Json::parse(e.getText())["rlib-MML"]; !json.isNull()) { + const auto& map = json["fm4op"].get(); + if (!map.empty()) { + for (const auto it : map) { + const auto no = std::stoi(it.first); + const auto& name = it.second["name"].get(); + const auto& data = it.second["reg"].get(); + *mmls.rbegin() += string::format( + u8"\nDefinePresetFM(no:%d, name:%s,\n" + u8"// AR DR SR RR SL TL KS ML DT\n" + u8" %3d,%3d,%3d,%3d,%3d,%3d,%2d,%2d,%2d,\n" + u8" %3d,%3d,%3d,%3d,%3d,%3d,%2d,%2d,%2d,\n" + u8" %3d,%3d,%3d,%3d,%3d,%3d,%2d,%2d,%2d,\n" + u8" %3d,%3d,%3d,%3d,%3d,%3d,%2d,%2d,%2d,\n" + u8"// AL FB\n" + u8" %3d,%3d)\n" + , no, safeText(name), data); + } + } + return; + } + } catch (...) { + } + break; + case midi::EventMeta::Type::endOfTrack: + case midi::EventMeta::Type::sequenceName: + case midi::EventMeta::Type::instrumentName: + return; // 何も出力しない + case midi::EventMeta::Type::text: + case midi::EventMeta::Type::copyright: + case midi::EventMeta::Type::words: + if (auto text = decodeText(e.getText())) { // 文字列? + const auto s = safeText(*text); // 必要に応じて " で括る + *mmls.rbegin() += string::format(u8"\nMeta(type:0x%x,%s)", static_cast(e.type), s); + return; + } break; default: break; } + // バイナリデータとして出力 + const auto toJoinString = [](const std::vector& v)-> std::string { // std::vector をカンマ区切りの文字列に変換 + std::ostringstream oss; + for (auto& n : v) { + oss << "," << static_cast(n); + } + return oss.str(); + }; + *mmls.rbegin() += string::format(u8"\nMeta(type:0x%x%s)", static_cast(e.type), toJoinString(e.data)); }}, }; @@ -284,7 +404,7 @@ namespace rlib::sequencer { }; - std::map mapTrack = [](const midi::Smf& smf) { + std::map mapTrack = [&decodeText](const midi::Smf& smf) { std::map resultMapTrack; @@ -297,28 +417,32 @@ namespace rlib::sequencer { resultMapTrack[trackKey][e->channel].insert(event); } else if (auto e = std::dynamic_pointer_cast(event.event)) { - const auto name = [](const std::string &s) { + const auto name = [&decodeText](const std::string& s)->std::optional { + + const auto decoded = decodeText(s).value_or(s); + // 文字列 trim 全角スペース対応 const auto stringTrim = [](const std::string& s)->std::string { - std::smatch m; - static const std::regex re(u8R"(^(\s| )*([\s\S]+?)(\s| )*$)"); - if (std::regex_search(s, m, re)) { - if (auto sm = m[2]; sm.matched) { - return sm.str(); - } - } - return ""; +#ifndef _MSC_VER + using namespace std; +#else + using namespace boost; // MSCだとstd::regex_searchで落ちるケースがあるのでとりあえずboostを使う +#endif + static const regex re(R"(^([ \a\b\e\f\n\r\t\v]| )+|([ \a\b\e\f\n\r\t\v]| )+$)"); // "\s"は日本語でヘンになる + return regex_replace(s, re, ""); }; - auto name = stringTrim(s); - return name.empty() ? boost::optional() : boost::optional(name); + if (auto result = stringTrim(decoded); !result.empty()) return result; + return std::nullopt; }; switch (e->type) { case midi::EventMeta::Type::sequenceName: trackKey.sequenceName = name(e->getText()).value_or(TrackKey().sequenceName); + resultMapTrack[trackKey][metaChannel].insert(event); break; case midi::EventMeta::Type::instrumentName: trackKey.instrumentName = name(e->getText()).value_or(TrackKey().instrumentName); + resultMapTrack[trackKey][metaChannel].insert(event); break; default: resultMapTrack[trackKey][metaChannel].insert(event); @@ -364,27 +488,6 @@ namespace rlib::sequencer { const int channel = iMapEvents.first; const auto needChannelName = iMapTrack.second.size() > 1; // 名前に Cannel を含める必要アリ(含めないと重複する) - const auto ensureName = [](const std::string &name) { - const auto isValidName = [](const std::string& s) { - auto r = MmlCompiler::Util::parseWord(s.begin(), s.end()); - if (r && r->word) { - if (std::holds_alternative>(*r->word)) { - return r->next == s.end(); - } - } - return false; - }; - if (isValidName(name)) return name; - auto t = string::format(u8R"("%s")",name); // ダメなら " で括る - if (isValidName(t)) return t; - - for (int i = 0; i < 1000; i++) { - auto t = string::format(u8R"(R"t%d(%s)t%d")", i, name, i); // それでもダメなら R"t?(○○)t?" で括る - if (isValidName(t)) return t; - } - return string::format(u8R"(R"t_(%s)t_")", name); // 最後は R"t_(○○)t_" で諦める - }; - const auto trackName = [&] { std::string s(sequenceName); if (needInstrumentName) { @@ -393,13 +496,13 @@ namespace rlib::sequencer { if (needChannelName) { s += string::format("-%02d", channel + 1); } - return ensureName(s); + return safeText(s); }(); const auto instrument = [&] { std::string s(iMapTrack.first.instrumentName); if (s.empty()) return s; - return ensureName(s); + return safeText(s); }(); Smf::Events& events = iMapEvents.second; @@ -410,130 +513,6 @@ namespace rlib::sequencer { } } - -#if 0 - auto mapTracks = [&] { - std::list smfTracks = [&] { - std::list smfTracks; - for (auto& track : smf2.tracks) { - SmfTrack smfTrack; - Smf::Events metaEvents; - for (auto& event : track.events) { - if (auto e = std::dynamic_pointer_cast(event.event)) { - smfTrack.mapEvents[e->channel].insert(event); - } else if (auto e = std::dynamic_pointer_cast(event.event)) { - metaEvents.insert(event); - switch (e->type) { - case midi::EventMeta::Type::sequenceName: - if (smfTrack.sequenceName.empty()) smfTrack.sequenceName = e->getText(); - break; - case midi::EventMeta::Type::instrumentName: - if (smfTrack.instrumentName.empty()) smfTrack.instrumentName = e->getText(); - break; - default: - break; - } - } else if (auto e = std::dynamic_pointer_cast(event.event)) { - metaEvents.insert(event); - } else { - assert(false); - } - } - - // metaイベントを一番若いチャンネルの列に - auto& events = smfTrack.mapEvents.empty() ? smfTrack.mapEvents[0] : smfTrack.mapEvents.begin()->second; - for (auto& e : metaEvents) { - events.insert(events.lower_bound(e.position), e); // 等価の列の先頭に挿入 - } - smfTracks.emplace_back(std::move(smfTrack)); - } - return smfTracks; - }(); - - // 文字列 trim 全角スペース対応 - const auto stringTrim = [](const std::string& s)->std::string { - // boost::regex で全角スペースを正しく処理する術がわからない。バイナリとして扱うことも不可だった。 - std::smatch m; - static const std::regex re(u8R"(^(\s| )*([\s\S]+?)(\s| )*$)"); - if (std::regex_search(s, m, re)) { - if (auto sm = m[2]; sm.matched) { - return sm.str(); - } - } - return ""; - }; - - // sequenceName, instrumentName 毎にまとめる - std::map>> mapTracks; // > - for (auto& smfTrack : smfTracks) { - const auto sequenceName = [&]{ - std::string s = smfTrack.sequenceName.empty() ? "tr" : smfTrack.sequenceName; // sequenceNameが未定義だったら "tr" とする - return stringTrim(s); - }(); - const auto instrumentName = stringTrim(smfTrack.instrumentName); - auto& list = mapTracks[sequenceName][instrumentName]; - // 合成する(合成しない場合はココをお休み) - if (!list.empty()) { - auto &dstMapEvents = list[0].mapEvents; - for (auto& pair : smfTrack.mapEvents) { - const int ch = pair.first; - auto& dst = dstMapEvents[ch]; - for (auto& e : pair.second) { - dst.insert(e); - } - } - } else { - list.emplace_back(std::move(smfTrack)); - } - } - return mapTracks; - }(); - - std::string result; - for (const auto& i : mapTracks) { - const auto sequenceName = i.first; - for (const auto& j : i.second) { - const auto instrumentName = j.first; - const auto& smfTracks = j.second; - const auto needInstrumentName = smfTracks.size() > 1; // 名前に InstrumentName を含める必要アリ(含めないと重複する) - for (const auto& smfTrack : smfTracks) { - const auto needChannelName = smfTrack.mapEvents.size() > 1; // 名前に channel を含める必要アリ(含めないと重複する) - for (auto pairEvent : smfTrack.mapEvents) { - const auto channel = pairEvent.first; - const auto trackName = [&]{ - std::string s(sequenceName); - if (needInstrumentName) { - s += "-" + instrumentName; - } - if (needChannelName) { - s += string::format("%02d", channel); - } - - const auto isValidName = [](const std::string& s) { - auto r = MmlCompiler::Util::parseWord(s.begin(), s.end()); - if (r && r->word) { - if (std::holds_alternative>(*r->word)) { - return r->next == s.end(); - } - } - return false; - }; - - if( isValidName(s) ) return s; - auto t = "\"" + s + "\""; // " で括る - if (isValidName(t)) return t; - return u8R"(R"tr()" + s + u8R"()tr")"; // それでもダメなら R"tr(○○)tr" で括る - }(); - - const auto mml = makeMml(trackName, channel, pairEvent.second); - result += mml; - } - } - } - } - - -#endif return result; } diff --git a/smftomml.cpp b/smftomml.cpp index 366d396..39cb5c5 100644 --- a/smftomml.cpp +++ b/smftomml.cpp @@ -29,7 +29,7 @@ int main(const int argc, const char* const argv[]) po::store(po::command_line_parser(argc, argv).options(desc).positional(pd).run(), vm); if (vm.count("version")) { - std::cout << "rlib-MML smftomml version 1.1.2" << std::endl; + std::cout << "smftomml version 1.2.2" << std::endl; return 0; } diff --git a/stringformat/StringFormat.h b/stringformat/StringFormat.h index 47db5af..01a3539 100644 --- a/stringformat/StringFormat.h +++ b/stringformat/StringFormat.h @@ -166,7 +166,7 @@ namespace rlib f(format, i); } f(format, tail...); -#ifdef _MSC_VER +#if defined(_MSC_VER) && defined(AFX_EXT_CLASS) } else if constexpr (std::is_same_v || std::is_same_v) { f(format % std::basic_string(head.GetString()), tail...); #endif