Click here to Skip to main content
15,886,919 members
Articles / Desktop Programming / MFC

Playing Midi Files with DirectMusic

Rate me:
Please Sign up or sign in to vote.
4.83/5 (17 votes)
30 Dec 2010LGPL34 min read 376.8K   6.4K   64  
A DirectX 8 class to perform Midi music playback
/*
Module : MidiPlayerDlg.cpp
Purpose: Defines the code implementation for the CMidiPlayerDlg class
Created: CJP / 20-09-2001
History: CJP / 23-01-2002 
	
	1. Started the new version of MidiPlayer with MFC 

	2. Fixed bugs with the combo list and the midi port selection

	3. Fixed problems with the player playback states

	4. Increased the maximum number of midi ports

Copyright (c) 2001 by C. Jim�nez de Parga  
All rights reserved.
*/


#include "stdafx.h"
#include "MidiPlayer.h"
#include "MidiPlayerDlg.h"
#include "About.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMidiPlayerDlg dialog

CMidiPlayerDlg::CMidiPlayerDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CMidiPlayerDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CMidiPlayerDlg)
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_pMidi=new CMidiMusic; // Create the class dinamically
	m_b3D=TRUE;				// Start in 3d mode
	m_bRepeat=FALSE;
	m_nPosX=m_nPosY=m_nPosZ=0;
	State=INIT;
}

void CMidiPlayerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CMidiPlayerDlg)
	DDX_Control(pDX, IDC_CHECK_CENTERZ, m_CenterZ);
	DDX_Control(pDX, IDC_CHECK_CENTERY, m_CenterY);
	DDX_Control(pDX, IDC_CHECK_CENTERX, m_CenterX);
	DDX_Control(pDX, IDC_PROGRESS, m_Progress);
	DDX_Control(pDX, IDC_TEMPO, m_SliderTempo);
	DDX_Control(pDX, IDC_VOLUME, m_SliderVolume);
	DDX_Control(pDX, IDC_Z, m_SliderZ);
	DDX_Control(pDX, IDC_Y, m_SliderY);
	DDX_Control(pDX, IDC_X, m_SliderX);
	DDX_Control(pDX, IDC_REVERB, m_ReverbButton);
	DDX_Control(pDX, IDC_PORTS, m_PortList);
	DDX_Control(pDX, IDC_3D, m_3DButton);
	DDX_Control(pDX, IDC_REPLAY, m_ReplayButton);
	DDX_Control(pDX, IDC_BACKWARD, m_BackwardButton);
	DDX_Control(pDX, IDC_STOP, m_StopButton);
	DDX_Control(pDX, IDC_ABOUT, m_AboutButton);
	DDX_Control(pDX, IDC_FORWARD, m_ForwardButton);
	DDX_Control(pDX, IDC_PAUSE, m_PauseButton);
	DDX_Control(pDX, IDC_OPEN, m_OpenButton);
	DDX_Control(pDX, IDC_PLAY, m_PlayButton);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CMidiPlayerDlg, CDialog)
	//{{AFX_MSG_MAP(CMidiPlayerDlg)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_3D, On3d)
	ON_BN_CLICKED(IDC_OPEN, OnOpen)
	ON_WM_CLOSE()
	ON_BN_CLICKED(IDC_PLAY, OnPlay)
	ON_CBN_SELCHANGE(IDC_PORTS, OnSelchangePorts)
	ON_BN_CLICKED(IDC_STOP, OnStop)
	ON_BN_CLICKED(IDC_PAUSE, OnPause)
	ON_BN_CLICKED(IDC_FORWARD, OnForward)
	ON_BN_CLICKED(IDC_BACKWARD, OnBackward)
	ON_BN_CLICKED(IDC_REVERB, OnReverb)
	ON_WM_VSCROLL()
	ON_WM_HSCROLL()
	ON_BN_CLICKED(IDC_REPLAY, OnReplay)
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_ABOUT, OnAbout)
	ON_BN_CLICKED(IDC_CHECK_CENTERX, OnCheckCenterx)
	ON_BN_CLICKED(IDC_CHECK_CENTERY, OnCheckCentery)
	ON_BN_CLICKED(IDC_CHECK_CENTERZ, OnCheckCenterz)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CMidiPlayerDlg message handlers

// Entry point to the dialog 

BOOL CMidiPlayerDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here
	// Set the pictures on the buttons
	SetPictures();
	
	// Initialize the library, the controls and set a timer for player events notifiaction 
	if (InitMidiLibrary())
	{
		PopulateMidiPorts();
		InitControls();
		SetTimer(1,100,NULL);
	} else 
	{	
		delete m_pMidi;
		EndDialog(0);
	}
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}


// Member function that initialize the parameters of the controls 

bool CMidiPlayerDlg::InitControls()
{
	m_3DButton.SetCheck(1);
	m_SliderX.SetRange(-299,299,TRUE);
	m_SliderX.SetPos(0);
	m_SliderY.SetRange(-299,299,TRUE);
	m_SliderY.SetPos(0);
	m_SliderZ.SetRange(-299,299,TRUE);
	m_SliderZ.SetPos(0);
	m_SliderTempo.SetRange(0,8,TRUE);
	m_SliderTempo.SetPos(4);
	m_pMidi->SetMaxDistance(D3DVALUE(5.2));
	m_pMidi->SetMode(DS3DMODE_HEADRELATIVE);
	m_pMidi->SetPosition(0,0,0);
	m_Display.InitDisplay(this,8,15,157,75);
	m_Display.SetText(this,"Tick count:",5,5,RGB(255,255,0));
	m_Display.SetText(this,"Song title:",5,25,RGB(255,255,0));
	m_SliderVolume.SetRange(m_Volume.GetMinimalVolume(),m_Volume.GetMaximalVolume(),TRUE);
	m_SliderVolume.SetPos(m_Volume.GetCurrentVolume());
	m_CenterX.SetCheck(1);
	m_CenterY.SetCheck(1);
	m_CenterZ.SetCheck(1);
	return true;
}

// Member function to initialize the midi library 

bool CMidiPlayerDlg::InitMidiLibrary()
{
	if FAILED(m_pMidi->Initialize(m_b3D)) 
	{
		CString strMsg="Can't initialize DirectMusic objects."\
		"Ensure you have an audio card device and DirectX 8.0 or above"\
		"installed in your system.";
		AfxMessageBox(strMsg);
		return false;
	}
	return true;
}

// Member function to populate the midi ports at start up 

bool CMidiPlayerDlg::PopulateMidiPorts()
{
	DWORD dwcount=0;
    int nSel=-1,nCount=0;		

	while ((m_pMidi->PortEnumeration(dwcount,&Info[dwcount])==S_OK) && (dwcount<MAX_PORTS))
	{
		if (Info[dwcount].dwClass==DMUS_PC_OUTPUTCLASS) 
		{
			m_PortList.AddString(Info[dwcount].szPortDescription);
			m_PortList.SetItemDataPtr(nCount,(LPINFOPORT)&Info[dwcount]);
			if (((Info[dwcount].dwFlags & DMUS_PC_SOFTWARESYNTH)) && (nSel<0))
			{
				m_pMidi->SelectPort(&Info[dwcount]);
				nSel=nCount;
			}
			nCount++;
		}
	dwcount++;
	}
	
	m_PortList.SetCurSel(nSel);
	return TRUE;
} 	


	
// Member function to set the pictures on the buttons 

void CMidiPlayerDlg::SetPictures()
{
	HICON hIcon;

	hIcon=AfxGetApp()->LoadIcon(IDI_ICON1);
	m_OpenButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON2);
	m_ForwardButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON3);
	m_PauseButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON4);
	m_AboutButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON5);
	m_BackwardButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON6);
	m_ReplayButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON7);
	m_StopButton.SetIcon(hIcon);	
	hIcon=AfxGetApp()->LoadIcon(IDI_ICON8);
	m_PlayButton.SetIcon(hIcon);	

}

// Member function called when painting is needed 

void CMidiPlayerDlg::OnPaint() 
{
	
	CPaintDC dc(this);
	dc.BitBlt(m_Display.m_x,m_Display.m_y,m_Display.m_w,m_Display.m_h,
			  &m_Display.m_memDC,0,0,SRCCOPY);
	CDialog::OnPaint();
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.

HCURSOR CMidiPlayerDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

// Member function to handle the 3D button 

void CMidiPlayerDlg::On3d() 
{
	// TODO: Add your control notification handler code here
	m_b3D = !m_b3D;
	m_3DButton.SetCheck(m_b3D);
	
}


// Member class called when a file is requiered 

void CMidiPlayerDlg::OnOpen() 
{
	// TODO: Add your control notification handler code here
	char szFilters[]="Midi Files (*.mid)|*.mid||";
	char szCurdir[_MAX_PATH]; 
	CFileDialog CFileDlg (TRUE, "Mid", "*.mid",OFN_FILEMUSTEXIST, szFilters, this);
	GetCurrentDirectory(_MAX_PATH,szCurdir);
	CFileDlg.m_ofn.lpstrInitialDir=szCurdir;
	
	// If file selected then proceed to load and check it
	if(CFileDlg.DoModal() == IDOK)    
	{ 
		if (State!=INIT) m_pMidi->Stop();
		
		State=LOADING;
		
		if (strstr(CFileDlg.m_ofn.lpstrFile,".mid")) 
			m_bMidiFile=TRUE;
		else m_bMidiFile=FALSE;
		
		if FAILED(m_pMidi->LoadMidiFromFile(CFileDlg.m_ofn.lpstrFile))
		{
			State=INIT;
			AfxMessageBox("Error loading file");
			return;
		}
		
		// Set the controls' parameters depending on the segment length
		strSongSel=CFileDlg.m_ofn.lpstrFile;
		m_pMidi->GetLength(&mtTotalTicks);
		m_Progress.SetRange32(0,mtTotalTicks);
		mtIncrement=mtTotalTicks/20;
		m_Display.EraseText(this,"                                                                                ",
		5,40);
		m_Display.SetText(this,CFileDlg.m_ofn.lpstrFileTitle,5,40,RGB(0,200,255));
		State=LOADED;
	}
}


// Handle the closing message

void CMidiPlayerDlg::OnClose() 
{
	// TODO: Add your message handler code here and/or call default
	KillTimer(1);
	delete m_pMidi;
	CDialog::OnClose();
}

// Handle the play button

void CMidiPlayerDlg::OnPlay() 
{
	// TODO: Add your control notification handler code here
	if (State!=INIT)
	{
		if (State!=PAUSED) 
		{	
			m_pMidi->Stop();
			m_Display.EraseText(this,"999999999999",65,5);
		}
		m_pMidi->Play();
		State=PLAYING;
	}
}


// This member function is called when the user changes the midi port

void CMidiPlayerDlg::OnSelchangePorts() 
{
	// TODO: Add your control notification handler code here
	int nSel;
	INFOPORT InfoSel;
	
	OnStop();
	
	nSel=m_PortList.GetCurSel();	// Get the item index on the list 
	KillTimer(1);
	delete m_pMidi;					// Delete the object CMidiMusic
	
	m_pMidi=new CMidiMusic();			// Create a new CMidiMusic object 
	
	InfoSel=*(LPINFOPORT)m_PortList.GetItemDataPtr(nSel); // Get a pointer to INFOPORT item in the array
	
	if (!(InfoSel.dwFlags & DMUS_PC_SOFTWARESYNTH))		  // Check synthesizer mode	
	{
		m_b3D=FALSE;
		m_3DButton.SetCheck(0);
	}
	
	m_pMidi->Initialize(m_b3D);							 // Initialize the object 
	m_pMidi->SelectPort(&InfoSel);						 // Set the selected port 	

	if (!strSongSel.IsEmpty())							 // If there was already a file selected 
	{		
		m_pMidi->LoadMidiFromFile(strSongSel);	 
		State=LOADED;
	}

	m_SliderTempo.SetPos(4);	
	
	SetTimer(1,100,NULL);								// Set the timer again
}


// On Stop handler 

void CMidiPlayerDlg::OnStop() 
{
	// TODO: Add your control notification handler code here
	if (State==PLAYING)
	{
		m_pMidi->Stop();	
		State=STOPPED;
	}
}

// On Pause handler 

void CMidiPlayerDlg::OnPause() 
{
	// TODO: Add your control notification handler code here
	if (State==PLAYING)
	{
		m_pMidi->Pause();	
		State=PAUSED;
	}
	else 
	if (State==PAUSED)
		OnPlay();
			
}


// Member function to handle the forward button 

void CMidiPlayerDlg::OnForward() 
{
	// TODO: Add your control notification handler code here
	if (State==PLAYING)
	{
		MUSIC_TIME mtNow;
		m_pMidi->GetSeek(&mtNow);
		mtNow+=mtIncrement;
		if (mtNow<mtTotalTicks) m_pMidi->Seek(mtNow); 
		else m_pMidi->Seek(mtIncrement);
		m_pMidi->Play();
	}
}

// Member function to handle the rewind button 

void CMidiPlayerDlg::OnBackward() 
{
	// TODO: Add your control notification handler code here
	if (State==PLAYING)
	{
		MUSIC_TIME mtNow;
		m_pMidi->GetSeek(&mtNow);
		mtNow-=mtIncrement;
		if (mtNow>0)
			m_pMidi->Seek(mtNow);
		else m_pMidi->Seek(mtTotalTicks-mtIncrement);
		m_pMidi->Play();
	}
}

// Handle the Reverb button 

void CMidiPlayerDlg::OnReverb() 
{
	// TODO: Add your control notification handler code here
	static BOOL bReverb=TRUE;
	m_ReverbButton.SetCheck(bReverb);
	m_pMidi->SetEffect(bReverb,SET_CHORUS | SET_REVERB);
	bReverb= !bReverb;
}


// Control the 3D sliders 

void CMidiPlayerDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	// TODO: Add your message handler code here and/or call default
    char strLabel[5];

	if (pScrollBar==(CScrollBar*)&m_SliderX)
	{
		m_nPosX=(float)m_SliderX.GetPos();
		m_nPosX/=100;
		m_pMidi->SetPosition(m_nPosX,m_nPosY,m_nPosZ);	
		sprintf(strLabel,"%.1f",m_nPosX);
		SendDlgItemMessage(IDC_COUNTER_X,WM_SETTEXT,0,(LPARAM)strLabel);
		m_CenterX.SetCheck(0);
	}
	else if (pScrollBar==(CScrollBar*)&m_SliderY)
	{
		m_nPosY=(float)m_SliderY.GetPos();
		m_nPosY/=100;
		m_pMidi->SetPosition(m_nPosX,m_nPosY,m_nPosZ);	
		sprintf(strLabel,"%.1f",m_nPosY);
		SendDlgItemMessage(IDC_COUNTER_Y,WM_SETTEXT,0,(LPARAM)strLabel);
		m_CenterY.SetCheck(0);
	}
	else if (pScrollBar==(CScrollBar*)&m_SliderZ)
	{
		m_nPosZ=(float)m_SliderZ.GetPos();
		m_nPosZ/=100;
		m_pMidi->SetPosition(m_nPosX,m_nPosY,m_nPosZ);	
		sprintf(strLabel,"%.1f",m_nPosZ);
		SendDlgItemMessage(IDC_COUNTER_Z,WM_SETTEXT,0,(LPARAM)strLabel);
		m_CenterZ.SetCheck(0);
	}
	CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}

// Handle the volume and tempo sliders 

void CMidiPlayerDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	// TODO: Add your message handler code here and/or call default
	int nVolume;
	float nTempo;
	if (pScrollBar==(CScrollBar*)&m_SliderVolume)
	{
		nVolume=m_SliderVolume.GetPos();
		m_Volume.SetCurrentVolume(nVolume);
	}
	else if (pScrollBar==(CScrollBar*)&m_SliderTempo)
	{
		nTempo=(float)m_SliderTempo.GetPos();
		nTempo=nTempo/4;
		m_pMidi->SetMasterTempo(nTempo);
	}	
		CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}


// Toggle the file repeat function 

void CMidiPlayerDlg::OnReplay() 
{
	// TODO: Add your control notification handler code here
	m_bRepeat=!m_bRepeat; 
	(m_bRepeat) ? m_Display.SetText(this,"Replay activated",5,55,RGB(0,255,0)) :
		m_Display.EraseText(this,"Replay Activated",5,55);
}

// Timer message handler 

void CMidiPlayerDlg::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	switch(State)
	{
	case STOPPED:
		m_Display.SetText(this,"Stopped    ",100,55,RGB(255,255,255));
		break;
	case PLAYING:
		m_Display.SetText(this,"Playing    ",100,55,RGB(255,255,255));
		break;
	case PAUSED:
		m_Display.SetText(this,"Paused     ",100,55,RGB(255,255,255));
		break;
	case LOADING:	
		m_Display.SetText(this,"Loading    ",100,55,RGB(255,255,255));
		break;
	case LOADED:
		m_Display.SetText(this,"Loaded     ",100,55,RGB(255,255,255));
		break;
	case INIT:
		m_Display.SetText(this,"Initialized",100,55,RGB(255,255,255));
		break;
	default:
		break;
	}

	// In case is playing then display the tick count and check for repeat again 

	if (State==PLAYING)
	{
		MUSIC_TIME mtTicks,mtLength;
		char strTicks[19];
		m_pMidi->GetLength(&mtLength);
		m_pMidi->GetTicks(&mtTicks);
		sprintf(strTicks,"%d",mtTicks);
		strcat(strTicks,"          ");
		m_Display.SetText(this,strTicks,65,5,RGB(0,200,255));
		if ((mtTicks>=mtLength) && (m_bMidiFile))
		{	
			State=STOPPED;
			if (m_bRepeat) OnPlay();
		}
		m_Progress.SetPos(mtTicks);
	}
	CDialog::OnTimer(nIDEvent);
}

// About button handler 

void CMidiPlayerDlg::OnAbout() 
{
	// TODO: Add your control notification handler code here
	CAbout AboutDlg;
	AboutDlg.DoModal();
}


// Center the X-axis 

void CMidiPlayerDlg::OnCheckCenterx() 
{
	// TODO: Add your control notification handler code here
	m_nPosX=0;
	m_pMidi->SetPosition(0,m_nPosY,m_nPosZ);
	m_SliderX.SetPos(0);
	m_CenterX.SetCheck(1);
	SendDlgItemMessage(IDC_COUNTER_X,WM_SETTEXT,0,(LPARAM)"0");
}

// Center the Y-axis

void CMidiPlayerDlg::OnCheckCentery() 
{
	// TODO: Add your control notification handler code here
	m_nPosY=0;
	m_pMidi->SetPosition(m_nPosX,0,m_nPosZ);
	m_SliderY.SetPos(0);
	m_CenterY.SetCheck(1);
	SendDlgItemMessage(IDC_COUNTER_Y,WM_SETTEXT,0,(LPARAM)"0");
}

// Center the Z-axis

void CMidiPlayerDlg::OnCheckCenterz() 
{
	// TODO: Add your control notification handler code here
	m_nPosZ=0;
	m_pMidi->SetPosition(m_nPosX,m_nPosY,0);
	m_SliderZ.SetPos(0);
	m_CenterZ.SetCheck(1);
	SendDlgItemMessage(IDC_COUNTER_Z,WM_SETTEXT,0,(LPARAM)"0");
}

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 GNU Lesser General Public License (LGPLv3)


Written By
Software Developer
Spain Spain
I obtained my PhD degree in Computer Graphics at the National Distance Education University (UNED) in October 2019. I also hold a Ms. degree in Software Engineering and Computer Systems and a Bs. degree in Computer Science from the National Distance Education University (UNED).
I have been employed as a C++ software developer in several companies since year 2000.
I currently work as a Tutor-Professor of Symbolic Logic, Discrete Math and Java Object-Oriented Programming at UNED-Cartagena (Spain) since 2015.

Comments and Discussions