diff --git a/CMakeLists.txt b/CMakeLists.txt index 332a0f2c9610..3289709125d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,15 +123,16 @@ endif (NOT APPLE AND NOT MINGW AND NOT MSVC) option(AEOLUS "Enable pipe organ synthesizer" OFF) option(ZERBERUS "Enable experimental SFZ sampler" ON) option(OSC "Enable OSC remote control protocol" ON) -option(OMR "Enable PDF import" OFF) # OMR - optical music recognition +option(AVSOMR "Enable AVSOMR import" OFF) # AVSOMR - optical music recognition by audirevis +option(OMR "Enable PDF import" OFF) # OMR - optical music recognition # For installation see: http://ubuntuforums.org/showthread.php?t=1647350 -option(OCR "Enable OCR, requires OMR" OFF) # Requires tesseract 3.0, needs work on mac/win -option(SOUNDFONT3 "Ogg Vorbis compressed fonts" ON) # Enable Ogg Vorbis compressed fonts, requires Ogg & Vorbis -option(HAS_AUDIOFILE "Enable audio export" ON) # Requires libsndfile +option(OCR "Enable OCR, requires OMR" OFF) # Requires tesseract 3.0, needs work on mac/win +option(SOUNDFONT3 "Ogg Vorbis compressed fonts" ON) # Enable Ogg Vorbis compressed fonts, requires Ogg & Vorbis +option(HAS_AUDIOFILE "Enable audio export" ON) # Requires libsndfile option(USE_SYSTEM_QTSINGLEAPPLICATION "Use system QtSingleApplication" OFF) -option(USE_SYSTEM_FREETYPE "Use system FreeType" OFF) # requires freetype >= 2.5.2, does not work on win -option(USE_SYSTEM_POPPLER "Use system Poppler for OMR" OFF) -option(BUILD_LAME "Enable MP3 export" ON) # Requires libmp3lame, call CMake with -DBUILD_LAME="OFF" to disable +option(USE_SYSTEM_FREETYPE "Use system FreeType" OFF) # requires freetype >= 2.5.2, does not work on win +option(USE_SYSTEM_POPPLER "Use system Poppler for OMR" OFF) +option(BUILD_LAME "Enable MP3 export" ON) # Requires libmp3lame, call CMake with -DBUILD_LAME="OFF" to disable option(DOWNLOAD_SOUNDFONT "Download the latest soundfont version as part of the build process" ON) # licence incompatibility, must never be distributed @@ -852,30 +853,34 @@ add_subdirectory(manual) add_subdirectory(demos) if (USE_PORTMIDI AND (MINGW OR APPLE OR MSVC)) - subdirs (thirdparty/portmidi) + add_subdirectory(thirdparty/portmidi) endif (USE_PORTMIDI AND (MINGW OR APPLE OR MSVC)) if (AEOLUS) - subdirs (aeolus) + add_subdirectory(aeolus) endif (AEOLUS) if (ZERBERUS) - subdirs (zerberus) + add_subdirectory(zerberus) endif (ZERBERUS) +if (AVSOMR) + add_subdirectory(avsomr) +endif (AVSOMR) + if (OMR) - subdirs (omr) + add_subdirectory(omr) if (NOT USE_SYSTEM_POPPLER) - subdirs (thirdparty/poppler) + add_subdirectory(thirdparty/poppler) endif (NOT USE_SYSTEM_POPPLER) endif (OMR) if (OSC) - subdirs (thirdparty/ofqf) + add_subdirectory(thirdparty/ofqf) endif (OSC) if (NOT USE_SYSTEM_FREETYPE) - subdirs (thirdparty/freetype) + add_subdirectory(thirdparty/freetype) endif (NOT USE_SYSTEM_FREETYPE) ## diff --git a/all.h b/all.h index d6a24ffc93d8..49e60b48d5cb 100644 --- a/all.h +++ b/all.h @@ -95,6 +95,7 @@ #include #include +#include #include #include @@ -185,6 +186,9 @@ #include #include +#include +#include + #include #include #include diff --git a/avsomr/CMakeLists.txt b/avsomr/CMakeLists.txt new file mode 100644 index 000000000000..1358a567a3d7 --- /dev/null +++ b/avsomr/CMakeLists.txt @@ -0,0 +1,95 @@ +#============================================================================= +# MuseScore +# Music Composition & Notation +# +# Copyright (C) 2020 MuseScore BVBA and others +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2. +# +# 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +#============================================================================= + +include (${PROJECT_SOURCE_DIR}/build/gch.cmake) + +include_directories( + ${PROJECT_SOURCE_DIR} + ) + +if (NOT MSVC) + set(_all_h_file "${PROJECT_BINARY_DIR}/all.h") +else (NOT MSVC) + set(_all_h_file "${PROJECT_SOURCE_DIR}/all.h") +endif (NOT MSVC) + +add_library ( + avsomr STATIC + ${_all_h_file} + ${PCH} + avsimport.cpp + avslog.h + ret.cpp + ret.h + avsomr.cpp + avsomr.h + avsomrreader.cpp + avsomrreader.h + msmrfile.cpp + msmrfile.h + msmrwriter.cpp + msmrwriter.h + avsomrdrawer.cpp + avsomrdrawer.h + iavsomrrecognizer.h + avsomrnetrecognizer.cpp + avsomrnetrecognizer.h + avsomrlocal.cpp + avsomrlocal.h + avsomrlocalrecognizer.cpp + avsomrlocalrecognizer.h + avsomrlocalinstaller.cpp + avsomrlocalinstaller.h + avsomrsetup.cpp + avsomrsetup.h + ui/recognitionproccessdialog.cpp + ui/recognitionproccessdialog.h + ui/taskbarprogress.cpp + ui/taskbarprogress.h + ui/infopopup.cpp + ui/infopopup.h + ui/setupavsomrview.cpp + ui/setupavsomrview.h + ) + +if (NOT MSVC) + set_target_properties ( + avsomr + PROPERTIES + COMPILE_FLAGS "${PCH_INCLUDE} -g -Wall -Wextra -Winvalid-pch -Wno-unused-private-field" + ) +else (NOT MSVC) + set_target_properties ( + avsomr + PROPERTIES + COMPILE_FLAGS "${PCH_INCLUDE}" # TODO: Add disabling of unused private field warning? + ) +endif (NOT MSVC) + +xcode_pch(avsomr all) + +# Use MSVC pre-compiled headers +vstudio_pch( avsomr ) + +# MSVC does not depend on mops1 & mops2 for PCH +if (NOT MSVC) + ADD_DEPENDENCIES(avsomr mops1) + ADD_DEPENDENCIES(avsomr mops2) +endif (NOT MSVC) + diff --git a/avsomr/avsimport.cpp b/avsomr/avsimport.cpp new file mode 100644 index 000000000000..51de7757f3f2 --- /dev/null +++ b/avsomr/avsimport.cpp @@ -0,0 +1,188 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include +#include +#include +#include + +#include "libmscore/score.h" +#include "libmscore/text.h" + +#include "avslog.h" +#include "msmrfile.h" +#include "avsomr.h" +#include "avsomrreader.h" +#include "avsomrnetrecognizer.h" +#include "avsomrlocalrecognizer.h" +#include "avsomrlocal.h" +#include "ui/recognitionproccessdialog.h" +#include "ui/infopopup.h" + +namespace Ms { + +extern Score::FileError importMusicXml(MasterScore* score, QIODevice* dev, const QString& name); + +//--------------------------------------------------------- +// doImportMSMR +// return Score::FileError::FILE_* errors +//--------------------------------------------------------- + +static Score::FileError doImportMSMR(MasterScore* score + , QIODevice *data + , const QString& filePath + , bool created) + { + IF_ASSERT(score) { + return Score::FileError::FILE_ERROR; + } + + IF_ASSERT(data) { + return Score::FileError::FILE_ERROR; + } + + QString baseName = QFileInfo(filePath).baseName(); + auto msmr = std::make_shared(data->readAll(), baseName); + + // import score + QByteArray msczData = msmr->readMscz(); + if (!msczData.isEmpty()) { + // read mscz + QBuffer msczBuf(&msczData); + msczBuf.open(QIODevice::ReadOnly); + Score::FileError err = score->loadMsc("score.mscz", &msczBuf, true); + if (err != Score::FileError::FILE_NO_ERROR) + return err; + } + else { + // import MusicXml + QByteArray muzicXmlData = msmr->readMuzicXml(); + if (muzicXmlData.isEmpty()) + return Score::FileError::FILE_BAD_FORMAT; + + QBuffer muzicXmlBuf(&muzicXmlData); + muzicXmlBuf.open(QIODevice::ReadOnly); + + Score::FileError err = importMusicXml(score, &muzicXmlBuf, baseName); + if (err != Score::FileError::FILE_NO_ERROR) + return err; + + // corrections after import + + //! NOTE Audiveris sets the title "[Audiveris detected movement]", + //! I decided it was not convenient for the user, + //! the file base name as title is more convenient. + QString titleText = baseName; + Text* title = score->getText(Tid::TITLE); + if (title) + title->setPlainText(titleText); + + score->setMetaTag("movementNumber", ""); + score->setMetaTag("movementTitle", ""); + score->setMetaTag("source", QFileInfo(filePath).fileName()); + score->setMetaTag("workTitle", title ? title->plainText() : titleText); + } + + // import omr + QByteArray ormData = msmr->readOmr(); + if (ormData.isEmpty()) { + return Score::FileError::FILE_BAD_FORMAT; + } + + QBuffer ormBuf(&ormData); + Avs::AvsOmrReader reader; + std::shared_ptr omr = reader.read(&ormBuf); + if (!omr) + return Score::FileError::FILE_BAD_FORMAT; + + omr->setMsmrFile(msmr); + score->setAvsOmr(omr); + + // set format + score->setCreated(created); + + //! NOTE format of file determined by .ext, + //! therefore setting .msmr for correctly saving + score->fileInfo()->setFile(QFileInfo(filePath).path() + "/" + baseName + ".msmr"); + + return Score::FileError::FILE_NO_ERROR; + } + +//--------------------------------------------------------- +// importMSMR +// return Score::FileError::FILE_* errors +//--------------------------------------------------------- + +Score::FileError importMSMR(MasterScore* score, const QString& filePath) + { + QFile file(filePath); + if (!file.exists()) { + LOGE() << "not exists file: " << filePath; + return Score::FileError::FILE_NOT_FOUND; + } + + if (!file.open(QIODevice::ReadOnly)) { + LOGE() << "failed open file: " << filePath; + return Score::FileError::FILE_OPEN_ERROR; + } + + return doImportMSMR(score, &file, filePath, false); + } + +//--------------------------------------------------------- +// loadAndImportMSMR +// return Score::FileError::FILE_* errors +//--------------------------------------------------------- + +Score::FileError loadAndImportMSMR(MasterScore* score, const QString& filePath) + { + Avs::RecognitionProccessDialog progDialog; + + auto onStep = [&progDialog](const Avs::AvsOmrNetRecognizer::Step& step) { + progDialog.onStep(step); + }; + + std::shared_ptr recognizer; + bool useLocal = Avs::AvsOmrLocal::instance()->isUseLocal(); + if (useLocal) + recognizer = std::make_shared(); + else + recognizer = std::make_shared(); + + LOGI() << "try use avs omr recognizer: " << recognizer->type(); + progDialog.setType(recognizer->type()); + progDialog.show(); + + QByteArray data; + bool ok = recognizer->recognize(filePath, &data, onStep); + + progDialog.onFinished(ok); + + if (!ok) { + //! NOTE A message has already been shown to the user + return Score::FileError::FILE_IGNORE_ERROR; + } + + QBuffer buf(&data); + buf.open(QIODevice::ReadOnly); + + return doImportMSMR(score, &buf, filePath, true); + } + +} diff --git a/avsomr/avslog.h b/avsomr/avslog.h new file mode 100644 index 000000000000..fba60c0579a9 --- /dev/null +++ b/avsomr/avslog.h @@ -0,0 +1,42 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_LOG_H +#define AVS_LOG_H + +#include + +#define LOGD() qDebug() +#define LOGI() qInfo() +#define LOGW() qWarning() +#define LOGE() qCritical() + +#define IF_ASSERT(cond) if (!(cond)) { \ + LOGE() << "\"ASSERT FAILED!\":" << #cond << __FILE__ << __LINE__; \ + Q_ASSERT(cond); \ +} \ +if (!(cond)) \ + +#define IF_FAILED(cond) if (!(cond)) { \ + LOGE() << "\"FAILED!\":" << #cond << __FILE__ << __LINE__; \ +} \ +if (!(cond)) \ + + +#endif // AVS_LOG_H diff --git a/avsomr/avsomr.cpp b/avsomr/avsomr.cpp new file mode 100644 index 000000000000..0c9730456b39 --- /dev/null +++ b/avsomr/avsomr.cpp @@ -0,0 +1,386 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomr.h" + +#include "avslog.h" + +namespace { +static int TRASH_GLYPH_SIZE{10}; +static int DEFAULT_SYSTEM_GAP{200}; +static float SYSTEM_GAP_PERCENT{0.55f}; +} + +using namespace Ms::Avs; + +AvsOmr::AvsOmr() + { + + } + +//--------------------------------------------------------- +// resolve - calculates some elements and attributes +//--------------------------------------------------------- + +void AvsOmr::resolve() + { + auto firstMS = [](Sheet* sh) -> const MStack* { + if (sh->page.systems.empty()) + return nullptr; + + for (int si = 0; si < sh->page.systems.count(); ++si) { + if (sh->page.systems.at(si).mstacks.empty()) + continue; + + return &sh->page.systems.at(si).mstacks.first(); + } + return nullptr; + }; + + auto lastMS = [](Sheet* sh) -> const MStack* { + if (sh->page.systems.empty()) + return nullptr; + + for (int si = (sh->page.systems.count() - 1); si != 0; --si) { + if (sh->page.systems.at(si).mstacks.empty()) { + continue; + } + return &sh->page.systems.at(si).mstacks.last(); + } + return nullptr; + }; + + auto staffBarlineBBox = [](const Sheet* sh, const System& sys, const Staff& staff) { + + IF_FAILED(!staff.barlines.empty()) { + return QRect(); + } + + ID barID = staff.barlines.first(); + Barline bar = sys.inters.barlines.value(barID); + const Glyph* gly = sh->glyphs.value(bar.glyphID, nullptr); + IF_FAILED(gly) { + return QRect(); + } + + return gly->bbox; + }; + + auto isTrashGlyph = [](const AvsOmr::Glyph* g) { + if (g->bbox.width() <= TRASH_GLYPH_SIZE && g->bbox.height() <= TRASH_GLYPH_SIZE) + return true; + + return false; + }; + + Idx midx{0}; + for (int shi = 0; shi < _sheets.count(); ++shi) { + + Sheet* sh = _sheets[shi]; + if (sh->page.systems.empty()) + continue; + + for (System& sys : sh->page.systems) { + // meausure idx + for (MStack& ms : sys.mstacks) { + ms.idx = midx; + ++midx; + } + + // system top and bottom + IF_FAILED(!sys.part.staffs.empty()) { + continue; + } + + QRect topBarBBox = staffBarlineBBox(sh, sys, sys.part.staffs.first()); + QRect bottomBarBBox = staffBarlineBBox(sh, sys, sys.part.staffs.last()); + sys.top = topBarBBox.top(); + sys.bottom = bottomBarBBox.bottom() + 40; + } + + // meausure ranges + const MStack* fms = firstMS(sh); + const MStack* lms = lastMS(sh); + sh->mbeginIdx = fms ? fms->idx : 0; + sh->mendIdx = lms ? lms->idx : 0; + + // glyph used + QList usedBBoxs; + int gap = 5; + for (const Glyph* g : sh->glyphs) { + if (GlyphUsed::Used == g->used) + usedBBoxs.append(g->bbox.adjusted(-gap, -gap, gap, gap)); + } + + auto isUsedContains = [](const QList& usedBBoxs, const QRect& bbox) { + for (const QRect& r : usedBBoxs) { + if (r.contains(bbox)) + return true; + } + return false; + }; + + //! TODO optimization may be needed because complexity O^2 + for (Glyph* g : sh->glyphs) { + if (isTrashGlyph(g)) { + g->used = GlyphUsed::Trash; + } + else if (GlyphUsed::Free == g->used) { + if (isUsedContains(usedBBoxs, g->bbox)) { + g->used = GlyphUsed::Free_Covered; + } + } + } + } + } + +//--------------------------------------------------------- +// sheetNumByMeausereIdx +//--------------------------------------------------------- + +AvsOmr::Num AvsOmr::sheetNumByMeausereIdx(const Idx& meausureIdx) const + { + for (const Sheet* sh : _sheets) + if (meausureIdx >= sh->mbeginIdx && meausureIdx <= sh->mendIdx) + return sh->num; + + return 0; + } + +//--------------------------------------------------------- +// sheet +//--------------------------------------------------------- + +const AvsOmr::Sheet* AvsOmr::sheet(const Num& sheetNum) const + { + for (const Sheet* sh : _sheets) + if (sheetNum == sh->num) + return sh; + + return nullptr; + } + +//--------------------------------------------------------- +// isGlyphUsed +//--------------------------------------------------------- + +bool AvsOmr::Sheet::isGlyphUsed(const ID& glypthID) const + { + for (const System& sys : page.systems) + if (sys.inters.usedglyphs.contains(glypthID)) + return true; + + return false; + } + +//--------------------------------------------------------- +// isGlyphFree +//--------------------------------------------------------- + +bool AvsOmr::Sheet::isGlyphFree(const ID& glypthID) const + { + for (const System& sys : page.systems) + if (sys.freeglyphs.contains(glypthID)) + return true; + + return false; + } + +//--------------------------------------------------------- +// stackByIdx +//--------------------------------------------------------- + +const AvsOmr::MStack& AvsOmr::System::stackByIdx(Idx idx, Idx *idxInSys) const + { + for (int i = 0; i < mstacks.count(); ++i) { + const MStack& m = mstacks.at(i); + if (m.idx == idx) { + if (idxInSys) + *idxInSys = i; + + return m; + } + } + + static MStack dummy; + return dummy; + } + +//--------------------------------------------------------- +// mmetrics +//--------------------------------------------------------- + +AvsOmr::MMetrics AvsOmr::mmetrics(const Num &sheetNum, const Idx &meausureIdx) const + { + const Sheet* sh = sheet(sheetNum); + IF_ASSERT(sh) { + return MMetrics(); + } + + MMetrics mm; + int sysCount = sh->page.systems.count(); + for (int si = 0; si < sysCount; ++si) { + const System& sys = sh->page.systems.at(si); + + Idx idxInSys{0}; + const AvsOmr::MStack& m = sys.stackByIdx(meausureIdx, &idxInSys); + if (!m.isValid()) + continue; + + // bbox + mm.bbox.setLeft(m.left); + mm.bbox.setRight(m.right); + mm.bbox.setTop(sys.top); + mm.bbox.setBottom(sys.bottom); + + // bbox header + if (0 == idxInSys) { + if (!sys.part.staffs.empty()) { + const Staff& topStaff = sys.part.staffs.first(); + mm.hbbox.setLeft(topStaff.header.start); + mm.hbbox.setRight(topStaff.header.stop); + mm.hbbox.setTop(mm.bbox.top()); + mm.hbbox.setBottom(mm.bbox.bottom()); + } + } + + // bbox elements + mm.ebbox = mm.bbox; + + bool isFirstSys = (0 == si); + bool isLastSys = ((sysCount - 1) == si); + int halfH = mm.bbox.height() / 2; + + if (1 == sysCount) { + mm.ebbox.setTop(mm.ebbox.top() - halfH); + mm.ebbox.setBottom(mm.ebbox.bottom() + halfH); + } + else { + + auto gapToNextSys = [sh](const System& sys, size_t si) { + const System& nextSys = sh->page.systems.at(si + 1); + int gapSys = nextSys.top - sys.bottom; + IF_ASSERT(gapSys > 0) { + gapSys = DEFAULT_SYSTEM_GAP; + } + return gapSys; + }; + + auto gapToPrevSys = [sh](const System& sys, size_t si) { + const System& prevSys = sh->page.systems.at(si - 1); + int gapSys = sys.top - prevSys.bottom; + IF_ASSERT(gapSys > 0) { + gapSys = DEFAULT_SYSTEM_GAP; + } + return gapSys; + }; + + if (isFirstSys) { + mm.ebbox.setTop(mm.ebbox.top() - halfH); + int gapSys = gapToNextSys(sys, si); + mm.ebbox.setBottom(mm.ebbox.bottom() + (gapSys * SYSTEM_GAP_PERCENT)); + } + else if (isLastSys) { + int gapSys = gapToPrevSys(sys, si); + mm.ebbox.setTop(mm.ebbox.top() - (gapSys * SYSTEM_GAP_PERCENT)); + mm.ebbox.setBottom(mm.ebbox.bottom() + halfH); + } + else { + int gapPrevSys = gapToPrevSys(sys, si); + mm.ebbox.setTop(mm.ebbox.top() - (gapPrevSys * SYSTEM_GAP_PERCENT)); + + int gapNextSys = gapToNextSys(sys, si); + mm.ebbox.setBottom(mm.ebbox.bottom() + (gapNextSys * SYSTEM_GAP_PERCENT)); + } + } + + //! RETURN + return mm; + } + + return mm; + } + +//--------------------------------------------------------- +// glyphsByBBox +//--------------------------------------------------------- + +QList AvsOmr::glyphsByBBox(const Num& sheetNum, const QRect& bbox, QList& accepted) const + { + const Sheet* sh = sheet(sheetNum); + IF_ASSERT(sh) { + return QList(); + } + + QList list; + for (auto g : sh->glyphs) { + if (!accepted.contains(g->used)) + continue; + + if (bbox.contains(g->bbox)) + list.append(g); + } + + return list; + } + +//--------------------------------------------------------- +// config +//--------------------------------------------------------- + +AvsOmr::Config& AvsOmr::config() + { + return _config; + } + +//--------------------------------------------------------- +// config const +//--------------------------------------------------------- + +const AvsOmr::Config& AvsOmr::config() const + { + return _config; + } + +//--------------------------------------------------------- +// setMsmrFile +//--------------------------------------------------------- + +void AvsOmr::setMsmrFile(std::shared_ptr file) + { + _msmrFile = file; + } + +//--------------------------------------------------------- +// msmrFile +//--------------------------------------------------------- + +std::shared_ptr AvsOmr::msmrFile() const + { + return _msmrFile; + } + +//--------------------------------------------------------- +// info +//--------------------------------------------------------- + +const AvsOmr::Info& AvsOmr::info() const + { + return _info; + } diff --git a/avsomr/avsomr.h b/avsomr/avsomr.h new file mode 100644 index 000000000000..945930856abf --- /dev/null +++ b/avsomr/avsomr.h @@ -0,0 +1,196 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMR_H +#define AVS_AVSOMR_H + +#include +#include +#include +#include +#include +#include +#include + +namespace Ms { +namespace Avs { + +class MsmrFile; +class AvsOmr + { + public: + AvsOmr(); + using ID = uint32_t; + using Num = uint16_t; + using Idx = uint16_t; + + // Model + struct Barline { + ID id{0}; + ID glyphID{0}; + }; + + struct Staff { + ID id{0}; + QList barlines; + + struct { + int32_t start{0}; + int32_t stop{0}; + ID clefID{0}; + ID keyID{0}; + ID timeID{0}; + } header; + + bool isValid() const { return id > 0; } + }; + + struct MStack { + ID id{0}; + Idx idx{0}; // resolved + int32_t left{0}; + int32_t right{0}; + bool isValid() const { return right > 0; } + }; + + struct Part { + QList staffs; + }; + + struct Inters { + QHash barlines; + QSet usedglyphs; + }; + + struct System { + QList mstacks; + Part part; + Inters inters; + QSet freeglyphs; + + // resolved + int32_t top{0}; + int32_t bottom{0}; + + const MStack& stackByIdx(Idx idx, Idx* idxInSys = nullptr) const; + }; + + struct Page { + QList systems; + }; + + enum class GlyphUsed { + Undefined = 0, + Used, + Free, + Free_Covered, // Avs marked as free, but fully covered used + Trash // Detected as trash (too small) + }; + + struct Glyph { + ID id{0}; + QRect bbox; + GlyphUsed used{GlyphUsed::Undefined}; + QImage img; + }; + + struct Sheet { + Num num{0}; + Page page; + Idx mbeginIdx{0}; + Idx mendIdx{0}; + QHash glyphs; + + bool isGlyphUsed(const ID& glypthID) const; + bool isGlyphFree(const ID& glypthID) const; + }; + + struct Book { + uint16_t sheets{0}; + }; + + void resolve(); + + // Configure + struct Config { + bool isHiddenAll() const { return !_isShowRecognized && !_isShowNotRecognized; } + void setIsShowRecognized(bool arg) { _isShowRecognized = arg; } + bool isShowRecognized() const { return _isShowRecognized; } + void setIsShowNotRecognized(bool arg) { _isShowNotRecognized = arg; } + bool isShowNotRecognized() const { return _isShowNotRecognized; } + + private: + bool _isShowRecognized{true}; + bool _isShowNotRecognized{true}; + }; + + Config& config(); + const Config& config() const; + + // Access + struct MMetrics { + QRect bbox; //! NOTE bbox of measure + QRect ebbox; //! NOTE bbox in which there may be elements belonging to measure + QRect hbbox; //! NOTE bbox of measure header (clef, key, time) + + QRect headerBBox() const { + return hbbox; + } + + QRect chordBBox() const { + QRect c = bbox; + c.setLeft(c.left() + hbbox.width()); + return c; + } + }; + + Num sheetNumByMeausereIdx(const Idx& meausureIdx) const; + MMetrics mmetrics(const Num& sheetNum, const Idx &meausureIdx) const; + QList glyphsByBBox(const Num& sheetNum, const QRect& bbox, QList& accepted) const; + + // Data + void setMsmrFile(std::shared_ptr file); // keep data for saving + std::shared_ptr msmrFile() const; + + // Info + struct Info { + QColor usedColor; + uint32_t usedCount{0}; + QColor freeColor; + uint32_t freeCount{0}; + }; + + const Info& info() const; + + private: + friend class AvsOmrReader; + + const Sheet* sheet(const Num& sheetNum) const; + + Book _book; + QList _sheets; + Config _config; + std::shared_ptr _msmrFile; + Info _info; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMR_H diff --git a/avsomr/avsomrdrawer.cpp b/avsomr/avsomrdrawer.cpp new file mode 100644 index 000000000000..26277ae448b2 --- /dev/null +++ b/avsomr/avsomrdrawer.cpp @@ -0,0 +1,227 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrdrawer.h" + +#include + +#include "libmscore/score.h" +#include "libmscore/measure.h" + +#include "avslog.h" + +using namespace Ms::Avs; + +AvsOmrDrawer::AvsOmrDrawer() + { + + } + +//--------------------------------------------------------- +// makeContext +//--------------------------------------------------------- + +std::shared_ptr AvsOmrDrawer::makeContext(QPainter* p, const Ms::Score* score) const + { + IF_ASSERT(p) { + return nullptr; + } + + const Ms::MasterScore* ms = score->masterScore(); + IF_ASSERT(ms) { + return nullptr; + } + + std::shared_ptr omr = ms->avsOmr(); + if (!omr) + return nullptr; + + std::shared_ptr ctx = std::make_shared(); + ctx->omr = omr; + ctx->p = p; + + //! TODO Think about how to do it better. + Avs::AvsOmr::Idx idx{0}; + for (Ms::Measure* m = score->firstMeasure(); m; m = m->nextMeasure()) { + ctx->measureIdxs.insert(m, idx); + ++idx; + } + + return ctx; + } + +//--------------------------------------------------------- +// draw +//--------------------------------------------------------- + +void AvsOmrDrawer::draw(std::shared_ptr ctx, const QList &ml) + { + IF_ASSERT(ctx) { + return; + } + + if (ctx->omr->config().isHiddenAll()) + return; + + ctx->p->save(); + + for (const Ms::Measure* m : ml) { + ctx->ormMeasureIdx = ctx->measureIdxs.value(m); + ctx->ormSheetNum = ctx->omr->sheetNumByMeausereIdx(ctx->ormMeasureIdx); + + MMetrics mm; + mm.bbox = m->bbox(); + + auto setMSegment = [](MSegment& ms, const Ms::Segment* s) { + ms.pos = s->pos(); + ms.w = s->width(); + }; + + for (Ms::Segment* s = m->first(); s; s = s->next()) { + if (Ms::SegmentType::Clef == s->segmentType() || Ms::SegmentType::HeaderClef == s->segmentType()) { + setMSegment(mm.clef, s); + } + else if (Ms::SegmentType::KeySig == s->segmentType()) { + setMSegment(mm.key, s); + } + else if (Ms::SegmentType::TimeSig == s->segmentType()) { + setMSegment(mm.time, s); + } + else if (Ms::SegmentType::ChordRest == s->segmentType()) { + setMSegment(mm.fchord, s); + break; //! NOTE The first chord and we don’t need anything else + } + } + + QPointF ppos = m->pagePos(); + ctx->p->translate(ppos); + drawMeasure(ctx, mm); + ctx->p->translate(-ppos); + } + + ctx->p->restore(); + } + +//--------------------------------------------------------- +// drawMeasure +//--------------------------------------------------------- + +void AvsOmrDrawer::drawMeasure(std::shared_ptr ctx, const MMetrics &scoreMM) const + { + AvsOmr::MMetrics omm = ctx->omr->mmetrics(ctx->ormSheetNum, ctx->ormMeasureIdx); + + QRectF scoreMH = scoreMM.headerBBox(); + QRectF scoreMCH = scoreMM.chordBBox(); + + QRect ormMH = omm.headerBBox(); + ormMH.setRight(ormMH.right() + 30); + QRect ormMCH = omm.chordBBox(); + + int topGap = omm.ebbox.top() - omm.bbox.top(); + int bottomGap = omm.ebbox.bottom() - omm.bbox.bottom(); + + drawGlyphs(ctx, ormMH, topGap, bottomGap, scoreMH); + ctx->p->translate(scoreMCH.left(), 0); + drawGlyphs(ctx, ormMCH, topGap, bottomGap, scoreMCH); + ctx->p->translate(-scoreMCH.left(), 0); + } + +//--------------------------------------------------------- +// drawGlyphs +//--------------------------------------------------------- + +void AvsOmrDrawer::drawGlyphs(std::shared_ptr ctx + , const QRect omrBB + , int topGap, int bottomGap + , const QRectF scoreBB) const + { + QTransform originTransform = ctx->p->transform(); + + // Scale + qreal sx = scoreBB.width() / static_cast(omrBB.width()); + qreal sy = scoreBB.height() / static_cast(omrBB.height()); + + QTransform transform = originTransform; + transform.scale(sx, sy); + ctx->p->setTransform(transform); + + QRect abb = omrBB.adjusted(0, topGap, 0, bottomGap); + + QList accepted; + if (ctx->omr->config().isShowRecognized()) + accepted << AvsOmr::GlyphUsed::Used; + + if (ctx->omr->config().isShowNotRecognized()) + accepted << AvsOmr::GlyphUsed::Free; + + QList glys = ctx->omr->glyphsByBBox(ctx->ormSheetNum, abb, accepted); + for (const auto& g : glys) { + QRect gb = g->bbox.translated(-omrBB.left(), -omrBB.top()); + ctx->p->drawImage(gb.topLeft(), g->img); + } + + ctx->p->setTransform(originTransform); + } + +//--------------------------------------------------------- +// nextColor (for debug) +//--------------------------------------------------------- + +QColor AvsOmrDrawer::nextColor() const + { + static int cf = static_cast(Qt::darkGray); + static int cl = static_cast(Qt::darkYellow); + static int ci = cf; + + ++ci; + if (ci > cl) + ci = cf; + + return QColor(static_cast(ci)); + } + +//--------------------------------------------------------- +// drawBBox (for debug) +//--------------------------------------------------------- + +void AvsOmrDrawer::drawBBox(QPainter* p, const QRect& r, Qt::GlobalColor colr) const + { + QPen pen; + pen.setColor(QColor(colr)); + pen.setWidth(8); + p->setPen(pen); + p->drawRect(r); + } + +//--------------------------------------------------------- +// drawMSegment (for debug) +//--------------------------------------------------------- + +void AvsOmrDrawer::drawMSegment(QPainter* p, const MSegment& s, Qt::GlobalColor colr) const + { + if (s.isNull()) + return; + + QPen pen; + pen.setColor(QColor(colr)); + pen.setWidth(20); + + p->setPen(pen); + p->drawLine(s.pos, QPoint(s.pos.x()+s.w, s.pos.y())); + } diff --git a/avsomr/avsomrdrawer.h b/avsomr/avsomrdrawer.h new file mode 100644 index 000000000000..07d7eaf2a0cd --- /dev/null +++ b/avsomr/avsomrdrawer.h @@ -0,0 +1,96 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRDRAWER_H +#define AVS_AVSOMRDRAWER_H + +#include "avsomr.h" + +namespace Ms { + +class Score; +class Measure; + +namespace Avs { + +class AvsOmrDrawer + { + public: + AvsOmrDrawer(); + + struct Context { + QPainter* p{nullptr}; + std::shared_ptr omr; + QHash measureIdxs; + AvsOmr::Num ormSheetNum{0}; + AvsOmr::Idx ormMeasureIdx{0}; + }; + + std::shared_ptr makeContext(QPainter* p, const Ms::Score* score) const; + + void draw(std::shared_ptr ctx, const QList& ml); + + private: + + struct MSegment { + QPointF pos; + qreal w{0}; + bool isNull() const { return qFuzzyIsNull(w); } + }; + + struct MMetrics { + QRectF bbox; + MSegment clef; + MSegment key; + MSegment time; + MSegment fchord; + + qreal headerW() const { + return clef.w + key.w + time.w; + } + + QRectF headerBBox() const { + QRectF h = bbox; + h.setRight(headerW()); + return h; + } + + QRectF chordBBox() const { + QRectF c = bbox; + c.setLeft(headerW()); + return c; + } + }; + + void drawMeasure(std::shared_ptr ctx, const MMetrics& scoreMM) const; + void drawGlyphs(std::shared_ptr ctx + , const QRect omrBB + , int topGap, int bottomGap + , const QRectF scoreBB) const; + + // debug + QColor nextColor() const; + void drawBBox(QPainter* p, const QRect& r, Qt::GlobalColor colr) const; + void drawMSegment(QPainter* p, const MSegment& s, Qt::GlobalColor colr) const; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRDRAWER_H diff --git a/avsomr/avsomrlocal.cpp b/avsomr/avsomrlocal.cpp new file mode 100644 index 000000000000..260c375b792b --- /dev/null +++ b/avsomr/avsomrlocal.cpp @@ -0,0 +1,354 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrlocal.h" + +#include +#include +#include +#include +#include + +#include "mscore/preferences.h" + +#include "avslog.h" +#include "avsomrlocalinstaller.h" + +namespace { + +static const QString AVS_EXT_NAME("avsomr_local"); + +#ifdef Q_OS_WIN + +static const QString SHELL("cmd"); +static const QStringList SHELL_ARGS("/c"); +static const QString AVS_MAKE_SCRIPT("make_avs_win.bat"); + +#elif defined(Q_OS_MAC) + +static const QString SHELL("sh"); +static const QStringList SHELL_ARGS; +static const QString AVS_MAKE_SCRIPT("make_avs_mac.sh"); + +#else + +static const QString SHELL("sh"); +static const QStringList SHELL_ARGS; +static const QString AVS_MAKE_SCRIPT("make_avs_lin.sh"); + +#endif +} + +using namespace Ms::Avs; + +AvsOmrLocal::AvsOmrLocal() + { + + } + +AvsOmrLocal::~AvsOmrLocal() + { + delete _installer; + } + +//--------------------------------------------------------- +// installer +//--------------------------------------------------------- + +AvsOmrLocalInstaller* AvsOmrLocal::installer() const + { + if (!_installer) + _installer = new AvsOmrLocalInstaller(avsHomePath()); + + return _installer; + } + +//--------------------------------------------------------- +// avsHomePath +//--------------------------------------------------------- + +QString AvsOmrLocal::avsHomePath() const + { + QString path = preferences.getString(PREF_APP_PATHS_MYEXTENSIONS) + "/" + AVS_EXT_NAME; + return path; + } + +//--------------------------------------------------------- +// makeAvsFilePath +//--------------------------------------------------------- + +QString AvsOmrLocal::makeAvsFilePath(const QString& buildDir, const QString& baseName) const + { + return buildDir + "/" + baseName + "/" + baseName + ".msmr"; + } + +//--------------------------------------------------------- +// version +//--------------------------------------------------------- + +QString AvsOmrLocal::version() const + { + QString versionPath = avsHomePath()+"/VERSION"; + QFile versionFile(versionPath); + bool ok = versionFile.open(QIODevice::ReadOnly); + if (!ok) + return QString(); + + QString ver = versionFile.readAll(); + return ver.trimmed(); + } + +//--------------------------------------------------------- +// state +//--------------------------------------------------------- + +const AvsOmrLocal::State& AvsOmrLocal::state() const + { + if (State::Undefined == _state) { + bool ok = execAvs("check", QString(), QString()); + _state = ok ? State::Ready : State::NotInstalled; + } + return _state; + } + +//--------------------------------------------------------- +// stateString +//--------------------------------------------------------- + +QString AvsOmrLocal::stateString() const + { + switch (state()) { + case State::Undefined: return QStringLiteral("Undefined"); + case State::NotInstalled: return QStringLiteral("NotInstalled"); + case State::Instaling: return QStringLiteral("Instaling"); + case State::Ready: return QStringLiteral("Ready"); + case State::Building: return QStringLiteral("Building"); + } + Q_UNREACHABLE(); + return QString(); + } + +//--------------------------------------------------------- +// setState +//--------------------------------------------------------- + +Ret AvsOmrLocal::stateToRet(const State& st) const + { + switch (st) { + case State::Undefined: return Ret::UnknownError; + case State::NotInstalled: return Ret::LocalNotInstalled; + case State::Instaling: return Ret::LocalInstaling; + case State::Ready: return Ret::Ok; + case State::Building: return Ret::LocalAlreadyBuilding; + } + Q_UNREACHABLE(); + return Ret::Undefined; + } + +//--------------------------------------------------------- +// setState +//--------------------------------------------------------- + +void AvsOmrLocal::setState(State st) + { + _state = st; + } + +//--------------------------------------------------------- +// isUseLocal +//--------------------------------------------------------- + +bool AvsOmrLocal::isUseLocal() const + { + bool useLocal = preferences.getBool(PREF_IMPORT_AVSOMR_USELOCAL); + return useLocal; + } + +//--------------------------------------------------------- +// isInstalled +//--------------------------------------------------------- + +bool AvsOmrLocal::isInstalled() const + { + return state() != State::NotInstalled; + } + +//--------------------------------------------------------- +// isInstalledAsync +//--------------------------------------------------------- + +void AvsOmrLocal::isInstalledAsync(const std::function& callback) const + { + if (_state != State::Undefined) { + callback(_state != State::NotInstalled); + return; + } + + QFutureWatcher* watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcher::finished, [watcher, callback]() { + bool isInstl = watcher->result(); + callback(isInstl); + watcher->deleteLater(); + }); + + QFuture future = QtConcurrent::run([this]() { return isInstalled(); }); + watcher->setFuture(future); + } + +//--------------------------------------------------------- +// checkInstallOrUpdate +//--------------------------------------------------------- + +Ret AvsOmrLocal::checkInstallOrUpdate(bool isWait) + { + const State& st = state(); + if (State::NotInstalled == st) { + installBackground(); + } + else if (State::Instaling == st) { + // noop, only waiting + } + else { + if (isNeedUpdate()) + installBackground(); + } + + if (isWait) + waitInstallOrUpdate(); + + if (State::Ready != state()) + return stateToRet(state()); + + return Ret::Ok; + } + +//--------------------------------------------------------- +// isNeedUpdate +//--------------------------------------------------------- + +bool AvsOmrLocal::isNeedUpdate() const + { + const AvsOmrLocalInstaller::ReleaseInfo& info = installer()->loadReleaseInfo(); + if (!info.isValid()) { + LOGW() << "failed load release info"; + return false; + } + + QString localVer = version(); + if (localVer.isEmpty()) { + LOGW() << "failed read version"; + return true; + } + + if (localVer != info.tag) { + LOGI() << "avs need update, installed version: " << localVer << ", latest: " << info.tag; + return true; + } + + return false; + } + +//--------------------------------------------------------- +// installBackground +//--------------------------------------------------------- + +void AvsOmrLocal::installBackground() + { + setState(State::Instaling); + installer()->installBackground(); + } + +//--------------------------------------------------------- +// waitInstallOrUpdate +//--------------------------------------------------------- + +void AvsOmrLocal::waitInstallOrUpdate() + { + installer()->waitForFinished(); + setState(State::Undefined); + } + +//--------------------------------------------------------- +// build +//--------------------------------------------------------- + +Ret AvsOmrLocal::build(const QString& filePath, const QString& buildDir) + { + if (State::Ready != state()) { + LOGE() << "unable start build, state: " << stateString(); + return stateToRet(state()); + } + + setState(State::Building); + + Ret ret = execAvs("build", filePath, buildDir); + + setState(State::Instaling); + + return ret; + } + +//--------------------------------------------------------- +// execAvs +//--------------------------------------------------------- + +Ret AvsOmrLocal::execAvs(const QString &cmd, const QString& filePath, const QString& buildDir) const + { + QString home = avsHomePath(); + if (home.isEmpty()) { + LOGE() << "not found avs plugin"; + return Ret::LocalNotInstalled; + } + + QString make_avs = avsHomePath() + "/" + AVS_MAKE_SCRIPT; + if (!QFileInfo(make_avs).exists()) { + LOGE() << "not found make script: " << make_avs; + return Ret::LocalNotInstalled; + } + + QStringList arguments; + if (!SHELL_ARGS.isEmpty()) { + arguments << SHELL_ARGS; + } + + arguments << make_avs << cmd << filePath << buildDir; + + QEventLoop loop; + QProcess proc; + + int exitCode = -1; + auto onFinished = [&loop, &exitCode](int code, QProcess::ExitStatus exitStatus) { + LOGI() << "onFinished exitCode: " << code << ", exitStatus: " << exitStatus; + exitCode = code; + loop.quit(); + }; + + QObject::connect(&proc, static_cast(&QProcess::finished), onFinished); + + LOGI() << "try start: " << SHELL << arguments; + proc.start(SHELL, arguments); + loop.exec(); + + LOGD() << QString::fromLatin1(proc.readAll()); + + if (exitCode != 0) + return Ret::LocalFailedExec; + + return Ret::Ok; + } diff --git a/avsomr/avsomrlocal.h b/avsomr/avsomrlocal.h new file mode 100644 index 000000000000..b9b23b8a8390 --- /dev/null +++ b/avsomr/avsomrlocal.h @@ -0,0 +1,84 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRLOCAL_H +#define AVS_AVSOMRLOCAL_H + +#include +#include +#include "ret.h" + +namespace Ms { +namespace Avs { + +class AvsOmrLocalInstaller; +class AvsOmrLocal + { + public: + + static AvsOmrLocal* instance() { + static AvsOmrLocal l; + return &l; + } + + enum class State { + Undefined = 0, + NotInstalled, + Instaling, + Ready, + Building + }; + + QString version() const; + const State& state() const; + QString stateString() const; + Ret stateToRet(const State& st) const; + + bool isUseLocal() const; + bool isInstalled() const; + void isInstalledAsync(const std::function& callback) const; + Ret checkInstallOrUpdate(bool isWait); + + QString avsHomePath() const; + QString makeAvsFilePath(const QString& buildDir, const QString& baseName) const; + + Ret build(const QString& filePath, const QString& buildDir); + + private: + + AvsOmrLocal(); + ~AvsOmrLocal(); + + AvsOmrLocalInstaller* installer() const; + + bool isNeedUpdate() const; + void installBackground(); + void waitInstallOrUpdate(); + + void setState(State st); + Ret execAvs(const QString &cmd, const QString& filePath, const QString& buildDir) const; + + mutable State _state{State::Undefined}; + mutable AvsOmrLocalInstaller* _installer{nullptr}; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRLOCAL_H diff --git a/avsomr/avsomrlocalinstaller.cpp b/avsomr/avsomrlocalinstaller.cpp new file mode 100644 index 000000000000..e5cc17a75499 --- /dev/null +++ b/avsomr/avsomrlocalinstaller.cpp @@ -0,0 +1,271 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrlocalinstaller.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thirdparty/qzip/qzipreader_p.h" + +#include "avslog.h" + +namespace { + +static const QString AVS_RELEASE_LATEST_API("https://api.github.com/repos/musescore/omr-local/releases/latest"); + +#ifdef Q_OS_WIN + +static const QString RELEASE_PREFIX("_windows_"); + +#elif defined(Q_OS_MAC) + +static const QString RELEASE_PREFIX("_macos_"); + +#else + +static const QString RELEASE_PREFIX("_linux_"); + +#endif +} + + +using namespace Ms::Avs; + +AvsOmrLocalInstaller::AvsOmrLocalInstaller(const QString &avsHomePath) + : _avsHomePath(avsHomePath) + { + _loop = new QEventLoop(); + } + +//--------------------------------------------------------- +// installBackground +//--------------------------------------------------------- + +void AvsOmrLocalInstaller::installBackground() + { + IF_ASSERT(!_watcher) { + LOGE() << "already run installing"; + return; + } + + const ReleaseInfo& info = loadReleaseInfo(); + if (!info.isValid()) { + LOGW() << "failed load release info"; + return; + } + + _watcher = new QFutureWatcher(); + QObject::connect(_watcher, &QFutureWatcher::finished, [this]() { + if (_loop->isRunning()) + _loop->quit(); + + _watcher->deleteLater(); + _watcher = nullptr; + }); + + QFuture future = QtConcurrent::run(this, &AvsOmrLocalInstaller::doInstallAvs, info.url); + _watcher->setFuture(future); + } + +//--------------------------------------------------------- +// waitForFinished +//--------------------------------------------------------- + +void AvsOmrLocalInstaller::waitForFinished() + { + if (!_watcher) + return; + + _loop->exec(); + + } + +//--------------------------------------------------------- +// doInstallAvs +//--------------------------------------------------------- + +bool AvsOmrLocalInstaller::doInstallAvs(const QString& url) + { + LOGI() << "try load avs, url: " << url; + + QByteArray avsZipPack; + bool ok = getData(&avsZipPack, url, "application/zip"); + if (!ok) { + LOGE() << "failed load avs, url: " << url; + return false; + } + LOGI() << "success loaded avs, url: " << url; + + ok = unpackAvs(&avsZipPack, _avsHomePath); + if (!ok) { + LOGE() << "failed unpack avs, path: " << _avsHomePath; + return false; + } + LOGI() << "success unpack avs, path: " << _avsHomePath; + + return true; + } + +//--------------------------------------------------------- +// loadReleaseInfo +//--------------------------------------------------------- + +const AvsOmrLocalInstaller::ReleaseInfo& AvsOmrLocalInstaller::loadReleaseInfo() const + { + if (_info.isValid()) + return _info; + + doLoadReleaseInfo(&_info, AVS_RELEASE_LATEST_API); + return _info; + } + +//--------------------------------------------------------- +// doLoadReleaseInfo +//--------------------------------------------------------- + +bool AvsOmrLocalInstaller::doLoadReleaseInfo(ReleaseInfo* info, const QString& url) const + { + IF_ASSERT(info) { + return false; + } + + QByteArray json_data; + bool ok = getData(&json_data, url, "application/json"); + if (!ok) { + LOGE() << "failed get data, url: " << url; + return false; + } + + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(json_data, &err); + if (err.error != QJsonParseError::NoError) { + LOGE() << "failed parse json, err: " << err.errorString() << ", json: " << json_data; + return false; + } + + QJsonObject infoObj = doc.object(); + info->tag = infoObj.value("tag_name").toString(); + QJsonArray assetsArr = infoObj.value("assets").toArray(); + for (const auto& av : assetsArr) { + QJsonObject ao = av.toObject(); + QString name = ao.value("name").toString(); + if (!name.contains(RELEASE_PREFIX)) + continue; + + info->url = ao.value("browser_download_url").toString(); + break; + } + + if (info->tag.isEmpty() || info->url.isEmpty()) + return false; + + return true; + } + +namespace +{ + +//--------------------------------------------------------- +// execReq +//--------------------------------------------------------- + +static QNetworkReply* execReq(QNetworkAccessManager& net, QNetworkRequest req) + { + QNetworkReply* reply = net.get(req); + + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status >= 300 && status < 399) { // redirect + QString newurl = reply->rawHeader("Location"); + req.setUrl(newurl); + return execReq(net, req); // recursion + } + + return reply; + } +} + +//--------------------------------------------------------- +// getData +//--------------------------------------------------------- + +bool AvsOmrLocalInstaller::getData(QByteArray* data, const QString& url, const QByteArray &mime) const + { + QNetworkRequest req; + req.setUrl(QUrl(url)); + req.setRawHeader("accept", mime); + QNetworkAccessManager net; + + QNetworkReply* reply = execReq(net, req); + if (reply->error() != QNetworkReply::NoError) { + LOGE() << "reply error: " << reply->errorString(); + reply->close(); + return false; + } + + if (data) + *data = reply->readAll(); + + reply->close(); + return true; + } + +//--------------------------------------------------------- +// unpackAvs +//--------------------------------------------------------- + +bool AvsOmrLocalInstaller::unpackAvs(QByteArray *avsZipPack, const QString& path) + { + bool ok = cleanDir(path); + if (!ok) { + LOGE() << "failed clean avs dir: " << path; + return false; + } + + QBuffer buf(avsZipPack); + MQZipReader zip(&buf); + + ok = zip.extractAll(path); + if (!ok) { + LOGE() << "failed unpack avs, path: " << path; + return false; + } + + return true; + } + +//--------------------------------------------------------- +// cleanDir +//--------------------------------------------------------- + +bool AvsOmrLocalInstaller::cleanDir(const QString& dirPath) + { + return QDir(dirPath).removeRecursively(); + } diff --git a/avsomr/avsomrlocalinstaller.h b/avsomr/avsomrlocalinstaller.h new file mode 100644 index 000000000000..5ae5eb663ed7 --- /dev/null +++ b/avsomr/avsomrlocalinstaller.h @@ -0,0 +1,71 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRLOCALINSTALLER_H +#define AVS_AVSOMRLOCALINSTALLER_H + +#include +#include +#include + +template +class QFutureWatcher; + +class QEventLoop; + +namespace Ms { +namespace Avs { + +class AvsOmrLocalInstaller + { + private: + + friend class AvsOmrLocal; + + AvsOmrLocalInstaller(const QString& avsHomePath); + + struct ReleaseInfo { + QString tag; + QString url; + bool isValid() const { return !tag.isEmpty() && !url.isEmpty(); } + }; + + const ReleaseInfo& loadReleaseInfo() const; + + void installBackground(); + void waitForFinished(); + + private: + + bool doLoadReleaseInfo(ReleaseInfo* info, const QString &url) const; + bool doInstallAvs(const QString& url); + bool getData(QByteArray* data, const QString &url, const QByteArray &mime) const; + bool unpackAvs(QByteArray* avsZipPack, const QString& path); + bool cleanDir(const QString& dirPath); + + QString _avsHomePath; + mutable ReleaseInfo _info; + QFutureWatcher* _watcher{nullptr}; + QEventLoop* _loop{nullptr}; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRLOCALINSTALLER_H diff --git a/avsomr/avsomrlocalrecognizer.cpp b/avsomr/avsomrlocalrecognizer.cpp new file mode 100644 index 000000000000..e408e7a95050 --- /dev/null +++ b/avsomr/avsomrlocalrecognizer.cpp @@ -0,0 +1,168 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrlocalrecognizer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "avslog.h" +#include "avsomrlocal.h" + +using namespace Ms::Avs; + +AvsOmrLocalRecognizer::AvsOmrLocalRecognizer() + { + } + +AvsOmrLocalRecognizer::~AvsOmrLocalRecognizer() + { + } + +//--------------------------------------------------------- +// type +//--------------------------------------------------------- + +QString AvsOmrLocalRecognizer::type() const + { + return "local"; + } + +//--------------------------------------------------------- +// avsOmrLocal +//--------------------------------------------------------- + +AvsOmrLocal* AvsOmrLocalRecognizer::avsOmrLocal() const + { + return AvsOmrLocal::instance(); + } + +//--------------------------------------------------------- +// makeBuildPath +//--------------------------------------------------------- + +QString AvsOmrLocalRecognizer::makeBuildPath() const + { + QString tempPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + QString path = tempPath + + "/" + QCoreApplication::applicationName() + + "/avsomr/build"; + + return path; + } + +//--------------------------------------------------------- +// isAvailable +//--------------------------------------------------------- + +bool AvsOmrLocalRecognizer::isAvailable() const + { + return avsOmrLocal()->isInstalled(); + } + +//--------------------------------------------------------- +// recognize +//--------------------------------------------------------- + +bool AvsOmrLocalRecognizer::recognize(const QString& filePath, QByteArray* avsFileData, const OnStep &onStep) + { + auto step = [&onStep](Step::Type t, uint perc, uint16_t percMax, Ret ret = Ret::Ok) { + if (!ret) + LOGE() << "failed step: " << t << ", ret: " << ret.formatedText(); + else + LOGI() << "success step: " << t << ", ret: " << ret.formatedText(); + + if (onStep) + onStep(Step(t, perc, percMax, ret)); + }; + + step(Step::PrepareStart, 1, 10); + + Ret ret = avsOmrLocal()->checkInstallOrUpdate(true); + + const QString buildDir = makeBuildPath(); + if (ret) + ret = cleanDir(buildDir); + + step(Step::PrepareFinish, 10, 10, ret); + if (!ret) + return false; + + step(Step::ProcessingStart, 11, 90); + ret = avsOmrLocal()->build(filePath, buildDir); + step(Step::ProcessingFinish, 90, 90, ret); + if (!ret) + return false; + + step(Step::LoadStart, 91, 100); + QString avsPath = avsOmrLocal()->makeAvsFilePath(buildDir, QFileInfo(filePath).baseName()); + ret = readFile(avsFileData, avsPath); + if (!ret) { + //! NOTE If we cannot read the resulting file, + //! then this means failed to execute + ret = Ret::LocalFailedExec; + } + step(Step::LoadFinish, 100, 100, ret); + if (!ret) + return false; + + cleanDir(buildDir); + + return true; + } + +//--------------------------------------------------------- +// cleanDir +//--------------------------------------------------------- + +Ret AvsOmrLocalRecognizer::cleanDir(const QString& dirPath) + { + return QDir(dirPath).removeRecursively() ? Ret::Ok : Ret::FailedClearDir; + } + +//--------------------------------------------------------- +// readFile +//--------------------------------------------------------- + +Ret AvsOmrLocalRecognizer::readFile(QByteArray* avsData, const QString& avsPath) + { + IF_ASSERT(avsData) { + return Ret::FailedReadFile; + } + + QFile avsFile(avsPath); + if (!avsFile.exists()) { + LOGE() << "not found avs file: " << avsPath; + return Ret::FailedReadFile; + } + + if (!avsFile.open(QIODevice::ReadOnly)) { + LOGE() << "failed open avs file: " << avsPath; + return Ret::FailedReadFile; + } + + *avsData = avsFile.readAll(); + + return Ret::Ok; + } diff --git a/avsomr/avsomrlocalrecognizer.h b/avsomr/avsomrlocalrecognizer.h new file mode 100644 index 000000000000..7bf58edffd1e --- /dev/null +++ b/avsomr/avsomrlocalrecognizer.h @@ -0,0 +1,51 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRLOCALRECOGNIZER_H +#define AVS_AVSOMRLOCALRECOGNIZER_H + +#include "iavsomrrecognizer.h" + +namespace Ms { +namespace Avs { + +class AvsOmrLocal; +class AvsOmrLocalRecognizer : public IAvsOmrRecognizer + { + public: + AvsOmrLocalRecognizer(); + ~AvsOmrLocalRecognizer() override; + + QString type() const override; + bool isAvailable() const override; + bool recognize(const QString& filePath, QByteArray* avsFileData, const OnStep& onStep) override; + + private: + + AvsOmrLocal* avsOmrLocal() const; + + QString makeBuildPath() const; + Ret cleanDir(const QString& dirPath); + Ret readFile(QByteArray* avsData, const QString& avsPath); + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRLOCALRECOGNIZER_H diff --git a/avsomr/avsomrnetrecognizer.cpp b/avsomr/avsomrnetrecognizer.cpp new file mode 100644 index 000000000000..31497ed776bb --- /dev/null +++ b/avsomr/avsomrnetrecognizer.cpp @@ -0,0 +1,287 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrnetrecognizer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "avslog.h" + +namespace { +static const QString HOST_API_OMR("https://musescore.com/api/omr"); +static const int STATUS_OK{200}; +static const int STATUS_NOT_FOUND{404}; +static const int STATUS_PROCESSING{411}; +static const int STATUS_NOT_SUPPORTED{422}; + +static const int CHECK_COUNT{1000}; +static const int CHECK_INTERVAL_MS{300}; +} + +using namespace Ms::Avs; + +AvsOmrNetRecognizer::AvsOmrNetRecognizer() + { + _net = new QNetworkAccessManager(); + } + +AvsOmrNetRecognizer::~AvsOmrNetRecognizer() + { + delete _net; + } + +//--------------------------------------------------------- +// type +//--------------------------------------------------------- + +QString AvsOmrNetRecognizer::type() const + { + return "cloud"; + } + +//--------------------------------------------------------- +// isAvailable +//--------------------------------------------------------- + +bool AvsOmrNetRecognizer::isAvailable() const + { + //! TODO add allow check + return true; + } + +//--------------------------------------------------------- +// recognize +//--------------------------------------------------------- + +bool AvsOmrNetRecognizer::recognize(const QString& filePath, QByteArray* avsFileData, const OnStep &onStep) + { + QString getUrl; + LOGI() << "begin send file: " << filePath; + + auto step = [&onStep](Step::Type t, uint16_t perc, uint16_t percMax, Ret ret = Ret::Ok) { + if (!ret) + LOGE() << "failed step: " << t << ", err: " << ret.formatedText(); + else + LOGI() << "success step: " << t << ", err: " << ret.formatedText(); + + if (onStep) + onStep(Step(t, perc, percMax, ret)); + }; + + step(Step::PrepareStart, 1, 10); + Ret ret = send(filePath, &getUrl); + step(Step::PrepareFinish, 10, 10, ret); + if (!ret) + return false; + + step(Step::ProcessingStart, 11, 90); + QString fileUrl; + ret = check(getUrl, &fileUrl); + step(Step::ProcessingFinish, 90, 90, ret); + if (!ret) + return false; + + step(Step::LoadStart, 91, 100); + ret = load(fileUrl, avsFileData); + step(Step::LoadFinish, 100, 100, ret); + if (!ret) + return false; + + return true; + } + +//--------------------------------------------------------- +// send +//--------------------------------------------------------- + +Ret AvsOmrNetRecognizer::netRetToRet(const NetRet& nr) const + { + if (nr.httpStatus < 100) { + return Ret::NetworkError; + } + + if (nr.httpStatus >= 200 && nr.httpStatus < 300) { + return Ret::Ok; + } + else if (nr.httpStatus == STATUS_NOT_SUPPORTED) { + return Ret::FileNotSupported; + } + + return Ret::ServerError; + } + +//--------------------------------------------------------- +// send +//--------------------------------------------------------- + +Ret AvsOmrNetRecognizer::send(const QString& filePath, QString *getUrl) + { + QFile *file = new QFile(filePath); + if (!file->open(QIODevice::ReadOnly)) { + LOGE() << "failed open file: " << filePath; + delete file; + return Ret::FailedReadFile; + } + + //request + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart part; + part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + part.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"file\"; filename=\"%1\"").arg(QFileInfo(filePath).fileName())); + + part.setBodyDevice(file); + file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart + + multiPart->append(part); + + QNetworkRequest req; + req.setUrl(QUrl(HOST_API_OMR)); + req.setRawHeader("accept", "application/json"); + QNetworkReply* rep = _net->post(req, multiPart); + multiPart->setParent(rep); + + QByteArray ba; + NetRet netRet = doExecReply(rep, &ba); + + LOGI() << "send status: " << netRet.httpStatus << ", data: " << ba; + + if (netRet.httpStatus != STATUS_OK) { + LOGE() << "failed send status: " << netRet.httpStatus << ", file: " << filePath; + return netRetToRet(netRet); + } + + QVariantMap info = parseInfo(ba); + if (getUrl) + *getUrl = info.value("url").toString(); + + return Ret::Ok; + } + +//--------------------------------------------------------- +// doExecReply +//--------------------------------------------------------- + +AvsOmrNetRecognizer::NetRet AvsOmrNetRecognizer::doExecReply(QNetworkReply* reply, QByteArray *ba) + { + NetRet ret; + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + ret.replyError = static_cast(reply->error()); + ret.httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (ba) + *ba = reply->readAll(); + + reply->close(); + delete reply; + + return ret; + } + +//--------------------------------------------------------- +// parseInfo +//--------------------------------------------------------- + +QVariantMap AvsOmrNetRecognizer::parseInfo(const QByteArray& ba) const + { + QVariantMap info; + + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(ba, &err); + if (err.error != QJsonParseError::NoError) { + LOGE() << "failed parse reply, err: " << err.errorString(); + return QVariantMap(); + } + + QJsonObject replyObj = doc.object(); + QJsonObject infoObj = replyObj.value("info").toObject(); + info["url"] = infoObj.value("url").toString(); + + return info; + } + +//--------------------------------------------------------- +// check +//--------------------------------------------------------- + +Ret AvsOmrNetRecognizer::check(const QString& url, QString *fileUrl) + { + NetRet netRet; + QByteArray data; + + for (size_t t = 0; t < CHECK_COUNT; ++t) { + netRet = doCheck(url, &data); + LOGI() << "[" << t << "] status: " << netRet.httpStatus; + if (netRet.httpStatus != STATUS_PROCESSING) + break; + + // sleep + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + timer.start(CHECK_INTERVAL_MS); + loop.exec(); + } + + if (netRet.httpStatus != STATUS_OK) + return netRetToRet(netRet); + + QVariantMap info = parseInfo(data); + if (fileUrl) + *fileUrl = info.value("url").toString(); + + return Ret::Ok; + } + +//--------------------------------------------------------- +// doCheck +//--------------------------------------------------------- + +AvsOmrNetRecognizer::NetRet AvsOmrNetRecognizer::doCheck(const QString& url, QByteArray *ba) + { + QNetworkRequest req; + req.setUrl(QUrl(url)); + QNetworkReply* reply = _net->get(req); + return doExecReply(reply, ba); + } + +//--------------------------------------------------------- +// load +//--------------------------------------------------------- + +Ret AvsOmrNetRecognizer::load(const QString& url, QByteArray* ba) + { + QNetworkRequest req; + req.setUrl(QUrl(url)); + QNetworkReply* reply = _net->get(req); + + NetRet netRet = doExecReply(reply, ba); + return netRetToRet(netRet); + } diff --git a/avsomr/avsomrnetrecognizer.h b/avsomr/avsomrnetrecognizer.h new file mode 100644 index 000000000000..34b05c651dfb --- /dev/null +++ b/avsomr/avsomrnetrecognizer.h @@ -0,0 +1,70 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRNETRECOGNIZER_H +#define AVS_AVSOMRNETRECOGNIZER_H + +#include +#include +#include + +#include "iavsomrrecognizer.h" + +class QNetworkAccessManager; +class QNetworkReply; + +namespace Ms { +namespace Avs { + +class AvsOmrNetRecognizer : public IAvsOmrRecognizer + { + public: + AvsOmrNetRecognizer(); + ~AvsOmrNetRecognizer() override; + + QString type() const override; + bool isAvailable() const override; + bool recognize(const QString& filePath, QByteArray* avsFileData, const OnStep& onStep) override; + + private: + + Ret send(const QString& filePath, QString* getUrl); + Ret check(const QString& url, QString* fileUrl); + Ret load(const QString& url, QByteArray* ba); + + struct NetRet + { + int httpStatus{0}; + int replyError{0}; + }; + + Ret netRetToRet(const NetRet& nr) const; + + NetRet doExecReply(QNetworkReply* reply, QByteArray* ba); + NetRet doCheck(const QString& url, QByteArray* ba); + + QVariantMap parseInfo(const QByteArray& ba) const; + + QNetworkAccessManager* _net{nullptr}; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRNETRECOGNIZER_H diff --git a/avsomr/avsomrreader.cpp b/avsomr/avsomrreader.cpp new file mode 100644 index 000000000000..7f4b3eb937c7 --- /dev/null +++ b/avsomr/avsomrreader.cpp @@ -0,0 +1,551 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrreader.h" + +#include +#include +#include +#include + +#include "thirdparty/qzip/qzipreader_p.h" +#include "mscore/preferences.h" + +#include "avslog.h" + +using namespace Ms::Avs; + +AvsOmrReader::AvsOmrReader() + { + + } + +//--------------------------------------------------------- +// glyphColor +//--------------------------------------------------------- + +QColor AvsOmrReader::glyphColor(AvsOmr::GlyphUsed used) const + { + if (_glyphColors.isEmpty()) { + _glyphColors[AvsOmr::GlyphUsed::Used] = preferences.getColor(PREF_UI_AVSOMR_RECOGNITION_COLOR); + _glyphColors[AvsOmr::GlyphUsed::Free] = preferences.getColor(PREF_UI_AVSOMR_NOT_RECOGNITION_COLOR); + _glyphColors[AvsOmr::GlyphUsed::Free_Covered] = preferences.getColor(PREF_UI_AVSOMR_NOT_RECOGNITION_COLOR); + } + + return _glyphColors.value(used); + } + +//--------------------------------------------------------- +// read +//--------------------------------------------------------- + +std::shared_ptr AvsOmrReader::read(const QString &ormFilePath) const + { + QFile zipFile(ormFilePath); + if (!zipFile.exists()) { + LOGE() << "not exists orm file: " << zipFile.fileName(); + return nullptr; + } + + if (!zipFile.open(QIODevice::ReadOnly)) { + LOGE() << "failed open orm file: " << zipFile.fileName(); + return nullptr; + } + + return read(&zipFile); + } + +//--------------------------------------------------------- +// read +//--------------------------------------------------------- + +std::shared_ptr AvsOmrReader::read(QIODevice *ormFile) const + { + MQZipReader zip(ormFile); + + QByteArray book = zip.fileData("book.xml"); + if (book.isEmpty()) { + LOGE() << "not exists book.xml"; + return nullptr; + } + + QBuffer bookBuf(&book); + bookBuf.open(QIODevice::ReadOnly); + + std::shared_ptr omr = std::make_shared(); + + bool ok = readBook(omr->_book, &bookBuf); + if (!ok) { + LOGE() << "failed read book.xml"; + return nullptr; + } + + for (uint16_t n = 1; n <= omr->_book.sheets; ++n) { + QString path = makeSheetPath(n); + QByteArray sheet = zip.fileData(path); + if (sheet.isEmpty()) { + LOGE() << "not exists sheet: " << path; + continue; + } + QBuffer sheetBuf(&sheet); + sheetBuf.open(QIODevice::ReadOnly); + + AvsOmr::Sheet* sh = new AvsOmr::Sheet(); + ok = readSheet(*sh, &sheetBuf); + if (!ok) { + LOGE() << "failed read sheet: " << path; + continue; + } + + omr->_sheets.insert(sh->num, sh); + } + + omr->resolve(); + + AvsOmr::Info& info = omr->_info; + info.usedColor = glyphColor(AvsOmr::GlyphUsed::Used); + info.freeColor = glyphColor(AvsOmr::GlyphUsed::Free); + + for (const AvsOmr::Sheet* sh : omr->_sheets) { + for (AvsOmr::Glyph* g : sh->glyphs) { + if (AvsOmr::GlyphUsed::Used == g->used) + ++info.usedCount; + else if (AvsOmr::GlyphUsed::Free == g->used) + ++info.freeCount; + } + } + + return omr; + } + +//--------------------------------------------------------- +// makeSheetPath +//--------------------------------------------------------- + +QString AvsOmrReader::makeSheetPath(uint16_t n) const + { + QString name = "sheet#"+QString::number(n); + return name + "/" + name + ".xml"; + } + +//--------------------------------------------------------- +// readBook +//--------------------------------------------------------- + +bool AvsOmrReader::readBook(AvsOmr::Book& b, QIODevice* xmlData) const + { + QXmlStreamReader xml(xmlData); + if (xml.readNextStartElement()) { + if (!(xml.name() == "book")) + return false; + + while(xml.readNextStartElement()) { + QStringRef tag = xml.name(); + if ("sheet" == tag) { + ++b.sheets; + xml.skipCurrentElement(); + } + else { + xml.skipCurrentElement(); + } + } + } + + bool ok = !xml.hasError(); + if (!ok) + LOGE() << "failed read xml, err: " << xml.errorString(); + + return ok; + } + +//--------------------------------------------------------- +// readSheet +//--------------------------------------------------------- + +bool AvsOmrReader::readSheet(AvsOmr::Sheet &sh, QIODevice* xmlData) const + { + QXmlStreamReader xml(xmlData); + if (xml.readNextStartElement()) { + if (xml.name() == "sheet") { + sh.num = xml.attributes().value("number").toInt(); + + while(xml.readNextStartElement()) { + QStringRef tag = xml.name(); + if (tag == "page") { + readPage(sh.page, xml); + } + else if (tag == "glyph-index") { + readGlyphs(sh, xml); + } + else { + xml.skipCurrentElement(); + } + } + } + } + + bool ok = !xml.hasError(); + if (!ok) + LOGE() << "failed read xml, err: " << xml.errorString(); + + return ok; + } + +//--------------------------------------------------------- +// readPage +//--------------------------------------------------------- + +void AvsOmrReader::readPage(AvsOmr::Page &p, QXmlStreamReader& xml) const + { + while (xml.readNextStartElement()) { + if (xml.name() == "system") { + AvsOmr::System sys; + readSystem(sys, xml); + p.systems.append(sys); + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readSystem +//--------------------------------------------------------- + +void AvsOmrReader::readSystem(AvsOmr::System& sys, QXmlStreamReader& xml) const + { + while (xml.readNextStartElement()) { + auto tag = xml.name(); + if (tag == "stack") { + AvsOmr::MStack st; + readStack(st, xml); + sys.mstacks.append(st); + } + else if (tag == "part") { + readPart(sys.part, xml); + } + else if (tag == "free-glyphs") { + QString str = xml.readElementText(); + sys.freeglyphs = toIDSet(str); + } + else if (tag == "sig") { + while (xml.readNextStartElement()) { + if (xml.name() == "inters") + readInters(sys.inters, xml); + else + xml.skipCurrentElement(); + } + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readStack +//--------------------------------------------------------- + +void AvsOmrReader::readStack(AvsOmr::MStack &st, QXmlStreamReader& xml) const + { + auto attrs = xml.attributes(); + st.id = attrs.value("id").toInt(); + st.left = attrs.value("left").toInt(); + st.right = attrs.value("right").toInt(); + + xml.skipCurrentElement(); + } + +//--------------------------------------------------------- +// readPart +//--------------------------------------------------------- + +void AvsOmrReader::readPart(AvsOmr::Part &pt, QXmlStreamReader& xml) const + { + while (xml.readNextStartElement()) { + auto tag = xml.name(); + if (tag == "staff") { + AvsOmr::Staff sf; + readStaff(sf, xml); + pt.staffs.append(sf); + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readStaff +//--------------------------------------------------------- + +void AvsOmrReader::readStaff(AvsOmr::Staff& sf, QXmlStreamReader& xml) const + { + auto attrs = xml.attributes(); + sf.id = attrs.value("id").toInt(); + + while (xml.readNextStartElement()) { + auto tag = xml.name(); + if (tag == "header") { + sf.header.start = xml.attributes().value("start").toInt(); + sf.header.stop = xml.attributes().value("stop").toInt(); + while (xml.readNextStartElement()) { + if (xml.name() == "clef") { + sf.header.clefID = xml.readElementText().toInt(); + } + else if (xml.name() == "key") { + sf.header.keyID = xml.readElementText().toInt(); + } + else if (xml.name() == "time") { + sf.header.timeID = xml.readElementText().toInt(); + } + else { + xml.skipCurrentElement(); + } + } + + } + else if (tag == "barlines") { + QString str = xml.readElementText(); + sf.barlines = toIDList(str); + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readInters +//--------------------------------------------------------- + +void AvsOmrReader::readInters(AvsOmr::Inters& its, QXmlStreamReader& xml) const + { + while (xml.readNextStartElement()) { + auto tag = xml.name(); + if ("barline" == tag) { + AvsOmr::Barline bl; + readBarline(bl, xml); + its.barlines.insert(bl.id, bl); + + } + else if ("brace" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("clef" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("time-whole" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("beam" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("ledger" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("head" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("stem" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("dynamics" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else if ("alter" == tag) { + readInterGlyphID(its.usedglyphs, xml); + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readBarline +//--------------------------------------------------------- + +void AvsOmrReader::readBarline(AvsOmr::Barline& bl, QXmlStreamReader& xml) const + { + auto attrs = xml.attributes(); + bl.id = attrs.value("id").toInt(); + bl.glyphID = attrs.value("glyph").toInt(); + + xml.skipCurrentElement(); + } + +//--------------------------------------------------------- +// readInterGlyphID +//--------------------------------------------------------- + +void AvsOmrReader::readInterGlyphID(QSet& gls, QXmlStreamReader& xml) const + { + auto attrs = xml.attributes(); + AvsOmr::ID glyphID = attrs.value("glyph").toInt(); + gls.insert(glyphID); + + xml.skipCurrentElement(); + } + +//--------------------------------------------------------- +// toIDList +//--------------------------------------------------------- + +QList AvsOmrReader::toIDList(const QString& str) const + { + if (str.isEmpty()) + return QList(); + + QList ids; + QStringList strList = str.split(' '); + for (const QString& s : strList) + ids.append(s.toInt()); + + return ids; + } + +//--------------------------------------------------------- +// toIDSet +//--------------------------------------------------------- + +QSet AvsOmrReader::toIDSet(const QString& str) const + { + if (str.isEmpty()) + return QSet(); + + QSet ids; + QStringList strList = str.split(' '); + for (const QString& s : strList) + ids.insert(s.toInt()); + + return ids; + } + +//--------------------------------------------------------- +// readGlyphs +//--------------------------------------------------------- + +void AvsOmrReader::readGlyphs(AvsOmr::Sheet &sh, QXmlStreamReader& xml) const + { + while (xml.readNextStartElement()) { + if (xml.name() == "glyph") { + auto attrs = xml.attributes(); + AvsOmr::Glyph* g = new AvsOmr::Glyph(); + g->bbox.setLeft(attrs.value("left").toInt()); + g->bbox.setTop(attrs.value("top").toInt()); + g->id = attrs.value("id").toInt(); + + if (sh.isGlyphUsed(g->id)) + g->used = AvsOmr::GlyphUsed::Used; + else if (sh.isGlyphFree(g->id)) + g->used = AvsOmr::GlyphUsed::Free; + + while (xml.readNextStartElement()) { + if (xml.name() == "run-table") + readImage(g->img, xml, glyphColor(g->used)); + else + xml.skipCurrentElement(); + } + + g->bbox.setRight(g->bbox.left() + g->img.width()); + g->bbox.setBottom(g->bbox.top() + g->img.height()); + + sh.glyphs.insert(g->id, g); + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readImage +//--------------------------------------------------------- + +void AvsOmrReader::readImage(QImage& img, QXmlStreamReader& xml, const QColor &color) const + { + IF_ASSERT(xml.isStartElement() && xml.name() == "run-table") { + return; + } + + auto attrs = xml.attributes(); + int width = attrs.value("width").toInt(); + int height = attrs.value("height").toInt(); + bool isVertical = attrs.value("orientation") == "VERTICAL"; + + img = QImage(width, height, QImage::Format_ARGB32); + img.fill(Qt::transparent); + + QPainter p(&img); + p.setPen(color); + + auto drawLine = [&p, isVertical](int i, const QString& line) { + + if (line.isEmpty()) + return; + + QStringList segs = line.split(' '); + bool isFill{true}; + int j{0}; + for (const QString& seg : segs) { + int length = seg.toInt(); + if (length > 0 && isFill) { + if (isVertical) + p.drawLine(i, j, i, j + length); + else + p.drawLine(j, i, j + length, i); + } + j += length; + isFill = !isFill; + } + }; + + + int i = 0; + + while (xml.readNextStartElement()) { + if (xml.name() == "runs") { + QString line = xml.readElementText(); + drawLine(i, line); + ++i; + } + else { + xml.skipCurrentElement(); + } + } + } + +//--------------------------------------------------------- +// readPicture +//--------------------------------------------------------- + +bool AvsOmrReader::readPicture(QImage& img, QIODevice* xmlData) const + { + QXmlStreamReader xml(xmlData); + while (xml.readNextStartElement()) { + if (xml.name() == "run-table") + readImage(img, xml, QColor(Qt::darkBlue)); + else + xml.skipCurrentElement(); + } + + return true; + } diff --git a/avsomr/avsomrreader.h b/avsomr/avsomrreader.h new file mode 100644 index 000000000000..2ad00e65d160 --- /dev/null +++ b/avsomr/avsomrreader.h @@ -0,0 +1,75 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRREADER_H +#define AVS_AVSOMRREADER_H + +#include +#include +#include +#include +#include + +#include "avsomr.h" + +class QXmlStreamReader; + +namespace Ms { +namespace Avs { + +class AvsOmrReader + { + public: + AvsOmrReader(); + + std::shared_ptr read(const QString &ormFilePath) const; + std::shared_ptr read(QIODevice *ormFile) const; + + private: + QString makeSheetPath(uint16_t n) const; + + bool readBook(AvsOmr::Book& b, QIODevice* xmlData) const; + bool readSheet(AvsOmr::Sheet& sh, QIODevice* xmlData) const; + bool readPicture(QImage& img, QIODevice* xmlData) const; + + void readPage(AvsOmr::Page& p, QXmlStreamReader& xml) const; + void readSystem(AvsOmr::System &sys, QXmlStreamReader& xml) const; + void readStack(AvsOmr::MStack &st, QXmlStreamReader& xml) const; + void readPart(AvsOmr::Part& pt, QXmlStreamReader& xml) const; + void readStaff(AvsOmr::Staff& sf, QXmlStreamReader& xml) const; + void readInters(AvsOmr::Inters& its, QXmlStreamReader& xml) const; + void readBarline(AvsOmr::Barline& bl, QXmlStreamReader& xml) const; + void readInterGlyphID(QSet &gls, QXmlStreamReader& xml) const; + + void readGlyphs(AvsOmr::Sheet &sh, QXmlStreamReader& xml) const; + void readImage(QImage& img, QXmlStreamReader& xml, const QColor& color) const; + + QList toIDList(const QString& str) const; + QSet toIDSet(const QString& str) const; + + QColor glyphColor(AvsOmr::GlyphUsed used) const; + mutable QMap _glyphColors; + + }; + +} // Avs +} // Ms + + +#endif // AVS_AVSOMRREADER_H diff --git a/avsomr/avsomrsetup.cpp b/avsomr/avsomrsetup.cpp new file mode 100644 index 000000000000..a2829014d5f7 --- /dev/null +++ b/avsomr/avsomrsetup.cpp @@ -0,0 +1,69 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "avsomrsetup.h" + +#include + +#include "mscore/preferences.h" + +#include "avslog.h" +#include "avsomrlocal.h" + +using namespace Ms::Avs; + +//--------------------------------------------------------- +// moduleName +//--------------------------------------------------------- + +QString AvsOmrSetup::moduleName() const + { + return "avsomr"; + } + +//--------------------------------------------------------- +// onStartInit +//--------------------------------------------------------- + +void AvsOmrSetup::onStartInit() + { + AvsOmrLocal* avsLocal = AvsOmrLocal::instance(); + + //! NOTE If enabled use local avs then check the installation or update + //! on application start + QTimer::singleShot(10000, [avsLocal]() { + if (avsLocal->isUseLocal()) + avsLocal->checkInstallOrUpdate(false); + }); + + + //! NOTE If enabled use local avs then immediately start the installation + //! on preference changed + preferences.addOnSetListener([avsLocal](const QString& key, const QVariant& value) { + + if (key != PREF_IMPORT_AVSOMR_USELOCAL) + return; + + bool useLocalAvsOmr = value.toBool(); + if (!useLocalAvsOmr) + return; + + avsLocal->checkInstallOrUpdate(false); + }); + } diff --git a/avsomr/avsomrsetup.h b/avsomr/avsomrsetup.h new file mode 100644 index 000000000000..85b7648aee29 --- /dev/null +++ b/avsomr/avsomrsetup.h @@ -0,0 +1,40 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_AVSOMRSETUP_H +#define AVS_AVSOMRSETUP_H + +#include "global/interfaces/abstractmodulesetup.h" + +namespace Ms { +namespace Avs { + +class AvsOmrSetup : public AbstractModuleSetup + { + + private: + + QString moduleName() const override; + void onStartInit() override; + }; + +} // Avs +} // Ms + +#endif // AVS_AVSOMRSETUP_H diff --git a/avsomr/data/TESTS.md b/avsomr/data/TESTS.md new file mode 100644 index 000000000000..cb40ea825b48 --- /dev/null +++ b/avsomr/data/TESTS.md @@ -0,0 +1,17 @@ +First testing phase +=================== + +examples: +* allegretto.png - [OK] +* BachInvention5.jpg - [WARN] a lot of things out of sync +* batuque.png - [OK] +* carmen.png - [OK] +* chula.png - [OK] +* cucaracha.png - [OK] +* D0392410-1.256.png - [BAD] +* Dichterliebe01.pdf - [BAD] +* inaccessibl_etoile.pdf - [OK] +* SchbAvMaSample.pdf - [BAD] +* zizi.png - [OK] + + diff --git a/avsomr/data/examples/BachInvention5.jpg b/avsomr/data/examples/BachInvention5.jpg new file mode 100644 index 000000000000..4393a4f36496 Binary files /dev/null and b/avsomr/data/examples/BachInvention5.jpg differ diff --git a/avsomr/data/examples/D0392410-1.256.png b/avsomr/data/examples/D0392410-1.256.png new file mode 100644 index 000000000000..c1ee212ee09d Binary files /dev/null and b/avsomr/data/examples/D0392410-1.256.png differ diff --git a/avsomr/data/examples/Dichterliebe01.pdf b/avsomr/data/examples/Dichterliebe01.pdf new file mode 100644 index 000000000000..7c53237e79cb Binary files /dev/null and b/avsomr/data/examples/Dichterliebe01.pdf differ diff --git a/avsomr/data/examples/SchbAvMaSample.pdf b/avsomr/data/examples/SchbAvMaSample.pdf new file mode 100644 index 000000000000..9e017ee3d998 Binary files /dev/null and b/avsomr/data/examples/SchbAvMaSample.pdf differ diff --git a/avsomr/data/examples/allegretto.png b/avsomr/data/examples/allegretto.png new file mode 100644 index 000000000000..11eb5b733c5f Binary files /dev/null and b/avsomr/data/examples/allegretto.png differ diff --git a/avsomr/data/examples/batuque.png b/avsomr/data/examples/batuque.png new file mode 100644 index 000000000000..5b7299ebfb9e Binary files /dev/null and b/avsomr/data/examples/batuque.png differ diff --git a/avsomr/data/examples/carmen.png b/avsomr/data/examples/carmen.png new file mode 100644 index 000000000000..bc7d9cbfba8c Binary files /dev/null and b/avsomr/data/examples/carmen.png differ diff --git a/avsomr/data/examples/chopin-frederic-nocturnes-opus-9-no-2.pdf b/avsomr/data/examples/chopin-frederic-nocturnes-opus-9-no-2.pdf new file mode 100644 index 000000000000..877580365888 Binary files /dev/null and b/avsomr/data/examples/chopin-frederic-nocturnes-opus-9-no-2.pdf differ diff --git a/avsomr/data/examples/chula.png b/avsomr/data/examples/chula.png new file mode 100644 index 000000000000..abd2cc0434f0 Binary files /dev/null and b/avsomr/data/examples/chula.png differ diff --git a/avsomr/data/examples/cucaracha.png b/avsomr/data/examples/cucaracha.png new file mode 100644 index 000000000000..dc25e68a8120 Binary files /dev/null and b/avsomr/data/examples/cucaracha.png differ diff --git a/avsomr/data/examples/inaccessibl_etoile.pdf b/avsomr/data/examples/inaccessibl_etoile.pdf new file mode 100644 index 000000000000..dff0f7b9b2d1 Binary files /dev/null and b/avsomr/data/examples/inaccessibl_etoile.pdf differ diff --git a/avsomr/data/examples/zizi.png b/avsomr/data/examples/zizi.png new file mode 100644 index 000000000000..fde5ed9e8790 Binary files /dev/null and b/avsomr/data/examples/zizi.png differ diff --git a/avsomr/iavsomrrecognizer.h b/avsomr/iavsomrrecognizer.h new file mode 100644 index 000000000000..be14e3bc1c86 --- /dev/null +++ b/avsomr/iavsomrrecognizer.h @@ -0,0 +1,71 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_IAVSOMRRECOGNIZER_H +#define AVS_IAVSOMRRECOGNIZER_H + +#include +#include +#include + +#include "ret.h" + +namespace Ms { +namespace Avs { + +class IAvsOmrRecognizer + { + public: + virtual ~IAvsOmrRecognizer() {} + + struct Step { + + enum Type { + Undefined = 0, + PrepareStart, + PrepareFinish, + ProcessingStart, + ProcessingFinish, + LoadStart, + LoadFinish, + }; + + Type type{Undefined}; + uint16_t percent{0}; //! NOTE Estimated percent of completion + uint16_t percentMax{0}; //! NOTE Max percent of current step + Ret error{Ret::Ok}; + + Step() {} + Step(Type t, uint16_t perc, uint16_t percMax, Ret err) + : type(t), percent(perc), percentMax(percMax), error(err) {} + + bool success() const { return error.success(); } + }; + + using OnStep = std::function; + + virtual QString type() const = 0; + virtual bool isAvailable() const = 0; + virtual bool recognize(const QString& filePath, QByteArray* avsFileData, const OnStep& onStep) = 0; + }; + +} // Avs +} // Ms + +#endif // AVS_IAVSOMRRECOGNIZER_H diff --git a/avsomr/msmrfile.cpp b/avsomr/msmrfile.cpp new file mode 100644 index 000000000000..c679b7d765e8 --- /dev/null +++ b/avsomr/msmrfile.cpp @@ -0,0 +1,349 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "msmrfile.h" + +#include +#include +#include +#include + +#include "thirdparty/qzip/qzipreader_p.h" +#include "thirdparty/qzip/qzipwriter_p.h" + +#include "avslog.h" + +using namespace Ms::Avs; + +MsmrFile::MsmrFile(const QByteArray &data, const QString &name) + : _data(data), _name(QFileInfo(name).baseName()) + { + } + +MsmrFile::~MsmrFile() + { + } + + +//--------------------------------------------------------- +// readMxl +//--------------------------------------------------------- + +QByteArray MsmrFile::readMxl() + { + QBuffer buf(&_data); + MQZipReader zip(&buf); + + MetaInf meta; + meta.read(zip); + + QString scoreMxlPath = meta.fileByExt(".mxl"); + QByteArray scoreMxlData = zip.fileData(scoreMxlPath); + if (scoreMxlData.isEmpty()) { + LOGE() << "failed read mxl: " << scoreMxlPath; + return QByteArray(); + } + + return scoreMxlData; + } + +//--------------------------------------------------------- +// readMuzicXml +//--------------------------------------------------------- + +QByteArray MsmrFile::readMuzicXml() + { + // compressed + QByteArray scoreMxlData = readMxl(); + QBuffer scoreMxlBuf(&scoreMxlData); + scoreMxlBuf.open(QIODevice::ReadOnly); + + // xml + MQZipReader mxlZip(&scoreMxlBuf); + + MetaInf meta; + meta.read(mxlZip); + + QString scoreXmlPath = meta.fileByExt(".xml"); + QByteArray scoreXmlData = mxlZip.fileData(scoreXmlPath); + if (scoreXmlData.isEmpty()) { + LOGE() << "failed read xml: " << scoreXmlPath; + return QByteArray(); + } + + return scoreXmlData; + } + +//--------------------------------------------------------- +// readOmr +//--------------------------------------------------------- + +QByteArray MsmrFile::readOmr() + { + QBuffer buf(&_data); + MQZipReader zip(&buf); + + MetaInf meta; + meta.read(zip); + + QString ormPath = meta.fileByExt(".omr"); + QByteArray ormData = zip.fileData(ormPath); + if (ormData.isEmpty()) { + LOGE() << "failed read omr: " << ormPath; + return QByteArray(); + } + + return ormData; + } + +//--------------------------------------------------------- +// readMscz +//--------------------------------------------------------- + +QByteArray MsmrFile::readMscz() + { + QBuffer buf(&_data); + MQZipReader zip(&buf); + + MetaInf meta; + meta.read(zip); + + QString msczPath = meta.fileByExt(".mscz"); + if (msczPath.isEmpty()) { //! NOTE No error, maybe not + LOGI() << "no mscz data"; + return QByteArray(); + } + + QByteArray msczData = zip.fileData(msczPath); + if (msczData.isEmpty()) { + LOGE() << "failed read mscz: " << msczPath; + return QByteArray(); + } + + return msczData; + } + +//--------------------------------------------------------- +// writeMscz +//--------------------------------------------------------- + +bool MsmrFile::writeMscz(const QByteArray& mscz) + { + QByteArray mxl = readMxl(); + QByteArray omr = readOmr(); + + //! NOTE MQZipWriter will overwrite the data, + //! so we’ll clear it explicitly so as not to be misleading + _data.clear(); + + QBuffer buf(&_data); + MQZipWriter zip(&buf); + + //! NOTE all items already compressed + zip.setCompressionPolicy(MQZipWriter::NeverCompress); + + MetaInf meta; + + auto addFile = [&zip, &meta](const QString& path, const QByteArray& data) { + meta.addFile(path); + zip.addFile(path, data); + }; + + addFile(_name + ".mxl", mxl); + addFile(_name + ".omr", omr); + addFile(_name + ".mscz", mscz); + + meta.write(zip); + + if (zip.status() != MQZipWriter::NoError) { + LOGE() << "failed add mscz, zip status: " << zip.status(); + return false; + } + + return true; + } + +//--------------------------------------------------------- +// writeTo +//--------------------------------------------------------- + +bool MsmrFile::writeTo(QIODevice* d) + { + IF_ASSERT(d) { + return false; + } + + if (!d->isOpen()) + d->open(QIODevice::WriteOnly); + + IF_ASSERT(d->isOpen()) { + return false; + } + + d->seek(0); + qint64 size = d->write(_data); + if (size != _data.size()) { + LOGE() << "failed write data"; + return false; + } + + return true; + } + +//--------------------------------------------------------- +// addFile +//--------------------------------------------------------- + +void MsmrFile::MetaInf::addFile(const QString& path) + { + IF_ASSERT(!_containerFiles.contains(path)) { + return; + } + + _containerFiles << path; + + IF_ASSERT(!_storedFiles.contains(path)) { + return; + } + + _storedFiles << path; + } + +//--------------------------------------------------------- +// fileByExt +//--------------------------------------------------------- + +QString MsmrFile::MetaInf::fileByExt(const QString& ext) const + { + auto findByExt = [](const QStringList& list, const QString& ext) { + for(const QString& f : list) { + if (f.endsWith(ext)) + return f; + } + return QString(); + }; + + QString file = findByExt(_containerFiles, ext); + + //! NOTE If the file is not found in the container, + //! then we will look among the stored files for backward compatibility + //! and eliminating any errors + if (file.isEmpty()) { + file = findByExt(_storedFiles, ext); + } + else { + if (!_storedFiles.contains(file)) { + LOGE() << "not found file: " << file; + file = findByExt(_storedFiles, ext); + } + } + + return file; + } + +//--------------------------------------------------------- +// write +//--------------------------------------------------------- + +void MsmrFile::MetaInf::write(MQZipWriter& zip) + { + QByteArray data; + writeContainer(&data); + zip.addFile("META-INF/container.xml", data); + } + +//--------------------------------------------------------- +// read +//--------------------------------------------------------- + +void MsmrFile::MetaInf::read(const MQZipReader& zip) + { + // read container + QByteArray container = zip.fileData("META-INF/container.xml"); + if (container.isEmpty()) + LOGW() << "not found META-INF/container.xml"; + else + readContainer(container); + + // read files list + QVector fis = zip.fileInfoList(); + for (const MQZipReader::FileInfo& fi : fis) + _storedFiles << fi.filePath; + } + +//--------------------------------------------------------- +// readContainer +//--------------------------------------------------------- + +void MsmrFile::MetaInf::readContainer(const QByteArray& data) + { + _containerFiles.clear(); + + QXmlStreamReader xml(data); + while(xml.readNextStartElement()) { + if ("container" != xml.name()) { + xml.skipCurrentElement(); + continue; + } + + while(xml.readNextStartElement()) { + if ("rootfiles" != xml.name()) { + xml.skipCurrentElement(); + continue; + } + + while(xml.readNextStartElement()) { + if ("rootfile" != xml.name()) { + xml.skipCurrentElement(); + continue; + } + + QString path = xml.attributes().value("full-path").toString(); + _containerFiles << path; + } + } + } + } + +//--------------------------------------------------------- +// writeContainer +//--------------------------------------------------------- + +void MsmrFile::MetaInf::writeContainer(QByteArray* data) const + { + IF_ASSERT(data) { + return; + } + + QXmlStreamWriter xml(data); + xml.setAutoFormatting(true); + xml.writeStartDocument(); + xml.writeStartElement("container"); + xml.writeStartElement("rootfiles"); + + for (const QString& f : _containerFiles) { + xml.writeStartElement("rootfile"); + xml.writeAttribute("full-path", f); + xml.writeEndElement(); + } + + xml.writeEndElement(); + xml.writeEndElement(); + xml.writeEndDocument(); + } diff --git a/avsomr/msmrfile.h b/avsomr/msmrfile.h new file mode 100644 index 000000000000..02e7a5ef41f9 --- /dev/null +++ b/avsomr/msmrfile.h @@ -0,0 +1,73 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_MSMRFILE_H +#define AVS_MSMRFILE_H + +#include +#include +#include + +class MQZipReader; +class MQZipWriter; + +namespace Ms { +namespace Avs { + +class MsmrFile + { + public: + MsmrFile(const QByteArray& data, const QString& name); + ~MsmrFile(); + + QByteArray readMxl(); // compressed MuzicXml + QByteArray readMuzicXml(); + QByteArray readOmr(); + + QByteArray readMscz(); + bool writeMscz(const QByteArray& mscz); // add or replace + + bool writeTo(QIODevice* d); + + private: + + struct MetaInf { + void addFile(const QString& path); + QString fileByExt(const QString& ext) const; + + void write(MQZipWriter& zip); + void read(const MQZipReader& zip); + + private: + + void readContainer(const QByteArray& data); + void writeContainer(QByteArray* data) const; + + QStringList _containerFiles; + QStringList _storedFiles; + }; + + QByteArray _data; + QString _name; + }; + +} // Avs +} // Ms + +#endif // AVS_MSMRFILE_H diff --git a/avsomr/msmrwriter.cpp b/avsomr/msmrwriter.cpp new file mode 100644 index 000000000000..070e175266ae --- /dev/null +++ b/avsomr/msmrwriter.cpp @@ -0,0 +1,69 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "msmrwriter.h" + +#include + +#include "libmscore/score.h" + +#include "avslog.h" +#include "msmrfile.h" + +using namespace Ms::Avs; + +MsmrWriter::MsmrWriter() {} + +//--------------------------------------------------------- +// saveMsmrFile +//--------------------------------------------------------- + +bool MsmrWriter::saveMsmrFile(Ms::MasterScore* score, QIODevice* file, const QFileInfo& info) { + + IF_ASSERT(score->avsOmr()) { + return false; + } + + std::shared_ptr msmr = score->avsOmr()->msmrFile(); + IF_ASSERT(msmr) { + return false; + } + + QByteArray mscz; + QBuffer b(&mscz); + bool ok = score->saveCompressedFile(&b, info, false, false); + if (!ok) { + LOGE() << "failed save mscz file"; + return false; + } + + ok = msmr->writeMscz(mscz); + if (!ok) { + LOGE() << "failed save mscz data"; + return false; + } + + ok = msmr->writeTo(file); + if (!ok) { + LOGE() << "failed write msmr file"; + return false; + } + + return true; + } diff --git a/avsomr/msmrwriter.h b/avsomr/msmrwriter.h new file mode 100644 index 000000000000..65a892c68576 --- /dev/null +++ b/avsomr/msmrwriter.h @@ -0,0 +1,46 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_MSMRWRITER_H +#define AVS_MSMRWRITER_H + +#include +#include +#include + +#include "avsomr.h" + +namespace Ms { +class MasterScore; + +namespace Avs { + +class MsmrWriter + { + public: + MsmrWriter(); + + bool saveMsmrFile(Ms::MasterScore* score, QIODevice* file, const QFileInfo& info); + + }; + +} // Avs +} // Ms + +#endif // AVS_MSMRWRITER_H diff --git a/avsomr/ret.cpp b/avsomr/ret.cpp new file mode 100644 index 000000000000..0f852b209f65 --- /dev/null +++ b/avsomr/ret.cpp @@ -0,0 +1,157 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "ret.h" + +#include + +#include "avslog.h" + +using namespace Ms::Avs; + +Ret::Ret() + { + } + +Ret::Ret(Code c) + : _code(c) + { + } + +//--------------------------------------------------------- +// valid +//--------------------------------------------------------- + +bool Ret::valid() const + { + return _code > Undefined; + } + +//--------------------------------------------------------- +// success +//--------------------------------------------------------- + +bool Ret::success() const + { + return _code == Ok; + } + +//--------------------------------------------------------- +// code +//--------------------------------------------------------- + +Ret::Code Ret::code() const + { + return _code; + } + +//--------------------------------------------------------- +// formatedText +//--------------------------------------------------------- + +QString Ret::formatedText() const + { + return formatedText(_code); + } + +//--------------------------------------------------------- +// supportHint +//--------------------------------------------------------- + +QString Ret::supportHint() const + { + return supportHint(_code); + } + +//--------------------------------------------------------- +// text +//--------------------------------------------------------- + +QString Ret::text() const + { + return text(_code); + } + +//--------------------------------------------------------- +// formatedText /static/ +//--------------------------------------------------------- + +QString Ret::formatedText(Code c) + { + QString str; + str += "[" + QString::number(static_cast(c)) + "] "; + str += text(c); + return str; + } + +//--------------------------------------------------------- +// text /static/ +//--------------------------------------------------------- + +QString Ret::text(Code c) + { + switch (c) { + case Undefined: return "Undefined error"; + case Ok: return QObject::tr("Ok"); + // common + case UnknownError: return QObject::tr("Unknown error"); + case FailedReadFile: return QObject::tr("Failed read file"); + case FailedClearDir: return QObject::tr("Failed clear directory"); + case FileNotSupported: return QObject::tr("File not supported"); + // network + case NetworkError: return QObject::tr("Network error"); + case ServerError: return QObject::tr("Server error"); + // local + case LocalNotInstalled: return QObject::tr("Local OMR engine not installed"); + case LocalInstaling: return QObject::tr("Local OMR engine installing..."); + case LocalFailedExec: return QObject::tr("Failed execute local OMR engine"); + case LocalAlreadyBuilding: return QObject::tr("Local OMR engine already building..."); + } + return QString(); + } + +//--------------------------------------------------------- +// supportHint /static/ +//--------------------------------------------------------- + +QString Ret::supportHint(Code c) + { + //! TODO Make information better + + static QString PleaseTryAgain = QObject::tr("Please try again"); + + switch (c) { + case Undefined: return QString(); + case Ok: return QString(); + // common + case UnknownError: return PleaseTryAgain; + case FailedReadFile: return PleaseTryAgain; + case FailedClearDir: return PleaseTryAgain; + case FileNotSupported: return QObject::tr("Choose another file"); + // network + case NetworkError: return QObject::tr("Check your internet connection"); + case ServerError: return PleaseTryAgain; + // local + case LocalNotInstalled: return QObject::tr("Try to uncheck and check again use the local OMR engine"); + case LocalInstaling: return PleaseTryAgain; + case LocalFailedExec: return PleaseTryAgain; + case LocalAlreadyBuilding: return PleaseTryAgain; + } + return QString(); + } diff --git a/avsomr/ret.h b/avsomr/ret.h new file mode 100644 index 000000000000..1a50fa60582b --- /dev/null +++ b/avsomr/ret.h @@ -0,0 +1,80 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_ERR_H +#define AVS_ERR_H + +#include + +namespace Ms { +namespace Avs { + +class Ret + { + public: + + enum Code { + Undefined = -1, + Ok = 0, + AvsOmrFirst = 9000, // reserved codes less than 9000, avs codes will be 90xx + // common + UnknownError, + FailedReadFile, + FailedClearDir, + FileNotSupported, + // network + NetworkError = 9020, + ServerError, + // local + LocalNotInstalled = 9040, + LocalInstaling, + LocalFailedExec, + LocalAlreadyBuilding, + + AvsOmrLast + }; + + Ret(); + Ret(Code c); + + bool valid() const; + bool success() const; + Code code() const; + + inline Ret& operator=(Code c) { _code = c; return *this; } + inline operator bool() const { return success(); } + inline bool operator!() const { return !success(); } + + QString text() const; + QString formatedText() const; + QString supportHint() const; + + static QString text(Code c); + static QString formatedText(Code c); + static QString supportHint(Code c); + + private: + Code _code{Undefined}; + + }; + +} // Avs +} // Ms + +#endif // AVS_ERR_H diff --git a/avsomr/ui/infopopup.cpp b/avsomr/ui/infopopup.cpp new file mode 100644 index 000000000000..d44a87f93fca --- /dev/null +++ b/avsomr/ui/infopopup.cpp @@ -0,0 +1,180 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "infopopup.h" + +#include +#include +#include +#include +#include + +#include "mscore/scoreview.h" + +#include "avslog.h" + +namespace { +static int POPUP_MARGIN{10}; +} + +using namespace Ms; +using namespace Ms::Avs; + +InfoPopup::InfoPopup() + { + setFixedSize(220, 80); + setWindowFlags(Qt::Popup); + setFrameStyle(QFrame::Panel | QFrame::Raised); + + setAutoFillBackground(true); + setBackgroundRole(QPalette::ToolTipBase); + + _recognizedCheck = new QCheckBox(this); + QObject::connect(_recognizedCheck, &QCheckBox::stateChanged, [this](int state) { + emit recognizedCheckedChanged(Qt::Checked == state); + }); + + _notrecognizedCheck = new QCheckBox(this); + QObject::connect(_notrecognizedCheck, &QCheckBox::stateChanged, [this](int state) { + emit notrecognizedCheckedChanged(Qt::Checked == state); + }); + } + +//--------------------------------------------------------- +// setRecognizedChecked +//--------------------------------------------------------- + +void InfoPopup::setRecognizedChecked(bool arg) + { + _recognizedCheck->setChecked(arg); + } + +//--------------------------------------------------------- +// setNotRecognizedChecked +//--------------------------------------------------------- + +void InfoPopup::setNotRecognizedChecked(bool arg) + { + _notrecognizedCheck->setChecked(arg); + } + +//--------------------------------------------------------- +// showOnView +//--------------------------------------------------------- + +void InfoPopup::showOnView(ScoreView* view + , const AvsOmr::Info &info + ) + { + IF_ASSERT(view) { + return; + } + + QVBoxLayout* layout = new QVBoxLayout(this); + + auto addRow = [this, layout](QCheckBox* check, const QColor& color, const QString& text) { + + QHBoxLayout* row = new QHBoxLayout(this); + + row->addWidget(check, 0, Qt::AlignLeft); + + QFrame* rec = new QFrame(this); + rec->setFrameStyle(QFrame::Panel); + rec->setFixedSize(40, 20); + rec->setStyleSheet(QString("background-color: %1;").arg(color.name())); + row->addWidget(rec, 0, Qt::AlignLeft); + + QLabel* label = new QLabel(this); + label->setText(text); + row->addWidget(label, 1); + + layout->addLayout(row); + }; + + addRow(_recognizedCheck, info.usedColor, QString("- recognized")); + addRow(_notrecognizedCheck, info.freeColor, QString("- not recognized")); + + setLayout(layout); + + setParent(view); + + QObject::connect(view, &ScoreView::viewRectChanged, this, &InfoPopup::onViewChanged); + QObject::connect(view, &ScoreView::sizeChanged, this, &InfoPopup::onViewChanged); + + updatePopup(view); + + show(); + } + +//--------------------------------------------------------- +// onViewChanged +//--------------------------------------------------------- + +void InfoPopup::onViewChanged() + { + ScoreView* view = static_cast(sender()); + updatePopup(view); + } + +//--------------------------------------------------------- +// updatePopup +//--------------------------------------------------------- + +void InfoPopup::updatePopup(ScoreView* view) + { + IF_ASSERT(view) { + return; + } + + move(view->width() - width() - POPUP_MARGIN, view->height() - height() - POPUP_MARGIN); + + //! NOTE When scrolling the view, only a small part is redrawn, + //! therefore the previous position of the info panel remains drawn and + //! the panel is draws in a new position, so the panel is duplicated. + //! To prevent this from happening while the panel is showing, + //! we will redraw the view completely. + view->update(); + } + +//--------------------------------------------------------- +// mouseMoveEvent +//--------------------------------------------------------- + +void InfoPopup::mouseMoveEvent(QMouseEvent* event) + { + event->accept(); + } + +//--------------------------------------------------------- +// mousePressEvent +//--------------------------------------------------------- + +void InfoPopup::mousePressEvent(QMouseEvent* event) + { + event->accept(); + } + +//--------------------------------------------------------- +// mouseReleaseEvent +//--------------------------------------------------------- + +void InfoPopup::mouseReleaseEvent(QMouseEvent* event) + { + event->accept(); + } diff --git a/avsomr/ui/infopopup.h b/avsomr/ui/infopopup.h new file mode 100644 index 000000000000..d24cd3c19272 --- /dev/null +++ b/avsomr/ui/infopopup.h @@ -0,0 +1,68 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_INFOPOPUP_H +#define AVS_INFOPOPUP_H + +#include + +#include "avsomr/avsomr.h" + +class QCheckBox; + +namespace Ms { +class ScoreView; + +namespace Avs { + +class InfoPopup : public QFrame + { + Q_OBJECT + + public: + InfoPopup(); + + void setRecognizedChecked(bool arg); + void setNotRecognizedChecked(bool arg); + + void showOnView(ScoreView* view, const AvsOmr::Info& info); + + signals: + void recognizedCheckedChanged(bool checked); + void notrecognizedCheckedChanged(bool checked); + + private slots: + void onViewChanged(); + + private: + + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + + void updatePopup(ScoreView* view); + + QCheckBox* _recognizedCheck{nullptr}; + QCheckBox* _notrecognizedCheck{nullptr}; + }; + +} // Avs +} // Ms + +#endif // AVS_INFOPOPUP_H diff --git a/avsomr/ui/recognitionproccessdialog.cpp b/avsomr/ui/recognitionproccessdialog.cpp new file mode 100644 index 000000000000..56320f92589d --- /dev/null +++ b/avsomr/ui/recognitionproccessdialog.cpp @@ -0,0 +1,165 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "recognitionproccessdialog.h" + +#include + +#include "taskbarprogress.h" + +using namespace Ms::Avs; + +RecognitionProccessDialog::RecognitionProccessDialog() + : QProgressDialog() + { + setWindowFlags(windowFlags() & ~Qt::WindowCloseButtonHint & ~Qt::WindowContextHelpButtonHint); + + setFixedSize(300, 140); + setAutoReset(false); + + _closeBtn = new QPushButton(QObject::tr("Close")); + setCancelButton(_closeBtn); + + _taskbarProgress = new TaskbarProgress(this); + + setRange(0, 100); + _taskbarProgress->setRange(0, 100); + + setValue(0); + _taskbarProgress->setValue(0); + + _updater.setInterval(1000); + _updater.setSingleShot(false); + + QObject::connect(&_updater, &QTimer::timeout, [this]() { + update(); + }); + } + +//--------------------------------------------------------- +// formatStep +//--------------------------------------------------------- + +QString RecognitionProccessDialog::formatStep(const IAvsOmrRecognizer::Step& step) const + { + switch (step.type) { + case IAvsOmrRecognizer::Step::Undefined: + return QString(); + case IAvsOmrRecognizer::Step::PrepareStart: + return QObject::tr("Preparing..."); + case IAvsOmrRecognizer::Step::PrepareFinish: + if (step.success()) + return QObject::tr("Success prepare"); + + return QObject::tr("Failed prepare") + + "\n" + step.error.formatedText() + + "\n" + step.error.supportHint(); + case IAvsOmrRecognizer::Step::ProcessingStart: + return QObject::tr("Procesing..."); + case IAvsOmrRecognizer::Step::ProcessingFinish: + if (step.success()) + return QObject::tr("Success procesing"); + + return QObject::tr("Failed procesing") + + "\n" + step.error.formatedText() + + "\n" + step.error.supportHint(); + case IAvsOmrRecognizer::Step::LoadStart: + return QObject::tr("Loading..."); + case IAvsOmrRecognizer::Step::LoadFinish: + if (step.success()) + return QObject::tr("Success loading"); + + return QObject::tr("Failed loading") + + "\n" + step.error.formatedText() + + "\n" + step.error.supportHint(); + } + return QString(); + } + +//--------------------------------------------------------- +// setType +//--------------------------------------------------------- + +void RecognitionProccessDialog::setType(const QString& type) + { + _type = type; + } + +//--------------------------------------------------------- +// show +//--------------------------------------------------------- + +void RecognitionProccessDialog::show() + { + setWindowTitle(QString("Optical Music Recognition (%1)").arg(_type)); + _closeBtn->setEnabled(false); + + _updater.start(); + _time.start(); + + QProgressDialog::show(); + _taskbarProgress->show(); + } + +//--------------------------------------------------------- +// update +//--------------------------------------------------------- + +void RecognitionProccessDialog::update() + { + //! NOTE Imitation of continuous progress + if (_lastStep.percent < _lastStep.percentMax) + _lastStep.percent++; + + setValue(_lastStep.percent); + _taskbarProgress->setValue(_lastStep.percent); + + setLabelText(formatStep(_lastStep)); + } + +//--------------------------------------------------------- +// onStep +//--------------------------------------------------------- + +void RecognitionProccessDialog::onStep(const IAvsOmrRecognizer::Step& step) + { + _lastStep = step; + update(); + } + +//--------------------------------------------------------- +// onFinished +//--------------------------------------------------------- + +void RecognitionProccessDialog::onFinished(bool success) + { + + _updater.stop(); + + if (success) { + QProgressDialog::hide(); + _taskbarProgress->hide(); + } + else { + _closeBtn->setEnabled(true); + _taskbarProgress->stop(); + QProgressDialog::exec(); + _taskbarProgress->hide(); + } + } diff --git a/avsomr/ui/recognitionproccessdialog.h b/avsomr/ui/recognitionproccessdialog.h new file mode 100644 index 000000000000..a70404c599bb --- /dev/null +++ b/avsomr/ui/recognitionproccessdialog.h @@ -0,0 +1,65 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_RECOGNITIONPROCCESSDIALOG_H +#define AVS_RECOGNITIONPROCCESSDIALOG_H + +#include +#include +#include +#include + +#include "iavsomrrecognizer.h" + +class QPushButton; + +namespace Ms { +class TaskbarProgress; + +namespace Avs { + +class RecognitionProccessDialog : private QProgressDialog + { + public: + RecognitionProccessDialog(); + + void setType(const QString& type); + void show(); + + void onStep(const IAvsOmrRecognizer::Step& step); + void onFinished(bool success); + + private: + + QString formatStep(const IAvsOmrRecognizer::Step& step) const; + void update(); + + QString _type; + IAvsOmrRecognizer::Step _lastStep; + + QTimer _updater; + QTime _time; + QPushButton* _closeBtn{nullptr}; + TaskbarProgress* _taskbarProgress{nullptr}; + }; + +} // Avs +} // Ms + +#endif // AVS_RECOGNITIONPROCCESSDIALOG_H diff --git a/avsomr/ui/setupavsomrview.cpp b/avsomr/ui/setupavsomrview.cpp new file mode 100644 index 000000000000..64399dc21966 --- /dev/null +++ b/avsomr/ui/setupavsomrview.cpp @@ -0,0 +1,71 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "setupavsomrview.h" + +#include "mscore/scoreview.h" + +#include "avslog.h" +#include "infopopup.h" + +using namespace Ms::Avs; + +SetupAvsOmrView::SetupAvsOmrView() + { + + } + +//--------------------------------------------------------- +// setupView +//--------------------------------------------------------- + +void SetupAvsOmrView::setupView(Ms::ScoreView* view, std::shared_ptr avsOmr) + { + IF_ASSERT(view) { + return; + } + + IF_ASSERT(avsOmr) { + return; + } + + AvsOmr::Config& config = avsOmr->config(); + + InfoPopup* infoPopup = new InfoPopup(); + infoPopup->setParent(view); + + // recognized + config.setIsShowRecognized(true); + infoPopup->setRecognizedChecked(true); + QObject::connect(infoPopup, &InfoPopup::recognizedCheckedChanged, [view, avsOmr](bool checked) { + avsOmr->config().setIsShowRecognized(checked); + view->update(); + }); + + // notrecognized + config.setIsShowNotRecognized(true); + infoPopup->setNotRecognizedChecked(true); + QObject::connect(infoPopup, &InfoPopup::notrecognizedCheckedChanged, [view, avsOmr](bool checked) { + avsOmr->config().setIsShowNotRecognized(checked); + view->update(); + }); + + // show popup + infoPopup->showOnView(view, avsOmr->info()); + } diff --git a/avsomr/ui/setupavsomrview.h b/avsomr/ui/setupavsomrview.h new file mode 100644 index 000000000000..0b64d4afbac8 --- /dev/null +++ b/avsomr/ui/setupavsomrview.h @@ -0,0 +1,42 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_SETUPAVSOMRVIEW_H +#define AVS_SETUPAVSOMRVIEW_H + +#include +#include "avsomr/avsomr.h" + +namespace Ms { +class ScoreView; + +namespace Avs { + +class SetupAvsOmrView + { + public: + SetupAvsOmrView(); + + void setupView(Ms::ScoreView* view, std::shared_ptr avsOmr); + }; + +} // Avs +} // Ms + +#endif // AVS_SETUPAVSOMRVIEW_H diff --git a/avsomr/ui/taskbarprogress.cpp b/avsomr/ui/taskbarprogress.cpp new file mode 100644 index 000000000000..6fe9ed185018 --- /dev/null +++ b/avsomr/ui/taskbarprogress.cpp @@ -0,0 +1,122 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "taskbarprogress.h" + +#ifdef Q_OS_WIN +#include +#endif + +using namespace Ms; + +TaskbarProgress::TaskbarProgress(QObject* parent) + : QObject(parent) + { +#ifdef Q_OS_WIN + _winTaskbarProgress = new QWinTaskbarProgress(); +#endif + } + +TaskbarProgress::~TaskbarProgress() + { +#ifdef Q_OS_WIN + delete _winTaskbarProgress; +#endif + } + +//--------------------------------------------------------- +// isAvalabled +//--------------------------------------------------------- + +bool TaskbarProgress::isAvalabled() const + { +#ifdef Q_OS_WIN + return true; +#else + return false; +#endif + } + +//--------------------------------------------------------- +// show +//--------------------------------------------------------- + +void TaskbarProgress::show() + { +#ifdef Q_OS_WIN + _winTaskbarProgress->show(); +#else + // noop +#endif + } + +//--------------------------------------------------------- +// hide +//--------------------------------------------------------- + +void TaskbarProgress::hide() + { +#ifdef Q_OS_WIN + _winTaskbarProgress->hide(); +#else + // noop +#endif + } + +//--------------------------------------------------------- +// stop +//--------------------------------------------------------- + +void TaskbarProgress::stop() + { +#ifdef Q_OS_WIN + _winTaskbarProgress->stop(); +#else + // noop +#endif + } + +//--------------------------------------------------------- +// setRange +//--------------------------------------------------------- + +void TaskbarProgress::setRange(int min, int max) + { +#ifdef Q_OS_WIN + _winTaskbarProgress->setRange(min, max); +#else + Q_UNUSED(min); + Q_UNUSED(max); + // noop +#endif + } + +//--------------------------------------------------------- +// setValue +//--------------------------------------------------------- + +void TaskbarProgress::setValue(int val) + { +#ifdef Q_OS_WIN + _winTaskbarProgress->setValue(val); +#else + Q_UNUSED(val); + // noop +#endif + } diff --git a/avsomr/ui/taskbarprogress.h b/avsomr/ui/taskbarprogress.h new file mode 100644 index 000000000000..51ba7e8290dd --- /dev/null +++ b/avsomr/ui/taskbarprogress.h @@ -0,0 +1,57 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Copyright (C) 2020 MuseScore BVBA and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#ifndef AVS_TASKBARPROGRESS_H +#define AVS_TASKBARPROGRESS_H + +#include + +#ifdef Q_OS_WIN +class QWinTaskbarProgress; +#endif + +namespace Ms { + +//! NOTE Probably somewhere there should be a common place for common UI controls +class TaskbarProgress : public QObject + { + Q_OBJECT + public: + TaskbarProgress(QObject *parent); + ~TaskbarProgress(); + + bool isAvalabled() const; + + public slots: + void show(); + void hide(); + void stop(); + + void setRange(int min, int max); + void setValue(int val); + + private: +#ifdef Q_OS_WIN + QWinTaskbarProgress* _winTaskbarProgress{nullptr}; +#endif + }; + +} // Ms + +#endif // AVS_TASKBARPROGRESS_H diff --git a/build/FindQt5.cmake b/build/FindQt5.cmake index 3728f6b715d0..b6ac3650123e 100644 --- a/build/FindQt5.cmake +++ b/build/FindQt5.cmake @@ -28,6 +28,14 @@ if (USE_WEBENGINE) WebEngineWidgets ) endif(USE_WEBENGINE) + +if (WIN32) + set(_components + ${_components} + WinExtras + ) +endif(WIN32) + foreach(_component ${_components}) find_package(Qt5${_component}) list(APPEND QT_LIBRARIES ${Qt5${_component}_LIBRARIES}) diff --git a/build/config.h.in b/build/config.h.in index a948a447871f..5cacffe158d6 100644 --- a/build/config.h.in +++ b/build/config.h.in @@ -57,6 +57,7 @@ #cmakedefine ZERBERUS #cmakedefine OMR #cmakedefine OCR +#cmakedefine AVSOMR #cmakedefine OSC #cmakedefine OPENGL #cmakedefine SOUNDFONT3 diff --git a/global/CMakeLists.txt b/global/CMakeLists.txt index f0a3fe17cc73..5b2574ab2244 100644 --- a/global/CMakeLists.txt +++ b/global/CMakeLists.txt @@ -25,7 +25,6 @@ add_definitions( ) include_directories( ${Qt5Core_INCLUDE_DIRS} - ${PROJECT_SOURCE_DIR}/telemetry ) set(CMAKE_AUTOMOC ON) diff --git a/global/interfaces/abstractmodulesetup.h b/global/interfaces/abstractmodulesetup.h index e76bd31f2e23..c9f31aa19c74 100644 --- a/global/interfaces/abstractmodulesetup.h +++ b/global/interfaces/abstractmodulesetup.h @@ -33,16 +33,19 @@ class AbstractModuleSetup { registerExports(); registerResources(); registerQmlTypes(); + onStartInit(); } protected: - virtual QString moduleName() = 0; + virtual QString moduleName() const = 0; virtual void registerExports() {} virtual void registerResources() {} virtual void registerQmlTypes() {} + + virtual void onStartInit() {} }; #endif // IMODULESETUP_H diff --git a/global/settings/types/preferencekeys.h b/global/settings/types/preferencekeys.h index a377c5fff6e5..d58dbbd3d0bd 100644 --- a/global/settings/types/preferencekeys.h +++ b/global/settings/types/preferencekeys.h @@ -63,6 +63,7 @@ #define PREF_IMPORT_GUITARPRO_CHARSET "import/guitarpro/charset" #define PREF_IMPORT_MUSICXML_IMPORTBREAKS "import/musicXML/importBreaks" #define PREF_IMPORT_MUSICXML_IMPORTLAYOUT "import/musicXML/importLayout" +#define PREF_IMPORT_AVSOMR_USELOCAL "import/avsomr/useLocalEngine" #define PREF_IMPORT_OVERTURE_CHARSET "import/overture/charset" #define PREF_IMPORT_STYLE_STYLEFILE "import/style/styleFile" #define PREF_IMPORT_COMPATIBILITY_RESET_ELEMENT_POSITIONS "import/compatibility/resetElementPositions" @@ -166,5 +167,7 @@ #define PREF_UI_BUTTON_HIGHLIGHT_COLOR_ENABLED_LIGHT_OFF "ui/button/highlight/color/enabled/light/off" #define PREF_UI_INSPECTOR_STYLED_TEXT_COLOR_LIGHT "ui/inspector/styledtext/color/light" #define PREF_UI_INSPECTOR_STYLED_TEXT_COLOR_DARK "ui/inspector/styledtext/color/dark" +#define PREF_UI_AVSOMR_RECOGNITION_COLOR "ui/avsomr/recognition/valid/color" +#define PREF_UI_AVSOMR_NOT_RECOGNITION_COLOR "ui/avsomr/recognition/notValid/color" #endif // PREFERENCEKEYS_H diff --git a/libmscore/CMakeLists.txt b/libmscore/CMakeLists.txt index d4ad42770290..5641e59e6f58 100644 --- a/libmscore/CMakeLists.txt +++ b/libmscore/CMakeLists.txt @@ -102,6 +102,10 @@ add_library ( unrollrepeats.cpp ) +if (AVSOMR) + target_link_libraries(libmscore avsomr) +endif (AVSOMR) + ## ## Code coverage. Only affects DEBUG builds. ## diff --git a/libmscore/score.h b/libmscore/score.h index aabd2fcfcb14..89beff657e2e 100644 --- a/libmscore/score.h +++ b/libmscore/score.h @@ -30,6 +30,10 @@ namespace Ms { +namespace Avs { + class AvsOmr; +} + class Articulation; class Audio; class BarLine; @@ -791,7 +795,7 @@ class Score : public QObject, public ScoreElement { bool saveFile(QFileInfo& info); bool saveFile(QIODevice* f, bool msczFormat, bool onlySelection = false); bool saveCompressedFile(QFileInfo&, bool onlySelection, bool createThumbnail = true); - bool saveCompressedFile(QFileDevice*, QFileInfo&, bool onlySelection, bool createThumbnail = true); + bool saveCompressedFile(QIODevice *, const QFileInfo &, bool onlySelection, bool createThumbnail = true); void print(QPainter* printer, int page); ChordRest* getSelectedChordRest() const; @@ -1247,6 +1251,8 @@ class MasterScore : public Score { Omr* _omr { 0 }; bool _showOmr { false }; + std::shared_ptr _avsOmr { nullptr }; + Fraction _pos[3]; ///< 0 - current, 1 - left loop, 2 - right loop int _midiPortCount { 0 }; // A count of JACK/ALSA midi out ports @@ -1341,6 +1347,9 @@ class MasterScore : public Score { bool showOmr() const { return _showOmr; } void setShowOmr(bool v) { _showOmr = v; } + std::shared_ptr avsOmr() const { return _avsOmr; } + void setAvsOmr(std::shared_ptr omr) { _avsOmr = omr; } + int midiPortCount() const { return _midiPortCount; } void setMidiPortCount(int val) { _midiPortCount = val; } std::vector& midiMapping() { return _midiMapping; } diff --git a/libmscore/scorefile.cpp b/libmscore/scorefile.cpp index d13922052954..cd4904a12c00 100644 --- a/libmscore/scorefile.cpp +++ b/libmscore/scorefile.cpp @@ -41,6 +41,10 @@ #include "omr/omrpage.h" #endif +#ifdef AVSOMR +#include "avsomr/msmrwriter.h" +#endif + #include "sig.h" #include "undo.h" #include "imageStore.h" @@ -392,7 +396,21 @@ bool MasterScore::saveFile() MScore::lastError = tr("Open Temp File\n%1\nfailed: %2").arg(tempName, strerror(errno)); return false; } - bool rv = suffix == "mscx" ? Score::saveFile(&temp, false) : Score::saveCompressedFile(&temp, info, false); + + bool rv = false; + if ("mscx" == suffix) { + rv = Score::saveFile(&temp, false); + } +#ifdef AVSOMR + else if ("msmr" == suffix) { + Avs::MsmrWriter msmrWriter; + rv = msmrWriter.saveMsmrFile(this, &temp, info); + } +#endif + else { + rv = Score::saveCompressedFile(&temp, info, false); + } + if (!rv) { return false; } @@ -545,7 +563,7 @@ QImage Score::createThumbnail() // file is already opened //--------------------------------------------------------- -bool Score::saveCompressedFile(QFileDevice* f, QFileInfo& info, bool onlySelection, bool doCreateThumbnail) +bool Score::saveCompressedFile(QIODevice* f, const QFileInfo& info, bool onlySelection, bool doCreateThumbnail) { MQZipWriter uz(f); @@ -576,8 +594,11 @@ bool Score::saveCompressedFile(QFileDevice* f, QFileInfo& info, bool onlySelecti saveFile(&dbuf, true, onlySelection); dbuf.seek(0); uz.addFile(fn, dbuf.data()); - f->flush(); // flush to preserve score data in case of - // any failures on the further operations. + + QFileDevice* fd = dynamic_cast(f); + if (fd) // if is file (may be buffer) + fd->flush(); // flush to preserve score data in case of + // any failures on the further operations. // save images //uz.addDirectory("Pictures"); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6224bd60eba3..fb31037b059d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -51,6 +51,8 @@ include_directories( add_executable(${ExecutableName} ${resource_file} main.cpp + modulessetup.cpp + modulessetup.h ) if (MINGW OR MSVC) diff --git a/global/modulessetup.cpp b/main/modulessetup.cpp similarity index 85% rename from global/modulessetup.cpp rename to main/modulessetup.cpp index 9cf6d14061ec..b6820e331bae 100644 --- a/global/modulessetup.cpp +++ b/main/modulessetup.cpp @@ -21,7 +21,11 @@ #include "config.h" #ifdef BUILD_TELEMETRY_MODULE -#include "telemetrysetup.h" +#include "telemetry/telemetrysetup.h" +#endif + +#ifdef AVSOMR +#include "avsomr/avsomrsetup.h" #endif //--------------------------------------------------------- @@ -30,9 +34,15 @@ ModulesSetup::ModulesSetup() { + + m_modulesSetupList #ifdef BUILD_TELEMETRY_MODULE - m_modulesSetupList << new TelemetrySetup(); + << new TelemetrySetup() +#endif +#ifdef AVSOMR + << new Ms::Avs::AvsOmrSetup() #endif + ; } //--------------------------------------------------------- diff --git a/global/modulessetup.h b/main/modulessetup.h similarity index 100% rename from global/modulessetup.h rename to main/modulessetup.h diff --git a/mscore/CMakeLists.txt b/mscore/CMakeLists.txt index 504da3fd67e0..170f13e80abf 100644 --- a/mscore/CMakeLists.txt +++ b/mscore/CMakeLists.txt @@ -551,6 +551,10 @@ if (OSC) target_link_libraries(mscoreapp ofqf) endif (OSC) +if (AVSOMR) + target_link_libraries(mscoreapp avsomr) +endif (AVSOMR) + if (MINGW) string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) # Windows: Add -mconsole to LINK_FLAGS to get a console window for debug output diff --git a/mscore/file.cpp b/mscore/file.cpp index c21fa05a0535..41361fcdfb21 100644 --- a/mscore/file.cpp +++ b/mscore/file.cpp @@ -14,6 +14,8 @@ File handling: loading and saving. */ +#include + #include "config.h" #include "globals.h" #include "musescore.h" @@ -268,7 +270,51 @@ bool MuseScore::checkDirty(MasterScore* s) } //--------------------------------------------------------- -// loadFile +// openFiles +//--------------------------------------------------------- + +void MuseScore::openFiles(bool switchTab, bool singleFile) + { + QString allExt = "*.mscz *.mscx *.mxl *.musicxml *.xml *.mid *.midi *.kar *.md *.mgu *.sgu *.cap *.capx *.ove *.scw *.bww *.gtp *.gp3 *.gp4 *.gp5 *.gpx *.ptb"; +#ifdef AVSOMR + allExt += " *.msmr"; // omr project with omr data and musicxml or score +#endif + + QStringList filter; + filter << tr("All Supported Files") + " (" + allExt + ")" + << tr("MuseScore Files") + " (*.mscz *.mscx)" + << tr("MusicXML Files") + " (*.mxl *.musicxml *.xml)" + << tr("MIDI Files") + " (*.mid *.midi *.kar)" + << tr("MuseData Files") + " (*.md)" + << tr("Capella Files") + " (*.cap *.capx)" + << tr("BB Files (experimental)") + " (*.mgu *.sgu)" + << tr("Overture / Score Writer Files (experimental)") + " (*.ove *.scw)" + << tr("Bagpipe Music Writer Files (experimental)") + " (*.bww)" + << tr("Guitar Pro Files") + " (*.gtp *.gp3 *.gp4 *.gp5 *.gpx)" + << tr("Power Tab Editor Files (experimental)") + " (*.ptb)"; + + doLoadFiles(filter, switchTab, singleFile); + } + +//--------------------------------------------------------- +// importFiles +//--------------------------------------------------------- + +void MuseScore::importScore(bool switchTab, bool singleFile) + { +#ifndef AVSOMR + Q_UNUSED(switchTab); + Q_UNUSED(singleFile); + openExternalLink("https://musescore.com/import"); +#else + QStringList filter; + filter << tr("Optical Music Recognition") + " (*.pdf *.png *.jpg)"; + doLoadFiles(filter, switchTab, singleFile); +#endif + } + +//--------------------------------------------------------- +// doLoadFiles //--------------------------------------------------------- /** @@ -277,30 +323,10 @@ bool MuseScore::checkDirty(MasterScore* s) Handles the GUI's file-open action. */ -void MuseScore::loadFiles(bool switchTab, bool singleFile) +void MuseScore::doLoadFiles(const QStringList& filter, bool switchTab, bool singleFile) { - QStringList files = getOpenScoreNames( -#ifdef OMR - tr("All Supported Files") + " (*.mscz *.mscx *.mxl *.musicxml *.xml *.mid *.midi *.kar *.md *.mgu *.sgu *.cap *.capx *.pdf *.ove *.scw *.bww *.gtp *.gp3 *.gp4 *.gp5 *.gpx);;" + -#else - tr("All Supported Files") + " (*.mscz *.mscx *.mxl *.musicxml *.xml *.mid *.midi *.kar *.md *.mgu *.sgu *.cap *.capx *.ove *.scw *.bww *.gtp *.gp3 *.gp4 *.gp5 *.gpx *.ptb);;" + -#endif - tr("MuseScore Files") + " (*.mscz *.mscx);;" + - tr("MusicXML Files") + " (*.mxl *.musicxml *.xml);;" + - tr("MIDI Files") + " (*.mid *.midi *.kar);;" + - tr("MuseData Files") + " (*.md);;" + - tr("Capella Files") + " (*.cap *.capx);;" + - tr("BB Files (experimental)") + " (*.mgu *.sgu);;" + -#ifdef OMR - tr("PDF Files (experimental OMR)") + " (*.pdf);;" + -#endif - tr("Overture / Score Writer Files (experimental)") + " (*.ove *.scw);;" + - tr("Bagpipe Music Writer Files (experimental)") + " (*.bww);;" + - tr("Guitar Pro Files") + " (*.gtp *.gp3 *.gp4 *.gp5 *.gpx);;" + - tr("Power Tab Editor Files (experimental)") + " (*.ptb)", - tr("Load Score"), - singleFile - ); + QString filterStr = filter.join(";;"); + QStringList files = getOpenScoreNames(filterStr, tr("Load Score"), singleFile); for (const QString& s : files) openScore(s, switchTab); mscore->tourHandler()->showDelayedWelcomeTour(); @@ -413,8 +439,8 @@ bool MuseScore::saveFile(MasterScore* score) if (t) fn = t->plainText(); QString name = createDefaultFileName(fn); - QString f1 = tr("MuseScore 3 File") + " (*.mscz)"; - QString f2 = tr("Uncompressed MuseScore 3 File") + " (*.mscx)"; // for debugging purposes + QString msczType = tr("MuseScore 3 File") + " (*.mscz)"; + QString mscxType = tr("Uncompressed MuseScore 3 File") + " (*.mscx)"; // for debugging purposes QSettings set; if (mscore->lastSaveDirectory.isEmpty()) @@ -425,9 +451,19 @@ bool MuseScore::saveFile(MasterScore* score) saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES); QString fname = QString("%1/%2").arg(saveDirectory).arg(name); - QString filter = f1 + ";;" + f2; - if (QFileInfo(fname).suffix().isEmpty()) + QString filter; +#ifdef AVSOMR + if (score->avsOmr()) { + QString msmrType = tr("Music Recognition MuseScore 3 File") + " (*.msmr)"; + fname = QFileInfo(fname).baseName() + ".msmr"; + filter = msmrType + ";;" + mscxType + ";;" + msczType; + } + else +#endif + if (QFileInfo(fname).suffix().isEmpty()) { fname += ".mscz"; + filter = msczType + ";;" + mscxType; + } fn = mscore->getSaveScoreName(tr("Save Score"), fname, filter); if (fn.isEmpty()) @@ -2317,6 +2353,12 @@ Score::FileError readScore(MasterScore* score, QString name, bool ignoreVersionE { "gp5", &importGTP }, { "gpx", &importGTP }, { "ptb", &importGTP }, +#ifdef AVSOMR + { "msmr", &importMSMR }, + { "pdf", &loadAndImportMSMR }, + { "png", &loadAndImportMSMR }, + { "jpg", &loadAndImportMSMR }, +#endif }; // import @@ -2345,7 +2387,8 @@ Score::FileError readScore(MasterScore* score, QString name, bool ignoreVersionE } score->setMetaTag("originalFormat", suffix); score->connectTies(); - score->setCreated(true); // force save as for imported files + if (!score->avsOmr()) //! NOTE For avsomr сreated is set upon import + score->setCreated(true); // force save as for imported files } score->rebuildMidiMapping(); diff --git a/mscore/importxml.cpp b/mscore/importxml.cpp index e998fb0c4780..796c24d4aefc 100644 --- a/mscore/importxml.cpp +++ b/mscore/importxml.cpp @@ -245,26 +245,39 @@ static Score::FileError doValidateAndImport(Score* score, const QString& name, Q Import MusicXML file \a name into the Score. */ -Score::FileError importMusicXml(MasterScore* score, const QString& name) +Score::FileError importMusicXml(MasterScore* score, QIODevice* dev, const QString& name) { ScoreLoad sl; // suppress warnings for undo push/pop - //qDebug("importMusicXml(%p, %s)", score, qPrintable(name)); - - // open the MusicXML file - QFile xmlFile(name); - if (!xmlFile.exists()) - return Score::FileError::FILE_NOT_FOUND; - if (!xmlFile.open(QIODevice::ReadOnly)) { + if (!dev->open(QIODevice::ReadOnly)) { qDebug("importMusicXml() could not open MusicXML file '%s'", qPrintable(name)); MScore::lastError = QObject::tr("Could not open MusicXML file\n%1").arg(name); return Score::FileError::FILE_OPEN_ERROR; } // and import it - return doValidateAndImport(score, name, &xmlFile); + return doValidateAndImport(score, name, dev); } +Score::FileError importMusicXml(MasterScore* score, const QString& name) { + + ScoreLoad sl; // suppress warnings for undo push/pop + + //qDebug("importMusicXml(%p, %s)", score, qPrintable(name)); + + // open the MusicXML file + QFile xmlFile(name); + if (!xmlFile.exists()) + return Score::FileError::FILE_NOT_FOUND; + if (!xmlFile.open(QIODevice::ReadOnly)) { + qDebug("importMusicXml() could not open MusicXML file '%s'", qPrintable(name)); + MScore::lastError = QObject::tr("Could not open MusicXML file\n%1").arg(name); + return Score::FileError::FILE_OPEN_ERROR; + } + + // and import it + return doValidateAndImport(score, name, &xmlFile); +} //--------------------------------------------------------- // importCompressedMusicXml diff --git a/mscore/musescore.cpp b/mscore/musescore.cpp index c31ffdf66870..d7075c0e197f 100644 --- a/mscore/musescore.cpp +++ b/mscore/musescore.cpp @@ -6041,7 +6041,7 @@ void MuseScore::cmd(QAction* a, const QString& cmd) else if (cmd == "keys") showKeyEditor(); else if (cmd == "file-open") - loadFiles(); + openFiles(); else if (cmd == "file-save") saveFile(); else if (cmd == saveOnlineMenuItem) @@ -6051,7 +6051,7 @@ void MuseScore::cmd(QAction* a, const QString& cmd) else if (cmd == "file-part-export") exportParts(); else if (cmd == "file-import-pdf") - openExternalLink("https://musescore.com/import"); + importScore(); else if (cmd == "file-close") closeScore(cs); else if (cmd == "file-save-as") diff --git a/mscore/musescore.h b/mscore/musescore.h index 06d8b76377d4..80646bd714f9 100644 --- a/mscore/musescore.h +++ b/mscore/musescore.h @@ -470,6 +470,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore { void checkForUpdatesNoUI(); + void doLoadFiles(const QStringList& filter, bool switchTab, bool singleFile); + signals: void windowSplit(bool); void musescoreWindowWasShown(); @@ -764,7 +766,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore { bool countIn() const { return countInAction->isChecked(); } bool panDuringPlayback() const { return panAction->isChecked(); } void noteTooShortForTupletDialog(); - void loadFiles(bool switchTab = true, bool singleFile = false); + void openFiles(bool switchTab = true, bool singleFile = false); + void importScore(bool switchTab = true, bool singleFile = false); // midi panel functions void midiPanelOnSwitchToFile(const QString &file); void midiPanelOnCloseFile(const QString &file); @@ -927,6 +930,10 @@ extern bool saveXml(Score*, const QString& name); extern QString getSharePath(); +#ifdef AVSOMR +extern Score::FileError importMSMR(MasterScore*, const QString& name); +extern Score::FileError loadAndImportMSMR(MasterScore*, const QString& name); +#endif extern Score::FileError importMidi(MasterScore*, const QString& name); extern Score::FileError importGTP(MasterScore*, const QString& name); extern Score::FileError importBww(MasterScore*, const QString& path); diff --git a/mscore/preferences.cpp b/mscore/preferences.cpp index 3f813862ccc5..cc46ffba6034 100644 --- a/mscore/preferences.cpp +++ b/mscore/preferences.cpp @@ -211,7 +211,12 @@ void Preferences::init(bool storeInMemoryOnly) {PREF_UI_BUTTON_HIGHLIGHT_COLOR_ENABLED_LIGHT_ON, new ColorPreference(QColor("#0065BF"))}, {PREF_UI_BUTTON_HIGHLIGHT_COLOR_ENABLED_LIGHT_OFF, new ColorPreference(QColor("#3b3f45"))}, {PREF_UI_INSPECTOR_STYLED_TEXT_COLOR_LIGHT, new ColorPreference(QColor("#0066BF"))}, - {PREF_UI_INSPECTOR_STYLED_TEXT_COLOR_DARK, new ColorPreference(QColor("#36B2FF"))} + {PREF_UI_INSPECTOR_STYLED_TEXT_COLOR_DARK, new ColorPreference(QColor("#36B2FF"))}, +#ifdef AVSOMR + {PREF_IMPORT_AVSOMR_USELOCAL, new BoolPreference(false, false)}, + {PREF_UI_AVSOMR_RECOGNITION_COLOR, new ColorPreference(QColor("#1DCCA0"))}, + {PREF_UI_AVSOMR_NOT_RECOGNITION_COLOR, new ColorPreference(QColor("#C31989"))}, +#endif }); _initialized = true; @@ -412,6 +417,21 @@ void Preferences::setPreference(const QString key, QVariant value) { checkIfKeyExists(key); set(key, value); + for (const OnSetListener& l : _onSetListeners) + l(key, value); + } + +Preferences::ListenerID Preferences::addOnSetListener(const OnSetListener& l) + { + static ListenerID lastId{0}; + ++lastId; + _onSetListeners[lastId] = l; + return lastId; + } + +void Preferences::removeOnSetListener(const ListenerID& id) + { + _onSetListeners.remove(id); } void Preferences::setTemporaryPreference(const QString key, QVariant value) diff --git a/mscore/preferences.h b/mscore/preferences.h index 8c3ac9b0ed14..f68a40fe1bc7 100644 --- a/mscore/preferences.h +++ b/mscore/preferences.h @@ -29,6 +29,7 @@ * using getString(), getInt(), etc., and changed using setPreference() */ +#include #include "globals.h" #include "global/settings/types/preferencekeys.h" @@ -141,6 +142,8 @@ class EnumPreference: public Preference { class Preferences { public: typedef QHash prefs_map_t; + using OnSetListener = std::function; + using ListenerID = uint32_t; private: @@ -174,6 +177,8 @@ class Preferences { QMap getDefaultLocalPreferences(); bool useLocalPrefs = false; + QMap _onSetListeners; + public: Preferences(); ~Preferences(); @@ -196,6 +201,10 @@ class Preferences { void setToDefaultValue(const QString key); void setPreference(const QString key, QVariant value); + // set listeners + ListenerID addOnSetListener(const OnSetListener& l); + void removeOnSetListener(const ListenerID& id); + // A temporary preference is stored "in memory" only and not written to file. // If there is both a "normal" preference and a temporary preference with the same // key the temporary preference is used diff --git a/mscore/prefsdialog.cpp b/mscore/prefsdialog.cpp index 9c4339bc8621..3af297a5b41b 100644 --- a/mscore/prefsdialog.cpp +++ b/mscore/prefsdialog.cpp @@ -35,6 +35,10 @@ #include "resourceManager.h" #include "synthesizer/msynthesizer.h" +#ifdef AVSOMR +#include "avsomr/avsomrlocal.h" +#endif + namespace Ms { //--------------------------------------------------------- @@ -409,6 +413,22 @@ void PreferenceDialog::updateValues(bool useDefaultValues) importLayout->setChecked(preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTLAYOUT)); importBreaks->setChecked(preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTBREAKS)); + +#ifdef AVSOMR + useLocalAvsOmr->setChecked(preferences.getBool(PREF_IMPORT_AVSOMR_USELOCAL)); + Avs::AvsOmrLocal::instance()->isInstalledAsync([this](bool isInstalled) { + QString text = QObject::tr("Use local OMR engine"); + if (isInstalled) + text += " (" + QObject::tr("Installed") + ")"; + else + text += " (" + QObject::tr("Not installed, needs internet connection for installing") + ")"; + + useLocalAvsOmr->setText(text); + }); +#else + groupBox_omr->setVisible(false); +#endif + QString resPref = preferences.getString(PREF_IMPORT_COMPATIBILITY_RESET_ELEMENT_POSITIONS); if (resPref == "No") resetElementPositionsNo->setChecked(true); @@ -416,6 +436,7 @@ void PreferenceDialog::updateValues(bool useDefaultValues) resetElementPositionsYes->setChecked(true); else // "Ask" or unset (or anything else) resetElementPositionsAlwaysAsk->setChecked(true); + if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)) { exportAllLayouts->setChecked(true); } @@ -961,6 +982,7 @@ void PreferenceDialog::apply() preferences.setPreference(PREF_IMPORT_COMPATIBILITY_RESET_ELEMENT_POSITIONS, "Yes"); else if (resetElementPositionsNo->isChecked()) preferences.setPreference(PREF_IMPORT_COMPATIBILITY_RESET_ELEMENT_POSITIONS, "No"); + preferences.setPreference(PREF_IMPORT_AVSOMR_USELOCAL, useLocalAvsOmr->isChecked()); preferences.setPreference(PREF_IO_MIDI_ADVANCEONRELEASE, advanceOnRelease->isChecked()); preferences.setPreference(PREF_IO_MIDI_ENABLEINPUT, enableMidiInput->isChecked()); preferences.setPreference(PREF_IO_MIDI_EXPANDREPEATS, expandRepeats->isChecked()); diff --git a/mscore/prefsdialog.ui b/mscore/prefsdialog.ui index b5c5212678ee..fc4c4b2d56fd 100644 --- a/mscore/prefsdialog.ui +++ b/mscore/prefsdialog.ui @@ -7,7 +7,7 @@ 0 0 834 - 569 + 682 @@ -90,7 +90,7 @@ Preferences Tab Manager - 0 + 5 @@ -3377,6 +3377,22 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter + + + + OMR + + + + + + Use local OMR engine + + + + + + @@ -4384,6 +4400,7 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter + diff --git a/mscore/scorecmp/scorecmp.cpp b/mscore/scorecmp/scorecmp.cpp index e0d64c2edd00..6f04be3b5c79 100644 --- a/mscore/scorecmp/scorecmp.cpp +++ b/mscore/scorecmp/scorecmp.cpp @@ -323,7 +323,7 @@ void ScoreComparisonTool::updateScoreVersions(const Score* s) void ScoreComparisonTool::on_browseFileButton_clicked() { Score* prevScore = mscore->currentScore(); - mscore->loadFiles(/* switchTab */ true, /* singleFile */ true); + mscore->openFiles(/* switchTab */ true, /* singleFile */ true); Score* loadedScore = mscore->currentScore(); if (loadedScore == prevScore) // we didn't load anything? return; diff --git a/mscore/scoretab.cpp b/mscore/scoretab.cpp index ed7e6aad5f9e..6c8942efa8e4 100644 --- a/mscore/scoretab.cpp +++ b/mscore/scoretab.cpp @@ -17,6 +17,8 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= +#include + #include "shortcut.h" #include "musescore.h" #include "config.h" @@ -28,6 +30,12 @@ #include "omr/omr.h" #include "omr/omrview.h" #endif + +#ifdef AVSOMR +#include "avsomr/avsomr.h" +#include "avsomr/ui/setupavsomrview.h" +#endif + #include "libmscore/excerpt.h" namespace Ms { @@ -203,6 +211,13 @@ void ScoreTab::setCurrent(int n) vs->addWidget(v); v->setScore(scoreList->value(n)); stack->addWidget(vs); +#ifdef AVSOMR + Score* score = v->score(); + if (score->masterScore()->avsOmr()) { + Avs::SetupAvsOmrView setuperView; + setuperView.setupView(v, score->masterScore()->avsOmr()); + } +#endif } else { v = static_cast(vs->widget(0)); @@ -236,6 +251,7 @@ void ScoreTab::setCurrent(int n) } } #endif + stack->setCurrentWidget(vs); clearTab2(); if (v) { diff --git a/mscore/scoreview.cpp b/mscore/scoreview.cpp index eec9b2e0f6c0..443cbb23f601 100644 --- a/mscore/scoreview.cpp +++ b/mscore/scoreview.cpp @@ -92,6 +92,11 @@ #include "libmscore/textline.h" #include "libmscore/shape.h" +#ifdef AVSOMR +#include "avsomr/avsomr.h" +#include "avsomr/avsomrdrawer.h" +#endif + namespace Ms { extern QErrorMessage* errorMessage; @@ -1112,11 +1117,41 @@ void ScoreView::paint(const QRect& r, QPainter& p) } } + + // AvsOmr ----- +#ifdef AVSOMR + Avs::AvsOmrDrawer omrDrawer; + std::shared_ptr omrDrawCtx = omrDrawer.makeContext(&p, _score); + auto pageMeasures = [](Page* page) -> QList { + QList ml; + for (const System* s : page->systems()) { + const std::vector& measures = s->measures(); + for (const MeasureBase* mb : measures) { + if (mb->isMeasure()) + ml << toMeasure(mb); + } + } + return ml; + }; +#endif + // ------------ + QRegion r1(r); if ((_score->layoutMode() == LayoutMode::LINE) || (_score->layoutMode() == LayoutMode::SYSTEM)) { if (_score->pages().size() > 0) { + Page* page = _score->pages().front(); QList ell = page->items(fr); + + // AvsOmr ----- +#ifdef AVSOMR + if (omrDrawCtx) { + QList ml = pageMeasures(page); + omrDrawer.draw(omrDrawCtx, ml); + } +#endif + // ------------ + drawElements(p, ell, editElement); } } @@ -1133,6 +1168,16 @@ void ScoreView::paint(const QRect& r, QPainter& p) QList ell = page->items(fr.translated(-page->pos())); QPointF pos(page->pos()); p.translate(pos); + + // AvsOmr ----- +#ifdef AVSOMR + if (omrDrawCtx) { + QList ml = pageMeasures(page); + omrDrawer.draw(omrDrawCtx, ml); + } +#endif + // ------------ + drawElements(p, ell, editElement); #ifndef NDEBUG diff --git a/telemetry/telemetrysetup.cpp b/telemetry/telemetrysetup.cpp index 323b3efbb971..2b3207d4e4d9 100644 --- a/telemetry/telemetrysetup.cpp +++ b/telemetry/telemetrysetup.cpp @@ -49,7 +49,7 @@ void TelemetrySetup::registerExports() // moduleName //--------------------------------------------------------- -QString TelemetrySetup::moduleName() +QString TelemetrySetup::moduleName() const { return QStringLiteral("telemetry"); } diff --git a/telemetry/telemetrysetup.h b/telemetry/telemetrysetup.h index 977918facfc5..18535d46d386 100644 --- a/telemetry/telemetrysetup.h +++ b/telemetry/telemetrysetup.h @@ -33,7 +33,7 @@ class TelemetrySetup : public AbstractModuleSetup { protected: void registerExports() override; - QString moduleName() override; + QString moduleName() const override; void registerResources() override;