//*********************************************************
// CMPT365 - 1087 - Multimedia
// Term Project - MIDI Star
// Amory Kai Chee Wong
// November 27, 2008
// Version 1.0 - Release May 2009
/*********************************************************
Copyright (c) 2009, Amory Kai Chee Wong
All rights reserved.
Code Project Open License 1.02
*********************************************************/
//*********************************************************
// Class to keep track of input mappings
#include "stdafx.h"
#include "Mapper.h"
Mapper *Mapper::gMapper = NULL;
//*********************************************************
// Protected constructor - singleton class
Mapper::Mapper()
{
this->mDevice = 0;
this->InitMaps();
}
//*********************************************************
Mapper::~Mapper()
{
}
//*********************************************************
// Get pointer to singleton, create if necessary
Mapper *Mapper::Map()
{
if (gMapper == NULL)
gMapper = new Mapper();
return gMapper;
}
//*********************************************************
// Safely clean up all the allocated memory
void Mapper::Destroy()
{
SAFE_DELETE(gMapper);
}
//*********************************************************
// All MIDI notes are automatically mapped to themselves by default
// All keyboard and game buttons are mapped to nothing by default
void Mapper::InitMaps()
{
for (UCHAR i=0; i<MAX_MAPS; i++)
{
this->mUse[i] = false;
this->mKeyCodes[i] = 0;
this->mMIDICodes[i] = i;
this->mGamepadCodes[i] = 0xFF;
}
this->mLastIndex = 0;
}
//*********************************************************
// Clear all the keyboard mappings
void Mapper::ClearKeys()
{
for (int i=0; i<MAX_MAPS; i++)
this->mKeyCodes[i] = 0;
}
//*********************************************************
// Add a playable note from MIDI file
UCHAR Mapper::AddIndex(UCHAR note, int count)
{
this->mNoteIndices[this->mLastIndex] = note;
this->mNoteCount[note] = count;
return this->mLastIndex++;
}
//*********************************************************
// Add a playable note from MIDI file
UCHAR Mapper::AddIndex(UCHAR note)
{
this->mNoteIndices[this->mLastIndex] = note;
return this->mLastIndex++;
}
//*********************************************************
// Get the playable note index of note
UCHAR Mapper::NoteIndex(UCHAR note)
{
for (UCHAR i=0; i<this->mLastIndex; i++)
if (this->mNoteIndices[i] == note)
return i;
return 0xFF;
}
//*********************************************************
// swap playable note indices
void Mapper::SwapIndices(UCHAR ind1, UCHAR ind2)
{
UCHAR note1 = this->mNoteIndices[ind1];
this->mNoteIndices[ind1] = this->mNoteIndices[ind2];
this->mNoteIndices[ind2] = note1;
}
//*********************************************************
// set MIDI code of playable note index
// automatically swaps MIDI code matching MIDI code
void Mapper::MIDICode(UCHAR ind, UCHAR code)
{
for (int i=0; i<MAX_MAPS; i++)
if (this->mMIDICodes[i] == code)
this->mMIDICodes[i] = this->mMIDICodes[this->mNoteIndices[ind]];
this->mMIDICodes[this->mNoteIndices[ind]] = code;
}
//*********************************************************
// find playable note index of keycode
UCHAR Mapper::FindKey(int code)
{
for (UCHAR i=0; i<this->mLastIndex; i++)
if (this->mKeyCodes[this->mNoteIndices[i]] == code)
return i;
return 0xFF;
}
//*********************************************************
// find playable note index of MIDI code
UCHAR Mapper::FindMIDI(UCHAR code)
{
for (UCHAR i=0; i<this->mLastIndex; i++)
if (this->mMIDICodes[this->mNoteIndices[i]] == code)
return i;
return 0xFF;
}
//*********************************************************
// find playable note index of gamepad button number
UCHAR Mapper::FindGamepad(UCHAR code)
{
for (UCHAR i=0; i<this->mLastIndex; i++)
if (this->mGamepadCodes[this->mNoteIndices[i]] == code)
return i;
return 0xFF;
}
//*********************************************************
// find playable note of keycode
UCHAR Mapper::CodeKey(int code)
{
for (UCHAR i=0; i<128; i++)
if (this->mKeyCodes[i] == code)
return i;
return 0xFF;
}
//*********************************************************
// find playable note of MIDI code
UCHAR Mapper::CodeMIDI(UCHAR code)
{
for (UCHAR i=0; i<128; i++)
if (this->mMIDICodes[i] == code)
return i;
return 0xFF;
}
//*********************************************************
// find playable note of gamepad button number
UCHAR Mapper::CodeGamepad(UCHAR code)
{
for (UCHAR i=0; i<128; i++)
if (this->mGamepadCodes[i] == code)
return i;
return 0xFF;
}
//*********************************************************
// find the index of the next playable note
UCHAR Mapper::GetUsedInd(UCHAR start)
{
do
{
start++;
if (start >= this->mLastIndex)
return 0xFF;
} while (!this->mUse[this->mNoteIndices[start]]);
return start;
}
//*********************************************************
// return a string of the note count for playable note index
TCHAR *Mapper::NoteCountS(UCHAR ind)
{
_tcscpy_s(this->mNoteCountStr, 16, TPrint(_T("%d"), this->mNoteCount[this->mNoteIndices[ind]]));
return this->mNoteCountStr;
}
//*********************************************************
// return marked string for playable note index
TCHAR *Mapper::UseS(UCHAR ind)
{
if (this->mUse[this->mNoteIndices[ind]])
return _T("use");
return TNull;
}
//*********************************************************
// return MIDI code string for playable note index
TCHAR *Mapper::MIDIS(UCHAR ind)
{
if (this->mMIDICodes[this->mNoteIndices[ind]] == this->mNoteIndices[ind])
return TNull;
_tcscpy_s(this->mMIDIStr, 16, TPrint(_T("%d"), this->mMIDICodes[this->mNoteIndices[ind]]));
return this->mMIDIStr;
}
//*********************************************************
// return gamepad button number string for playable note index
TCHAR *Mapper::GamepadS(UCHAR ind)
{
if (this->mGamepadCodes[this->mNoteIndices[ind]] == 0xFF)
return TNull;
_tcscpy_s(this->mGamepadStr, 16, TPrint(_T("%d"), this->mGamepadCodes[this->mNoteIndices[ind]]));
return this->mGamepadStr;
}
//*********************************************************
// load mapping from file
// overwrite the playable note order if applicable (these are set by the track)
bool Mapper::LoadMap(LPCTSTR filename)
{
CStdioFile file;
UCHAR noteOrder[MAX_MAPS]; // copy the playable list
UCHAR saveLast = this->LastIndex();
for (UCHAR i=0; i<this->LastIndex(); i++)
noteOrder[i] = this->GetNote(i);
TRY
{
if (!file.Open(filename, CFile::modeRead|CFile::typeText))
{
MsgBox(_T("Error"), _T("Unable to open file %s"), filename);
return false;
}
TCHAR line[1024];
for (UCHAR i=0; i<MAX_MAPS; i++)
{
file.ReadString(line, 1024-1);
TCHAR *e1 = _tcschr(line, ',');
TCHAR *e2 = _tcschr(e1+1, ',');
TCHAR *e3 = _tcschr(e2+1, ',');
TCHAR *e4 = _tcschr(e3+1, ',');
if (e4 == NULL)
{
errorout:
MsgBox(_T("Error"), _T("The file format is incompatible %s"), filename);
this->InitMaps();
file.Close();
return false;
}
*e1 = 0;
*e2 = 0;
*e3 = 0;
*e4 = 0;
if (_tcscmp(line, NotesStr[i]) != 0)
goto errorout;
if (_tcscmp(e1+1, _T("use")) == 0)
this->mUse[i] = true;
else if (_tcscmp(e1+1, _T("not")) == 0)
this->mUse[i] = false;
else
goto errorout;
if (e3-e2 == 1)
this->mKeyCodes[i] = 0;
else
this->mKeyCodes[i] = _ttoi(e2+1);
this->mMIDICodes[i] = (UCHAR)_ttoi(e3+1);
this->mGamepadCodes[i] = (UCHAR)_ttoi(e4+1);
}
// read in order of notes used
file.ReadString(line, 1023);
TCHAR *e1 = line;
UCHAR ind = 0;
while (*e1 != 0)
{
TCHAR *e2 = _tcschr(e1, ',');
if (e2 == NULL)
break;
*e2 = 0;
this->mNoteIndices[ind++] = (UCHAR)_ttoi(e1);
e1 = e2+1;
}
this->mLastIndex = ind;
file.Close();
bool match = (saveLast == this->LastIndex());
if (match)
for (int i=0; i<saveLast; i++)
match &= (this->NoteIndex(noteOrder[i]) != 0xFF);
if (!match)
{
// Notes do not match, so restore old note order
this->mLastIndex = 0;
for (UCHAR i=0; i<saveLast; i++)
this->AddIndex(noteOrder[i]);
}
return true;
}
CATCH (CFileException, e)
{
this->mLastIndex = 0;
for (UCHAR i=0; i<saveLast; i++)
this->AddIndex(noteOrder[i]);
MsgBox(_T("Error"), _T("Unable to read file %s: %s"), filename, e->m_cause);
return false;
}
END_CATCH
}
//*********************************************************
// save mapping to file
bool Mapper::SaveMap(LPCTSTR filename)
{
CStdioFile file;
TRY
{
if (!file.Open(filename, CFile::modeCreate|CFile::modeWrite|CFile::typeText))
{
MsgBox(_T("Error"), _T("Unable to create file %s"), filename);
return false;
}
for (int i=0; i<MAX_MAPS; i++)
{
if (this->mUse[i])
_tcscpy_s(this->mUseStr, 16, _T("use"));
else
_tcscpy_s(this->mUseStr, 16, _T("not"));
file.WriteString(TPrint(_T("%s,%s,%d,%d,%d\n"), NotesStr[i], this->mUseStr,
this->mKeyCodes[i], this->mMIDICodes[i], this->mGamepadCodes[i]));
}
// write out order of notes used
for (int i=0; i<this->mLastIndex; i++)
file.WriteString(TPrint(_T("%d,"), this->mNoteIndices[i]));
file.WriteString(_T("\n"));
file.Close();
return true;
}
CATCH (CFileException, e)
{
MsgBox(_T("Error"), _T("Unable to create file %s: %s"), filename, e->m_cause);
return false;
}
END_CATCH
}
//*********************************************************
// Calculate range for display purposes
// Calculate whether the notes are in ascending order (if not, force gaming style)
// Calculate the number of playable notes
void Mapper::FindRange(int &low, int &high, bool &inorder, int &count)
{
low = 127;
high = 0;
inorder = true;
count = 0;
int last = -1;
for (int i=0; i<this->mLastIndex; i++)
{
if (this->mUse[this->mNoteIndices[i]])
{
count++;
if (this->mNoteIndices[i] < low)
low = this->mNoteIndices[i];
if (this->mNoteIndices[i] > high)
high = this->mNoteIndices[i];
if (i > 0 && this->mNoteIndices[i] < this->mNoteIndices[last])
inorder = false;
last = i;
}
}
this->mLowNote = low;
this->mHighNote = high;
this->mUNoteCount = count;
this->mNotesInOrder = inorder;
}