Program Listing for File editorTest.cpp

Return to documentation for file (include\tests\editorTest.cpp)

#include "PDJE_interface.hpp"
#include "editor.hpp"

#include "PDJE_Benchmark.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <optional>
#include <sstream>
#include <set>
#include <string>
// #include <NanoLog.hpp>

namespace {

using test_json = nlohmann::json;

std::set<std::string>
CollectLogOids(const std::string &logs_json)
{
    std::set<std::string> oids;
    auto                  root = test_json::parse(logs_json);
    if (!root.contains("LOGS") || !root["LOGS"].is_array()) {
        return oids;
    }
    for (const auto &entry : root["LOGS"]) {
        if (entry.contains("OID") && entry["OID"].is_string()) {
            oids.insert(entry["OID"].get<std::string>());
        }
    }
    return oids;
}

std::optional<std::string>
FindExactlyOneNewOid(const std::set<std::string> &before,
                     const std::set<std::string> &after)
{
    std::optional<std::string> found;
    for (const auto &oid : after) {
        if (!before.contains(oid)) {
            if (found.has_value()) {
                return std::nullopt;
            }
            found = oid;
        }
    }
    return found;
}

bool
RecordNewCommitOID(auto &timeline,
                   std::set<std::string> &oid_cache,
                   std::optional<std::string> &new_oid)
{
    timeline->UpdateLogs();
    auto next_oids = CollectLogOids(timeline->GetLogs());
    new_oid        = FindExactlyOneNewOid(oid_cache, next_oids);
    oid_cache      = std::move(next_oids);
    return new_oid.has_value();
}

std::optional<std::string>
ReadTextFile(const std::filesystem::path &path)
{
    std::ifstream file(path, std::ios::binary);
    if (!file.is_open()) {
        return std::nullopt;
    }
    std::ostringstream oss;
    oss << file.rdbuf();
    return oss.str();
}

std::optional<test_json>
ParseJsonNoThrow(const std::string &text)
{
    try {
        return test_json::parse(text);
    } catch (...) {
        return std::nullopt;
    }
}

std::optional<std::filesystem::path>
FindFirstFileNamed(const std::filesystem::path &root, const std::string &filename)
{
    std::error_code ec;
    if (!std::filesystem::exists(root, ec)) {
        return std::nullopt;
    }
    for (const auto &entry : std::filesystem::recursive_directory_iterator(root, ec)) {
        if (ec) {
            return std::nullopt;
        }
        if (!entry.is_regular_file(ec)) {
            continue;
        }
        if (entry.path().filename() == filename) {
            return entry.path();
        }
    }
    return std::nullopt;
}

int
RunTimeLineDiffFailureSmoke()
{
    namespace fs = std::filesystem;

    bool           ok = true;
    std::error_code ec;
    auto           root = fs::temp_directory_path() / "pdje_timeline_diff_failure_smoke";
    fs::remove_all(root, ec);
    ec.clear();

    std::cout << "[diff-failure-smoke] root: " << root.string() << std::endl;
    PDJE_Editor editor(root, "diff-failure-smoke", "diff-failure-smoke@test");
    if (!editor.mixHandle || !editor.KVHandle) {
        std::cout << "[diff-failure-smoke] FAIL: timeline handles not initialized"
                  << std::endl;
        return 1;
    }

    auto print_check = [&ok](bool cond, const std::string &label) {
        std::cout << "[diff-failure-smoke] " << (cond ? "PASS: " : "FAIL: ")
                  << label << std::endl;
        ok = ok && cond;
    };

    auto bad_mix = editor.mixHandle->Diff("not-an-oid", "still-not-an-oid");
    print_check(!bad_mix.has_value(), "mix diff invalid oid returns nullopt");

    auto bad_kv = editor.KVHandle->Diff("not-an-oid", "still-not-an-oid");
    print_check(!bad_kv.has_value(), "kv diff invalid oid returns nullopt");

    fs::remove_all(root, ec);
    if (!ok) {
        std::cout << "[diff-failure-smoke] RESULT: FAIL" << std::endl;
        return 2;
    }
    std::cout << "[diff-failure-smoke] RESULT: PASS" << std::endl;
    return 0;
}

int
RunTimeLineJsonFormatSmoke()
{
    namespace fs = std::filesystem;

    bool           ok = true;
    std::error_code ec;
    auto           root = fs::temp_directory_path() / "pdje_timeline_json_format_smoke";
    fs::remove_all(root, ec);
    ec.clear();

    std::cout << "[json-format-smoke] root: " << root.string() << std::endl;
    PDJE_Editor editor(root, "json-format-smoke", "json-format-smoke@test");
    if (!editor.mixHandle || !editor.noteHandle || !editor.KVHandle) {
        std::cout << "[json-format-smoke] FAIL: timeline handles not initialized"
                  << std::endl;
        return 1;
    }

    auto print_check = [&ok](bool cond, const std::string &label) {
        std::cout << "[json-format-smoke] " << (cond ? "PASS: " : "FAIL: ")
                  << label << std::endl;
        ok = ok && cond;
    };

    auto read_json = [&print_check](const fs::path &path, const std::string &label)
        -> std::optional<test_json> {
        auto text = ReadTextFile(path);
        print_check(text.has_value(), label + " readable");
        if (!text) {
            return std::nullopt;
        }
        auto parsed = ParseJsonNoThrow(*text);
        print_check(parsed.has_value(), label + " valid json");
        return parsed;
    };

    auto read_text = [&print_check](const fs::path &path, const std::string &label)
        -> std::optional<std::string> {
        auto text = ReadTextFile(path);
        print_check(text.has_value(), label + " readable");
        return text;
    };

    const auto mix_path  = root / "Mixes" / "mixmetadata.PDJE";
    const auto note_path = root / "Notes" / "notemetadata.PDJE";
    const auto kv_path   = root / "KeyValues" / "keyvaluemetadata.PDJE";

    if (auto mix_json = read_json(mix_path, "mix skeleton file")) {
        print_check(mix_json->contains(PDJEARR), "mix skeleton has array key");
        print_check((*mix_json)[PDJEARR].is_array(), "mix skeleton array type");
        print_check((*mix_json)[PDJEARR].empty(), "mix skeleton array empty");
    }
    if (auto note_json = read_json(note_path, "note skeleton file")) {
        print_check(note_json->contains(PDJENOTE), "note skeleton has array key");
        print_check((*note_json)[PDJENOTE].is_array(), "note skeleton array type");
        print_check((*note_json)[PDJENOTE].empty(), "note skeleton array empty");
    }
    if (auto kv_json = read_json(kv_path, "kv skeleton file")) {
        print_check(kv_json->is_object(), "kv skeleton object type");
        print_check(kv_json->empty(), "kv skeleton object empty");
    }

    print_check(editor.AddMusicConfig(
                    "json_smoke_music",
                    "json_smoke_composer",
                    "0",
                    root / "dummy.wav"),
                "music config create");
    auto music_path = FindFirstFileNamed(root / "Musics", "musicmetadata.PDJE");
    print_check(music_path.has_value(), "music metadata file exists");
    if (music_path) {
        if (auto music_json = read_json(*music_path, "music skeleton file")) {
            print_check(music_json->contains(PDJEMUSICBPM),
                        "music skeleton has bpm array key");
            print_check((*music_json)[PDJEMUSICBPM].is_array(),
                        "music skeleton array type");
            print_check((*music_json)[PDJEMUSICBPM].empty(),
                        "music skeleton array empty");
        }
    }

    MixArgs mix_a;
    mix_a.type     = TypeEnum::LOAD;
    mix_a.details  = DetailEnum::HIGH;
    mix_a.ID       = 100;
    mix_a.first    = "fmt_a";
    mix_a.beat     = 1;
    mix_a.subBeat  = 0;
    mix_a.separate = 4;
    MixArgs mix_b  = mix_a;
    mix_b.ID       = 101;
    mix_b.first    = "fmt_b";
    mix_b.beat     = 2;

    NoteArgs note_a;
    note_a.Note_Type   = "tap";
    note_a.Note_Detail = 1;
    note_a.beat        = 1;
    note_a.subBeat     = 0;
    note_a.separate    = 4;
    note_a.railID      = 1;

    MusicArgs mus_a;
    mus_a.bpm      = "120";
    mus_a.beat     = 0;
    mus_a.subBeat  = 0;
    mus_a.separate = 4;
    MusicArgs mus_b = mus_a;
    mus_b.bpm       = "140";
    mus_b.beat      = 4;

    print_check(editor.mixHandle->WriteData(mix_a), "mix format write #1");
    print_check(editor.mixHandle->WriteData(mix_b), "mix format write #2");
    print_check(editor.noteHandle->WriteData(note_a), "note format write #1");
    print_check(editor.KVHandle->WriteData(KEY_VALUE{ "fmt_key_a", "v1" }),
                "kv format write #1");
    print_check(editor.KVHandle->WriteData(KEY_VALUE{ "fmt_key_b", "v2" }),
                "kv format write #2");

    bool music_write_ok = false;
    if (!editor.musicHandle.empty() && editor.musicHandle.back().handle) {
        music_write_ok =
            editor.musicHandle.back().handle->WriteData(mus_a) &&
            editor.musicHandle.back().handle->WriteData(mus_b);
    }
    print_check(music_write_ok, "music format writes");

    if (auto mix_text = read_text(mix_path, "mix format file")) {
        print_check(ParseJsonNoThrow(*mix_text).has_value(),
                    "mix format file valid json");
        print_check(mix_text->find("\n    , {") != std::string::npos,
                    "mix array uses leading comma on appended row");
        print_check(mix_text->find("\n      \"") == std::string::npos,
                    "mix array object is single-line");
    }
    if (auto note_text = read_text(note_path, "note format file")) {
        print_check(ParseJsonNoThrow(*note_text).has_value(),
                    "note format file valid json");
        print_check(note_text->find("\n    {") != std::string::npos,
                    "note array row is line-based");
        print_check(note_text->find("\n      \"") == std::string::npos,
                    "note array object is single-line");
    }
    if (auto kv_text = read_text(kv_path, "kv format file")) {
        print_check(ParseJsonNoThrow(*kv_text).has_value(),
                    "kv format file valid json");
        print_check(kv_text->find("\n  , \"") != std::string::npos,
                    "kv object uses leading comma for later fields");
    }
    if (music_path) {
        if (auto music_text = read_text(*music_path, "music format file")) {
            print_check(ParseJsonNoThrow(*music_text).has_value(),
                        "music format file valid json");
            print_check(music_text->find("\n    , {") != std::string::npos,
                        "music bpm array uses leading comma on appended row");
            print_check(music_text->find("\n      \"") == std::string::npos,
                        "music bpm array object is single-line");
        }
    }

    fs::remove_all(root, ec);
    if (!ok) {
        std::cout << "[json-format-smoke] RESULT: FAIL" << std::endl;
        return 2;
    }
    std::cout << "[json-format-smoke] RESULT: PASS" << std::endl;
    return 0;
}

int
RunTimeLineDiffSmoke()
{
    namespace fs = std::filesystem;

    bool           ok = true;
    std::error_code ec;
    auto           root = fs::temp_directory_path() / "pdje_timeline_diff_smoke";
    fs::remove_all(root, ec);
    ec.clear();

    std::cout << "[diff-smoke] root: " << root.string() << std::endl;
    PDJE_Editor editor(root, "diff-smoke", "diff-smoke@test");
    if (!editor.mixHandle || !editor.KVHandle) {
        std::cout << "[diff-smoke] FAIL: timeline handles not initialized"
                  << std::endl;
        return 1;
    }

    auto print_check = [&ok](bool cond, const std::string &label) {
        std::cout << "[diff-smoke] " << (cond ? "PASS: " : "FAIL: ") << label
                  << std::endl;
        ok = ok && cond;
    };

    // MIX add -> add diff (object recovery path)
    editor.mixHandle->UpdateLogs();
    auto mix_oids = CollectLogOids(editor.mixHandle->GetLogs());
    MixArgs mix_a;
    mix_a.type     = TypeEnum::LOAD;
    mix_a.details  = DetailEnum::HIGH;
    mix_a.ID       = 10;
    mix_a.first    = "smoke_a";
    mix_a.beat     = 1;
    mix_a.subBeat  = 0;
    mix_a.separate = 4;

    MixArgs mix_b = mix_a;
    mix_b.ID      = 11;
    mix_b.first   = "smoke_b";
    mix_b.beat    = 2;

    std::optional<std::string> mix_commit_1;
    std::optional<std::string> mix_commit_2;
    std::optional<std::string> mix_commit_3;
    print_check(editor.mixHandle->WriteData(mix_a), "mix write #1");
    print_check(RecordNewCommitOID(editor.mixHandle, mix_oids, mix_commit_1),
                "capture mix commit #1 oid");
    print_check(editor.mixHandle->WriteData(mix_b), "mix write #2");
    print_check(RecordNewCommitOID(editor.mixHandle, mix_oids, mix_commit_2),
                "capture mix commit #2 oid");

    if (mix_commit_1 && mix_commit_2) {
        auto mix_diff = editor.mixHandle->Diff(*mix_commit_1, *mix_commit_2);
        print_check(mix_diff.has_value(), "mix diff returns value");
        if (mix_diff) {
            print_check(mix_diff->mixRemoved.empty(),
                        "mix diff removed count == 0 for append");
            print_check(mix_diff->mixAdded.size() == 1,
                        "mix diff added count == 1 for append");
            if (!mix_diff->mixAdded.empty()) {
                print_check(mix_diff->mixAdded.front().ID == mix_b.ID,
                            "mix diff added row ID matches");
            }
        }
    }

    print_check(editor.mixHandle->DeleteData(mix_b, false, false) == 1,
                "mix delete #1 removes 1 row");
    print_check(RecordNewCommitOID(editor.mixHandle, mix_oids, mix_commit_3),
                "capture mix commit #3 oid (delete)");

    if (mix_commit_2 && mix_commit_3) {
        auto mix_delete_diff = editor.mixHandle->Diff(*mix_commit_2, *mix_commit_3);
        print_check(mix_delete_diff.has_value(), "mix delete diff returns value");
        if (mix_delete_diff) {
            print_check(mix_delete_diff->mixRemoved.size() == 1,
                        "mix delete diff removed count == 1");
            print_check(mix_delete_diff->mixAdded.empty(),
                        "mix delete diff added count == 0");
            if (!mix_delete_diff->mixRemoved.empty()) {
                print_check(mix_delete_diff->mixRemoved.front().ID == mix_b.ID,
                            "mix delete diff removed row ID matches");
            }
        }
    }

    // KV overwrite -> removed+added diff (field recovery path)
    editor.KVHandle->UpdateLogs();
    auto kv_oids = CollectLogOids(editor.KVHandle->GetLogs());
    std::optional<std::string> kv_commit_1;
    std::optional<std::string> kv_commit_2;

    print_check(editor.KVHandle->WriteData(KEY_VALUE{ "diff_smoke_key", "v1" }),
                "kv write #1");
    print_check(RecordNewCommitOID(editor.KVHandle, kv_oids, kv_commit_1),
                "capture kv commit #1 oid");
    print_check(editor.KVHandle->WriteData(KEY_VALUE{ "diff_smoke_key", "v2" }),
                "kv write #2 (overwrite)");
    print_check(RecordNewCommitOID(editor.KVHandle, kv_oids, kv_commit_2),
                "capture kv commit #2 oid");

    if (kv_commit_1 && kv_commit_2) {
        auto kv_diff = editor.KVHandle->Diff(*kv_commit_1, *kv_commit_2);
        print_check(kv_diff.has_value(), "kv diff returns value");
        if (kv_diff) {
            print_check(kv_diff->kvRemoved.size() == 1,
                        "kv diff removed count == 1 for overwrite");
            print_check(kv_diff->kvAdded.size() == 1,
                        "kv diff added count == 1 for overwrite");
            if (!kv_diff->kvRemoved.empty() && !kv_diff->kvAdded.empty()) {
                print_check(kv_diff->kvRemoved.front().first == "diff_smoke_key",
                            "kv removed key matches");
                print_check(kv_diff->kvAdded.front().first == "diff_smoke_key",
                            "kv added key matches");
            }
        }
    }

    fs::remove_all(root, ec);
    if (!ok) {
        std::cout << "[diff-smoke] RESULT: FAIL" << std::endl;
        return 2;
    }
    std::cout << "[diff-smoke] RESULT: PASS" << std::endl;
    return 0;
}

} // namespace

int
main(int argc, char **argv)
{
    if (argc > 1 && std::string(argv[1]) == "--timeline-diff-smoke") {
        return RunTimeLineDiffSmoke();
    }
    if (argc > 1 && std::string(argv[1]) == "--timeline-diff-failure-smoke") {
        return RunTimeLineDiffFailureSmoke();
    }
    if (argc > 1 && std::string(argv[1]) == "--timeline-json-format-smoke") {
        return RunTimeLineJsonFormatSmoke();
    }

    std::cout << "editor tester" << std::endl;

    auto engine = new PDJE(std::string("testRoot.db"));

    if (engine->InitEditor("test", "test", "testEditorProject")) {
        std::cout << "init ok" << std::endl;
        bool Flag_Already_has_music = false;
        engine->editor->getAll<EDIT_ARG_MUSIC>(
            [&Flag_Already_has_music](const EDIT_ARG_MUSIC &margs) {
                if (margs.musicName == "testMiku") {
                    Flag_Already_has_music = true;
                }
            });
        if (!Flag_Already_has_music) {
            if (engine->editor->ConfigNewMusic(
                    "testMiku",
                    "Camellia",
                    "../../DMCA_FREE_DEMO_MUSIC/miku_temp.wav",
                    "41280")) {

                EDIT_ARG_MUSIC temp;
                temp.musicName    = "testMiku";
                temp.arg.beat     = 0;
                temp.arg.subBeat  = 0;
                temp.arg.separate = 4;
                temp.arg.bpm      = "138";
                engine->editor->AddLine<EDIT_ARG_MUSIC>(temp);

                EDIT_ARG_MIX bpmSet;
                bpmSet.beat     = 0;
                bpmSet.subBeat  = 0;
                bpmSet.type     = TypeEnum::BPM_CONTROL;
                bpmSet.details  = DetailEnum::TIME_STRETCH;
                bpmSet.separate = 4;
                bpmSet.ID       = 0;
                bpmSet.first    = "138";
                engine->editor->AddLine<EDIT_ARG_MIX>(bpmSet);

                EDIT_ARG_MIX loadMusic;
                loadMusic.beat    = 0;
                loadMusic.subBeat = 0;
                loadMusic.type    = TypeEnum::LOAD;

                loadMusic.separate = 4;
                loadMusic.first    = "testMiku";
                loadMusic.second   = "Camellia";
                loadMusic.third    = "138";
                loadMusic.ID       = 0;
                engine->editor->AddLine<EDIT_ARG_MIX>(loadMusic);

                EDIT_ARG_MIX changeBpm;
                changeBpm.beat     = 40;
                changeBpm.subBeat  = 0;
                changeBpm.type     = TypeEnum::BPM_CONTROL;
                changeBpm.details  = DetailEnum::TIME_STRETCH;
                changeBpm.separate = 4;
                changeBpm.ID       = 0;
                changeBpm.first    = "170";
                engine->editor->AddLine<EDIT_ARG_MIX>(changeBpm);

                EDIT_ARG_MIX unloadMusic;
                unloadMusic.beat     = 200;
                unloadMusic.subBeat  = 0;
                unloadMusic.type     = TypeEnum::UNLOAD;
                unloadMusic.ID       = 0;
                unloadMusic.separate = 4;
                engine->editor->AddLine<EDIT_ARG_MIX>(unloadMusic);

                std::cout << "config init ok" << std::endl;
            } else {
                std::cout << "config init failed" << std::endl;
            }
            if (engine->editor->ConfigNewMusic(
                    "ヒアソビ",
                    "Camellia",
                    "../../DMCA_FREE_DEMO_MUSIC/miku_temp.wav",
                    "41280")) {
                EDIT_ARG_MUSIC temp;
                temp.musicName    = "ヒアソビ";
                temp.arg.beat     = 0;
                temp.arg.subBeat  = 0;
                temp.arg.separate = 4;
                temp.arg.bpm      = "138";
                engine->editor->AddLine<EDIT_ARG_MUSIC>(temp);
            }
            EDIT_ARG_NOTE notetemp;
            notetemp.railID = 1;
            for (int i = 0; i < 50; ++i) {
                notetemp.beat = i;
                engine->editor->AddLine<EDIT_ARG_NOTE>(notetemp);
            }
            engine->editor->Undo<EDIT_ARG_NOTE>();
            engine->editor->Redo<EDIT_ARG_NOTE>();
        }
        if (engine->SearchMusic("testMiku", "Camellia").empty()) {
            std::string linter_msg;
            bool        renderRes = engine->editor->render(
                "testTrack", *(engine->DBROOT), linter_msg);
            bool pushRes = engine->editor->pushToRootDB(
                *(engine->DBROOT), "testMiku", "Camellia");
            bool pushResSecond = engine->editor->pushToRootDB(
                *(engine->DBROOT), "ヒアソビ", "Camellia");
            bool trackPushRes =
                engine->editor->pushToRootDB(*(engine->DBROOT), "testTrack");
            if (pushRes)
                std::cout << "pushRes ok" << std::endl;
            if (renderRes)
                std::cout << "renderRes ok" << std::endl;
            if (trackPushRes)
                std::cout << "trackPushRes ok" << std::endl;
            if (pushResSecond)
                std::cout << "pushResSecond ok" << std::endl;
            if (pushRes && renderRes && trackPushRes && pushResSecond)
                std::cout << "push ok" << std::endl;
            else
                std::cout << "push failed" << std::endl;

            // std::shared_ptr<audioPlayer> ap;
            // engine->editor->demoPlayInit(ap, 48, "testTrack");
            // if (!ap) {
            //     std::cout << "failed to init demo player. " << std::endl;
            // }
            // if (ap->Activate()) {
            //     std::cout << "Activated demo" << std::endl;
            // }
            // getchar();
            // if (ap->Deactivate()) {
            //     std::cout << "DeActivated demo" << std::endl;
            // }
        }
        trackdata td;
        td             = engine->SearchTrack("testTrack").front();
        auto mode      = PLAY_MODE::HYBRID_RENDER;
        auto initres   = engine->InitPlayer(mode, td, 480);
        auto activeres = engine->player->Activate();
        if (mode == PLAY_MODE::FULL_PRE_RENDER) {
            getchar();
            engine->player->Deactivate();
            delete engine;
            return 0;
        }
        WBCH("FLAG GetMusicControlPanel()")
        auto musPanel = engine->player->GetMusicControlPanel();
        WBCH("FLAG SearchMusic()")
        auto muses = engine->SearchMusic("ヒアソビ", "Camellia");
        WBCH("FLAG LoadMusic()")
        musPanel->LoadMusic(*(engine->DBROOT), muses.front());
        getchar();
        WBCH("FLAG SetMusic()")
        musPanel->SetMusic("ヒアソビ", true);

        // musPanel->
        getchar();
        WBCH("FLAG getFXHandle()")
        auto Fxhandle = musPanel->getFXHandle("ヒアソビ");
        WBCH("FLAG FX_ON_OFF1()")
        Fxhandle->FX_ON_OFF(FXList::OCSFILTER, true);
        WBCH("FLAG FX_ON_OFF2()")
        Fxhandle->FX_ON_OFF(FXList::EQ, true);
        WBCH("FLAG GetArgSetter()")
        auto ocshandle = Fxhandle->GetArgSetter(FXList::OCSFILTER);
        WBCH("FLAG SetArgSetter1()")
        ocshandle["OCSFilterHighLowSW"](1);
        WBCH("FLAG SetArgSetter2()")
        ocshandle["RangeFreqHalf"](2500);
        WBCH("FLAG SetArgSetter3()")
        ocshandle["MiddleFreq"](5000);
        WBCH("FLAG SetArgSetter4()")
        ocshandle["Bps"](2.2333333);
        WBCH("FLAG SetArgSetter5()")
        ocshandle["OCSFilterDryWet"](0.7);
        getchar();
        WBCH("FLAG ChangeBpm()")
        musPanel->ChangeBpm("ヒアソビ", 120, 60);
        WBCH("FLAG GetArgSetter2()")
        auto eqhandle = Fxhandle->GetArgSetter(FXList::EQ);
        WBCH("FLAG SetArgSetter6()")
        eqhandle["EQHigh"](-20);
        WBCH("FLAG SetArgSetter7()")
        eqhandle["EQMid"](-20);
        WBCH("FLAG SetArgSetter8()")
        eqhandle["EQLow"](20);

        getchar();
        WBCH("FLAG Deactivate()")
        auto deactres = engine->player->Deactivate();

        // auto editor = engine->GetEditorObject();
        // editor->UpdateLog<EDIT_ARG_MIX>();
        // editor->UpdateLog<EDIT_ARG_KEY_VALUE>();
        // editor->UpdateLog<EDIT_ARG_NOTE>();
        // editor->UpdateLog<EDIT_ARG_MUSIC>();

        // editor->GetLogWithJSONGraph<EDIT_ARG_MIX>();
        // editor->GetLogWithJSONGraph<EDIT_ARG_KEY_VALUE>();
        // editor->GetLogWithJSONGraph<EDIT_ARG_NOTE>();
        // editor->GetLogWithJSONGraph<EDIT_ARG_MUSIC>("music name");
        // auto core_line = engine->PullOutDataLine();
        // core_line.preRenderedData;
        // core_line.maxCursor;
        // core_line.nowCursor;
        // core_line.used_frame;

    } else {
        std::cout << "init failed " << std::endl;
        delete engine;
        return 1;
    }
    delete engine;
    // std::cout<<engine.InitEditor("test", "test", "./testEditorProject") <<
    // std::endl; engine.editor->ConfigNewMusic("testMiku", "Camellia", "")
    return 0;
}