Compare commits
1 Commits
master
...
midiexport
Author | SHA1 | Date |
---|---|---|
mikekirin | d26589e7ff |
|
@ -80,6 +80,7 @@ option(MUE_BUILD_CLOUD_MODULE "Build cloud module" ON)
|
|||
option(MUE_BUILD_CONVERTER_MODULE "Build converter module" ON)
|
||||
option(MUE_BUILD_DIAGNOSTICS_MODULE "Build diagnostic code" ON)
|
||||
option(MUE_BUILD_IMPORTEXPORT_MODULE "Build importexport module" ON)
|
||||
option(MUE_ENABLE_MIDI_IMPORTEXPORT "Enable midi import and export" ON)
|
||||
option(MUE_BUILD_VIDEOEXPORT_MODULE "Build videoexport module" OFF)
|
||||
option(MUE_BUILD_IMAGESEXPORT_MODULE "Build imagesexport module" ON)
|
||||
option(MUE_BUILD_INSPECTOR_MODULE "Build inspector module" ON)
|
||||
|
|
|
@ -251,6 +251,7 @@ def_opt(MUE_BUILD_ACCESSIBILITY_MODULE ${MUE_BUILD_ACCESSIBILITY_MODULE})
|
|||
def_opt(MUE_BUILD_AUDIO_MODULE ${MUE_BUILD_AUDIO_MODULE})
|
||||
def_opt(MUE_ENABLE_AUDIO_EXPORT ${MUE_ENABLE_AUDIO_EXPORT})
|
||||
def_opt(MUE_BUILD_MIDI_MODULE ${MUE_BUILD_MIDI_MODULE})
|
||||
def_opt(MUE_ENABLE_MIDI_IMPORTEXPORT ${MUE_ENABLE_MIDI_IMPORTEXPORT})
|
||||
def_opt(MUE_BUILD_MPE_MODULE ${MUE_BUILD_MPE_MODULE})
|
||||
def_opt(MUE_BUILD_MUSESAMPLER_MODULE ${MUE_BUILD_MUSESAMPLER_MODULE})
|
||||
def_opt(MUE_BUILD_NETWORK_MODULE ${MUE_BUILD_NETWORK_MODULE})
|
||||
|
|
|
@ -5,7 +5,7 @@ void CompatMidiRender::renderScore(Score* score, EventsHolder& events, const Com
|
|||
{
|
||||
score->masterScore()->setExpandRepeats(expandRepeats);
|
||||
CompatMidiRendererInternal internal{ score };
|
||||
internal.renderScore(events, ctx, expandRepeats);
|
||||
internal.renderScore(events, ctx);
|
||||
}
|
||||
|
||||
int CompatMidiRender::getControllerForSnd(Score* score, int globalSndController)
|
||||
|
|
|
@ -1170,10 +1170,10 @@ void CompatMidiRendererInternal::renderMetronome(EventsHolder& events, Measure c
|
|||
}
|
||||
}
|
||||
|
||||
void CompatMidiRendererInternal::renderScore(EventsHolder& events, const Context& ctx, bool expandRepeats)
|
||||
void CompatMidiRendererInternal::renderScore(EventsHolder& events, const Context& ctx)
|
||||
{
|
||||
UNUSED(expandRepeats);
|
||||
_context = ctx;
|
||||
_context.instrumentsHaveEffects = true;
|
||||
PitchWheelRenderer pitchWheelRender(wheelSpec);
|
||||
|
||||
score->updateSwing();
|
||||
|
|
|
@ -151,7 +151,7 @@ public:
|
|||
|
||||
explicit CompatMidiRendererInternal(Score* s);
|
||||
|
||||
void renderScore(EventsHolder& events, const Context& ctx, bool expandRepeats);
|
||||
void renderScore(EventsHolder& events, const Context& ctx);
|
||||
|
||||
static const int ARTICULATION_CONV_FACTOR { 100000 };
|
||||
static bool graceNotesMerged(Chord* chord);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "utils/scorerw.h"
|
||||
#include "engraving/compat/midi/compatmidirender.h"
|
||||
#include "engraving/compat/midi/compatmidirenderinternal.h"
|
||||
#include "engraving/infrastructure/localfileinfoprovider.h"
|
||||
#include "engraving/rw/mscloader.h"
|
||||
#include "engraving/dom/noteevent.h"
|
||||
|
|
|
@ -20,29 +20,40 @@
|
|||
|
||||
set(MODULE iex_midi)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midishared/midishared.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midiimport/midiimport.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midiexport/midiexport.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midirender/midirender.cmake)
|
||||
|
||||
set(MODULE_SRC
|
||||
${MIDISHARED_SRC}
|
||||
${MIDIIMPORT_SRC}
|
||||
${MIDIEXPORT_SRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/midimodule.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/midimodule.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/imidiconfiguration.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/midiconfiguration.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/midiconfiguration.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidireader.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidireader.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidiwriter.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidiwriter.h
|
||||
${MIDIRENDER_SRC}
|
||||
)
|
||||
if (MUE_ENABLE_MIDI_IMPORTEXPORT)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midishared/midishared.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midiimport/midiimport.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/internal/midiexport/midiexport.cmake)
|
||||
set(MODULE_SRC
|
||||
${MODULE_SRC}
|
||||
${MIDISHARED_SRC}
|
||||
${MIDIIMPORT_SRC}
|
||||
${MIDIEXPORT_SRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/midimodule.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/midimodule.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/imidiconfiguration.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/midiconfiguration.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/midiconfiguration.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidireader.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidireader.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidiwriter.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmidiwriter.h
|
||||
)
|
||||
|
||||
add_subdirectory(${THIRDPARTY_DIR}/beatroot beatroot)
|
||||
|
||||
set(MODULE_LINK
|
||||
beatroot
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
||||
set(MODULE_LINK
|
||||
${MODULE_LINK}
|
||||
engraving
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_IMPORTEXPORT_IMIDICONVERTER_H
|
||||
#define MU_IMPORTEXPORT_IMIDICONVERTER_H
|
||||
|
||||
|
||||
#include "modularity/imoduleinterface.h"
|
||||
|
||||
namespace mu::engraving {
|
||||
class Score;
|
||||
}
|
||||
|
||||
namespace mu::iex::midi {
|
||||
class RenderStrategy;
|
||||
|
||||
class RenderContext;
|
||||
|
||||
struct MidiRenderOutData;
|
||||
|
||||
class IMidiRender : MODULE_EXPORT_INTERFACE {
|
||||
INTERFACE_ID(IMidiConverter)
|
||||
public:
|
||||
~IMidiRender() override = default;
|
||||
|
||||
virtual void
|
||||
render(mu::engraving::Score *, RenderContext &ctx, MidiRenderOutData &outData, RenderStrategy &strategy) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_IMPORTEXPORT_IMIDICONVERTER_H
|
|
@ -51,7 +51,7 @@ private:
|
|||
//---------------------------------------------------
|
||||
// PauseMap
|
||||
// MIDI files cannot contain pauses so need to insert
|
||||
// extra ticks extra ticks and tempo changes instead.
|
||||
// extra ticks and tempo changes instead.
|
||||
//---------------------------------------------------
|
||||
class PauseMap : std::map<int, int>
|
||||
{
|
||||
|
|
|
@ -94,16 +94,16 @@ double findChordSalience2(
|
|||
return velocity;
|
||||
}
|
||||
|
||||
::EventList prepareChordEvents(
|
||||
BeatTracker::EventList prepareChordEvents(
|
||||
const std::multimap<ReducedFraction, MidiChord>& chords,
|
||||
const std::function<double(const std::pair<const ReducedFraction, MidiChord>&,
|
||||
double)>& findChordSalience,
|
||||
double ticksPerSec)
|
||||
{
|
||||
::EventList events;
|
||||
BeatTracker::EventList events;
|
||||
double minSalience = std::numeric_limits<double>::max();
|
||||
for (const auto& chord: chords) {
|
||||
::Event e;
|
||||
BeatTracker::Event e;
|
||||
e.time = chord.first.ticks() / ticksPerSec;
|
||||
e.salience = findChordSalience(chord, ticksPerSec);
|
||||
if (e.salience < minSalience) {
|
||||
|
@ -217,7 +217,7 @@ MidiOperations::HumanBeatData prepareHumanBeatData(
|
|||
}
|
||||
|
||||
double findMatchRank(const std::set<ReducedFraction>& beatSet,
|
||||
const ::EventList& events,
|
||||
const BeatTracker::EventList& events,
|
||||
const std::vector<int>& levels,
|
||||
int beatsInBar,
|
||||
double ticksPerSec)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
set (MIDIRENDER_SRC
|
||||
${CMAKE_CURRENT_LIST_DIR}/midirender.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/midirender.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/renderstrategy.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/renderstrategy.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/midirendertypes.h
|
||||
)
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "midirender.h"
|
||||
#include "renderstrategy.h"
|
||||
#include "engraving/dom/staff.h"
|
||||
#include "engraving/dom/part.h"
|
||||
#include "engraving/dom/score.h"
|
||||
#include "engraving/dom/chord.h"
|
||||
#include "engraving/dom/note.h"
|
||||
#include "engraving/dom/spanner.h"
|
||||
#include "engraving/dom/navigate.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace mu::iex::midi {
|
||||
void MidiRender::render(mu::engraving::Score* score, RenderContext& ctx, MidiRenderOutData& outData,
|
||||
RenderStrategy& strategy)
|
||||
{
|
||||
m_ctx = ctx;
|
||||
m_score = score;
|
||||
for (const auto p: m_score->parts()) {
|
||||
// TODO: Figure out what the diff between idx and id and why the differ
|
||||
m_ctx.parts().insert({ p->id().toUint64(), {} });
|
||||
}
|
||||
collectNotes();
|
||||
strategy.render(m_ctx, outData, m_renderMeta);
|
||||
}
|
||||
|
||||
void MidiRender::collectNotes()
|
||||
{
|
||||
track_idx_t numTracks = m_score->staves().size() * VOICES;
|
||||
for (auto m = m_score->firstMeasure(); m; m = m->nextMeasure()) {
|
||||
for (auto s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
|
||||
for (track_idx_t track = 0; track < numTracks; ++track) {
|
||||
Staff* staff = m_score->staff(track / VOICES);
|
||||
if (!staff->isPrimaryStaff()) {
|
||||
track += VOICES - 1;
|
||||
continue;
|
||||
}
|
||||
auto cr = s->element(track);
|
||||
if (!cr || !cr->isChord()) [[likely]] {
|
||||
continue;
|
||||
}
|
||||
Chord* chord = toChord(cr);
|
||||
const uint64_t partID = chord->part()->id().toUint64();
|
||||
auto graceBefore = chord->graceNotesBefore();
|
||||
auto graceAfter = chord->graceNotesBefore();
|
||||
auto startTick = chord->tick().ticks();
|
||||
if (!graceBefore.empty()) [[unlikely]] {
|
||||
for (const Chord* ch: graceBefore) {
|
||||
auto prevChord = prevChordRest(chord, true, true);
|
||||
collectGraceNotes(true, ch, startTick, partID,
|
||||
prevChord->isChord() ? toChord(prevChord) : nullptr);
|
||||
}
|
||||
}
|
||||
if (!graceAfter.empty()) [[unlikely]] {
|
||||
for (const Chord* ch: graceAfter) {
|
||||
collectGraceNotes(false, ch, startTick, partID, chord);
|
||||
}
|
||||
}
|
||||
for (const auto n: chord->notes()) {
|
||||
// TODO: This code for collecting spanners should be refactored.
|
||||
// Need to figure out why cant use n->isPalmMute;
|
||||
for (auto it: m_score->spannerMap().findOverlapping(startTick + 1, startTick + 2)) {
|
||||
Spanner* spanner = it.value;
|
||||
if (spanner->track() != chord->track()) [[likely]] {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spanner->isLetRing()) {
|
||||
// LetRing* letRing = toLetRing(spanner);
|
||||
} else if (spanner->isPalmMute()) {
|
||||
m_ctx.part(partID).palmMuteNotes.insert({ startTick, n });
|
||||
Meta meta;
|
||||
meta.isPalmMute = true;
|
||||
m_renderMeta.insert({ toEngravingItem(spanner), meta });
|
||||
}
|
||||
}
|
||||
if (n->isPalmMute()) {
|
||||
m_ctx.part(partID).palmMuteNotes.insert({ startTick, n });
|
||||
} else if (n->isHammerOn()) {
|
||||
LOGI() << "Hammer on";
|
||||
} else [[likely]] {
|
||||
m_renderMeta.insert({ toEngravingItem(n), {} });
|
||||
m_ctx.part(partID).regularNotes.insert({ startTick, n });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MidiRender::collectGraceNotes(bool isBefore, const mu::engraving::Chord* chord, int mainChordStartTick,
|
||||
uint64_t partID,
|
||||
mu::engraving::Chord* dependentItem)
|
||||
{
|
||||
// Collect grace notes
|
||||
// Give them the same start tick as root note has
|
||||
// We will handle that during events generating (merging)
|
||||
for (const auto n: chord->notes()) {
|
||||
if (isBefore) [[likely]] {
|
||||
m_ctx.part(partID).graceNotesBefore.insert({ mainChordStartTick, n });
|
||||
} else {
|
||||
m_ctx.part(partID).graceNotesAfter.insert({ mainChordStartTick, n });
|
||||
}
|
||||
Meta meta;
|
||||
meta.isGraceBefore = isBefore;
|
||||
meta.isGraceAfter = !isBefore;
|
||||
if (dependentItem) {
|
||||
for (const auto dependentNote: dependentItem->notes()) {
|
||||
meta.dependentItems.push_back(toEngravingItem(dependentNote));
|
||||
}
|
||||
}
|
||||
m_renderMeta.insert({ toEngravingItem(n), meta });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MU_IMPORTEXPORT_MIDIRENDER_HPP
|
||||
#define MU_IMPORTEXPORT_MIDIRENDER_HPP
|
||||
|
||||
#include "importexport/midi/imidirender.h"
|
||||
#include "midirendertypes.h"
|
||||
|
||||
namespace mu::iex::midi {
|
||||
class RenderStrategy;
|
||||
|
||||
class MidiRender : public IMidiRender
|
||||
{
|
||||
public:
|
||||
~MidiRender() override = default;
|
||||
|
||||
void render(mu::engraving::Score* score, RenderContext& ctx, MidiRenderOutData& outData, RenderStrategy& strategy) override;
|
||||
|
||||
private:
|
||||
RenderContext m_ctx;
|
||||
mu::engraving::Score* m_score;
|
||||
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta> m_renderMeta;
|
||||
|
||||
void collectNotes();
|
||||
|
||||
void
|
||||
collectGraceNotes(bool isBefore, const mu::engraving::Chord* chord, int mainChordStartTick, uint64_t partID,
|
||||
mu::engraving::Chord* dependentItem);
|
||||
};
|
||||
}
|
||||
|
||||
#endif //MU_IMPORTEXPORT_MIDIRENDER_HPP
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MU_IMPORTEXPORT_MIDIRENDERTYPES_H
|
||||
#define MU_IMPORTEXPORT_MIDIRENDERTYPES_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include "log.h"
|
||||
|
||||
namespace mu::engraving {
|
||||
class Chord;
|
||||
|
||||
class EngravingItem;
|
||||
|
||||
class Note;
|
||||
}
|
||||
|
||||
namespace mu::iex::midi {
|
||||
struct MidiRenderEvent {
|
||||
uint8_t type = 0;
|
||||
uint8_t channel = 0; // ???
|
||||
uint8_t a = 0;
|
||||
uint8_t b = 0;
|
||||
};
|
||||
using midirender_channels_t = std::array<std::multimap<size_t, MidiRenderEvent>, 16>; // midi can handle only 16 channels
|
||||
struct MidiRenderOutData {
|
||||
std::unordered_map<uint64_t, midirender_channels_t> tracks{};
|
||||
};
|
||||
|
||||
using notes_mmap_t = std::multimap<int, mu::engraving::Note*>;
|
||||
struct MRPart {
|
||||
notes_mmap_t regularNotes;
|
||||
notes_mmap_t palmMuteNotes;
|
||||
notes_mmap_t graceNotesBefore;
|
||||
notes_mmap_t graceNotesAfter;
|
||||
};
|
||||
|
||||
struct Meta {
|
||||
std::vector<mu::engraving::EngravingItem*> dependentItems;
|
||||
int pitch = 0;
|
||||
int noteOn = 0;
|
||||
int noteOff = 0;
|
||||
bool isGraceBefore = false;
|
||||
bool isGraceAfter = false;
|
||||
bool isPalmMute = false;
|
||||
};
|
||||
|
||||
class RenderContext
|
||||
{
|
||||
std::unordered_map<uint64_t, MRPart> m_parts;
|
||||
public:
|
||||
virtual ~RenderContext() = default;
|
||||
|
||||
std::unordered_map<uint64_t, MRPart>& parts()
|
||||
{
|
||||
return m_parts;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::unordered_map<uint64_t, MRPart>& parts() const
|
||||
{
|
||||
return m_parts;
|
||||
}
|
||||
|
||||
MRPart& part(uint64_t id)
|
||||
{
|
||||
IF_ASSERT_FAILED(parts().find(id) != parts().end()) {
|
||||
m_parts.insert({ 1, {} });
|
||||
return parts().at(1);
|
||||
}
|
||||
return parts().at(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //MU_IMPORTEXPORT_MIDIRENDERTYPES_H
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "renderstrategy.h"
|
||||
#include "midirendertypes.h"
|
||||
#include "engraving/dom/note.h"
|
||||
|
||||
namespace mu::iex::midi {
|
||||
void RenderStrategy::render(RenderContext& ctx, MidiRenderOutData& outData,
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta)
|
||||
{
|
||||
renderRegularNotes(ctx, renderMeta);
|
||||
renderGraceNotes(ctx, renderMeta);
|
||||
renderPalmMute(ctx, renderMeta);
|
||||
mergeEventsBefore(ctx, outData, renderMeta);
|
||||
// mergeEvents(ctx, outData, renderMeta);
|
||||
// mergeEventsAfter(ctx, outData, renderMeta);
|
||||
}
|
||||
|
||||
void RenderStrategy::renderRegularNotes(RenderContext& ctx,
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta)
|
||||
{
|
||||
for (const auto&[id, part]: ctx.parts()) {
|
||||
for (const auto&[tick, note]: part.regularNotes) {
|
||||
int on = tick;
|
||||
int off = on + note->playTicks();
|
||||
auto pitch = static_cast<uint8_t>(note->pitch());
|
||||
auto& meta = renderMeta.at(toEngravingItem(note));
|
||||
meta.noteOn = on;
|
||||
meta.noteOff = off;
|
||||
meta.pitch = pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderStrategy::renderGraceNotes(RenderContext& ctx,
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta)
|
||||
{
|
||||
for (const auto&[id, part]: ctx.parts()) {
|
||||
for (const auto&[tick, note]: part.graceNotesBefore) {
|
||||
int on = tick - note->playTicks();
|
||||
int off = on + note->playTicks();
|
||||
auto pitch = static_cast<uint8_t>(note->pitch());
|
||||
auto& meta = renderMeta.at(toEngravingItem(note));
|
||||
meta.noteOn = on;
|
||||
meta.noteOff = off;
|
||||
meta.pitch = pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderStrategy::renderPalmMute(RenderContext& ctx,
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta)
|
||||
{
|
||||
// TODO: setup program change
|
||||
}
|
||||
|
||||
void RenderStrategy::mergeEventsBefore(mu::iex::midi::RenderContext& ctx, mu::iex::midi::MidiRenderOutData& outData,
|
||||
std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta)
|
||||
{
|
||||
for (const auto&[id, part]: ctx.parts()) {
|
||||
for (const auto&[tick, note]: part.graceNotesBefore) {
|
||||
const auto& meta = renderMeta.at(toEngravingItem(note));
|
||||
for (auto i: meta.dependentItems) {
|
||||
auto& noteMeta = renderMeta.at(i);
|
||||
noteMeta.noteOff -= note->playTicks();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2021 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 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MU_IMPORTEXPORT_RENDERSTRATEGY_H
|
||||
#define MU_IMPORTEXPORT_RENDERSTRATEGY_H
|
||||
|
||||
namespace mu::iex::midi {
|
||||
class RenderContext;
|
||||
|
||||
struct MidiRenderOutData;
|
||||
|
||||
class RenderStrategy
|
||||
{
|
||||
virtual void
|
||||
renderRegularNotes(RenderContext& ctx, std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta);
|
||||
|
||||
virtual void
|
||||
renderGraceNotes(RenderContext& ctx, std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta);
|
||||
|
||||
virtual void
|
||||
renderPalmMute(RenderContext& ctx, std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta);
|
||||
|
||||
virtual void mergeEventsBefore(RenderContext& ctx, MidiRenderOutData& outData, std::unordered_map<mu::engraving::EngravingItem*,
|
||||
Meta>& renderMeta);
|
||||
|
||||
public:
|
||||
void render(RenderContext& ctx, MidiRenderOutData& outData, std::unordered_map<mu::engraving::EngravingItem*, Meta>& renderMeta);
|
||||
};
|
||||
}
|
||||
|
||||
#endif //MU_IMPORTEXPORT_RENDERSTRATEGY_H
|
|
@ -28,6 +28,7 @@
|
|||
#include "modularity/ioc.h"
|
||||
#include "notation/inotationconfiguration.h"
|
||||
#include "imidiconfiguration.h"
|
||||
#include "imidirender.h"
|
||||
|
||||
namespace mu::iex::midi {
|
||||
class NotationMidiWriter : public project::INotationWriter
|
||||
|
|
|
@ -23,48 +23,58 @@
|
|||
|
||||
#include "modularity/ioc.h"
|
||||
|
||||
#ifdef MUE_ENABLE_MIDI_IMPORTEXPORT
|
||||
#include "project/inotationreadersregister.h"
|
||||
#include "internal/notationmidireader.h"
|
||||
#include "project/inotationwritersregister.h"
|
||||
#include "internal/notationmidireader.h"
|
||||
#include "internal/notationmidiwriter.h"
|
||||
|
||||
#include "internal/midiconfiguration.h"
|
||||
using namespace mu::project;
|
||||
#endif
|
||||
#include "internal/midirender/midirender.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
using namespace mu::iex::midi;
|
||||
using namespace mu::project;
|
||||
|
||||
std::string MidiModule::moduleName() const
|
||||
namespace mu::iex::midi
|
||||
{
|
||||
return "iex_midi";
|
||||
}
|
||||
|
||||
void MidiModule::registerExports()
|
||||
{
|
||||
m_configuration = std::make_shared<MidiConfiguration>();
|
||||
|
||||
modularity::ioc()->registerExport<IMidiImportExportConfiguration>(moduleName(), m_configuration);
|
||||
}
|
||||
|
||||
void MidiModule::resolveImports()
|
||||
{
|
||||
auto readers = modularity::ioc()->resolve<INotationReadersRegister>(moduleName());
|
||||
if (readers) {
|
||||
readers->reg({ "mid", "midi", "kar" }, std::make_shared<NotationMidiReader>());
|
||||
std::string MidiModule::moduleName() const
|
||||
{
|
||||
return "iex_midi";
|
||||
}
|
||||
|
||||
auto writers = modularity::ioc()->resolve<INotationWritersRegister>(moduleName());
|
||||
if (writers) {
|
||||
writers->reg({ "mid", "midi", "kar" }, std::make_shared<NotationMidiWriter>());
|
||||
}
|
||||
}
|
||||
void MidiModule::registerExports()
|
||||
{
|
||||
#ifdef MUE_ENABLE_MIDI_IMPORTEXPORT
|
||||
m_configuration = std::make_shared<MidiConfiguration>();
|
||||
|
||||
void MidiModule::onInit(const framework::IApplication::RunMode& mode)
|
||||
{
|
||||
if (mode == framework::IApplication::RunMode::AudioPluginRegistration) {
|
||||
return;
|
||||
modularity::ioc()->registerExport<IMidiImportExportConfiguration>(moduleName(), m_configuration);
|
||||
// modularity::ioc()->registerExport<IMidiRenderStrategy>(moduleName());
|
||||
#endif
|
||||
modularity::ioc()->registerExport<IMidiRender>(moduleName(), new MidiRender());
|
||||
}
|
||||
|
||||
m_configuration->init();
|
||||
#ifdef MUE_ENABLE_MIDI_IMPORTEXPORT
|
||||
void MidiModule::resolveImports()
|
||||
{
|
||||
auto readers = modularity::ioc()->resolve<INotationReadersRegister>(moduleName());
|
||||
if (readers) {
|
||||
readers->reg({ "mid", "midi", "kar" }, std::make_shared<NotationMidiReader>());
|
||||
}
|
||||
|
||||
auto writers = modularity::ioc()->resolve<INotationWritersRegister>(moduleName());
|
||||
if (writers) {
|
||||
writers->reg({ "mid", "midi", "kar" }, std::make_shared<NotationMidiWriter>());
|
||||
}
|
||||
}
|
||||
|
||||
void MidiModule::onInit(const framework::IApplication::RunMode& mode)
|
||||
{
|
||||
if (mode == framework::IApplication::RunMode::AudioPluginRegistration) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_configuration->init();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,14 @@ public:
|
|||
|
||||
std::string moduleName() const override;
|
||||
void registerExports() override;
|
||||
|
||||
#ifdef MUE_ENABLE_MIDI_IMPORTEXPORT
|
||||
void resolveImports() override;
|
||||
void onInit(const framework::IApplication::RunMode& mode) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<MidiConfiguration> m_configuration;
|
||||
#endif
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ Agent *Agent::clone() const
|
|||
return a;
|
||||
}
|
||||
|
||||
void Agent::accept(const Event &e, double err, int beats)
|
||||
void Agent::accept(const BeatTracker::Event &e, double err, int beats)
|
||||
{
|
||||
beatTime = e.time;
|
||||
events.push_back(e);
|
||||
|
@ -69,14 +69,14 @@ void Agent::accept(const Event &e, double err, int beats)
|
|||
phaseScore += conFactor * e.salience;
|
||||
}
|
||||
|
||||
bool Agent::considerAsBeat(const Event &e, AgentList &a)
|
||||
bool Agent::considerAsBeat(const BeatTracker::Event &e, AgentList &a)
|
||||
{
|
||||
if (beatTime < 0) { // first event
|
||||
accept(e, 0, 1);
|
||||
return true;
|
||||
}
|
||||
else { // subsequent events
|
||||
EventList::iterator last = events.end();
|
||||
BeatTracker::EventList::iterator last = events.end();
|
||||
--last;
|
||||
if (e.time - last->time > expiryTime) {
|
||||
phaseScore = -1.0; // flag agent to be deleted
|
||||
|
@ -99,7 +99,7 @@ bool Agent::considerAsBeat(const Event &e, AgentList &a)
|
|||
|
||||
void Agent::fillBeats(double start)
|
||||
{
|
||||
EventList::iterator it = events.begin();
|
||||
BeatTracker::EventList::iterator it = events.begin();
|
||||
if (it == events.end())
|
||||
return;
|
||||
double prevBeat = it->time;
|
||||
|
@ -109,7 +109,7 @@ void Agent::fillBeats(double start)
|
|||
double currentInterval = (nextBeat - prevBeat) / beats;
|
||||
for ( ; (nextBeat > start) && (beats > 1.5); --beats) {
|
||||
prevBeat += currentInterval;
|
||||
events.insert(it, Event(prevBeat, 0));
|
||||
events.insert(it, BeatTracker::Event(prevBeat, 0));
|
||||
}
|
||||
prevBeat = nextBeat;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@
|
|||
|
||||
|
||||
class AgentList;
|
||||
namespace BeatTracker
|
||||
{
|
||||
struct Event;
|
||||
}
|
||||
|
||||
class AgentParameters
|
||||
{
|
||||
|
@ -103,7 +106,7 @@ class Agent
|
|||
|
||||
/** The list of Events (onsets) accepted by this Agent as beats,
|
||||
* plus interpolated beats. */
|
||||
EventList events;
|
||||
BeatTracker::EventList events;
|
||||
|
||||
/** Constructor: the work is performed by init()
|
||||
* @param ibi The beat period (inter-beat interval)
|
||||
|
@ -118,7 +121,7 @@ class Agent
|
|||
* @param err The difference between the predicted and actual beat times.
|
||||
* @param beats The number of beats since the last beat that matched an Event.
|
||||
*/
|
||||
void accept(const Event &e, double err, int beats);
|
||||
void accept(const BeatTracker::Event &e, double err, int beats);
|
||||
|
||||
/** The given Event is tested for a possible beat time.
|
||||
* The following situations can occur:
|
||||
|
@ -136,7 +139,7 @@ class Agent
|
|||
* @param a The list of all agents, which is updated if a new agent is created.
|
||||
* @return Indicate whether the given Event was accepted as a beat by this Agent.
|
||||
*/
|
||||
bool considerAsBeat(const Event &e, AgentList &a);
|
||||
bool considerAsBeat(const BeatTracker::Event &e, AgentList &a);
|
||||
|
||||
/** Interpolates missing beats in the Agent's beat track,
|
||||
* starting from the beginning of the piece.
|
||||
|
|
|
@ -86,14 +86,14 @@ void AgentList::removeDuplicates()
|
|||
}
|
||||
|
||||
|
||||
void AgentList::beatTrack(const EventList &el,
|
||||
void AgentList::beatTrack(const BeatTracker::EventList &el,
|
||||
const AgentParameters ¶ms,
|
||||
double stop)
|
||||
{
|
||||
EventList::const_iterator ei = el.begin();
|
||||
BeatTracker::EventList::const_iterator ei = el.begin();
|
||||
bool phaseGiven = !empty() && ((*begin())->beatTime >= 0); // if given for one, assume given for others
|
||||
while (ei != el.end()) {
|
||||
Event ev = *ei;
|
||||
BeatTracker::Event ev = *ei;
|
||||
++ei;
|
||||
if ((stop > 0) && (ev.time > stop))
|
||||
break;
|
||||
|
|
|
@ -72,7 +72,7 @@ class AgentList
|
|||
/** Perform beat tracking on a list of events (onsets).
|
||||
* @param el The list of onsets (or events or peaks) to beat track
|
||||
*/
|
||||
void beatTrack(const EventList &el, const AgentParameters ¶ms)
|
||||
void beatTrack(const BeatTracker::EventList &el, const AgentParameters ¶ms)
|
||||
{
|
||||
beatTrack(el, params, -1.0);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class AgentList
|
|||
* @param el The list of onsets (or events or peaks) to beat track.
|
||||
* @param stop Do not find beats after <code>stop</code> seconds.
|
||||
*/
|
||||
void beatTrack(const EventList &el, const AgentParameters ¶ms, double stop);
|
||||
void beatTrack(const BeatTracker::EventList &el, const AgentParameters ¶ms, double stop);
|
||||
|
||||
/** Finds the Agent with the highest score in the list,
|
||||
* or NULL if beat tracking has failed.
|
||||
|
|
|
@ -18,26 +18,28 @@
|
|||
|
||||
#include <list>
|
||||
|
||||
|
||||
namespace BeatTracker
|
||||
{
|
||||
struct Event
|
||||
{
|
||||
double time;
|
||||
double salience;
|
||||
{
|
||||
double time;
|
||||
double salience;
|
||||
|
||||
Event()
|
||||
Event()
|
||||
: time(0), salience(0)
|
||||
{}
|
||||
Event(double t, double s)
|
||||
{}
|
||||
Event(double t, double s)
|
||||
: time(t), salience(s)
|
||||
{}
|
||||
{}
|
||||
|
||||
bool operator!=(const Event &e)
|
||||
{
|
||||
return time != e.time || salience != e.salience;
|
||||
}
|
||||
};
|
||||
bool operator!=(const Event &e)
|
||||
{
|
||||
return time != e.time || salience != e.salience;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::list<Event> EventList;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,7 +30,7 @@ int Induction::topN = 10;
|
|||
|
||||
|
||||
AgentList Induction::beatInduction(const AgentParameters ¶ms,
|
||||
const EventList &events)
|
||||
const BeatTracker::EventList &events)
|
||||
{
|
||||
int i, j, b, bestCount;
|
||||
bool submult;
|
||||
|
@ -48,8 +48,8 @@ AgentList Induction::beatInduction(const AgentParameters ¶ms,
|
|||
std::vector<int> clusterScore;
|
||||
clusterScore.resize(maxClusterCount);
|
||||
|
||||
EventList::const_iterator ptr1, ptr2;
|
||||
Event e1, e2;
|
||||
BeatTracker::EventList::const_iterator ptr1, ptr2;
|
||||
BeatTracker::Event e1, e2;
|
||||
ptr1 = events.begin();
|
||||
|
||||
while (ptr1 != events.end()) {
|
||||
|
|
|
@ -64,7 +64,7 @@ class Induction
|
|||
* of the top tempo hypotheses but no beats
|
||||
*/
|
||||
static AgentList beatInduction(const AgentParameters ¶ms,
|
||||
const EventList &events);
|
||||
const BeatTracker::EventList &events);
|
||||
|
||||
private:
|
||||
/** For variable cluster widths in newInduction().
|
||||
|
|
Loading…
Reference in New Issue