281 lines
7.5 KiB
C++
281 lines
7.5 KiB
C++
/*********************************************************************************/
|
|
/*!
|
|
@file Song.cpp
|
|
|
|
@brief xxxxx.
|
|
|
|
@author L. J. Barman
|
|
|
|
Copyright (c) 2008-2020, L. J. Barman and others, 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 "Song.h"
|
|
#include "Score.h"
|
|
|
|
#include "resources/config.h"
|
|
|
|
void CSong::init2(CScore * scoreWin, CSettings* settings)
|
|
{
|
|
|
|
CNote::reset();
|
|
|
|
this->CConductor::init2(scoreWin, settings);
|
|
|
|
setActiveHand(PB_PART_both);
|
|
setPlayMode(PB_PLAY_MODE_followYou);
|
|
setSpeed(1.0);
|
|
setSkill(3);
|
|
}
|
|
|
|
void CSong::loadSong(const QString & filename)
|
|
{
|
|
CNote::reset();
|
|
|
|
m_songTitle = filename;
|
|
const auto index = m_songTitle.lastIndexOf(QChar('/'));
|
|
if (index >= 0)
|
|
m_songTitle = m_songTitle.right( m_songTitle.length() - index - 1);
|
|
|
|
QString fn = filename;
|
|
#ifdef _WIN32
|
|
fn = fn.replace('/','\\');
|
|
#endif
|
|
auto logLevelOk = false;
|
|
auto logLevel = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_MIDI_FILE_LOG_LEVEL", &logLevelOk);
|
|
ppLogInfo("Opening song %s", fn.toLocal8Bit().data());
|
|
m_midiFile->setLogLevel(logLevelOk ? logLevel : 3);
|
|
m_midiFile->openMidiFile(std::string(fn.toLocal8Bit().data()));
|
|
transpose(0);
|
|
midiFileInfo();
|
|
m_midiFile->setLogLevel(99);
|
|
playMusic(false);
|
|
rewind();
|
|
setPlayFromBar(0.0);
|
|
setLoopingBars(0.0);
|
|
setEventBits(EVENT_BITS_loadSong);
|
|
if (!m_midiFile->getSongTitle().isEmpty())
|
|
m_songTitle = m_midiFile->getSongTitle();
|
|
|
|
}
|
|
|
|
// read the file ahead to collect info about the song first
|
|
void CSong::midiFileInfo()
|
|
{
|
|
m_trackList->reset(m_midiFile->numberOfTracks());
|
|
setTimeSig(0,0);
|
|
CStavePos::setKeySignature( NOT_USED, 0 );
|
|
|
|
// Read the next events to find the active channels
|
|
CMidiEvent event;
|
|
while ( true )
|
|
{
|
|
event = m_midiFile->readMidiEvent();
|
|
m_trackList->examineMidiEvent(event);
|
|
|
|
if (event.type() == MIDI_PB_timeSignature)
|
|
{
|
|
setTimeSig(event.data1(),event.data2());
|
|
}
|
|
|
|
if (event.type() == MIDI_PB_EOF)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CSong::rewind()
|
|
{
|
|
m_midiFile->rewind();
|
|
this->CConductor::rewind();
|
|
m_scoreWin->reset();
|
|
reset();
|
|
forceScoreRedraw();
|
|
}
|
|
|
|
void CSong::setActiveHand(whichPart_t hand)
|
|
{
|
|
if (hand < PB_PART_both)
|
|
hand = PB_PART_both;
|
|
if (hand > PB_PART_left)
|
|
hand = PB_PART_left;
|
|
|
|
this->CConductor::setActiveHand(hand);
|
|
regenerateChordQueue();
|
|
|
|
m_scoreWin->setDisplayHand(hand);
|
|
}
|
|
|
|
void CSong::setActiveChannel(int chan)
|
|
{
|
|
this->CConductor::setActiveChannel(chan);
|
|
m_scoreWin->setActiveChannel(chan);
|
|
regenerateChordQueue();
|
|
}
|
|
|
|
void CSong::setPlayMode(playMode_t mode)
|
|
{
|
|
regenerateChordQueue();
|
|
this->CConductor::setPlayMode(mode);
|
|
forceScoreRedraw();
|
|
}
|
|
|
|
void CSong::regenerateChordQueue()
|
|
{
|
|
int i;
|
|
int length;
|
|
CMidiEvent event;
|
|
|
|
m_wantedChordQueue->clear();
|
|
m_findChord.reset();
|
|
|
|
length = m_songEventQueue->length();
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
event = m_songEventQueue->index(i);
|
|
// Find the next chord
|
|
if (m_findChord.findChord(event, getActiveChannel(), PB_PART_both) == true)
|
|
chordEventInsert( m_findChord.getChord() ); // give the Conductor the chord event
|
|
|
|
}
|
|
resetWantedChord();
|
|
}
|
|
|
|
void CSong::refreshScroll()
|
|
{
|
|
m_scoreWin->refreshScroll();
|
|
forceScoreRedraw();
|
|
}
|
|
|
|
eventBits_t CSong::task(qint64 ticks)
|
|
{
|
|
realTimeEngine(ticks);
|
|
|
|
for (auto scoreHasEnoughSpace = true; !m_reachedMidiEof && scoreHasEnoughSpace;)
|
|
{
|
|
// loop as long as there is space and that the score also has space
|
|
while (midiEventSpace() > 10 && chordEventSpace() > 10 && (scoreHasEnoughSpace = m_scoreWin->midiEventSpace() > 100))
|
|
{
|
|
// Read the next events
|
|
CMidiEvent event = m_midiFile->readMidiEvent();
|
|
|
|
//ppLogTrace("Song event delta %d type 0x%x chan %d Note %d", event.deltaTime(), event.type(), event.channel(), event.note());
|
|
|
|
// Find the next chord
|
|
if (m_findChord.findChord(event, getActiveChannel(), PB_PART_both))
|
|
chordEventInsert( m_findChord.getChord() ); // give the Conductor the chord event
|
|
|
|
// send the events to the other end
|
|
m_scoreWin->midiEventInsert(event);
|
|
|
|
// send the events to the other end
|
|
midiEventInsert(event);
|
|
|
|
if (event.type() == MIDI_PB_EOF)
|
|
{
|
|
m_reachedMidiEof = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// carry on with the data until we reach the bar we want
|
|
if (!seekingBarNumber() || m_reachedMidiEof || !playingMusic())
|
|
break;
|
|
|
|
realTimeEngine(0);
|
|
m_scoreWin->drawScrollingSymbols(false); // don't display any thing just remove from the queue
|
|
}
|
|
|
|
// unset the seeking state (noop if not seeking anyways)
|
|
doneSeekingBarNumber();
|
|
|
|
// return and unset event bits
|
|
const auto eventBits = m_realTimeEventBits;
|
|
m_realTimeEventBits = 0;
|
|
return eventBits;
|
|
}
|
|
|
|
static const struct pcNote_s
|
|
{
|
|
int key;
|
|
int note;
|
|
} pcNoteLookup[] =
|
|
{
|
|
{ 'a', PC_KEY_LOWEST_NOTE },
|
|
{ 'z', 59 }, // B
|
|
{ 'x', 60 }, // Middle C
|
|
{ 'd', 61 },
|
|
{ 'c', 62 }, // D
|
|
{ 'f', 63 },
|
|
{ 'v', 64 }, // E
|
|
{ 'b', 65 }, // F
|
|
{ 'h', 66 },
|
|
{ 'n', 67 }, // G
|
|
{ 'j', 68 },
|
|
{ 'm', 69 }, // A
|
|
{ 'k', 70 },
|
|
{ ',', 71 }, // B
|
|
{ '.', 72 }, // C
|
|
{ ';', 73 },
|
|
{ '/', 74 }, // D
|
|
{ '\'', PC_KEY_HIGHEST_NOTE },
|
|
};
|
|
|
|
// Fakes a midi piano keyboard using the PC keyboard
|
|
bool CSong::pcKeyPress(int key, bool down)
|
|
{
|
|
int i;
|
|
size_t j;
|
|
CMidiEvent midi;
|
|
const int cfg_pcKeyVolume = 64;
|
|
const int cfg_pcKeyChannel = 1-1;
|
|
|
|
if (key == 't') // the tab key on the PC fakes good notes
|
|
{
|
|
if (down)
|
|
m_fakeChord = getWantedChord();
|
|
for (i = 0; i < m_fakeChord.length(); i++)
|
|
{
|
|
if (down)
|
|
midi.noteOnEvent(0, cfg_pcKeyChannel, m_fakeChord.getNote(i).pitch() + getTranspose(), cfg_pcKeyVolume);
|
|
else
|
|
midi.noteOffEvent(0, cfg_pcKeyChannel, m_fakeChord.getNote(i).pitch() + getTranspose(), cfg_pcKeyVolume);
|
|
expandPianistInput(midi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
for (j = 0; j < arraySizeAs<std::size_t>(pcNoteLookup); j++)
|
|
{
|
|
if ( key==pcNoteLookup[j].key)
|
|
{
|
|
if (down)
|
|
midi.noteOnEvent(0, cfg_pcKeyChannel, pcNoteLookup[j].note, cfg_pcKeyVolume);
|
|
else
|
|
midi.noteOffEvent(0, cfg_pcKeyChannel, pcNoteLookup[j].note, cfg_pcKeyVolume);
|
|
|
|
expandPianistInput(midi);
|
|
return true;
|
|
}
|
|
}
|
|
//printf("pcKeyPress %d %d\n", m_pcNote, key);
|
|
return false;
|
|
}
|
|
|