Click here to Skip to main content
15,895,799 members
Articles / Desktop Programming / MFC

MIDI Star

Rate me:
Please Sign up or sign in to vote.
4.86/5 (10 votes)
10 May 2009CPOL19 min read 81.9K   5.7K   48  
An article on parsing MIDI files and using MIDI events.
//*********************************************************
// 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;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Retired
Canada Canada
I was a senior software engineer for Electronic Arts Canada for 20 years. Some of the projects that I have developed are Evolution, Test Drive, NHL '94, NBA Live series and Need For Speed. The platforms that I have developed on are Apple II, C64, Atari 8 bit, PC, NES, SNES, Genesis and XBOX. I have now obtained my teaching certificate for grade school.

Comments and Discussions