Skip to content

Commit 10bc57d

Browse files
committed
Fix #24813 Implement exporting of LRC lyric files
1 parent d177570 commit 10bc57d

16 files changed

+478
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ option(MUE_BUILD_IMPEXP_GUITARPRO_MODULE "Build importexport guitarpro module" O
8383
option(MUE_BUILD_IMPEXP_MEI_MODULE "Build importexport mei module" ON)
8484
option(MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE "Build importexport videoexport module" OFF)
8585
option(MUE_BUILD_IMPEXP_TABLEDIT_MODULE "Build importexport tabledit module" ON)
86+
option(MUE_BUILD_IMPEXP_LYRICS_MODULE "Build importexport lyrics module" ON)
8687

8788
option(MUE_BUILD_IMPORTEXPORT_TESTS "Build importexport tests" ON)
8889
option(MUE_BUILD_INSPECTOR_MODULE "Build inspector module" ON)

SetupConfigure.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ if(BUILD_CONFIGURATION STREQUAL "APP-WEB")
185185
set(MUE_BUILD_IMPEXP_MEI_MODULE OFF)
186186
set(MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE OFF)
187187
set(MUE_BUILD_IMPEXP_TABLEDIT_MODULE OFF)
188+
set(MUE_BUILD_IMPEXP_LYRICS_MODULE OFF)
188189

189190
set(MUE_INSTALL_SOUNDFONT OFF)
190191

@@ -246,6 +247,7 @@ if(BUILD_CONFIGURATION STREQUAL "VTEST")
246247
set(MUE_BUILD_IMPEXP_MEI_MODULE OFF)
247248
set(MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE OFF)
248249
set(MUE_BUILD_IMPEXP_TABLEDIT_MODULE OFF)
250+
set(MUE_BUILD_IMPEXP_LYRICS_MODULE OFF)
249251

250252
set(MUE_INSTALL_SOUNDFONT OFF)
251253

ninja_build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ MUSESCORE_NO_RPATH=${MUSESCORE_NO_RPATH:-"OFF"}
4747
MUSESCORE_MODULE_UPDATE=${MUSESCORE_MODULE_UPDATEE:-"ON"}
4848
MUSESCORE_BUILD_VST_MODULE=${MUSESCORE_BUILD_VST_MODULE:-"OFF"}
4949
MUSESCORE_BUILD_IMPEXP_VIDEOEXPORT_MODULE=${MUSESCORE_BUILD_IMPEXP_VIDEOEXPORT_MODULE:-"OFF"}
50+
MUSESCORE_BUILD_IMPEXP_LYRICS_MODULE==${MUSESCORE_BUILD_IMPEXP_LYRICS_MODULE:-"OFF"}
5051
MUSESCORE_BUILD_WEBSOCKET=${MUSESCORE_BUILD_WEBSOCKET:-"OFF"}
5152
MUSESCORE_BUILD_PIPEWIRE_AUDIO_DRIVER=${MUSESCORE_BUILD_PIPEWIRE_AUDIO_DRIVER:-"OFF"}
5253
MUSESCORE_COMPILE_USE_UNITY=${MUSESCORE_COMPILE_USE_UNITY:-"ON"}
@@ -93,6 +94,7 @@ function do_build() {
9394
-DMUSESCORE_REVISION="${MUSESCORE_REVISION}" \
9495
-DMUE_RUN_LRELEASE="${MUSESCORE_RUN_LRELEASE}" \
9596
-DMUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE="${MUSESCORE_BUILD_IMPEXP_VIDEOEXPORT_MODULE}" \
97+
-DMUE_BUILD_IMPEXP_LYRICS_MODULE="${MUSESCORE_BUILD_IMPEXP_LYRICS_MODULE}" \
9698
-DMUSE_MODULE_UPDATE="${MUSESCORE_MODULE_UPDATE}" \
9799
-DMUE_DOWNLOAD_SOUNDFONT="${MUSESCORE_DOWNLOAD_SOUNDFONT}" \
98100
-DMUSE_ENABLE_UNIT_TESTS="${MUSESCORE_BUILD_UNIT_TESTS}" \
@@ -171,6 +173,7 @@ case $TARGET in
171173
-DMUSESCORE_REVISION="${MUSESCORE_REVISION}" \
172174
-DMUE_RUN_LRELEASE="${MUSESCORE_RUN_LRELEASE}" \
173175
-DMUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE="${MUSESCORE_BUILD_IMPEXP_VIDEOEXPORT_MODULE}" \
176+
-DMUE_BUILD_IMPEXP_LYRICS_MODULE="${MUSESCORE_BUILD_IMPEXP_LYRICS_MODULE}" \
174177
-DMUSE_MODULE_UPDATE="${MUSESCORE_MODULE_UPDATE}" \
175178
-DMUE_DOWNLOAD_SOUNDFONT="${MUSESCORE_DOWNLOAD_SOUNDFONT}" \
176179
-DMUSE_ENABLE_UNIT_TESTS="${MUSESCORE_BUILD_UNIT_TESTS}" \

src/app/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ endif ()
197197
if (MUE_BUILD_IMPEXP_TABLEDIT_MODULE)
198198
list(APPEND LINK_LIB iex_tabledit)
199199
endif ()
200+
if (MUE_BUILD_IMPEXP_LYRICS_MODULE)
201+
list(APPEND LINK_LIB iex_lyricsexport)
202+
endif ()
200203

201204
set (MSCORE_APPEND_SRC)
202205

src/app/app_config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#cmakedefine MUE_BUILD_IMPEXP_MEI_MODULE ${MUE_BUILD_IMPEXP_MEI_MODULE})
4747
#cmakedefine MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE ${MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE})
4848
#cmakedefine MUE_BUILD_IMPEXP_TABLEDIT_MODULE ${MUE_BUILD_IMPEXP_TABLEDIT_MODULE})
49+
#cmakedefine MUE_BUILD_IMPEXP_LYRICS_MODULE ${MUE_BUILD_IMPEXP_LYRICS_MODULE})
4950

5051
/* ============================================== */
5152
/* Functions */

src/app/appfactory.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@
181181
#ifdef MUE_BUILD_IMPEXP_TABLEDIT_MODULE
182182
#include "importexport/tabledit/tableditmodule.h"
183183
#endif
184+
#ifdef MUE_BUILD_IMPEXP_LYRICS_MODULE
185+
#include "importexport/lyricsexport/lyricsexportmodule.h"
186+
#endif
184187

185188
#include "inspector/inspectormodule.h"
186189

@@ -355,6 +358,9 @@ std::shared_ptr<muse::IApplication> AppFactory::newGuiApp(const CmdOptions& opti
355358
#ifdef MUE_BUILD_IMPEXP_TABLEDIT_MODULE
356359
app->addModule(new mu::iex::tabledit::TablEditModule());
357360
#endif
361+
#ifdef MUE_BUILD_IMPEXP_LYRICS_MODULE
362+
app->addModule(new mu::iex::lrcexport::LyricsExportModule());
363+
#endif
358364

359365
app->addModule(new mu::inspector::InspectorModule());
360366
app->addModule(new mu::instrumentsscene::InstrumentsSceneModule());
@@ -499,6 +505,9 @@ std::shared_ptr<muse::IApplication> AppFactory::newConsoleApp(const CmdOptions&
499505
#ifdef MUE_BUILD_IMPEXP_TABLEDIT_MODULE
500506
app->addModule(new mu::iex::tabledit::TablEditModule());
501507
#endif
508+
#ifdef MUE_BUILD_IMPEXP_LYRICS_MODULE
509+
app->addModule(new mu::iex::lrcexport::LyricsExportModule());
510+
#endif
502511

503512
app->addModule(new mu::inspector::InspectorModule());
504513
app->addModule(new mu::instrumentsscene::InstrumentsSceneModule());

src/importexport/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,6 @@ endif()
5757
if (MUE_BUILD_IMPEXP_TABLEDIT_MODULE)
5858
add_subdirectory(tabledit)
5959
endif()
60+
if (MUE_BUILD_IMPEXP_LYRICS_MODULE)
61+
add_subdirectory(lyricsexport)
62+
endif()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-License-Identifier: GPL-3.0-only
2+
# MuseScore-Studio-CLA-applies
3+
#
4+
# MuseScore Studio
5+
# Music Composition & Notation
6+
#
7+
# Copyright (C) 2021 MuseScore Limited
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License version 3 as
11+
# published by the Free Software Foundation.
12+
#
13+
# This program is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
21+
declare_module(iex_lyricsexport)
22+
23+
set(MODULE_SRC
24+
${CMAKE_CURRENT_LIST_DIR}/lyricsexportmodule.cpp
25+
${CMAKE_CURRENT_LIST_DIR}/lyricsexportmodule.h
26+
${CMAKE_CURRENT_LIST_DIR}/ilyricsexportconfiguration.h
27+
28+
${CMAKE_CURRENT_LIST_DIR}/internal/lyricsexportconfiguration.cpp
29+
${CMAKE_CURRENT_LIST_DIR}/internal/lyricsexportconfiguration.h
30+
${CMAKE_CURRENT_LIST_DIR}/internal/iex_lyricsexport.cpp
31+
${CMAKE_CURRENT_LIST_DIR}/internal/iex_lyricsexport.h
32+
)
33+
34+
set(MODULE_LINK
35+
engraving
36+
)
37+
38+
if (QT_SUPPORT)
39+
list(APPEND MODULE_LINK Qt::Core5Compat)
40+
endif()
41+
42+
setup_module()
43+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-only
3+
* MuseScore-Studio-CLA-applies
4+
*
5+
* MuseScore Studio
6+
* Music Composition & Notation
7+
*
8+
* Copyright (C) 2021 MuseScore Limited
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License version 3 as
12+
* published by the Free Software Foundation.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
#pragma once
23+
24+
#include <string>
25+
#include <optional>
26+
27+
#include "modularity/imoduleinterface.h"
28+
29+
namespace mu::iex::lrcexport {
30+
class ILyricsExportConfiguration : MODULE_EXPORT_INTERFACE
31+
{
32+
INTERFACE_ID(ILyricsExportConfiguration)
33+
34+
public:
35+
virtual ~ILyricsExportConfiguration() = default;
36+
37+
// No specific setting for Lyrics required
38+
};
39+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-only
3+
* MuseScore-Studio-CLA-applies
4+
*
5+
* MuseScore Studio
6+
* Music Composition & Notation
7+
*
8+
* Copyright (C) 2021 MuseScore Limited
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License version 3 as
12+
* published by the Free Software Foundation.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
23+
#include "iex_lyricsexport.h"
24+
25+
#include <QBuffer>
26+
27+
#include "engraving/dom/masterscore.h"
28+
#include "engraving/dom/repeatlist.h"
29+
#include "engraving/dom/lyrics.h"
30+
31+
using namespace mu::engraving;
32+
33+
namespace mu::iex::lrcexport {
34+
// Interface implementation
35+
std::vector<project::INotationWriter::UnitType> LRCWriter::supportedUnitTypes() const
36+
{
37+
return { UnitType::PER_PART };
38+
}
39+
40+
bool LRCWriter::supportsUnitType(UnitType ut) const { return ut == UnitType::PER_PART; }
41+
42+
muse::Ret LRCWriter::write(notation::INotationPtr notation, muse::io::IODevice& device, const Options&)
43+
{
44+
Score* score = notation->elements()->msScore();
45+
QByteArray data;
46+
QBuffer buffer(&data);
47+
48+
/***********
49+
*
50+
* PENDING....
51+
Is there any advantage to writing to a buffer first, and then writing that buffer to the device? It would seem more efficient to me to write to the device directly.
52+
53+
******/
54+
55+
buffer.open(QIODevice::WriteOnly);
56+
57+
writeMetadata(buffer, score);
58+
59+
const auto lyrics = collectLyrics(score);
60+
61+
// Write lyrics
62+
for (auto it = lyrics.constBegin(); it != lyrics.constEnd(); ++it) {
63+
buffer.write(QString("[%1]%2\n").arg(formatTimestamp(it.key()), it.value()).toUtf8());
64+
}
65+
66+
device.write(data);
67+
return muse::Ret(muse::Ret::Code::Ok);
68+
}
69+
70+
muse::Ret LRCWriter::writeList(const notation::INotationPtrList&, muse::io::IODevice&, const Options&)
71+
{
72+
return muse::Ret(muse::Ret::Code::NotSupported);
73+
}
74+
75+
void LRCWriter::writeMetadata(QIODevice& device, const engraving::Score* score) const
76+
{
77+
QString metadata;
78+
79+
// Title
80+
QString title = QString::fromStdString(score->metaTag(muse::String("workTitle")).toStdString());
81+
if (!title.isEmpty()) {
82+
metadata += QString("[ti:%1]\n").arg(title);
83+
}
84+
85+
// Composer/Artist
86+
QString artist = QString::fromStdString(score->metaTag(muse::String("composer")).toStdString());
87+
if (!artist.isEmpty()) {
88+
metadata += QString("[ar:%1]\n").arg(artist);
89+
}
90+
91+
if (!metadata.isEmpty()) {
92+
device.write(metadata.toUtf8());
93+
}
94+
}
95+
96+
// Core lyric collection (simplified)
97+
QMap<qreal, QString> LRCWriter::collectLyrics(const Score* score) const
98+
{
99+
QMap<qreal, QString> lyrics;
100+
const RepeatList& repeats = score->repeatList();
101+
102+
for (const RepeatSegment* rs : repeats) {
103+
const int tickOffset = rs->utick - rs->tick;
104+
105+
for (const MeasureBase* mb = rs->firstMeasure(); mb; mb = mb->next()) {
106+
if (!mb->isMeasure()) {
107+
continue;
108+
}
109+
110+
for (Segment* seg = toMeasure(mb)->first(); seg; seg = seg->next()) {
111+
if (!seg->isChordRestType()) {
112+
continue;
113+
}
114+
115+
for (EngravingItem* e : seg->elist()) {
116+
if (!e || !e->isChordRest()) {
117+
continue;
118+
}
119+
120+
for (Lyrics* l : toChordRest(e)->lyrics()) {
121+
// if (l->text().empty())
122+
if (l->plainText().isEmpty()) {
123+
continue;
124+
}
125+
126+
const qreal time = score->utick2utime(l->tick().ticks() + tickOffset) * 1000;
127+
lyrics.insert(time, l->plainText());
128+
}
129+
}
130+
}
131+
}
132+
}
133+
return lyrics;
134+
}
135+
136+
QString LRCWriter::formatTimestamp(qreal ms) const
137+
{
138+
const int totalSec = static_cast<int>(ms / 1000);
139+
return QString("%1:%2.%3")
140+
.arg(totalSec / 60, 2, 10, QLatin1Char('0'))
141+
.arg(totalSec % 60, 2, 10, QLatin1Char('0'))
142+
.arg(static_cast<int>(ms) % 1000 / 10, 2, 10, QLatin1Char('0'));
143+
}
144+
} // namespace mu::iex::lrcexport

0 commit comments

Comments
 (0)