MuseScore/src/engraving/playback/playbackmodel.cpp
2022-06-06 12:43:03 +02:00

525 lines
16 KiB
C++

/*
* 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 "playbackmodel.h"
#include <QString>
#include "libmscore/score.h"
#include "libmscore/measure.h"
#include "libmscore/repeatlist.h"
#include "libmscore/segment.h"
#include "libmscore/dynamic.h"
#include "libmscore/part.h"
#include "libmscore/staff.h"
#include "libmscore/chord.h"
#include "libmscore/instrument.h"
#include "utils/pitchutils.h"
using namespace mu;
using namespace mu::engraving;
using namespace mu::mpe;
using namespace mu::async;
static const std::string METRONOME_INSTRUMENT_ID("metronome");
const InstrumentTrackId PlaybackModel::METRONOME_TRACK_ID = { 999, METRONOME_INSTRUMENT_ID };
void PlaybackModel::load(Score* score)
{
if (!score || score->measures()->empty() || !score->lastMeasure()) {
return;
}
m_score = score;
auto changesChannel = score->changesChannel();
changesChannel.resetOnReceive(this);
changesChannel.onReceive(this, [this](const ScoreChangesRange& range) {
TickBoundaries tickRange = tickBoundaries(range);
TrackBoundaries trackRange = trackBoundaries(range);
clearExpiredTracks();
clearExpiredContexts(trackRange.trackFrom, trackRange.trackTo);
clearExpiredEvents(tickRange.tickFrom, tickRange.tickTo, trackRange.trackFrom, trackRange.trackTo);
InstrumentTrackIdSet existingTracks = existingTrackIdSet();
ChangedTrackIdSet trackChanges;
update(tickRange.tickFrom, tickRange.tickTo, trackRange.trackFrom, trackRange.trackTo, &trackChanges);
notifyAboutChanges(std::move(trackChanges), std::move(existingTracks));
});
update(0, m_score->lastMeasure()->endTick().ticks(), 0, m_score->ntracks());
for (const auto& pair : m_playbackDataMap) {
m_trackAdded.send(pair.first);
}
m_dataChanged.notify();
}
void PlaybackModel::reload()
{
int trackFrom = 0;
size_t trackTo = m_score->ntracks();
int tickFrom = 0;
int tickTo = m_score->lastMeasure()->endTick().ticks();
clearExpiredTracks();
clearExpiredContexts(trackFrom, trackTo);
for (auto& pair : m_playbackDataMap) {
pair.second.originEvents.clear();
}
update(tickFrom, tickTo, trackFrom, trackTo);
for (auto& pair : m_playbackDataMap) {
pair.second.mainStream.send(pair.second.originEvents);
}
m_dataChanged.notify();
}
Notification PlaybackModel::dataChanged() const
{
return m_dataChanged;
}
bool PlaybackModel::isPlayRepeatsEnabled() const
{
return m_expandRepeats;
}
void PlaybackModel::setPlayRepeats(const bool isEnabled)
{
m_expandRepeats = isEnabled;
}
const InstrumentTrackId& PlaybackModel::metronomeTrackId() const
{
return METRONOME_TRACK_ID;
}
const PlaybackData& PlaybackModel::resolveTrackPlaybackData(const InstrumentTrackId& trackId)
{
auto search = m_playbackDataMap.find(trackId);
if (search != m_playbackDataMap.cend()) {
return search->second;
}
const Part* part = m_score ? m_score->partById(trackId.partId.toUint64()) : nullptr;
if (!part) {
static PlaybackData empty;
return empty;
}
update(0, m_score->lastMeasure()->tick().ticks(), part->startTrack(), part->endTrack());
return m_playbackDataMap[trackId];
}
const PlaybackData& PlaybackModel::resolveTrackPlaybackData(const ID& partId, const std::string& instrumentId)
{
return resolveTrackPlaybackData(idKey(partId, instrumentId));
}
void PlaybackModel::triggerEventsForItem(const EngravingItem* item)
{
IF_ASSERT_FAILED(item) {
return;
}
if (!item->isPlayable()) {
return;
}
InstrumentTrackId trackId = idKey(item);
auto trackPlaybackData = m_playbackDataMap.find(trackId);
if (trackPlaybackData == m_playbackDataMap.cend()) {
return;
}
int utick = repeatList().tick2utick(item->tick().ticks());
timestamp_t actualTimestamp = timestampFromTicks(item->score(), utick);
duration_t actualDuration = MScore::defaultPlayDuration;
dynamic_level_t actualDynamicLevel = dynamicLevelFromType(mpe::DynamicType::Natural);
const PlaybackContext& ctx = m_playbackCtxMap[trackId];
ArticulationsProfilePtr profile = profilesRepository()->defaultProfile(m_playbackDataMap[trackId].setupData.category);
if (!profile) {
LOGE() << "unsupported instrument family: " << trackId.partId.toUint64();
return;
}
PlaybackEventsMap result;
m_renderer.render(item, actualTimestamp, actualDuration, actualDynamicLevel, ctx.persistentArticulationType(utick), profile, result);
trackPlaybackData->second.offStream.send(std::move(result));
}
async::Channel<InstrumentTrackId> PlaybackModel::trackAdded() const
{
return m_trackAdded;
}
async::Channel<InstrumentTrackId> PlaybackModel::trackRemoved() const
{
return m_trackRemoved;
}
void PlaybackModel::update(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo,
ChangedTrackIdSet* trackChanges)
{
updateSetupData();
updateContext(trackFrom, trackTo);
updateEvents(tickFrom, tickTo, trackFrom, trackTo, trackChanges);
}
void PlaybackModel::updateSetupData()
{
for (const Part* part : m_score->parts()) {
for (const auto& pair : part->instruments()) {
InstrumentTrackId trackId = idKey(part->id(), pair.second->id().toStdString());
if (!trackId.isValid() || containsTrack(trackId)) {
continue;
}
m_setupResolver.resolveSetupData(pair.second, m_playbackDataMap[trackId].setupData);
}
}
m_setupResolver.resolveMetronomeSetupData(m_playbackDataMap[METRONOME_TRACK_ID].setupData);
}
void PlaybackModel::updateContext(const track_idx_t trackFrom, const track_idx_t trackTo)
{
for (const Part* part : m_score->parts()) {
if (trackTo < part->startTrack() || trackFrom >= part->endTrack()) {
continue;
}
for (const InstrumentTrackId& trackId : part->instrumentTrackIdSet()) {
PlaybackContext& ctx = m_playbackCtxMap[trackId];
ctx.update(trackId.partId, m_score);
PlaybackData& trackData = m_playbackDataMap[trackId];
trackData.dynamicLevelMap = ctx.dynamicLevelMap(m_score);
}
}
}
void PlaybackModel::updateEvents(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo,
ChangedTrackIdSet* trackChanges)
{
std::set<ID> changedPartIdSet = m_score->partIdsFromRange(trackFrom, trackTo);
for (const RepeatSegment* repeatSegment : repeatList()) {
int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
int repeatStartTick = repeatSegment->tick;
int repeatEndTick = repeatStartTick + repeatSegment->len();
if (repeatStartTick > tickTo || repeatEndTick <= tickFrom) {
continue;
}
for (const Measure* measure : repeatSegment->measureList()) {
int measureStartTick = measure->tick().ticks();
int measureEndTick = measure->endTick().ticks();
if (measureStartTick > tickTo || measureEndTick <= tickFrom) {
continue;
}
for (Segment* segment = measure->first(); segment; segment = segment->next()) {
if (!segment->isChordRestType()) {
continue;
}
int segmentStartTick = segment->tick().ticks();
int segmentEndTick = segmentStartTick + segment->ticks().ticks();
if (segmentStartTick > tickTo || segmentEndTick <= tickFrom) {
continue;
}
for (const EngravingItem* item : segment->elist()) {
if (!item || !item->isChordRest() || !item->part()) {
continue;
}
ID partId = item->part()->id();
if (changedPartIdSet.find(partId) == changedPartIdSet.cend()) {
continue;
}
InstrumentTrackId trackId = idKey(item);
if (!trackId.isValid()) {
continue;
}
const PlaybackContext& ctx = m_playbackCtxMap[trackId];
ArticulationsProfilePtr profile = profilesRepository()->defaultProfile(m_playbackDataMap[trackId].setupData.category);
if (!profile) {
LOGE() << "unsupported instrument family: " << partId;
continue;
}
m_renderer.render(item, tickPositionOffset, ctx.appliableDynamicLevel(segmentStartTick + tickPositionOffset),
ctx.persistentArticulationType(segmentStartTick + tickPositionOffset), std::move(profile),
m_playbackDataMap[trackId].originEvents);
collectChangesTracks(trackId, trackChanges);
}
m_renderer.renderMetronome(m_score, segmentStartTick, segment->ticks().ticks(),
tickPositionOffset, m_playbackDataMap[METRONOME_TRACK_ID].originEvents);
collectChangesTracks(METRONOME_TRACK_ID, trackChanges);
}
}
}
}
bool PlaybackModel::hasToReloadTracks(const std::unordered_set<ElementType>& changedTypes) const
{
static const std::unordered_set<ElementType> REQUIRED_TYPES = {
ElementType::PLAYTECH_ANNOTATION, ElementType::DYNAMIC, ElementType::HAIRPIN,
ElementType::HAIRPIN_SEGMENT
};
for (const ElementType type : REQUIRED_TYPES) {
if (changedTypes.find(type) == changedTypes.cend()) {
continue;
}
return true;
}
return false;
}
bool PlaybackModel::hasToReloadScore(const std::unordered_set<ElementType>& changedTypes) const
{
static const std::unordered_set<ElementType> REQUIRED_TYPES = {
ElementType::GRADUAL_TEMPO_CHANGE, ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT,
ElementType::TEMPO_TEXT,
ElementType::LAYOUT_BREAK, ElementType::FERMATA
};
for (const ElementType type : REQUIRED_TYPES) {
if (changedTypes.find(type) == changedTypes.cend()) {
continue;
}
return true;
}
return false;
}
bool PlaybackModel::containsTrack(const InstrumentTrackId& trackId) const
{
return m_playbackDataMap.find(trackId) != m_playbackDataMap.cend();
}
void PlaybackModel::clearExpiredTracks()
{
auto it = m_playbackDataMap.cbegin();
while (it != m_playbackDataMap.cend()) {
if (it->first == METRONOME_TRACK_ID) {
++it;
continue;
}
const Part* part = m_score->partById(it->first.partId.toUint64());
if (!part || !part->instruments().contains(it->first.instrumentId)) {
m_trackRemoved.send(it->first);
it = m_playbackDataMap.erase(it);
continue;
}
++it;
}
}
void PlaybackModel::clearExpiredContexts(const track_idx_t trackFrom, const track_idx_t trackTo)
{
for (const Part* part : m_score->parts()) {
if (part->startTrack() > trackTo || part->endTrack() <= trackFrom) {
continue;
}
for (const InstrumentTrackId& trackId : part->instrumentTrackIdSet()) {
PlaybackContext& ctx = m_playbackCtxMap[trackId];
ctx.clear();
}
}
}
void PlaybackModel::clearExpiredEvents(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo)
{
timestamp_t timestampFrom = timestampFromTicks(m_score, tickFrom);
timestamp_t timestampTo = timestampFromTicks(m_score, tickTo);
for (const Part* part : m_score->parts()) {
if (part->startTrack() > trackTo || part->endTrack() <= trackFrom) {
continue;
}
for (const InstrumentTrackId& trackId : part->instrumentTrackIdSet()) {
removeEvents(trackId, timestampFrom, timestampTo);
}
}
removeEvents(METRONOME_TRACK_ID, timestampFrom, timestampTo);
}
void PlaybackModel::collectChangesTracks(const InstrumentTrackId& trackId, ChangedTrackIdSet* result)
{
if (!result) {
return;
}
result->insert(trackId);
}
void PlaybackModel::notifyAboutChanges(ChangedTrackIdSet&& trackChanges, InstrumentTrackIdSet&& existingTracks)
{
for (const InstrumentTrackId& trackId : trackChanges) {
auto search = m_playbackDataMap.find(trackId);
if (search == m_playbackDataMap.cend()) {
continue;
}
search->second.mainStream.send(search->second.originEvents);
search->second.dynamicLevelChanges.send(search->second.dynamicLevelMap);
if (existingTracks.find(trackId) == existingTracks.cend()) {
m_trackAdded.send(trackId);
}
}
if (!trackChanges.empty()) {
m_dataChanged.notify();
}
}
void PlaybackModel::removeEvents(const InstrumentTrackId& trackId, const mpe::timestamp_t timestampFrom, const mpe::timestamp_t timestampTo)
{
auto search = m_playbackDataMap.find(trackId);
if (search == m_playbackDataMap.cend()) {
return;
}
PlaybackData& trackPlaybackData = search->second;
PlaybackEventsMap::const_iterator lowerBound;
if (timestampFrom == 0) {
//!Note Some events might be started RIGHT before the "official" start of the track
//! Need to make sure that we don't miss those events
lowerBound = trackPlaybackData.originEvents.begin();
} else {
lowerBound = trackPlaybackData.originEvents.lower_bound(timestampFrom);
}
auto upperBound = trackPlaybackData.originEvents.upper_bound(timestampTo);
for (auto it = lowerBound; it != upperBound;) {
it = trackPlaybackData.originEvents.erase(it);
}
}
PlaybackModel::TrackBoundaries PlaybackModel::trackBoundaries(const ScoreChangesRange& changesRange) const
{
TrackBoundaries result;
result.trackFrom = staff2track(changesRange.staffIdxFrom, 0);
result.trackTo = staff2track(changesRange.staffIdxTo, VOICES);
if (hasToReloadScore(changesRange.changedTypes) || !changesRange.isValidBoundary()) {
result.trackFrom = 0;
result.trackTo = m_score->ntracks();
}
return result;
}
PlaybackModel::TickBoundaries PlaybackModel::tickBoundaries(const ScoreChangesRange& changesRange) const
{
TickBoundaries result;
result.tickFrom = changesRange.tickFrom;
result.tickTo = changesRange.tickTo;
if (hasToReloadTracks(changesRange.changedTypes)
|| hasToReloadScore(changesRange.changedTypes)
|| !changesRange.isValidBoundary()) {
const Measure* lastMeasure = m_score->lastMeasure();
result.tickFrom = 0;
result.tickTo = lastMeasure ? lastMeasure->endTick().ticks() : 0;
}
return result;
}
const RepeatList& PlaybackModel::repeatList() const
{
return m_score->repeatList();
}
InstrumentTrackId PlaybackModel::idKey(const EngravingItem* item) const
{
return { item->part()->id(),
item->part()->instrumentId(item->tick()).toStdString() };
}
InstrumentTrackId PlaybackModel::idKey(const ID& partId, const std::string& instrumentId) const
{
return { partId, instrumentId };
}
InstrumentTrackIdSet PlaybackModel::existingTrackIdSet() const
{
InstrumentTrackIdSet result;
for (const auto& pair : m_playbackDataMap) {
result.insert(pair.first);
}
return result;
}