382 lines
13 KiB
C++
382 lines
13 KiB
C++
/*********************************************************************************/
|
|
/*!
|
|
@file Notation.cpp
|
|
|
|
@brief xxx.
|
|
|
|
@author L. J. Barman
|
|
|
|
Copyright (c) 2008-2013, L. J. Barman, all rights reserved
|
|
|
|
This file is part of the PianoBooster application
|
|
|
|
PianoBooster is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
PianoBooster 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 PianoBooster. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
/*********************************************************************************/
|
|
|
|
#include "Notation.h"
|
|
#include "Cfg.h"
|
|
|
|
#define OPTION_DEBUG_NOTATION 0
|
|
#if OPTION_DEBUG_NOTATION
|
|
#define ppDEBUG_NOTATION(args) ppLogDebug args
|
|
#else
|
|
#define ppDEBUG_NOTATION(args)
|
|
#endif
|
|
|
|
#define MERGESLOT_NOTE_INDEX 0
|
|
#define MERGESLOT_BEATMARK_INDEX 1
|
|
|
|
bool CSlot::addSymbol(CSymbol symbol)
|
|
{
|
|
int i;
|
|
if (m_length >= MAX_SYMBOLS)
|
|
return false;
|
|
|
|
// Sort the entries low to high
|
|
for (i = m_length - 1; i >= 0; i--)
|
|
{
|
|
if (m_symbols[i].getNote() <= symbol.getNote())
|
|
{
|
|
// don't add duplicates
|
|
if (m_symbols[i].getNote() == symbol.getNote() &&
|
|
m_symbols[i].getType() == symbol.getType() &&
|
|
m_symbols[i].getHand() == symbol.getHand())
|
|
return true;
|
|
break;
|
|
}
|
|
// move the previous entry up one position
|
|
m_symbols[i+1] = m_symbols[i];
|
|
}
|
|
m_symbols[i+1] = symbol;
|
|
m_length++;
|
|
return true;
|
|
}
|
|
|
|
// find
|
|
void CSlot::analyse()
|
|
{
|
|
int i;
|
|
int rightIndex = 0;
|
|
int leftIndex = 0;
|
|
int rightTotal = 0;
|
|
int leftTotal = 0;
|
|
|
|
for (i = 0; i < m_length; i++)
|
|
{
|
|
if (m_symbols[i].getType() >= PB_SYMBOL_noteHead)
|
|
{
|
|
if (m_symbols[i].getHand() == PB_PART_right)
|
|
rightTotal++;
|
|
else if (m_symbols[i].getHand() == PB_PART_left)
|
|
leftTotal++;
|
|
}
|
|
}
|
|
for (i = 0; i < m_length; i++)
|
|
{
|
|
if (m_symbols[i].getType() >= PB_SYMBOL_noteHead)
|
|
{
|
|
if (m_symbols[i].getHand() == PB_PART_right)
|
|
m_symbols[i].setIndex(rightIndex++, rightTotal);
|
|
else if (m_symbols[i].getHand() == PB_PART_left)
|
|
m_symbols[i].setIndex(leftIndex++, leftTotal );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CNotation::m_cfg_displayCourtesyAccidentals = false;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
CSlot CNotation::nextBeatMarker()
|
|
{
|
|
const int cfg_barGap = CMidiFile::ppqnAdjust(30);
|
|
|
|
CSlot slot;
|
|
|
|
m_beatPerBarCounter++;
|
|
|
|
if (m_beatPerBarCounter >= m_bar.getTimeSigTop())
|
|
m_beatPerBarCounter = -1;
|
|
|
|
if (m_beatPerBarCounter == -1) // Sneak in a bar line
|
|
slot.setSymbol( m_bar.getBeatLength() - cfg_barGap, CSymbol( PB_SYMBOL_barLine, PB_PART_both, 0 ));
|
|
else if (m_beatPerBarCounter == 0)
|
|
slot.setSymbol( cfg_barGap, CSymbol( PB_SYMBOL_barMarker, PB_PART_both, 0 ));
|
|
else
|
|
slot.setSymbol( m_bar.getBeatLength(), CSymbol( PB_SYMBOL_beatMarker, PB_PART_both, 0 ));
|
|
return slot;
|
|
}
|
|
|
|
int CNotation::nextMergeSlot()
|
|
{
|
|
int nearestIndex = 0;
|
|
CSlot nearestSlot = m_mergeSlots[0];
|
|
for(int i = 1; i < arraySize(m_mergeSlots); i++)
|
|
{
|
|
// find the slot with the lowest delta time
|
|
if (m_mergeSlots[i].getDeltaTime() < nearestSlot.getDeltaTime())
|
|
{
|
|
nearestSlot = m_mergeSlots[i];
|
|
nearestIndex = i;
|
|
}
|
|
}
|
|
|
|
// Now subtract the delta time from all the others
|
|
for(int i = 0; i < arraySize(m_mergeSlots); ++i)
|
|
{
|
|
if (i == nearestIndex)
|
|
continue;
|
|
m_mergeSlots[i].addDeltaTime( -nearestSlot.getDeltaTime() );
|
|
}
|
|
return nearestIndex;
|
|
}
|
|
|
|
accidentalModifer_t CNotation::detectSuppressedNatural(int note)
|
|
{
|
|
if (note <= 0 || note +1 >= MAX_MIDI_NOTES)
|
|
return PB_ACCIDENTAL_MODIFER_noChange;
|
|
|
|
accidentalModifer_t modifer = PB_ACCIDENTAL_MODIFER_noChange;
|
|
|
|
while (m_earlyBarChangeDelta >= m_bar.getBarLength())
|
|
{
|
|
m_earlyBarChangeDelta -= m_bar.getBarLength();
|
|
m_earlyBarChangeCounter++;
|
|
}
|
|
|
|
CNoteState * pNoteState = &m_noteState[note];
|
|
CNoteState * pBackLink = pNoteState->getBackLink();
|
|
|
|
int direction = -CStavePos::getStaveAccidentalDirection(note);
|
|
ppDEBUG_NOTATION(("Note %d %d %d", note, direction, pBackLink));
|
|
// check if this note has occurred in this bar before
|
|
if (pNoteState->getBarChange() == m_earlyBarChangeCounter)
|
|
{
|
|
if (pBackLink)
|
|
{
|
|
ppDEBUG_NOTATION(("Force %d", note));
|
|
modifer = PB_ACCIDENTAL_MODIFER_force;
|
|
}
|
|
else if (direction != 0 && m_cfg_displayCourtesyAccidentals == false)
|
|
{
|
|
ppDEBUG_NOTATION(("Suppress %d %d", note, direction));
|
|
modifer = PB_ACCIDENTAL_MODIFER_suppress;
|
|
}
|
|
}
|
|
|
|
if (direction != 0)
|
|
{
|
|
// we are display a accidental so force the note above (or below) to display
|
|
m_noteState[note + direction].setBackLink(pNoteState); // point back to this note
|
|
m_noteState[note + direction].setBarChange(m_earlyBarChangeCounter);
|
|
ppDEBUG_NOTATION(("setting backlink %d %d", note + direction, direction));
|
|
}
|
|
if (pBackLink)
|
|
{
|
|
pNoteState->setBackLink(nullptr);
|
|
pBackLink->setBarChange(-1); // this prevents further suppression on the original note
|
|
}
|
|
|
|
pNoteState->setBarChange(m_earlyBarChangeCounter);
|
|
return modifer;
|
|
}
|
|
|
|
int CNotation::cfg_param[NOTATE_MAX_PARAMS];
|
|
|
|
void CNotation::setupNotationParamaters()
|
|
{
|
|
cfg_param[NOTATE_demisemiquaverBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/8 + DEFAULT_PPQN/8 + 1);
|
|
cfg_param[NOTATE_threesixtyforthBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/8 + DEFAULT_PPQN/16 + 1);
|
|
cfg_param[NOTATE_semiquaverBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/4 + 2);
|
|
cfg_param[NOTATE_threethirtysecondBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/4 + DEFAULT_PPQN/8 + 2);
|
|
cfg_param[NOTATE_quaverBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/2 + 5);
|
|
cfg_param[NOTATE_threesixteenthBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN/2 + DEFAULT_PPQN/4 + 5);
|
|
cfg_param[NOTATE_crotchetBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN + 10);
|
|
cfg_param[NOTATE_threeeighthBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN + DEFAULT_PPQN/2 + 10);
|
|
cfg_param[NOTATE_minimBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN*2 + 10);
|
|
cfg_param[NOTATE_threequaterBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN*3 + 10);
|
|
cfg_param[NOTATE_semibreveBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN*4 + 10);
|
|
cfg_param[NOTATE_breveBoundary] = CMidiFile::ppqnAdjust(DEFAULT_PPQN*8 + 10);
|
|
}
|
|
|
|
void CNotation::calculateScoreNoteLength()
|
|
{
|
|
if (!Cfg::experimentalNoteLength)
|
|
return;
|
|
|
|
CSlot* slot = m_slotQueue->indexPtr(0);
|
|
for (int i = 0; i < slot->length(); i++)
|
|
{
|
|
CSymbol* symbol = slot->getSymbolPtr(i);
|
|
|
|
if (symbol-> getType() != PB_SYMBOL_noteHead)
|
|
break;
|
|
|
|
// you may get better results assuming all the notes are legato
|
|
// ie assume that this note ends at the exact time the following note starts.
|
|
auto midiDuration = symbol->getMidiDuration();
|
|
auto param = static_cast<int>(NOTATE_demisemiquaverBoundary);
|
|
auto noteLength = static_cast<int>(PB_SYMBOL_noteHead) + 1;
|
|
for (; param != NOTATE_MAX_PARAMS; ++param, ++noteLength) {
|
|
if (midiDuration < cfg_param[param]) {
|
|
break;
|
|
}
|
|
}
|
|
if (param != NOTATE_MAX_PARAMS) {
|
|
symbol->setNoteLength(static_cast<musicalSymbol_t>(noteLength));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CNotation::findNoteSlots()
|
|
{
|
|
const auto noteColor = Cfg::colorTheme().noteColor;
|
|
auto midi = CMidiEvent();
|
|
auto slot = CSlot();
|
|
|
|
while (m_midiInputQueue->length()) {
|
|
midi = m_midiInputQueue->pop();
|
|
m_currentDeltaTime += midi.deltaTime();
|
|
m_earlyBarChangeDelta += midi.deltaTime();
|
|
if (midi.type() == MIDI_PB_chordSeparator || midi.type() == MIDI_PB_EOF) {
|
|
if (m_currentSlot.length() > 0) {
|
|
// the cord separator arrives very late so we are behind the times
|
|
m_currentSlot.analyse();
|
|
m_slotQueue->push(m_currentSlot);
|
|
m_currentSlot.clear();
|
|
}
|
|
if (midi.type() == MIDI_PB_EOF) {
|
|
slot.setSymbol(0, CSymbol( PB_SYMBOL_theEndMarker, PB_PART_both, 0 ));
|
|
m_slotQueue->push(slot);
|
|
}
|
|
break;
|
|
} else if (midi.type() == MIDI_PB_timeSignature) {
|
|
m_bar.setTimeSig(midi.data1(), midi.data2());
|
|
} else if (midi.type() == MIDI_PB_keySignature) {
|
|
CStavePos::setKeySignature(midi.data1(), midi.data2());
|
|
} else if (midi.type() == MIDI_NOTE_ON) {
|
|
const auto hand = CNote::findHand( midi, m_displayChannel, PB_PART_both);
|
|
if (hand == PB_PART_none) {
|
|
continue;
|
|
}
|
|
const auto symbolType = musicalSymbol_t(midi.channel() == MIDI_DRUM_CHANNEL ? PB_SYMBOL_drum : PB_SYMBOL_noteHead);
|
|
auto symbol = CSymbol(symbolType, hand, midi.note());
|
|
symbol.setColor(noteColor);
|
|
symbol.setMidiDuration(midi.getDuration());
|
|
|
|
// check if this note has occurred in this bar before
|
|
symbol.setAccidentalModifer(detectSuppressedNatural(midi.note()));
|
|
|
|
if (!m_currentSlot.addSymbol(symbol)) {
|
|
ppLogWarn("[%d] Over the Max symbols limit", m_displayChannel + 1);
|
|
}
|
|
m_currentSlot.addDeltaTime(m_currentDeltaTime);
|
|
m_currentDeltaTime = 0;
|
|
if (hand == PB_PART_left && midi.note() < MIDI_BOTTOM_C) {
|
|
m_currentSlot.setAv8Left(MIDI_OCTAVE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CSlot CNotation::nextNoteSlot()
|
|
{
|
|
// only if the slot queue is empty should we try to find some more
|
|
if (m_slotQueue->length() == 0)
|
|
findNoteSlots();
|
|
|
|
if (m_slotQueue->length() > 0)
|
|
{
|
|
calculateScoreNoteLength();
|
|
return m_slotQueue->pop();
|
|
}
|
|
else
|
|
return CSlot(); // this is an empty slot which means end of file
|
|
}
|
|
|
|
CSlot CNotation::nextSlot()
|
|
{
|
|
int mergeIdx;
|
|
CSlot slot;
|
|
|
|
if (m_mergeSlots[MERGESLOT_BEATMARK_INDEX].length() == 0)
|
|
{
|
|
// load up the two slots on start up
|
|
m_mergeSlots[MERGESLOT_NOTE_INDEX] = nextNoteSlot();
|
|
m_mergeSlots[MERGESLOT_BEATMARK_INDEX] = nextBeatMarker();
|
|
|
|
// This inserts the beat marksers into the queue early (so they get drawn underneath)
|
|
m_mergeSlots[MERGESLOT_BEATMARK_INDEX].addDeltaTime(
|
|
-CMidiFile::getPulsesPerQuarterNote() * BEAT_MARKER_OFFSET / DEFAULT_PPQN);
|
|
}
|
|
|
|
if (m_mergeSlots[0].getSymbolType(0) == PB_SYMBOL_theEndMarker)
|
|
return m_mergeSlots[0];
|
|
|
|
mergeIdx = nextMergeSlot();
|
|
slot = m_mergeSlots[mergeIdx];
|
|
if (mergeIdx == 0)
|
|
m_mergeSlots[mergeIdx] = nextNoteSlot();
|
|
else
|
|
m_mergeSlots[mergeIdx] = nextBeatMarker();
|
|
return slot;
|
|
}
|
|
|
|
void CNotation::midiEventInsert(CMidiEvent event)
|
|
{
|
|
if (m_findScrollerChord.findChord(event, m_displayChannel, PB_PART_both ) == true)
|
|
{
|
|
// the Score works differently we just send down a chord separator
|
|
CMidiEvent separator;
|
|
separator.chordSeparator(event);
|
|
m_midiInputQueue->push(separator);
|
|
}
|
|
|
|
m_midiInputQueue->push(event);
|
|
}
|
|
|
|
void CNotation::reset()
|
|
{
|
|
const int cfg_earlBarLead = CMidiFile::ppqnAdjust(8);
|
|
|
|
m_currentDeltaTime = 0;
|
|
m_midiInputQueue->clear();
|
|
m_slotQueue->clear();
|
|
for (auto &slot : m_mergeSlots)
|
|
slot.clear();
|
|
m_currentSlot.clear();
|
|
m_beatPerBarCounter=0;
|
|
m_earlyBarChangeCounter = 0;
|
|
m_earlyBarChangeDelta = cfg_earlBarLead; // We want to detect the bar change early
|
|
|
|
m_bar.reset();
|
|
m_findScrollerChord.reset();
|
|
for (auto ¬eState : m_noteState)
|
|
noteState.clear();
|
|
setupNotationParamaters();
|
|
}
|
|
|
|
void CNotation::resetNoteColor(CColor color)
|
|
{
|
|
m_currentSlot.setNoteColor(0, color);
|
|
for (auto i = 0, len = m_slotQueue->length(); i != len; ++i) {
|
|
m_slotQueue->indexPtr(i)->setNoteColor(0, color);
|
|
}
|
|
for (auto &slot : m_mergeSlots) {
|
|
slot.setNoteColor(0, color);
|
|
}
|
|
}
|