Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/app/internal/commandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ void CommandLineParser::init()
"Transpose the given score and export the data to a single JSON file, print it to stdout",
"options"));
m_parser.addOption(QCommandLineOption("score-elements",
"Scan the given score and export elements to a single JSON file, print it to stdout",
"options"));
"Scan the given score and export elements to a single JSON file, print it to stdout"));
m_parser.addOption(QCommandLineOption("source-update", "Update the source in the given score"));

m_parser.addOption(QCommandLineOption({ "S", "style" }, "Load style file", "style"));
Expand Down Expand Up @@ -373,7 +372,6 @@ void CommandLineParser::parse(int argc, char** argv)
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreElements;
m_options.converterTask.inputFile = scorefiles[0];
m_options.converterTask.params[CmdOptions::ParamKey::ScoreElementsOptions] = m_parser.value("score-elements");
}

if (m_parser.isSet("source-update")) {
Expand Down
3 changes: 1 addition & 2 deletions src/app/internal/consoleapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,7 @@ int ConsoleApp::processConverter(const CmdOptions::ConverterTask& task)
ret = converter()->exportScoreTranspose(task.inputFile, task.outputFile, scoreTranspose, openParams);
} break;
case ConvertType::ExportScoreElements: {
std::string options = task.params[CmdOptions::ParamKey::ScoreElementsOptions].toString().toStdString();
ret = converter()->exportScoreElements(task.inputFile, task.outputFile, options, openParams);
ret = converter()->exportScoreElements(task.inputFile, task.outputFile, openParams);
} break;
case ConvertType::ExportScoreVideo: {
ret = converter()->exportScoreVideo(task.inputFile, task.outputFile, openParams);
Expand Down
1 change: 1 addition & 0 deletions src/converter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/internal/compat/backendapi.h
${CMAKE_CURRENT_LIST_DIR}/internal/compat/backendjsonwriter.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/compat/backendjsonwriter.h
${CMAKE_CURRENT_LIST_DIR}/internal/compat/backendtypes.h
${CMAKE_CURRENT_LIST_DIR}/internal/compat/notationmeta.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/compat/notationmeta.h
${CMAKE_CURRENT_LIST_DIR}/internal/compat/scoreelementsscanner.cpp
Expand Down
3 changes: 1 addition & 2 deletions src/converter/iconvertercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ class IConverterController : MODULE_EXPORT_INTERFACE
virtual muse::Ret exportScoreTranspose(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
const OpenParams& openParams = {}) = 0;

virtual muse::Ret exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
const OpenParams& openParams = {}) = 0;
virtual muse::Ret exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams = {}) = 0;

virtual muse::Ret exportScoreVideo(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams = {}) = 0;

Expand Down
146 changes: 65 additions & 81 deletions src/converter/internal/compat/backendapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,40 +69,6 @@ static const std::string DEV_INFO_NAME = "devinfo";
static constexpr bool ADD_SEPARATOR = true;
static constexpr auto NO_STYLE = "";

static ScoreElementScanner::Options parseScoreElementScannerOptions(const std::string& json)
{
if (json.empty()) {
return {};
}

QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(json), &parseError);
if (parseError.error != QJsonParseError::NoError) {
LOGE() << "JSON parse error:" << parseError.errorString();
return {};
}

const QJsonObject obj = doc.object();

ScoreElementScanner::Options options;
options.avoidDuplicates = obj.value("avoidDuplicates").toBool();

const QJsonArray typeArray = obj.value("types").toArray();
for (const auto typeObj : typeArray) {
if (!typeObj.isString()) {
continue;
}

const std::string typeStr = typeObj.toString().toStdString();
const ElementType type = TConv::fromXml(typeStr, ElementType::INVALID);
if (type != ElementType::INVALID) {
options.acceptedTypes.insert(type);
}
}

return options;
}

Ret BackendApi::exportScoreMedia(const muse::io::path_t& in, const muse::io::path_t& out, const muse::io::path_t& highlightConfigPath,
const muse::io::path_t& stylePath,
bool forceMode,
Expand Down Expand Up @@ -231,7 +197,7 @@ Ret BackendApi::exportScoreTranspose(const muse::io::path_t& in, const muse::io:
return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
}

Ret BackendApi::exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
Ret BackendApi::exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out,
const muse::io::path_t& stylePath, bool forceMode)
{
TRACEFUNC;
Expand All @@ -246,7 +212,7 @@ Ret BackendApi::exportScoreElements(const muse::io::path_t& in, const muse::io::
QFile outputFile;
openOutputFile(outputFile, out);

return doExportScoreElements(notation, optionsJson, outputFile);
return doExportScoreElements(notation, outputFile);
}

Ret BackendApi::openOutputFile(QFile& file, const muse::io::path_t& out)
Expand Down Expand Up @@ -686,18 +652,20 @@ Ret BackendApi::doExportScoreTranspose(const INotationPtr notation, BackendJsonW
return ret;
}

muse::Ret BackendApi::doExportScoreElements(const notation::INotationPtr notation, const std::string& optionsJson, QIODevice& out)
muse::Ret BackendApi::doExportScoreElements(const notation::INotationPtr notation, QIODevice& out)
{
mu::engraving::Score* score = notation->elements()->msScore();
ScoreElementScanner::Options options = parseScoreElementScannerOptions(optionsJson);
ScoreElementScanner::InstrumentElementMap elements = ScoreElementScanner::scanElements(score, options);
ElementMap elements = ScoreElementScanner::scanElements(score);

QJsonArray rootArray;

auto writeLocation = [](const ScoreElementScanner::ElementInfo::Location& loc, QJsonObject& obj) {
if (loc.trackIdx != muse::nidx) {
obj["staffIdx"] = (int)mu::engraving::track2staff(loc.trackIdx);
obj["voiceIdx"] = (int)mu::engraving::track2voice(loc.trackIdx);
auto writeLocation = [](const ElementInfo::Location& loc, QJsonObject& obj) {
if (loc.staffIdx != muse::nidx) {
obj["staffIdx"] = static_cast<int>(loc.staffIdx);
}

if (loc.voiceIdx != muse::nidx) {
obj["voiceIdx"] = static_cast<int>(loc.voiceIdx);
}

if (loc.measureIdx != muse::nidx) {
Expand All @@ -709,52 +677,68 @@ muse::Ret BackendApi::doExportScoreElements(const notation::INotationPtr notatio
}
};

auto writeFlags = [](const ElementInfo::Flags& flags, QJsonObject& obj) {
if (flags.testFlag(ElementInfo::FlagType::Tied)) {
obj["tied"] = true;
}
};

auto writeNotes = [&writeFlags](const ElementInfo::NoteList& notes, QJsonObject& obj) {
QJsonArray noteArray;

for (const ElementInfo::Note& note : notes) {
QJsonObject noteObj;
noteObj["name"] = note.name.toQString();
writeFlags(note.flags, noteObj);
noteArray << noteObj;
}

obj["notes"] = noteArray;
};

for (const auto& instrumentPair : elements) {
QJsonArray typeArray;

for (const auto& pair: instrumentPair.second) {
QJsonArray elementArray;

for (const ScoreElementScanner::ElementInfo& element : pair.second) {
QJsonObject obj;

if (!element.name.empty()) {
obj["name"] = element.name.toQString();
}

if (!element.notes.empty()) {
obj["notes"] = element.notes.toQString();
}

if (!element.text.empty()) {
obj["text"] = element.text.toQString();
}

if (element.start == element.end) {
writeLocation(element.start, obj);
} else {
QJsonObject start, end;
writeLocation(element.start, start);
writeLocation(element.end, end);
obj["start"] = start;
obj["end"] = end;
}

if (!obj.empty()) {
elementArray << obj;
}
QJsonArray elementArray;

for (const ElementInfo& element : instrumentPair.second) {
QJsonObject obj;

obj["type"] = mu::engraving::TConv::toXml(element.type).ascii();

if (!element.name.empty()) {
obj["name"] = element.name.toQString();
}

if (!element.notes.empty()) {
writeNotes(element.notes, obj);
} else {
writeFlags(element.flags, obj);
}

if (!element.duration.empty()) {
obj["duration"] = element.duration.toQString();
}

if (!element.text.empty()) {
obj["text"] = element.text.toQString();
}

if (element.start == element.end) {
writeLocation(element.start, obj);
} else {
QJsonObject start, end;
writeLocation(element.start, start);
writeLocation(element.end, end);
obj["start"] = start;
obj["end"] = end;
}

QString type = mu::engraving::TConv::toXml(pair.first).ascii();
QJsonObject typeObj;
typeObj[type] = elementArray;
typeArray << typeObj;
elementArray << obj;
}

QJsonObject instrumentObj;
instrumentObj["instrumentId"] = instrumentPair.first.instrumentId.toQString();
instrumentObj["partId"] = instrumentPair.first.partId.toQString();
instrumentObj["types"] = typeArray;
instrumentObj["elements"] = elementArray;
rootArray << instrumentObj;
}

Expand Down
6 changes: 3 additions & 3 deletions src/converter/internal/compat/backendapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ class BackendApi
static muse::Ret exportScoreTranspose(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
const muse::io::path_t& stylePath, bool forceMode = false, bool unrollRepeats = false);

static muse::Ret exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
const muse::io::path_t& stylePath, bool forceMode = false);
static muse::Ret exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out, const muse::io::path_t& stylePath,
bool forceMode = false);

static muse::Ret updateSource(const muse::io::path_t& in, const std::string& newSource, bool forceMode = false);

Expand Down Expand Up @@ -95,7 +95,7 @@ class BackendApi
static muse::Ret doExportScoreTranspose(const notation::INotationPtr notation, BackendJsonWriter& jsonWriter,
bool addSeparator = false);

static muse::Ret doExportScoreElements(const notation::INotationPtr notation, const std::string& optionsJson, QIODevice& out);
static muse::Ret doExportScoreElements(const notation::INotationPtr notation, QIODevice& out);

static muse::RetVal<QByteArray> scorePartJson(mu::engraving::Score* score, const std::string& fileName);

Expand Down
73 changes: 73 additions & 0 deletions src/converter/internal/compat/backendtypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-Studio-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2025 MuseScore Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once

#include "global/types/string.h"
#include "global/types/flags.h"
#include "global/realfn.h"

#include "engraving/types/types.h"

namespace mu::converter {
struct ElementInfo
{
mu::engraving::ElementType type = mu::engraving::ElementType::INVALID;
muse::String name;
muse::String duration;
muse::String text;

enum class FlagType {
NoFlags = 0,
Tied,
};

using Flags = muse::Flags<FlagType>;
Flags flags;

struct Note {
muse::String name;
muse::Flags<FlagType> flags;
};

using NoteList = std::vector<Note>;
NoteList notes;

struct Location {
mu::engraving::staff_idx_t staffIdx = muse::nidx;
mu::engraving::voice_idx_t voiceIdx = muse::nidx;
size_t measureIdx = muse::nidx;
float beat = -1.f;

bool operator==(const Location& l) const
{
return staffIdx == l.staffIdx
&& voiceIdx == l.voiceIdx
&& measureIdx == l.measureIdx
&& muse::RealIsEqual(beat, l.beat);
}
} start, end;
};

// Instrument -> Elements
using ElementInfoList = std::vector<ElementInfo>;
using ElementMap = std::map<mu::engraving::InstrumentTrackId, ElementInfoList>;
}
Loading
Loading