Some fixes basic chord symbol realization
This commit is contained in:
parent
86a4e7b105
commit
f9b1e499c5
25 changed files with 249 additions and 144 deletions
|
@ -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"
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,42 +583,46 @@ 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) {
|
||||
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");
|
||||
instr->appendChannel(c);
|
||||
_harmonyChannel = c;
|
||||
|
||||
masterScore()->rebuildMidiMapping();
|
||||
masterScore()->updateChannel();
|
||||
score()->setInstrumentsChanged(true);
|
||||
score()->setLayoutAll(); //do we need this?
|
||||
}
|
||||
if (!harmonyChannel() && harmonyCount() > 0) {
|
||||
Instrument* instr = instrument();
|
||||
Channel* c = new Channel(*instr->channel(0));
|
||||
c->setName(Channel::HARMONY_NAME);
|
||||
instr->appendChannel(c);
|
||||
onInstrumentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// hasPitchedStaff
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
#ifndef __REALIZEDHARMONY_H__
|
||||
#define __REALIZEDHARMONY_H__
|
||||
|
||||
#include <QMap>
|
||||
#include <QList>
|
||||
|
||||
namespace Ms {
|
||||
|
||||
class Harmony;
|
||||
|
|
|
@ -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,13 +586,15 @@ static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staf
|
|||
int tick = seg->tick().ticks();
|
||||
|
||||
//render harmony
|
||||
for (Element* e : seg->annotations()) {
|
||||
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
|
||||
continue;
|
||||
Harmony* h = toHarmony(e);
|
||||
if (!h->play())
|
||||
continue;
|
||||
renderHarmony(events, m, h, tickOffset);
|
||||
if (sctx.renderHarmony) {
|
||||
for (Element* e : seg->annotations()) {
|
||||
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
|
||||
continue;
|
||||
Harmony* h = toHarmony(e);
|
||||
if (!h->play())
|
||||
continue;
|
||||
renderHarmony(events, m, h, tickOffset);
|
||||
}
|
||||
}
|
||||
|
||||
for (int track = strack; track < etrack; ++track) {
|
||||
|
@ -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,13 +667,15 @@ static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* sta
|
|||
Fraction tick = seg->tick();
|
||||
|
||||
//render harmony
|
||||
for (Element* e : seg->annotations()) {
|
||||
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
|
||||
continue;
|
||||
Harmony* h = toHarmony(e);
|
||||
if (!h->play())
|
||||
continue;
|
||||
renderHarmony(events, m, h, tickOffset);
|
||||
if (sctx.renderHarmony) {
|
||||
for (Element* e : seg->annotations()) {
|
||||
if (!e->isHarmony() || (e->track() < strack) || (e->track() >= etrack))
|
||||
continue;
|
||||
Harmony* h = toHarmony(e);
|
||||
if (!h->play())
|
||||
continue;
|
||||
renderHarmony(events, m, h, tickOffset);
|
||||
}
|
||||
}
|
||||
|
||||
for (int track = strack; track < etrack; ++track) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -248,14 +250,16 @@ class NPlayEvent : public PlayEvent {
|
|||
NPlayEvent(const MidiCoreEvent& e) : PlayEvent(e) {}
|
||||
NPlayEvent(BeatType beatType);
|
||||
|
||||
const Note* note() const { return _note; }
|
||||
void setNote(const Note* v) { _note = v; }
|
||||
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;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue