pianobooster/src/Piano.cpp

339 lines
9.3 KiB
C++

/*********************************************************************************/
/*!
@file Piano.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 <cstring>
#include "Util.h"
#include "Piano.h"
#include "Cfg.h"
#include "StavePosition.h"
#include "Settings.h"
#define PIANO_LINE_LENGTH_LONG 64
#define PIANO_LINE_LENGTH_SHORT 48
static const float minNameGap = 14.0;
void CPiano::spaceNoteBunch(unsigned int bottomIndex, unsigned int topIndex)
{
const long long range = static_cast<long long>(topIndex) - static_cast<long long>(bottomIndex);
if (range <= 1)
return;
const long long midPoint = range/2 + bottomIndex;
if (midPoint >= arraySizeAs<long long>(m_noteNameList))
return;
float gap;
if (range%2 == 0) // test for an even number of notes
{
gap = m_noteNameList[midPoint].posY - m_noteNameList[midPoint -1].posY;
if (gap < minNameGap)
{
gap = (minNameGap - gap)/2;
m_noteNameList[midPoint].posY += gap; // then move the middle two notes apart by the same amount
m_noteNameList[midPoint-1].posY -= gap;
}
}
// Search from the middle upwards
float lastY = m_noteNameList[midPoint].posY;
for (auto i = midPoint + 1; i < static_cast<int>(topIndex); i++)
{
gap = m_noteNameList[i].posY - lastY;
// If the gap is too small make it bigger
if (gap < minNameGap)
m_noteNameList[i].posY = lastY + minNameGap;
lastY = m_noteNameList[i].posY;
}
lastY = m_noteNameList[midPoint].posY;
// now go the other way
for (auto i = midPoint - 1; i >= 0; i--)
{
gap = lastY - m_noteNameList[i].posY;
if (gap < minNameGap)
m_noteNameList[i].posY = lastY - minNameGap;
lastY = m_noteNameList[i].posY;
}
}
void CPiano::drawPianoInputNoteNames()
{
unsigned int i;
if (m_noteNameListLength >8) // too many notes so don't name any of them
return;
for (i = 0; i < m_noteNameListLength; i++)
{
drawNoteName(m_noteNameList[i].pitch, Cfg::playZoneX() - PIANO_LINE_LENGTH_SHORT - 14, m_noteNameList[i].posY, m_noteNameList[i].type);
}
}
void CPiano::drawPianoInputLines(CChord* chord, CColor color, int lineLength)
{
int i;
drColor(color);
CStavePos stavePos;
for ( i = 0; i < chord->length(); i++)
{
if (!m_rhythmTapping)
{
int pitch = chord->getNote(i).pitch();
stavePos.notePos(chord->getNote(i).part(), pitch);
glLineWidth (3.0);
if (stavePos.getAccidental() != 0)
{
glEnable (GL_LINE_STIPPLE);
glLineStipple (1, 0x0f0f); /* dashed */
glLineWidth (3.0);
}
float posY = stavePos.getPosYAccidental();
oneLine(Cfg::playZoneX() - static_cast<float>(lineLength), posY, Cfg::playZoneX(), posY);
glDisable (GL_LINE_STIPPLE);
}
else
{
// draw a vertical line instead
whichPart_t hand = chord->getNote(i).part();
CStavePos top = CStavePos(hand, 6);
CStavePos bottom = CStavePos(hand, -6);
glLineWidth (3.0);
oneLine(Cfg::playZoneX(), top.getPosY(), Cfg::playZoneX(), bottom.getPosY());
}
}
}
void CPiano::spaceNoteNames()
{
unsigned int bottomIndex;
unsigned int topIndex;
bool foundBunch = false;
m_noteNameList[0].posY = m_noteNameList[0].posYOriginal;
if (m_noteNameListLength <= 1)
return;
bottomIndex = m_noteNameListLength;
topIndex = 0;
foundBunch = false;
for (unsigned int i=1; i < m_noteNameListLength; i++)
{
m_noteNameList[i].posY = m_noteNameList[i].posYOriginal;
if (m_noteNameList[i].posY - m_noteNameList[i-1].posY < minNameGap)
{
if (bottomIndex>i) bottomIndex = i;
if (topIndex<i) topIndex = i;
foundBunch = true;
}
else
{
if (foundBunch == true)
spaceNoteBunch(bottomIndex -1, topIndex +1);
// reset everything ready for the next bunch
bottomIndex = m_noteNameListLength;
topIndex = 0;
foundBunch = false;
}
}
if (foundBunch == true)
spaceNoteBunch(bottomIndex -1, topIndex +1);
// finally try again in case moving things have created a fresh bunch
spaceNoteBunch(0, m_noteNameListLength);
}
void CPiano::addNoteNameItem(float posY, int pitch, int type)
{
if (m_noteNameListLength >= arraySizeAs<decltype(m_noteNameListLength)>(m_noteNameList))
return;
noteNameItem_t noteNameItem;
noteNameItem.posY = noteNameItem.posYOriginal = posY;
noteNameItem.type = type;
noteNameItem.pitch = pitch;
// Sort the entries low to high
int i;
for (i = static_cast<int>(m_noteNameListLength) - 1; i >= 0; i--)
{
if (m_noteNameList[i].pitch <= pitch)
break;
// move the previous entry up one position
m_noteNameList[i+1] = m_noteNameList[i];
}
if (m_noteNameList[i].pitch == pitch)
return; // ignore duplicates
m_noteNameList[i+1] = noteNameItem;
m_noteNameListLength++;
spaceNoteNames();
}
void CPiano::addPianistNote(whichPart_t part, CMidiEvent midiNote, bool good)
{
CStavePos stavePos;
float posY;
if ( midiNote.velocity() == -1 )
return;
int note = midiNote.note();
stavePos.notePos(part, note);
if (stavePos.getStaveIndex() >= MAX_STAVE_INDEX || stavePos.getStaveIndex() <= MIN_STAVE_INDEX )
return;
if (good)
m_goodChord.addNote(part, note);
else
m_badChord.addNote(part, note);
posY = stavePos.getPosYAccidental();
addNoteNameItem(posY, note, 0);
}
void CPiano::removeNoteNameItem(int pitch)
{
unsigned int i;
bool foundMatch = false;
for (i=0; i < m_noteNameListLength; i++)
{
if (foundMatch == true)
m_noteNameList[i-1] = m_noteNameList[i]; // found a match so move every thing else up
if (m_noteNameList[i].pitch == pitch)
foundMatch = true;
}
if (m_noteNameListLength>0 && foundMatch)
m_noteNameListLength--;
spaceNoteNames();
}
// returns true only if the note is in the bad note list
bool CPiano::removePianistNote(int note)
{
removeNoteNameItem( note);
m_goodChord.removeNote(note);
return m_badChord.removeNote(note);
}
void CPiano::noteNameListClear()
{
m_noteNameListLength = 0;
}
// Counts the number of notes the pianist has down
int CPiano::pianistAllNotesDown()
{
return m_goodChord.length() + m_badChord.length();
}
int CPiano::pianistBadNotesDown()
{
return m_badChord.length();
}
void CPiano::clear()
{
m_goodChord.clear();
m_badChord.clear();
noteNameListClear();
for (auto &chord : m_savedChordLookUp)
chord.pitchKey = 0;
}
void CPiano::drawPianoInput()
{
bool showNoteName = m_settings->showNoteNames();
int lineLength = (showNoteName) ? PIANO_LINE_LENGTH_SHORT : PIANO_LINE_LENGTH_LONG;
const auto &colorTheme = Cfg::colorTheme();
if (m_goodChord.length() > 0)
drawPianoInputLines(&m_goodChord, colorTheme.pianoGoodColor, lineLength);
if (m_badChord.length() > 0)
drawPianoInputLines(&m_badChord, colorTheme.pianoBadColor, lineLength);
if (showNoteName)
drawPianoInputNoteNames();
}
void CPiano::addSavedChord(CMidiEvent midiNote, CChord chord)
{
int key = midiNote.note();
for (auto &savedChord : m_savedChordLookUp) {
if (midiNote.type() == MIDI_NOTE_ON)
{
if (savedChord.pitchKey == 0 )
{
savedChord.pitchKey = key;
savedChord.savedNoteOffChord = chord;
return;
}
}
else if (midiNote.type() == MIDI_NOTE_OFF)
{
if (savedChord.pitchKey == key )
{
savedChord.pitchKey = 0;
return;
}
}
}
m_savedChordLookUp[0].savedNoteOffChord = chord;
}
CChord CPiano::removeSavedChord(int key)
{
int i = 0;
for (; i < arraySize(m_savedChordLookUp); ++i)
{
if (m_savedChordLookUp[i].pitchKey == key )
{
m_savedChordLookUp[i].pitchKey = 0;
return m_savedChordLookUp[i].savedNoteOffChord;
}
}
--i;
m_savedChordLookUp[i].savedNoteOffChord.clear();
return m_savedChordLookUp[i].savedNoteOffChord;
}