Some fixes basic chord symbol realization

This commit is contained in:
Igor Korsukov 2020-03-13 11:56:25 +02:00 committed by Igor Korsukov
parent 86a4e7b105
commit f9b1e499c5
25 changed files with 249 additions and 144 deletions

View file

@ -98,7 +98,8 @@
#define PREF_IO_PORTMIDI_OUTPUTLATENCYMILLISECONDS "io/portMidi/outputLatencyMilliseconds"
#define PREF_IO_PULSEAUDIO_USEPULSEAUDIO "io/pulseAudio/usePulseAudio"
#define PREF_SCORE_CHORD_PLAYONADDNOTE "score/chord/playOnAddNote"
#define PREF_SCORE_HARMONY_PLAYWHENEDITING "score/harmony/playWhileEditing"
#define PREF_SCORE_HARMONY_PLAY "score/harmony/play"
#define PREF_SCORE_HARMONY_PLAY_ONEDIT "score/harmony/play/onedit"
#define PREF_SCORE_MAGNIFICATION "score/magnification"
#define PREF_SCORE_NOTE_PLAYONCLICK "score/note/playOnClick"
#define PREF_SCORE_NOTE_DEFAULTPLAYDURATION "score/note/defaultPlayDuration"

View file

@ -150,14 +150,7 @@ void Excerpt::createExcerpt(Excerpt* excerpt)
// Set instruments and create linked staffs
for (const Part* part : parts) {
Part* p = new Part(score);
//properly set harmony channel for newly created part
const Instrument* instr = part->instrument();
int chanIdx = instr->channelIdx("harmony");
p->setInstrument(*instr);
if (chanIdx != -1)
p->setHarmonyChannel(const_cast<Channel*>(instr->channel(chanIdx)));
p->setInstrument(*part->instrument());
p->setPartName(part->partName());
for (Staff* staff : *part->staves()) {

View file

@ -106,6 +106,13 @@ QString Harmony::baseName()
return tpc2name(_baseTpc, _baseSpelling, _baseCase);
}
bool Harmony::isRealizable() const
{
return (_rootTpc != Tpc::TPC_INVALID)
|| (_harmonyType == HarmonyType::NASHVILLE); // unable to fully check at for nashville at the moment
}
//---------------------------------------------------------
// resolveDegreeList
// try to detect chord number and to eliminate degree

View file

@ -152,11 +152,8 @@ class Harmony final : public TextBase {
virtual bool edit(EditData&) override;
virtual void endEdit(EditData&) override;
bool isRealizable() const
{
return (_rootTpc != Tpc::TPC_INVALID)
|| (_harmonyType == HarmonyType::NASHVILLE); // unable to fully check at for nashville at the moment
}
bool isRealizable() const;
QString hFunction() const { return _function; }
QString hUserName() const { return _userName; }
QString hTextName() const { return _textName; }

View file

@ -25,6 +25,9 @@
namespace Ms {
const char* Channel::DEFAULT_NAME = "normal";
const char* Channel::HARMONY_NAME = "harmony";
Instrument InstrumentList::defaultInstrument;
const std::initializer_list<Channel::Prop> PartChannelSettingsLink::excerptProperties {
Channel::Prop::SOLOMUTE,
@ -420,10 +423,6 @@ NamedEventList* Instrument::midiAction(const QString& s, int channelIdx) const
return 0;
}
const char *Channel::DEFAULT_NAME = "normal";
//---------------------------------------------------------
// Channel
//---------------------------------------------------------

View file

@ -131,6 +131,7 @@ class Channel {
public:
static const char* DEFAULT_NAME;
static const char* HARMONY_NAME;
static constexpr char defaultVolume = 100;
enum class A : char {

View file

@ -540,10 +540,15 @@ int Part::harmonyCount() const
{
if (!score())
return 0;
int count = 0;
Measure* firstM = score()->firstMeasure();
if (!firstM)
return 0;
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = score()->firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (Element* e : seg->annotations()) {
int count = 0;
for (const Segment* seg = firstM->first(st); seg; seg = seg->next1(st)) {
for (const Element* e : seg->annotations()) {
if (e->type() == ElementType::HARMONY && e->track() >= startTrack() && e->track() < endTrack())
count++;
}
@ -559,8 +564,17 @@ int Part::harmonyCount() const
/// checkRemoval can be set to true to check to see if we
/// can remove the harmony channel
//---------------------------------------------------------
void Part::updateHarmonyChannels(bool checkRemoval)
void Part::updateHarmonyChannels(bool isDoOnInstrumentChanged, bool checkRemoval)
{
auto onInstrumentChanged = [this]() {
masterScore()->rebuildMidiMapping();
masterScore()->updateChannel();
score()->setInstrumentsChanged(true);
score()->setLayoutAll(); //do we need this?
};
// usage of harmony count is okay even if expensive since checking harmony channel will shortcircuit if existent
// harmonyCount will only be called on loading of a score (where it will need to be scanned for harmony anyway)
// or when the first harmony of a score is just added
@ -569,40 +583,44 @@ void Part::updateHarmonyChannels(bool checkRemoval)
//~OPTIM~
if (harmonyCount() == 0) {
Instrument* instr = instrument();
int hChannel = instr->channelIdx("harmony");
if (hChannel != -1) {
instr->removeChannel(instr->channel(hChannel));
_harmonyChannel = 0;
masterScore()->rebuildMidiMapping();
masterScore()->updateChannel();
score()->setInstrumentsChanged(true);
score()->setLayoutAll(); //do we need this?
int hChIdx= instr->channelIdx(Channel::HARMONY_NAME);
if (hChIdx != -1) {
Channel* hChan = instr->channel(hChIdx);
instr->removeChannel(hChan);
delete hChan;
if (isDoOnInstrumentChanged)
onInstrumentChanged();
return;
}
}
}
if (!_harmonyChannel) {
if (!harmonyChannel() && harmonyCount() > 0) {
Instrument* instr = instrument();
int hChannel = instr->channelIdx("harmony");
if (hChannel != -1) {
//we already have a channel, but it's just not properly set yet
//so we just need to set it
_harmonyChannel = instr->channel(hChannel);
return;
}
if (harmonyCount() > 0) {
Channel* c = new Channel(*instr->channel(0));
c->setName("harmony");
c->setName(Channel::HARMONY_NAME);
instr->appendChannel(c);
_harmonyChannel = c;
onInstrumentChanged();
}
}
masterScore()->rebuildMidiMapping();
masterScore()->updateChannel();
score()->setInstrumentsChanged(true);
score()->setLayoutAll(); //do we need this?
}
}
//---------------------------------------------------------
// harmonyChannel
//---------------------------------------------------------
const Channel* Part::harmonyChannel() const
{
const Instrument* instr = instrument();
if (!instr)
return nullptr;
int chanIdx = instr->channelIdx(Channel::HARMONY_NAME);
if (chanIdx == -1)
return nullptr;
const Channel* chan = instr->channel(chanIdx);
Q_ASSERT(chan);
return chan;
}
//---------------------------------------------------------

View file

@ -63,7 +63,6 @@ class Part final : public ScoreElement {
int _color; ///User specified color for helping to label parts
PreferSharpFlat _preferSharpFlat;
Channel* _harmonyChannel = 0; ///channel which harmony is played
public:
Part(Score* = 0);
@ -139,9 +138,8 @@ class Part final : public ScoreElement {
bool hasTabStaff() const;
bool hasDrumStaff() const;
void updateHarmonyChannels(bool checkRemoval = false);
const Channel* harmonyChannel() const { return _harmonyChannel; }
void setHarmonyChannel(Channel* c) { _harmonyChannel = c; }
void updateHarmonyChannels(bool isDoOnInstrumentChanged, bool checkRemoval = false);
const Channel* harmonyChannel() const;
const Part* masterPart() const;
Part* masterPart();

View file

@ -3266,13 +3266,14 @@ Score::FileError MasterScore::read114(XmlReader& e)
style().set(Sid::voltaPosAbove, QPointF(0.0, -2.0f));
fixTicks();
rebuildMidiMapping();
updateChannel();
for (Part* p : parts()) {
p->updateHarmonyChannels();
p->updateHarmonyChannels(false);
}
rebuildMidiMapping();
updateChannel();
// treat reading a 1.14 file as import
// on save warn if old file will be overwritten
setCreated(true);

View file

@ -3895,6 +3895,11 @@ static bool readScore(Score* score, XmlReader& e)
}
#endif
score->fixTicks();
for (Part* p : score->parts()) {
p->updateHarmonyChannels(false);
}
if (score->isMaster()) {
MasterScore* ms = static_cast<MasterScore*>(score);
if (!ms->omr())
@ -3904,10 +3909,6 @@ static bool readScore(Score* score, XmlReader& e)
// ms->createPlayEvents();
}
for (Part* p : score->parts()) {
p->updateHarmonyChannels();
}
return true;
}

View file

@ -262,12 +262,14 @@ bool Score::read(XmlReader& e)
masterScore()->setShowOmr(false);
fixTicks();
for (Part* p : _parts) {
p->updateHarmonyChannels(false);
}
masterScore()->rebuildMidiMapping();
masterScore()->updateChannel();
for (Part* p : _parts) {
p->updateHarmonyChannels();
}
// createPlayEvents();
return true;
}

View file

@ -457,7 +457,7 @@ RealizedHarmony::PitchMap RealizedHarmony::getIntervals(int rootTpc, bool litera
}
Harmony* next = _harmony->findNext();
if (!_literal && next) {
if (!_literal && next && tpcIsValid(next->rootTpc())) {
//jazz interpretation
QString qNext = next->parsedForm()->quality();
//pitch from current to next harmony normalized to a range between 0 and 12

View file

@ -20,6 +20,9 @@
#ifndef __REALIZEDHARMONY_H__
#define __REALIZEDHARMONY_H__
#include <QMap>
#include <QList>
namespace Ms {
class Harmony;

View file

@ -54,6 +54,8 @@
#include "sym.h"
#include "synthesizerstate.h"
#include "mscore/preferences.h"
namespace Ms {
//int printNoteEventLists(NoteEventList el, int prefix, int j){
@ -547,6 +549,7 @@ static void renderHarmony(EventMap* events, Measure* m, Harmony* h, int tickOffs
QList<int> pitches = r.pitches();
NPlayEvent ev(ME_NOTEON, channel->channel(), 0, velocity);
ev.setHarmony(h);
Fraction duration = r.getActualDuration();
int onTime = h->tick().ticks() + tickOffset;
@ -570,9 +573,9 @@ static void renderHarmony(EventMap* events, Measure* m, Harmony* h, int tickOffs
// the original, velocity-only method of collecting events.
//---------------------------------------------------------
static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staff, int tickOffset)
void MidiRenderer::collectMeasureEventsSimple(EventMap* events, Measure* m, const StaffContext& sctx, int tickOffset)
{
int firstStaffIdx = staff->idx();
int firstStaffIdx = sctx.staff->idx();
int nextStaffIdx = firstStaffIdx + 1;
SegmentType st = SegmentType::ChordRest;
@ -583,6 +586,7 @@ static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staf
int tick = seg->tick().ticks();
//render harmony
if (sctx.renderHarmony) {
for (Element* e : seg->annotations()) {
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
continue;
@ -591,6 +595,7 @@ static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staf
continue;
renderHarmony(events, m, h, tickOffset);
}
}
for (int track = strack; track < etrack; ++track) {
// skip linked staves, except primary
@ -643,16 +648,16 @@ static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staf
// SEG_START - note-on velocity is the same as the start velocity of the seg
//---------------------------------------------------------
static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* staff, int tickOffset, DynamicsRenderMethod method, int cc)
void MidiRenderer::collectMeasureEventsDefault(EventMap* events, Measure* m, const StaffContext& sctx, int tickOffset)
{
int controller = getControllerFromCC(cc);
int controller = getControllerFromCC(sctx.cc);
if (controller == -1) {
qWarning("controller for CC %d not valid", cc);
qWarning("controller for CC %d not valid", sctx.cc);
return;
}
int firstStaffIdx = staff->idx();
int firstStaffIdx = sctx.staff->idx();
int nextStaffIdx = firstStaffIdx + 1;
SegmentType st = SegmentType::ChordRest;
@ -662,6 +667,7 @@ static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* sta
Fraction tick = seg->tick();
//render harmony
if (sctx.renderHarmony) {
for (Element* e : seg->annotations()) {
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
continue;
@ -670,6 +676,7 @@ static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* sta
continue;
renderHarmony(events, m, h, tickOffset);
}
}
for (int track = strack; track < etrack; ++track) {
// Skip linked staves, except primary
@ -703,7 +710,7 @@ static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* sta
}
bool useSND = instr->singleNoteDynamics();
SndConfig config = SndConfig(useSND, controller, method);
SndConfig config = SndConfig(useSND, controller, sctx.method);
//
// Add normal note events
@ -730,22 +737,22 @@ static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* sta
// redirects to the correct function based on the passed method
//---------------------------------------------------------
static void collectMeasureEvents(EventMap* events, Measure* m, Staff* staff, int tickOffset, DynamicsRenderMethod method, int cc)
void MidiRenderer::collectMeasureEvents(EventMap* events, Measure* m, const StaffContext& sctx, int tickOffset)
{
switch (method) {
switch (sctx.method) {
case DynamicsRenderMethod::SIMPLE:
collectMeasureEventsSimple(events, m, staff, tickOffset);
collectMeasureEventsSimple(events, m, sctx, tickOffset);
break;
case DynamicsRenderMethod::SEG_START:
case DynamicsRenderMethod::FIXED_MAX:
collectMeasureEventsDefault(events, m, staff, tickOffset, method, cc);
collectMeasureEventsDefault(events, m, sctx, tickOffset);
break;
default:
qWarning("Unrecognized dynamics method: %d", int(method));
qWarning("Unrecognized dynamics method: %d", int(sctx.method));
break;
}
collectProgramChanges(events, m, staff, tickOffset);
collectProgramChanges(events, m, sctx.staff, tickOffset);
}
//---------------------------------------------------------
@ -897,7 +904,7 @@ void Score::updateVelo()
// renderStaffSegment
//---------------------------------------------------------
void MidiRenderer::renderStaffChunk(const Chunk& chunk, EventMap* events, Staff* staff, DynamicsRenderMethod method, int cc)
void MidiRenderer::renderStaffChunk(const Chunk& chunk, EventMap* events, const StaffContext& sctx)
{
Measure* start = chunk.startMeasure();
Measure* end = chunk.endMeasure();
@ -906,13 +913,13 @@ void MidiRenderer::renderStaffChunk(const Chunk& chunk, EventMap* events, Staff*
Measure* lastMeasure = start->prevMeasure();
for (Measure* m = start; m != end; m = m->nextMeasure()) {
if (lastMeasure && m->isRepeatMeasure(staff)) {
if (lastMeasure && m->isRepeatMeasure(sctx.staff)) {
int offset = (m->tick() - lastMeasure->tick()).ticks();
collectMeasureEvents(events, lastMeasure, staff, tickOffset + offset, method, cc);
collectMeasureEvents(events, lastMeasure, sctx, tickOffset + offset);
}
else {
lastMeasure = m;
collectMeasureEvents(events, lastMeasure, staff, tickOffset, method, cc);
collectMeasureEvents(events, lastMeasure, sctx, tickOffset);
}
}
}
@ -2142,18 +2149,21 @@ void Score::renderMidi(EventMap* events, const SynthesizerState& synthState)
void Score::renderMidi(EventMap* events, bool metronome, bool expandRepeats, const SynthesizerState& synthState)
{
masterScore()->setExpandRepeats(expandRepeats);
MidiRenderer(this).renderScore(events, synthState, metronome);
MidiRenderer::Context ctx(synthState);
ctx.metronome = metronome;
ctx.renderHarmony = preferences.getBool(PREF_SCORE_HARMONY_PLAY);
MidiRenderer(this).renderScore(events, ctx);
}
void MidiRenderer::renderScore(EventMap* events, const SynthesizerState& synthState, bool metronome)
void MidiRenderer::renderScore(EventMap* events, const Context& ctx)
{
updateState();
for (const Chunk& chunk : chunks) {
renderChunk(chunk, events, synthState, metronome);
renderChunk(chunk, events, ctx);
}
}
void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const SynthesizerState& synthState, bool metronome)
void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const Context& ctx)
{
// TODO: avoid doing it multiple times for the same measures
score->createPlayEvents(chunk.startMeasure(), chunk.endMeasure());
@ -2168,8 +2178,8 @@ void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const Synth
// check if the score synth settings are actually set
// if not, use the global synth state
if (method == -1) {
method = synthState.method();
cc = synthState.ccToUse();
method = ctx.synthState.method();
cc = ctx.synthState.ccToUse();
if (method == -1) {
// fall back to defaults - this may be needed to pass tests,
@ -2197,14 +2207,20 @@ void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const Synth
}
// create note & other events
for (Staff* st : score->staves())
renderStaffChunk(chunk, events, st, renderMethod, cc);
for (Staff* st : score->staves()) {
StaffContext sctx;
sctx.staff = st;
sctx.method = renderMethod;
sctx.cc = cc;
sctx.renderHarmony = ctx.renderHarmony;
renderStaffChunk(chunk, events, sctx);
}
events->fixupMIDI();
// create sustain pedal events
renderSpanners(chunk, events);
if (metronome)
if (ctx.metronome)
renderMetronome(chunk, events);
// NOTE:JT this is a temporary fix for duplicate events until polyphonic aftertouch support

View file

@ -61,7 +61,7 @@ class RangeMap {
//---------------------------------------------------------
class MidiRenderer {
Score* score;
Score* score{nullptr};
bool needUpdate = true;
int minChunkSize = 0;
@ -92,20 +92,40 @@ class MidiRenderer {
private:
std::vector<Chunk> chunks;
struct StaffContext
{
Staff* staff{nullptr};
DynamicsRenderMethod method{DynamicsRenderMethod::SIMPLE};
int cc{0};
bool renderHarmony{false};
};
void updateChunksPartition();
static bool canBreakChunk(const Measure* last);
void updateState();
void renderStaffChunk(const Chunk&, EventMap* events, Staff*, DynamicsRenderMethod method, int cc);
void renderStaffChunk(const Chunk&, EventMap* events, const StaffContext& sctx);
void renderSpanners(const Chunk&, EventMap* events);
void renderMetronome(const Chunk&, EventMap* events);
void renderMetronome(EventMap* events, Measure* m, const Fraction& tickOffset);
void collectMeasureEvents(EventMap* events, Measure* m, const MidiRenderer::StaffContext& sctx, int tickOffset);
void collectMeasureEventsSimple(EventMap* events, Measure* m, const StaffContext& sctx, int tickOffset);
void collectMeasureEventsDefault(EventMap* events, Measure* m, const StaffContext& sctx, int tickOffset);
public:
explicit MidiRenderer(Score* s) : score(s) {}
void renderScore(EventMap* events, const SynthesizerState& synthState, bool metronome = true);
void renderChunk(const Chunk&, EventMap* events, const SynthesizerState& synthState, bool metronome = true);
struct Context
{
const SynthesizerState& synthState;
bool metronome{true};
bool renderHarmony{false};
Context(const SynthesizerState& ss) : synthState(ss) {}
};
void renderScore(EventMap* events, const Context& ctx);
void renderChunk(const Chunk&, EventMap* events, const Context& ctx);
void setScoreChanged() { needUpdate = true; }
void setMinChunkSize(int sizeMeasures) { minChunkSize = sizeMeasures; needUpdate = true; }

View file

@ -1491,7 +1491,7 @@ void Score::addElement(Element* element)
}
break;
case ElementType::HARMONY:
element->part()->updateHarmonyChannels();
element->part()->updateHarmonyChannels(true);
break;
default:
@ -1655,7 +1655,7 @@ void Score::removeElement(Element* element)
}
break;
case ElementType::HARMONY:
element->part()->updateHarmonyChannels(true);
element->part()->updateHarmonyChannels(true, true);
break;
default:

View file

@ -1606,6 +1606,8 @@ void ChangePart::flip(EditData*)
part->setInstrument(instrument);
part->setPartName(partName);
part->updateHarmonyChannels(false);
Score* score = part->score();
score->masterScore()->rebuildMidiMapping();
score->setInstrumentsChanged(true);

View file

@ -320,7 +320,7 @@ bool ExportMidi::write(QIODevice* device, bool midiExpandRepeats, bool exportRPN
for (auto i = events.begin(); i != events.end(); ++i) {
const NPlayEvent& event = i->second;
if (event.isMuted(cs))
if (event.isMuted())
continue;
if (event.discard() == staffIdx + 1 && event.velo() > 0)
// turn note off so we can restrike it in another track

View file

@ -4667,7 +4667,9 @@ void MuseScore::play(Element* e) const
}
seq->startNoteTimer(MScore::defaultPlayDuration);
}
else if (e->isHarmony() && preferences.getBool(PREF_SCORE_HARMONY_PLAYWHENEDITING)) {
else if (e->isHarmony()
&& preferences.getBool(PREF_SCORE_HARMONY_PLAY)
&& preferences.getBool(PREF_SCORE_HARMONY_PLAY_ONEDIT)) {
seq->stopNotes();
Harmony* h = toHarmony(e);
if (!h->isRealizable())
@ -4675,7 +4677,17 @@ void MuseScore::play(Element* e) const
RealizedHarmony r = h->getRealizedHarmony();
QList<int> pitches = r.pitches();
int channel = e->part()->harmonyChannel()->channel();
const Channel* hChannel = e->part()->harmonyChannel();
if (!hChannel)
return;
int channel = hChannel->channel();
// reset the cc that is used for single note dynamics, if any
int cc = synthesizerState().ccToUse();
if (cc != -1)
seq->sendEvent(NPlayEvent(ME_CONTROLLER, channel, cc, 80));
for (int pitch : pitches)
seq->startNote(channel, pitch, 80, 0);
seq->startNoteTimer(MScore::defaultPlayDuration);

View file

@ -144,7 +144,8 @@ void Preferences::init(bool storeInMemoryOnly)
{PREF_IO_PORTMIDI_OUTPUTLATENCYMILLISECONDS, new IntPreference(0)},
{PREF_IO_PULSEAUDIO_USEPULSEAUDIO, new BoolPreference(defaultUsePulseAudio, false)},
{PREF_SCORE_CHORD_PLAYONADDNOTE, new BoolPreference(true, false)},
{PREF_SCORE_HARMONY_PLAYWHENEDITING, new BoolPreference(true, false)},
{PREF_SCORE_HARMONY_PLAY, new BoolPreference(false, false)},
{PREF_SCORE_HARMONY_PLAY_ONEDIT, new BoolPreference(true, false)},
{PREF_SCORE_MAGNIFICATION, new DoublePreference(1.0, false)},
{PREF_SCORE_NOTE_PLAYONCLICK, new BoolPreference(true, false)},
{PREF_SCORE_NOTE_DEFAULTPLAYDURATION, new IntPreference(300 /* ms */, false)},

View file

@ -368,7 +368,9 @@ void PreferenceDialog::updateValues(bool useDefaultValues)
realtimeDelay->setValue(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY));
playNotes->setChecked(preferences.getBool(PREF_SCORE_NOTE_PLAYONCLICK));
playChordOnAddNote->setChecked(preferences.getBool(PREF_SCORE_CHORD_PLAYONADDNOTE));
playHarmonyWhenEditing->setChecked(preferences.getBool(PREF_SCORE_HARMONY_PLAYWHENEDITING));
playHarmony->setChecked(preferences.getBool(PREF_SCORE_HARMONY_PLAY));
playHarmonyOnEdit->setChecked(preferences.getBool(PREF_SCORE_HARMONY_PLAY_ONEDIT));
checkUpdateStartup->setChecked(preferences.getBool(PREF_UI_APP_STARTUP_CHECKUPDATE));
@ -994,7 +996,8 @@ void PreferenceDialog::apply()
preferences.setPreference(PREF_IO_OSC_PORTNUMBER, oscPort->value());
preferences.setPreference(PREF_IO_OSC_USEREMOTECONTROL, oscServer->isChecked());
preferences.setPreference(PREF_SCORE_CHORD_PLAYONADDNOTE, playChordOnAddNote->isChecked());
preferences.setPreference(PREF_SCORE_HARMONY_PLAYWHENEDITING, playHarmonyWhenEditing->isChecked());
preferences.setPreference(PREF_SCORE_HARMONY_PLAY, playHarmony->isChecked());
preferences.setPreference(PREF_SCORE_HARMONY_PLAY_ONEDIT, playHarmonyOnEdit->isChecked());
preferences.setPreference(PREF_SCORE_NOTE_DEFAULTPLAYDURATION, defaultPlayDuration->value());
preferences.setPreference(PREF_SCORE_NOTE_PLAYONCLICK, playNotes->isChecked());
preferences.setPreference(PREF_UI_APP_STARTUP_CHECKUPDATE, checkUpdateStartup->isChecked());

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>834</width>
<height>682</height>
<height>701</height>
</rect>
</property>
<property name="sizePolicy">
@ -90,7 +90,7 @@
<string>Preferences Tab Manager</string>
</property>
<property name="currentIndex">
<number>5</number>
<number>0</number>
</property>
<widget class="QWidget" name="tabGeneral">
<property name="accessibleName">
@ -1278,13 +1278,6 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QCheckBox" name="playChordOnAddNote">
<property name="text">
<string>Play whole chord when editing</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
@ -1305,6 +1298,13 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="playChordOnAddNote">
<property name="text">
<string>Play whole chord when editing</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="defaultPlayDuration">
<property name="accessibleName">
@ -1327,11 +1327,35 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="playHarmonyWhenEditing">
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="playHarmony">
<property name="enabled">
<bool>true</bool>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="title">
<string>Play Harmony</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QCheckBox" name="playHarmonyOnEdit">
<property name="text">
<string>Play harmony when editing</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
@ -4298,7 +4322,6 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter
<tabstop>playNotes</tabstop>
<tabstop>defaultPlayDuration</tabstop>
<tabstop>playChordOnAddNote</tabstop>
<tabstop>playHarmonyWhenEditing</tabstop>
<tabstop>rcGroup</tabstop>
<tabstop>rewindActive</tabstop>
<tabstop>recordRewind</tabstop>

View file

@ -551,7 +551,7 @@ void Seq::playEvent(const NPlayEvent& event, unsigned framePos)
{
int type = event.type();
if (type == ME_NOTEON) {
if (!event.isMuted(cs->playbackScore())) {
if (!event.isMuted()) {
if (event.discard()) { // ignore noteoff but restrike noteon
if (event.velo() > 0)
putEvent(NPlayEvent(ME_NOTEON, event.channel(), event.pitch(), 0) ,framePos);
@ -1035,7 +1035,10 @@ void Seq::initInstruments(bool realTime)
void Seq::renderChunk(const MidiRenderer::Chunk& ch, EventMap* eventMap)
{
midi.renderChunk(ch, eventMap, mscore->synthesizerState(), /* metronome */ true);
MidiRenderer::Context ctx(mscore->synthesizerState());
ctx.metronome = true;
ctx.renderHarmony = preferences.getBool(PREF_SCORE_HARMONY_PLAY);
midi.renderChunk(ch, eventMap, ctx);
renderEventsStatus.setOccupied(ch.utick1(), ch.utick2());
}

View file

@ -12,6 +12,7 @@
#include "libmscore/xml.h"
#include "libmscore/note.h"
#include "libmscore/harmony.h"
#include "libmscore/sig.h"
#include "event.h"
#include "libmscore/staff.h"
@ -171,7 +172,7 @@ NPlayEvent::NPlayEvent(BeatType beatType)
// isMuted
//---------------------------------------------------------
bool NPlayEvent::isMuted(Score* score) const
bool NPlayEvent::isMuted() const
{
const Note* n = note();
if (n) {
@ -181,15 +182,14 @@ bool NPlayEvent::isMuted(Score* score) const
const Channel* a = instr->playbackChannel(n->subchannel(), cs);
return a->mute() || a->soloMute() || !staff->playbackVoice(n->voice());
}
else if (score) {
//maybe we have a harmony channel to account for
Staff* staff = score->staff(getOriginatingStaff());
if (!staff)
return false;
const Channel* a = staff->part()->harmonyChannel();
if (a) //if there is a harmony channel
return a->mute() || a->soloMute();
const Harmony* h = harmony();
if (h) {
const Channel* hCh = h->part()->harmonyChannel();
if (hCh) //if there is a harmony channel
return hCh->mute() || hCh->soloMute();
}
return false;
}

View file

@ -18,6 +18,7 @@
namespace Ms {
class Note;
class Harmony;
class XmlWriter;
class Score;
@ -237,7 +238,8 @@ class PlayEvent : public MidiCoreEvent {
//---------------------------------------------------------
class NPlayEvent : public PlayEvent {
const Note* _note = 0;
const Note* _note{nullptr};
const Harmony* _harmony{nullptr};
int _origin = -1;
int _discard = 0;
@ -250,12 +252,14 @@ class NPlayEvent : public PlayEvent {
const Note* note() const { return _note; }
void setNote(const Note* v) { _note = v; }
const Harmony* harmony() const { return _harmony; }
void setHarmony(const Harmony* v) { _harmony = v; }
int getOriginatingStaff() const { return _origin; }
void setOriginatingStaff(int i) { _origin = i; }
void setDiscard(int d) { _discard = d; }
int discard() const { return _discard; }
bool isMuted(Score* score = 0) const;
bool isMuted() const;
};
//---------------------------------------------------------