请问如何播放处于内存中的MIDI音乐?

Jimy 2000-04-20 09:09:00
如果用MIDIStreamOut来播放,
应该如何将处于内存中的MIDI音乐数据转化为MIDIStreamOut
能够处理的格式?
谢谢!
...全文
1020 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
jxuer 2000-12-29
  • 打赏
  • 举报
回复
重点是 以LPVOID 指针做参数的 Create() 方法
/////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1998 by J鰎g K鰊ig
// All rights reserved
//
// This file is part of the completely free tetris clone "CGTetris".
//
// This is free software.
// You may redistribute it by any means providing it is not sold for profit
// without the authors written consent.
//
// No warrantee of any kind, expressed or implied, is included with this
// software; use at your own risk, responsibility for damages (if any) to
// anyone resulting from the use of this software rests entirely with the
// user.
//
// Send bug reports, bug fixes, enhancements, requests, flames, etc., and
// I'll try to keep a version up to date. I can be reached as follows:
// J.Koenig@adg.de (company site)
// Joerg.Koenig@rhein-neckar.de (private site)
/////////////////////////////////////////////////////////////////////////////


// Midi.h : main header file for the MIDI application
//

// This class is based on the DirectX sample "mstream".

#ifndef MIDI_h
#define MIDI_h

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include <mmsystem.h>
#pragma message("linking with multimedia library")
#pragma comment(lib, "winmm.lib")

#include <vector>
using namespace std;


// This message is sent to the controlling window, if the volume changes in
// another way than explicitly set by the owner of the CMIDI object.
// WPARAM the pointer to the MIDI object
// LPARAM lo-word: the number of the channel that changed volume
// hi-word: the new volume in percent
#define WM_MIDI_VOLUMECHANGED WM_USER+23


#define MIDI_CTRLCHANGE ((BYTE)0xB0) // + ctrlr + value
#define MIDI_PRGMCHANGE ((BYTE)0xC0) // + new patch
#define MIDI_CHANPRESS ((BYTE)0xD0) // + pressure (1 byte)

#define MIDICTRL_VOLUME ((BYTE)0x07)

#define MIDIEVENT_CHANNEL(dw) (dw & 0x0000000F)
#define MIDIEVENT_TYPE(dw) (dw & 0x000000F0)
#define MIDIEVENT_DATA1(dw) ((dw & 0x0000FF00) >> 8)
#define MIDIEVENT_VOLUME(dw) ((dw & 0x007F0000) >> 16)

#define MIDI_SYSEX ((BYTE)0xF0) // SysEx begin
#define MIDI_SYSEXEND ((BYTE)0xF7) // SysEx end
#define MIDI_META ((BYTE)0xFF) // Meta event begin
#define MIDI_META_TEMPO ((BYTE)0x51) // Tempo change
#define MIDI_META_EOT ((BYTE)0x2F) // End-of-track


// flags for the ConvertToBuffer() method
#define CONVERTF_RESET 0x00000001
#define CONVERTF_STATUS_DONE 0x00000001
#define CONVERTF_STATUS_STUCK 0x00000002
#define CONVERTF_STATUS_GOTEVENT 0x00000004

// Return values from the ConvertToBuffer() method
#define CONVERTERR_NOERROR 0 // No error occured
#define CONVERTERR_CORRUPT -101 // The input file is corrupt
// The converter has already encountered a corrupt file and cannot convert any
// more of this file -- must reset the converter
#define CONVERTERR_STUCK -102
#define CONVERTERR_DONE -103 // Converter is done
#define CONVERTERR_BUFFERFULL -104 // The buffer is full
#define CONVERTERR_METASKIP -105 // Skipping unknown meta event

#define STATUS_KILLCALLBACK 100 // Signals that the callback should die
#define STATUS_CALLBACKDEAD 200 // Signals callback is done processing
#define STATUS_WAITINGFOREND 300 // Callback's waiting for buffers to play

// Description of a track
//
struct TRACK
{
DWORD fdwTrack; // Track's flags
DWORD dwTrackLength; // Total bytes in track
LPBYTE pTrackStart; // -> start of track data buffer
LPBYTE pTrackCurrent; // -> next byte to read in buffer
DWORD tkNextEventDue; // Absolute time of next event in track
BYTE byRunningStatus;// Running status from last channel msg

TRACK()
: fdwTrack(0)
, dwTrackLength(0)
, pTrackStart(0)
, pTrackCurrent(0)
, tkNextEventDue(0)
, byRunningStatus(0)
{
}
};

#define ITS_F_ENDOFTRK 0x00000001


// This structure is used to pass information to the ConvertToBuffer()
// system and then internally by that function to send information about the
// target stream buffer and current state of the conversion process to internal
// lower level conversion routines.
struct CONVERTINFO
{
MIDIHDR mhBuffer; // Standard Windows stream buffer header
DWORD dwStartOffset; // Start offset from mhStreamBuffer.lpStart
DWORD dwMaxLength; // Max length to convert on this pass
DWORD dwBytesRecorded;
DWORD tkStart;
BOOL bTimesUp;

CONVERTINFO()
: dwStartOffset(0)
, dwMaxLength(0)
, dwBytesRecorded(0)
, tkStart(0)
, bTimesUp(FALSE)
{
memset(&mhBuffer, 0, sizeof(MIDIHDR));
}
};

// Temporary event structure which stores event data until we're ready to
// dump it into a stream buffer
struct TEMPEVENT
{
DWORD tkEvent; // Absolute time of event
BYTE byShortData[4]; // Event type and parameters if channel msg
DWORD dwEventLength; // Length of data which follows if meta or sysex
LPBYTE pLongData; // -> Event data if applicable
};

class CMIDI
{
protected:
// 三个向量模板,分别为声道、音量和转换信息
typedef vector<TRACK> TrackArray_t;
typedef vector<DWORD> VolumeArray_t;
typedef vector<CONVERTINFO> ConvertArray_t;

enum {
NUM_CHANNELS = 16, // 16 volume channels
VOLUME_INIT = 100, // 100% volume by default
NUM_STREAM_BUFFERS = 2,
OUT_BUFFER_SIZE = 1024, // Max stream buffer size in bytes
DEBUG_CALLBACK_TIMEOUT = 2000,
VOLUME_MIN = 0,
VOLUME_MAX = 127 // == 100%
};

public:
BOOL ACreate(LPCTSTR fileName,CWnd* pWndParent = NULL);
CMIDI();
virtual ~CMIDI();

BOOL Create(LPVOID pSoundData, DWORD dwSize, CWnd * pParent = 0);
BOOL Create(LPCTSTR pszResID, CWnd * pParent = 0);
BOOL Create(UINT uResID, CWnd * pParent = 0);

BOOL Play(BOOL bInfinite = FALSE);
BOOL Stop(BOOL bReOpen = TRUE);
BOOL IsPlaying() const { return m_bPlaying; }

BOOL Pause();
BOOL Continue();
BOOL IsPaused() const { return m_bPaused; }

// Set playback position back to the start
BOOL Rewind();

// Get the number of volume channels
DWORD GetChannelCount() const;

// Set the volume of a channel in percent. Channels are from 0 to (GetChannelCount()-1)
void SetChannelVolume(DWORD channel, DWORD percent);

// Get the volume of a channel in percent
DWORD GetChannelVolume(DWORD channel) const;

// Set the volume for all channels in percent
void SetVolume(DWORD percent);

// Get the average volume for all channels
DWORD GetVolume() const;

// Set the tempo of the playback. Default: 100%
void SetTempo(DWORD percent);

// Get the current tempo in percent (usually 100)
DWORD GetTempo() const;

// You can (un)set an infinite loop during playback.
// Note that "Play()" resets this setting!
void SetInfinitePlay(BOOL bSet = TRUE);

void FreeBuffers();

protected: // implementation
// This function converts MIDI data from the track buffers.
int ConvertToBuffer(DWORD dwFlags, CONVERTINFO * lpciInfo);

// Fills in the event struct with the next event from the track
BOOL GetTrackEvent(TRACK * ptsTrack, TEMPEVENT * pteTemp);

// Retrieve the next byte from the track buffer, refilling the buffer from
// disk if necessary.
BOOL GetTrackByte(TRACK * ptsTrack, LPBYTE lpbyByte) {
if( DWORD(ptsTrack->pTrackCurrent - ptsTrack->pTrackStart) == ptsTrack->dwTrackLength )
return FALSE;
*lpbyByte = *ptsTrack->pTrackCurrent++;
return TRUE;
}

// Attempts to parse a variable length DWORD from the given track.
BOOL GetTrackVDWord(TRACK * ptsTrack, LPDWORD lpdw);

// Put the given event into the given stream buffer at the given location.
int AddEventToStreamBuffer( TEMPEVENT * pteTemp, CONVERTINFO * lpciInfo );

// Opens a MIDI stream. Then it goes about converting the data into a midiStream buffer for playback.
BOOL StreamBufferSetup();




protected: // error handling
// The default implementation writes the error message in the
// debuggers output window. Override if you want a different
// behavior.
virtual void MidiError(MMRESULT Result);

// Failure in converting track into stream.
// The default implementation displays the offset and the total
// number of bytes of the failed track and the error message in
// the debuggers output window.
virtual void TrackError(TRACK *, LPSTR ErrMsg);


protected: // overridables
// NOTE THAT, IF YOU OVERRIDE ONE OF THESE METHODS, YOU MUST CALL
// THE BASE CLASS IMPLEMENTATION TOO!

// called when a MIDI output device is opened
virtual void OnMidiOutOpen();

// called when the MIDI output device is closed
virtual void OnMidiOutClose();

// called when the specified system-exclusive or stream buffer
// has been played and is being returned to the application
virtual void OnMidiOutDone(MIDIHDR &);

// called when a MEVT_F_CALLBACK event is reached in the MIDI output stream
virtual void OnMidiOutPositionCB(MIDIHDR &, MIDIEVENT &);


private: // callback procedure
// This procedure calls the overridables above.
static void CALLBACK MidiProc(HMIDIOUT, UINT, DWORD, DWORD, DWORD);


public: // data members
CMIDI* SplitTrack(DWORD idx);
DWORD m_dwSoundSize;
LPVOID m_pSoundData;
DWORD m_dwFormat;
DWORD m_dwTrackCount;
DWORD m_dwTimeDivision;
BOOL m_bPlaying;
HMIDISTRM m_hStream;
DWORD m_dwProgressBytes;
BOOL m_bLooped;
DWORD m_tkCurrentTime;
DWORD m_dwBufferTickLength;
DWORD m_dwCurrentTempo;
DWORD m_dwTempoMultiplier;
BOOL m_bInsertTempo;
BOOL m_bBuffersPrepared;
int m_nCurrentBuffer;
UINT m_uMIDIDeviceID;
int m_nEmptyBuffers;
BOOL m_bPaused;
UINT m_uCallbackStatus;
HANDLE m_hBufferReturnEvent;
CWnd * m_pWndParent;
TrackArray_t m_Tracks;
VolumeArray_t m_Volumes;
ConvertArray_t m_StreamBuffers;

// data members especially for ConvertToBuffer()
TRACK * m_ptsTrack;
TRACK * m_ptsFound;
DWORD m_dwStatus;
DWORD m_tkNext;
DWORD m_dwMallocBlocks;
TEMPEVENT m_teTemp;
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // MIDI_h


/////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1998 by J鰎g K鰊ig
// All rights reserved
//
// This file is part of the completely free tetris clone "CGTetris".
//
// This is free software.
// You may redistribute it by any means providing it is not sold for profit
// without the authors written consent.
//
// No warrantee of any kind, expressed or implied, is included with this
// software; use at your own risk, responsibility for damages (if any) to
// anyone resulting from the use of this software rests entirely with the
// user.
//
// Send bug reports, bug fixes, enhancements, requests, flames, etc., and
// I'll try to keep a version up to date. I can be reached as follows:
// J.Koenig@adg.de (company site)
// Joerg.Koenig@rhein-neckar.de (private site)
/////////////////////////////////////////////////////////////////////////////


// Midi.cpp
//


// The CMIDI class is based on a sample in the DirectX SDK (mstream)

#include "stdafx.h"
#include "Midi.h"

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


#define MThd 0x6468544D // Start of file
#define MTrk 0x6B72544D // Start of track


#define BUFFER_TIME_LENGTH 60 // Amount to fill in milliseconds


// These structures are stored in MIDI files; they need to be byte aligned.
//
#pragma pack(1)

// Contents of MThd chunk.
struct MIDIFILEHDR
{
WORD wFormat; // Format (hi-lo)
WORD wTrackCount; // # tracks (hi-lo)
WORD wTimeDivision; // Time division (hi-lo)
};

#pragma pack() // End of need for byte-aligned structures


// Macros for swapping hi/lo-endian data
//
#define WORDSWAP(w) (((w) >> 8) | (((w) << 8) & 0xFF00))

#define DWORDSWAP(dw) (((dw) >> 24) | (((dw) >> 8) & 0x0000FF00) | (((dw) << 8) & 0x00FF0000) | (((dw) << 24) & 0xFF000000))


static char gteBadRunStat[] = "Reference to missing running status.";
static char gteRunStatMsgTrunc[]= "Running status message truncated";
static char gteChanMsgTrunc[] = "Channel message truncated";
static char gteSysExLenTrunc[] = "SysEx event truncated (length)";
static char gteSysExTrunc[] = "SysEx event truncated";
static char gteMetaNoClass[] = "Meta event truncated (no class byte)";
static char gteMetaLenTrunc[] = "Meta event truncated (length)";
static char gteMetaTrunc[] = "Meta event truncated";
static char gteNoMem[] = "Out of memory during malloc call";


//////////////////////////////////////////////////////////////////////
// CMIDI -- Construction/Destruction
//////////////////////////////////////////////////////////////////////

CMIDI::CMIDI()
: m_dwSoundSize(0)
, m_pSoundData(0)
, m_dwFormat(0)
, m_dwTrackCount(0)
, m_dwTimeDivision(0)
, m_bPlaying(FALSE)
, m_hStream(0)
, m_dwProgressBytes(0)
, m_bLooped(FALSE)
, m_tkCurrentTime(0)
, m_dwBufferTickLength(0)
, m_dwCurrentTempo(0)
, m_dwTempoMultiplier(100)
, m_bInsertTempo(FALSE)
, m_bBuffersPrepared(FALSE)
, m_nCurrentBuffer(0)
, m_uMIDIDeviceID(MIDI_MAPPER)
, m_nEmptyBuffers(0)
, m_bPaused(FALSE)
, m_uCallbackStatus(0)
, m_hBufferReturnEvent(0)

, m_ptsTrack(0)
, m_ptsFound(0)
, m_dwStatus(0)
, m_tkNext(0)
, m_dwMallocBlocks(0)
{
m_hBufferReturnEvent = ::CreateEvent(0, FALSE, FALSE, TEXT("Wait For Buffer Return"));
ASSERT(m_hBufferReturnEvent != 0);
}

CMIDI::~CMIDI()
{
Stop(FALSE);

if(m_hBufferReturnEvent)
::CloseHandle(m_hBufferReturnEvent);
}


BOOL CMIDI::Create(UINT uResID, CWnd * pWndParent /* = NULL */)
{
return Create(MAKEINTRESOURCE(uResID), pWndParent);
}


BOOL CMIDI::Create(LPCTSTR pszResID, CWnd * pWndParent /* = NULL */)
{
//////////////////////////////////////////////////////////////////
// load resource
HINSTANCE hApp = ::GetModuleHandle(0);
ASSERT(hApp);

HRSRC hResInfo = ::FindResource(hApp, pszResID, TEXT("MIDI"));
if(hResInfo == 0)
return FALSE;

HGLOBAL hRes = ::LoadResource(hApp, hResInfo);
if(hRes == 0)
return FALSE;

//LPVOID pTheSound = ::LockResource(hRes);
//ap changed sentence
LPVOID pTheSound = hRes;
//ap changed sentence
if(pTheSound == 0)
return FALSE;

DWORD dwTheSound = ::SizeofResource(hApp, hResInfo);

return Create(pTheSound, dwTheSound, pWndParent);
}


BOOL CMIDI::Create(LPVOID pSoundData, DWORD dwSize, CWnd * pWndParent /* = NULL */)
{
if( m_pSoundData ) {
// already created
//ASSERT(FALSE);
//return FALSE;
delete m_pSoundData;
m_bPlaying = false;
m_bPlaying = false;
}

ASSERT(pSoundData != 0);
ASSERT(dwSize > 0);

register LPBYTE p = LPBYTE(pSoundData);

// check header of MIDI
if(*(DWORD*)p != MThd) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);

// check header size
DWORD dwHeaderSize = DWORDSWAP(*(DWORD*)p);
if( dwHeaderSize != sizeof(MIDIFILEHDR) ) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);

// get header
MIDIFILEHDR hdr;
::CopyMemory(&hdr, p, dwHeaderSize);
m_dwFormat = DWORD(WORDSWAP(hdr.wFormat));
m_dwTrackCount = DWORD(WORDSWAP(hdr.wTrackCount));
m_dwTimeDivision = DWORD(WORDSWAP(hdr.wTimeDivision));
p += dwHeaderSize;

// create the array of tracks
m_Tracks.resize(m_dwTrackCount);
for(register DWORD i = 0; i < m_dwTrackCount; ++i) {
// check header of track
if(*(DWORD*)p != MTrk) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);

m_Tracks[i].dwTrackLength = DWORDSWAP(*(DWORD*)p);
p += sizeof(DWORD);

m_Tracks[i].pTrackStart = m_Tracks[i].pTrackCurrent = p;
p += m_Tracks[i].dwTrackLength;

// Handle bozo MIDI files which contain empty track chunks
if( !m_Tracks[i].dwTrackLength ) {
m_Tracks[i].fdwTrack |= ITS_F_ENDOFTRK;
continue;
}

// We always preread the time from each track so the mixer code can
// determine which track has the next event with a minimum of work
if( !GetTrackVDWord( &m_Tracks[i], &m_Tracks[i].tkNextEventDue )) {
TRACE0("Error in MIDI data\n");
ASSERT(FALSE);
return FALSE;
}
}


m_pSoundData = pSoundData;
m_dwSoundSize = dwSize;
m_pWndParent = pWndParent;

// allocate volume channels and initialise them
m_Volumes.resize(NUM_CHANNELS, VOLUME_INIT);
/*deleted by anp move it to the play() function
if( ! StreamBufferSetup() ) {
ASSERT(FALSE);
return FALSE;
}
*/
return TRUE;
}


BOOL CMIDI :: Play(BOOL bInfinite /* = FALSE */) {
if( IsPaused() ) {
Continue();
return TRUE;
}

// calling Play() while it is already playing will restart from scratch
if( IsPlaying() )
Stop();

// Clear the status of our callback so it will handle
// MOM_DONE callbacks once more
m_uCallbackStatus = 0;

if( !m_bLooped )
m_bInsertTempo = TRUE;
/*anp added from create() method*/
if( ! StreamBufferSetup() ) {
ASSERT(FALSE);
return FALSE;
}
/**/
MMRESULT mmResult;
if( (mmResult = midiStreamRestart(m_hStream)) != MMSYSERR_NOERROR ) {
MidiError(mmResult);
return FALSE;
}

m_bPlaying = TRUE;
m_bLooped = bInfinite;

return m_bPlaying;
}


BOOL CMIDI :: Stop(BOOL bReOpen /*=TRUE*/) {
MMRESULT mmrRetVal;

if( IsPlaying() || (m_uCallbackStatus != STATUS_CALLBACKDEAD) ) {
m_bPlaying = m_bPaused = FALSE;
if( m_uCallbackStatus != STATUS_CALLBACKDEAD && m_uCallbackStatus != STATUS_WAITINGFOREND )
m_uCallbackStatus = STATUS_KILLCALLBACK;

if( (mmrRetVal = midiStreamStop(m_hStream) ) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return FALSE;
}
if( (mmrRetVal = midiOutReset((HMIDIOUT)m_hStream)) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return FALSE;
}
// Wait for the callback thread to release this thread, which it will do by
// calling SetEvent() once all buffers are returned to it
if( WaitForSingleObject( m_hBufferReturnEvent, DEBUG_CALLBACK_TIMEOUT ) == WAIT_TIMEOUT ) {
// Note, this is a risky move because the callback may be genuinely busy, but
// when we're debugging, it's safer and faster than freezing the application,
// which leaves the MIDI device locked up and forces a system reset...
TRACE0("Timed out waiting for MIDI callback\n");
m_uCallbackStatus = STATUS_CALLBACKDEAD;
}
}

if( m_uCallbackStatus == STATUS_CALLBACKDEAD ) {
m_uCallbackStatus = 0;
FreeBuffers();
if( m_hStream ) {
if( (mmrRetVal = midiStreamClose(m_hStream) ) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
}
m_hStream = 0;
}

if( bReOpen ) {
if( !StreamBufferSetup() ) {
// Error setting up for MIDI file
// Notification is already taken care of...
return FALSE;
}
if( ! m_bLooped ) {
Rewind();
m_dwProgressBytes = 0;
m_dwStatus = 0;
}
}
}
return TRUE;
}


BOOL CMIDI :: Pause() {
if( ! m_bPaused && m_bPlaying && m_pSoundData && m_hStream ) {
midiStreamPause(m_hStream);
m_bPaused = TRUE;
}
return FALSE;
}


BOOL CMIDI :: Continue() {
if( m_bPaused && m_bPlaying && m_pSoundData && m_hStream ) {
midiStreamRestart(m_hStream);
m_bPaused = FALSE;
}
return FALSE;
}


BOOL CMIDI :: Rewind() {
if( ! m_pSoundData )
return FALSE;

for(register DWORD i = 0; i < m_dwTrackCount; ++i) {
m_Tracks[i].pTrackCurrent = m_Tracks[i].pTrackStart;
m_Tracks[i].byRunningStatus = 0;
m_Tracks[i].tkNextEventDue = 0;
m_Tracks[i].fdwTrack = 0;

// Handle bozo MIDI files which contain empty track chunks
if( !m_Tracks[i].dwTrackLength ) {
m_Tracks[i].fdwTrack |= ITS_F_ENDOFTRK;
continue;
}

// We always preread the time from each track so the mixer code can
// determine which track has the next event with a minimum of work
if( !GetTrackVDWord( &m_Tracks[i], &m_Tracks[i].tkNextEventDue )) {
TRACE0("Error in MIDI data\n");
ASSERT(FALSE);
return FALSE;
}
}

return TRUE;
}


DWORD CMIDI :: GetChannelCount() const {
return m_Volumes.size();
}


void CMIDI :: SetVolume(DWORD dwPercent) {
const DWORD dwSize = m_Volumes.size();
for( register DWORD i = 0; i < dwSize; ++i )
SetChannelVolume(i, dwPercent);
}


DWORD CMIDI :: GetVolume() const {
DWORD dwVolume = 0;
const DWORD dwSize = m_Volumes.size();
for( register DWORD i = 0; i < dwSize; ++i )
dwVolume += GetChannelVolume(i);

return dwVolume / GetChannelCount();
}


void CMIDI :: SetChannelVolume(DWORD dwChannel, DWORD dwPercent) {
ASSERT(dwChannel < m_Volumes.size());

if( !m_bPlaying )
return;

m_Volumes[dwChannel] = (dwPercent > 100) ? 100 : dwPercent;
DWORD dwEvent = MIDI_CTRLCHANGE | dwChannel | ((DWORD)MIDICTRL_VOLUME << 8) | ((DWORD)(m_Volumes[dwChannel]*VOLUME_MAX/100) << 16);
MMRESULT mmrRetVal;
if(( mmrRetVal = midiOutShortMsg((HMIDIOUT)m_hStream, dwEvent)) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return;
}
}


DWORD CMIDI :: GetChannelVolume(DWORD dwChannel) const {
ASSERT(dwChannel < GetChannelCount());
return m_Volumes[dwChannel];
}


void CMIDI :: SetTempo(DWORD dwPercent) {
m_dwTempoMultiplier = dwPercent ? dwPercent : 1;
m_bInsertTempo = TRUE;
}


DWORD CMIDI :: GetTempo() const {
return m_dwTempoMultiplier;
}


void CMIDI :: SetInfinitePlay(BOOL bSet) {
m_bLooped = bSet;
}

//////////////////////////////////////////////////////////////////////
// CMIDI -- implementation
//////////////////////////////////////////////////////////////////////

// This function converts MIDI data from the track buffers setup by a
// previous call to ConverterInit(). It will convert data until an error is
// encountered or the output buffer has been filled with as much event data
// as possible, not to exceed dwMaxLength. This function can take a couple
// bit flags, passed through dwFlags. Information about the success/failure
// of this operation and the number of output bytes actually converted will
// be returned in the CONVERTINFO structure pointed at by lpciInfo.
int CMIDI :: ConvertToBuffer(DWORD dwFlags, CONVERTINFO * lpciInfo) {
int nChkErr;

lpciInfo->dwBytesRecorded = 0;

if( dwFlags & CONVERTF_RESET ) {
m_dwProgressBytes = 0;
m_dwStatus = 0;
memset( &m_teTemp, 0, sizeof(TEMPEVENT));
m_ptsTrack = m_ptsFound = 0;
}

// If we were already done, then return with a warning...
if( m_dwStatus & CONVERTF_STATUS_DONE ) {
if( m_bLooped ) {
Rewind();
m_dwProgressBytes = 0;
m_dwStatus = 0;
} else
return CONVERTERR_DONE;
} else if( m_dwStatus & CONVERTF_STATUS_STUCK ) {
// The caller is asking us to continue, but we're already hosed because we
// previously identified something as corrupt, so complain louder this time.
return( CONVERTERR_STUCK );
} else if( m_dwStatus & CONVERTF_STATUS_GOTEVENT ) {
// Turn off this bit flag
m_dwStatus ^= CONVERTF_STATUS_GOTEVENT;

// The following code for this case is duplicated from below, and is
// designed to handle a "straggler" event, should we have one left over
// from previous processing the last time this function was called.

// Don't add end of track event 'til we're done
if( m_teTemp.byShortData[0] == MIDI_META && m_teTemp.byShortData[1] == MIDI_META_EOT ) {
if( m_dwMallocBlocks ) {
delete [] m_teTemp.pLongData;
--m_dwMallocBlocks;
}
} else if(( nChkErr = AddEventToStreamBuffer( &m_teTemp, lpciInfo )) != CONVERTERR_NOERROR ) {
if( nChkErr == CONVERTERR_BUFFERFULL ) {
// Do some processing and tell caller that this buffer's full
m_dwStatus |= CONVERTF_STATUS_GOTEVENT;
return CONVERTERR_NOERROR;
} else if( nChkErr == CONVERTERR_METASKIP ) {
// We skip by all meta events that aren't tempo changes...
} else {
TRACE0("Unable to add event to stream buffer.\n");
if( m_dwMallocBlocks ) {
delete [] m_teTemp.pLongData;
m_dwMallocBlocks--;
}
return( TRUE );
}
}
}

for(;;) {
m_ptsFound = 0;
m_tkNext = 0xFFFFFFFFL;
// Find nearest event due
for( register DWORD idx = 0; idx < m_Tracks.size(); ++idx ) {
m_ptsTrack = &m_Tracks[idx];
if( !(m_ptsTrack->fdwTrack & ITS_F_ENDOFTRK) && (m_ptsTrack->tkNextEventDue < m_tkNext) ) {
m_tkNext = m_ptsTrack->tkNextEventDue;
m_ptsFound = m_ptsTrack;
}
}

// None found? We must be done, so return to the caller with a smile.
if( !m_ptsFound ) {
m_dwStatus |= CONVERTF_STATUS_DONE;
// Need to set return buffer members properly
return CONVERTERR_NOERROR;
}

// Ok, get the event header from that track
if( !GetTrackEvent( m_ptsFound, &m_teTemp )) {
// Warn future calls that this converter is stuck at a corrupt spot
// and can't continue
m_dwStatus |= CONVERTF_STATUS_STUCK;
return CONVERTERR_CORRUPT;
}

// Don't add end of track event 'til we're done
if( m_teTemp.byShortData[0] == MIDI_META && m_teTemp.byShortData[1] == MIDI_META_EOT ) {
if( m_dwMallocBlocks ) {
delete [] m_teTemp.pLongData;
--m_dwMallocBlocks;
}
continue;
}

if(( nChkErr = AddEventToStreamBuffer( &m_teTemp, lpciInfo )) != CONVERTERR_NOERROR ) {
if( nChkErr == CONVERTERR_BUFFERFULL ) {
// Do some processing and tell somebody this buffer is full...
m_dwStatus |= CONVERTF_STATUS_GOTEVENT;
return CONVERTERR_NOERROR;
} else if( nChkErr == CONVERTERR_METASKIP ) {
// We skip by all meta events that aren't tempo changes...
} else {
TRACE0("Unable to add event to stream buffer.\n");
if( m_dwMallocBlocks ) {
delete [] m_teTemp.pLongData;
m_dwMallocBlocks--;
}
return TRUE;
}
}
}

return CONVERTERR_NOERROR;
}

// GetTrackEvent
//
// Fills in the event struct with the next event from the track
//
// pteTemp->tkEvent will contain the absolute tick time of the event
// pteTemp->byShortData[0] will contain
// MIDI_META if the event is a meta event;
// in this case pteTemp->byShortData[1] will contain the meta class
// MIDI_SYSEX or MIDI_SYSEXEND if the event is a SysEx event
// Otherwise, the event is a channel message and pteTemp->byShortData[1]
// and pteTemp->byShortData[2] will contain the rest of the event.
//
// pteTemp->dwEventLength will contain
// The total length of the channel message in pteTemp->byShortData if
// the event is a channel message
// The total length of the paramter data pointed to by
// pteTemp->pLongData otherwise
//
// pteTemp->pLongData will point at any additional paramters if the
// event is a SysEx or meta event with non-zero length; else
// it will contain NULL
//
// Returns TRUE on success or FALSE on any kind of parse error
// Prints its own error message ONLY in the debug version
//
// Maintains the state of the input track (i.e.
// ptsTrack->pTrackPointers, and ptsTrack->byRunningStatus).
//
BOOL CMIDI :: GetTrackEvent(TRACK * ptsTrack, TEMPEVENT * pteTemp) {
DWORD idx;
UINT dwEventLength;

// Clear out the temporary event structure to get rid of old data...
memset( pteTemp, 0, sizeof(TEMPEVENT));

// Already at end of track? There's nothing to read.
if( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )
return FALSE;

// Get the first byte, which determines the type of event.
BYTE byByte;
if( !GetTrackByte(ptsTrack, &byByte) )
return FALSE;

// If the high bit is not set, then this is a channel message
// which uses the status byte from the last channel message
// we saw. NOTE: We do not clear running status across SysEx or
// meta events even though the spec says to because there are
// actually files out there which contain that sequence of data.
if( !(byByte & 0x80) ) {
// No previous status byte? We're hosed.
if( !ptsTrack->byRunningStatus ) {
TrackError(ptsTrack, gteBadRunStat);
return FALSE;
}

pteTemp->byShortData[0] = ptsTrack->byRunningStatus;
pteTemp->byShortData[1] = byByte;

byByte = pteTemp->byShortData[0] & 0xF0;
pteTemp->dwEventLength = 2;

// Only program change and channel pressure events are 2 bytes long;
// the rest are 3 and need another byte
if(( byByte != MIDI_PRGMCHANGE ) && ( byByte != MIDI_CHANPRESS )) {
if( !GetTrackByte( ptsTrack, &pteTemp->byShortData[2] ))
return FALSE;
++pteTemp->dwEventLength;
}
} else if(( byByte & 0xF0 ) != MIDI_SYSEX ) {
// Not running status, not in SysEx range - must be
// normal channel message (0x80-0xEF)
pteTemp->byShortData[0] = byByte;
ptsTrack->byRunningStatus = byByte;

// Strip off channel and just keep message type
byByte &= 0xF0;

dwEventLength = ( byByte == MIDI_PRGMCHANGE || byByte == MIDI_CHANPRESS ) ? 1 : 2;
pteTemp->dwEventLength = dwEventLength + 1;

if( !GetTrackByte( ptsTrack, &pteTemp->byShortData[1] ))
return FALSE;
if( dwEventLength == 2 )
if( !GetTrackByte( ptsTrack, &pteTemp->byShortData[2] ))
return FALSE;
} else if(( byByte == MIDI_SYSEX ) || ( byByte == MIDI_SYSEXEND )) {
// One of the SysEx types. (They are the same as far as we're concerned;
// there is only a semantic difference in how the data would actually
// get sent when the file is played. We must take care to put the proper
// event type back on the output track, however.)
//
// Parse the general format of:
// BYTE bEvent (MIDI_SYSEX or MIDI_SYSEXEND)
// VDWORD cbParms
// BYTE abParms[cbParms]
pteTemp->byShortData[0] = byByte;
if( !GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength )) {
TrackError( ptsTrack, gteSysExLenTrunc );
return FALSE;
}

// Malloc a temporary memory block to hold the parameter data
pteTemp->pLongData = new BYTE [pteTemp->dwEventLength];
if( pteTemp->pLongData == 0 ) {
TrackError( ptsTrack, gteNoMem );
return FALSE;
}
// Increment our counter, which tells the program to look around for
// a malloc block to free, should it need to exit or reset before the
// block would normally be freed
++m_dwMallocBlocks;

// Copy from the input buffer to the parameter data buffer
for( idx = 0; idx < pteTemp->dwEventLength; idx++ )
if( !GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) {
TrackError( ptsTrack, gteSysExTrunc );
return FALSE;
}
} else if( byByte == MIDI_META ) {
// It's a meta event. Parse the general form:
// BYTE bEvent (MIDI_META)
// BYTE bClass
// VDWORD cbParms
// BYTE abParms[cbParms]
pteTemp->byShortData[0] = byByte;

if( !GetTrackByte( ptsTrack, &pteTemp->byShortData[1] ))
return FALSE;

if( !GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength )) {
TrackError( ptsTrack, gteMetaLenTrunc );
return FALSE;
}

// NOTE: It's perfectly valid to have a meta with no data
// In this case, dwEventLength == 0 and pLongData == NULL
if( pteTemp->dwEventLength ) {
// Malloc a temporary memory block to hold the parameter data
pteTemp->pLongData = new BYTE [pteTemp->dwEventLength];
if( pteTemp->pLongData == 0 ) {
TrackError( ptsTrack, gteNoMem );
return FALSE;
}
// Increment our counter, which tells the program to look around for
// a malloc block to free, should it need to exit or reset before the
// block would normally be freed
++m_dwMallocBlocks;

// Copy from the input buffer to the parameter data buffer
for( idx = 0; idx < pteTemp->dwEventLength; idx++ )
if( !GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) {
TrackError( ptsTrack, gteMetaTrunc );
return FALSE;
}
}

if( pteTemp->byShortData[1] == MIDI_META_EOT )
ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
} else {
// Messages in this range are system messages and aren't supposed to
// be in a normal MIDI file. If they are, we've either misparsed or the
// authoring software is stupid.
return FALSE;
}

// Event time was already stored as the current track time
pteTemp->tkEvent = ptsTrack->tkNextEventDue;

// Now update to the next event time. The code above MUST properly
// maintain the end of track flag in case the end of track meta is
// missing. NOTE: This code is a continuation of the track event
// time pre-read which is done at the end of track initialization.
if( !( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )) {
DWORD tkDelta;

if( !GetTrackVDWord( ptsTrack, &tkDelta ))
return FALSE;

ptsTrack->tkNextEventDue += tkDelta;
}

return TRUE;
}


// GetTrackVDWord
//
// Attempts to parse a variable length DWORD from the given track. A VDWord
// in a MIDI file
// (a) is in lo-hi format
// (b) has the high bit set on every byte except the last
//
// Returns the DWORD in *lpdw and TRUE on success; else
// FALSE if we hit end of track first.
BOOL CMIDI :: GetTrackVDWord(TRACK * ptsTrack, LPDWORD lpdw) {
ASSERT(ptsTrack != 0);
ASSERT(lpdw != 0);

if( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )
return FALSE;

BYTE byByte;
DWORD dw = 0;

do {
if( !GetTrackByte( ptsTrack, &byByte ))
return FALSE;

dw = ( dw << 7 ) | ( byByte & 0x7F );
} while( byByte & 0x80 );

*lpdw = dw;

return TRUE;
}


// AddEventToStreamBuffer
//
// Put the given event into the given stream buffer at the given location
// pteTemp must point to an event filled out in accordance with the
// description given in GetTrackEvent
//
// Handles its own error notification by displaying to the appropriate
// output device (either our debugging window, or the screen).
int CMIDI :: AddEventToStreamBuffer( TEMPEVENT * pteTemp, CONVERTINFO *lpciInfo ) {
MIDIEVENT * pmeEvent = (MIDIEVENT *)( lpciInfo->mhBuffer.lpData
+ lpciInfo->dwStartOffset
+ lpciInfo->dwBytesRecorded );

// When we see a new, empty buffer, set the start time on it...
if( !lpciInfo->dwBytesRecorded )
lpciInfo->tkStart = m_tkCurrentTime;

// Use the above set start time to figure out how much longer we should fill
// this buffer before officially declaring it as "full"
if( m_tkCurrentTime - lpciInfo->tkStart > m_dwBufferTickLength )
if( lpciInfo->bTimesUp ) {
lpciInfo->bTimesUp = FALSE;
return CONVERTERR_BUFFERFULL;
} else
lpciInfo->bTimesUp = TRUE;

DWORD tkNow = m_tkCurrentTime;

// Delta time is absolute event time minus absolute time
// already gone by on this track
DWORD tkDelta = pteTemp->tkEvent - m_tkCurrentTime;

// Event time is now current time on this track
m_tkCurrentTime = pteTemp->tkEvent;

if( m_bInsertTempo ) {
m_bInsertTempo = FALSE;

if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD)) {
// Cleanup from our write operation
return CONVERTERR_BUFFERFULL;
}
if( m_dwCurrentTempo ) {
pmeEvent->dwDeltaTime = 0;
pmeEvent->dwStreamID = 0;
pmeEvent->dwEvent = ( m_dwCurrentTempo * 100 ) / m_dwTempoMultiplier;
pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;

lpciInfo->dwBytesRecorded += 3 * sizeof(DWORD);
pmeEvent += 3 * sizeof(DWORD);
}
}

if( pteTemp->byShortData[0] < MIDI_SYSEX ) {
// Channel message. We know how long it is, just copy it.
// Need 3 DWORD's: delta-t, stream-ID, event
if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD)) {
// Cleanup from our write operation
return CONVERTERR_BUFFERFULL;
}

pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
pmeEvent->dwEvent = ( pteTemp->byShortData[0] )
| (((DWORD)pteTemp->byShortData[1] ) << 8 )
| (((DWORD)pteTemp->byShortData[2] ) << 16 )
| MEVT_F_SHORT;

if((( pteTemp->byShortData[0] & 0xF0) == MIDI_CTRLCHANGE ) && ( pteTemp->byShortData[1] == MIDICTRL_VOLUME )) {
// If this is a volume change, generate a callback so we can grab
// the new volume for our cache
pmeEvent->dwEvent |= MEVT_F_CALLBACK;
}
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
} else if(( pteTemp->byShortData[0] == MIDI_SYSEX ) || ( pteTemp->byShortData[0] == MIDI_SYSEXEND )) {
TRACE0("AddEventToStreamBuffer: Ignoring SysEx event.\n");
if( m_dwMallocBlocks ) {
delete [] pteTemp->pLongData;
--m_dwMallocBlocks;
}
} else {
// Better be a meta event.
// BYTE byEvent
// BYTE byEventType
// VDWORD dwEventLength
// BYTE pLongEventData[dwEventLength]
ASSERT( pteTemp->byShortData[0] == MIDI_META );

// The only meta-event we care about is change tempo
if( pteTemp->byShortData[1] != MIDI_META_TEMPO ) {
if( m_dwMallocBlocks ) {
delete [] pteTemp->pLongData;
--m_dwMallocBlocks;
}
return CONVERTERR_METASKIP;
}

// We should have three bytes of parameter data...
ASSERT(pteTemp->dwEventLength == 3);

// Need 3 DWORD's: delta-t, stream-ID, event data
if( lpciInfo->dwMaxLength - lpciInfo->dwBytesRecorded < 3 *sizeof(DWORD)) {
// Cleanup the temporary event if necessary and return
if( m_dwMallocBlocks ) {
delete [] pteTemp->pLongData;
--m_dwMallocBlocks;
}
return CONVERTERR_BUFFERFULL;
}

pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
// Note: this is backwards from above because we're converting a single
// data value from hi-lo to lo-hi format...
pmeEvent->dwEvent = ( pteTemp->pLongData[2] )
| (((DWORD)pteTemp->pLongData[1] ) << 8 )
| (((DWORD)pteTemp->pLongData[0] ) << 16 );

// This next step has absolutely nothing to do with the conversion of a
// MIDI file to a stream, it's simply put here to add the functionality
// of the tempo slider. If you don't need this, be sure to remove the
// next two lines.
m_dwCurrentTempo = pmeEvent->dwEvent;
pmeEvent->dwEvent = (pmeEvent->dwEvent * 100 ) / m_dwTempoMultiplier;

pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;

m_dwBufferTickLength = (m_dwTimeDivision * 1000 * BUFFER_TIME_LENGTH) / m_dwCurrentTempo;
TRACE1("m_dwBufferTickLength = %lu\n", m_dwBufferTickLength);

if( m_dwMallocBlocks ) {
delete [] pteTemp->pLongData;
--m_dwMallocBlocks;
}
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
}

return CONVERTERR_NOERROR;
}


// StreamBufferSetup()
//
// Opens a MIDI stream. Then it goes about converting the data into a midiStream buffer for playback.
BOOL CMIDI :: StreamBufferSetup()
{
int nChkErr;
BOOL bFoundEnd = FALSE;

MMRESULT mmrRetVal;

if( !m_hStream )
if(( mmrRetVal = midiStreamOpen( &m_hStream,
&m_uMIDIDeviceID,
DWORD(1), DWORD(MidiProc),
DWORD(this),
CALLBACK_FUNCTION )) != MMSYSERR_NOERROR )
{
MidiError(mmrRetVal);
// return FALSE;
}

// allocate stream buffers and initialise them
m_StreamBuffers.resize(NUM_STREAM_BUFFERS);

MIDIPROPTIMEDIV mptd;
mptd.cbStruct = sizeof(mptd);
mptd.dwTimeDiv = m_dwTimeDivision;
if(( mmrRetVal = midiStreamProperty( m_hStream, (LPBYTE)&mptd,
MIDIPROP_SET | MIDIPROP_TIMEDIV )) != MMSYSERR_NOERROR ) {
MidiError( mmrRetVal );
return FALSE;
}

m_nEmptyBuffers = 0;
DWORD dwConvertFlag = CONVERTF_RESET;

for( m_nCurrentBuffer = 0; m_nCurrentBuffer < NUM_STREAM_BUFFERS; m_nCurrentBuffer++ ) {
m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
m_StreamBuffers[m_nCurrentBuffer].mhBuffer.lpData = new char [OUT_BUFFER_SIZE];
if( m_StreamBuffers[m_nCurrentBuffer].mhBuffer.lpData == 0 )
return FALSE;

// Tell the converter to convert up to one entire buffer's length of output
// data. Also, set a flag so it knows to reset any saved state variables it
// may keep from call to call.
m_StreamBuffers[m_nCurrentBuffer].dwStartOffset = 0;
m_StreamBuffers[m_nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
m_StreamBuffers[m_nCurrentBuffer].tkStart = 0;
m_StreamBuffers[m_nCurrentBuffer].bTimesUp = FALSE;

if(( nChkErr = ConvertToBuffer( dwConvertFlag, &m_StreamBuffers[m_nCurrentBuffer] )) != CONVERTERR_NOERROR ) {
if( nChkErr == CONVERTERR_DONE ) {
bFoundEnd = TRUE;
} else {
TRACE0("Initial conversion pass failed\n");
return FALSE;
}
}
m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded = m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded;

if( !m_bBuffersPrepared )
if(( mmrRetVal = midiOutPrepareHeader( (HMIDIOUT)m_hStream,
&m_StreamBuffers[m_nCurrentBuffer].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) {
MidiError( mmrRetVal );
return FALSE;
}

if(( mmrRetVal = midiStreamOut( m_hStream,
&m_StreamBuffers[m_nCurrentBuffer].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
break;
}
dwConvertFlag = 0;

if( bFoundEnd )
break;
}

m_bBuffersPrepared = TRUE;
m_nCurrentBuffer = 0;
return TRUE;
}

// This function unprepares and frees all our buffers -- something we must
// do to work around a bug in MMYSYSTEM that prevents a device from playing
// back properly unless it is closed and reopened after each stop.
void CMIDI :: FreeBuffers() {
DWORD idx;
MMRESULT mmrRetVal;

if( m_bBuffersPrepared ) {
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
if(( mmrRetVal = midiOutUnprepareHeader( (HMIDIOUT)m_hStream,
&m_StreamBuffers[idx].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
}
m_bBuffersPrepared = FALSE;
}
// Free our stream buffers...
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
if( m_StreamBuffers[idx].mhBuffer.lpData ) {
delete [] m_StreamBuffers[idx].mhBuffer.lpData;
m_StreamBuffers[idx].mhBuffer.lpData = 0;
}
}

//////////////////////////////////////////////////////////////////////
// CMIDI -- error handling
//////////////////////////////////////////////////////////////////////

void CMIDI :: MidiError(MMRESULT mmResult) {
#ifdef _DEBUG
char chText[512];
midiOutGetErrorText(mmResult, chText, sizeof(chText));
TRACE1("Midi error: %hs\n", chText);
#endif
}


void CMIDI :: TrackError(TRACK * ptsTrack, LPSTR lpszErr ) {
TRACE1("Track buffer offset %lu\n", (DWORD)(ptsTrack->pTrackCurrent - ptsTrack->pTrackStart));
TRACE1("Track total length %lu\n", ptsTrack->dwTrackLength);
TRACE1("%hs\n", lpszErr);
}

//////////////////////////////////////////////////////////////////////
// CMIDI -- overridables
//////////////////////////////////////////////////////////////////////

void CMIDI :: OnMidiOutOpen() {
}


void CMIDI :: OnMidiOutDone(MIDIHDR & rHdr) {
if( m_uCallbackStatus == STATUS_CALLBACKDEAD )
return;

++m_nEmptyBuffers;

if( m_uCallbackStatus == STATUS_WAITINGFOREND ) {
if( m_nEmptyBuffers < NUM_STREAM_BUFFERS )
return;
else {
m_uCallbackStatus = STATUS_CALLBACKDEAD;
Stop();
SetEvent(m_hBufferReturnEvent);
return;
}
}

// This flag is set whenever the callback is waiting for all buffers to
// come back.
if( m_uCallbackStatus == STATUS_KILLCALLBACK ) {
// Count NUM_STREAM_BUFFERS-1 being returned for the last time
if( m_nEmptyBuffers < NUM_STREAM_BUFFERS )
return;
else {
// Change the status to callback dead
m_uCallbackStatus = STATUS_CALLBACKDEAD;
SetEvent(m_hBufferReturnEvent);
return;
}
}

m_dwProgressBytes += m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded;

///////////////////////////////////////////////////////////////////////////////
// Fill an available buffer with audio data again...

if( m_bPlaying && m_nEmptyBuffers ) {
m_StreamBuffers[m_nCurrentBuffer].dwStartOffset = 0;
m_StreamBuffers[m_nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
m_StreamBuffers[m_nCurrentBuffer].tkStart = 0;
m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded = 0;
m_StreamBuffers[m_nCurrentBuffer].bTimesUp = FALSE;

int nChkErr;

if(( nChkErr = ConvertToBuffer( 0, &m_StreamBuffers[m_nCurrentBuffer] )) != CONVERTERR_NOERROR ) {
if( nChkErr == CONVERTERR_DONE ) {
m_uCallbackStatus = STATUS_WAITINGFOREND;
return;
} else {
TRACE0("MidiProc() conversion pass failed!\n");
return;
}
}

m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded = m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded;

MMRESULT mmrRetVal;
if( (mmrRetVal = midiStreamOut(m_hStream, &m_StreamBuffers[m_nCurrentBuffer].mhBuffer, sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return;
}
m_nCurrentBuffer = ( m_nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS;
m_nEmptyBuffers--;
}
}


void CMIDI :: OnMidiOutPositionCB(MIDIHDR & rHdr, MIDIEVENT & rEvent) {
if( MIDIEVENT_TYPE(rEvent.dwEvent) == MIDI_CTRLCHANGE )
{
if( MIDIEVENT_DATA1(rEvent.dwEvent) == MIDICTRL_VOLUME ) {
// Mask off the channel number and cache the volume data byte
m_Volumes[MIDIEVENT_CHANNEL(rEvent.dwEvent)] = DWORD(MIDIEVENT_VOLUME(rEvent.dwEvent)*100/VOLUME_MAX);
if( m_pWndParent && ::IsWindow(m_pWndParent->GetSafeHwnd()) )
// Do not use SendMessage(), because a change of the midi stream has no effect
// during callback handling, so if the owner wants to adjust the volume, as a
// result of the windows message, (s)he will not hear that change.
m_pWndParent->PostMessage(
WM_MIDI_VOLUMECHANGED,
WPARAM(this),
LPARAM(
MAKELONG(
WORD(MIDIEVENT_CHANNEL(rEvent.dwEvent)),
WORD(MIDIEVENT_VOLUME(rEvent.dwEvent)*100/VOLUME_MAX)
)
)
);
}
}
}


void CMIDI :: OnMidiOutClose() {
}

//////////////////////////////////////////////////////////////////////
// CMIDI -- static members
//////////////////////////////////////////////////////////////////////

void CMIDI :: MidiProc(HMIDIOUT hMidi, UINT uMsg, DWORD dwInstanceData, DWORD dwParam1, DWORD dwParam2) {
CMIDI * pMidi = (CMIDI *) dwInstanceData;
ASSERT(pMidi != 0);
MIDIHDR * pHdr = (MIDIHDR*) dwParam1;

switch(uMsg) {
case MOM_OPEN:
pMidi->OnMidiOutOpen();
break;

case MOM_CLOSE:
pMidi->OnMidiOutClose();
break;

case MOM_DONE:
ASSERT(pHdr != 0);
pMidi->OnMidiOutDone(*pHdr);
break;

case MOM_POSITIONCB:
ASSERT(pHdr != 0);
pMidi->OnMidiOutPositionCB(*pHdr, *((MIDIEVENT*)(pHdr->lpData + pHdr->dwOffset)));
break;

default:
break;
}
}

BOOL CMIDI::ACreate(LPCTSTR fileName, CWnd *pWndParent)
{
DWORD dwTheSound;

CFile* file = new CFile();
file->Open("s.mid",CFile::modeRead);
dwTheSound = file->GetLength();
LPBYTE pTheSound = new BYTE[dwTheSound];
file->Read( pTheSound,dwTheSound);
file->Close();
delete file;
if(pTheSound == 0)
return FALSE;

return Create(pTheSound, dwTheSound, pWndParent);

}

CMIDI* CMIDI::SplitTrack(DWORD idx)
{
if ( idx > m_dwTrackCount )
return NULL;

// CMIDI* tMidi=new CMIDI();
CMIDI *tMidi;
LPBYTE tSoundData =(LPBYTE) m_pSoundData;
LPBYTE pResult;
DWORD i,j,start,count;
//Get the start,end&count info of the select track
j=0;
for (i=0;i < m_dwSoundSize;i++)
{
if ((*(DWORD*)(tSoundData)) == 0x6b72544d)
{
j++;
if (j == idx)
start=i;
if (j == idx+1)
break;
}
tSoundData++;
}
count = i - start + 1;

//Copy the midi header to the result
pResult = new BYTE(count+14);
memcpy(pResult,m_pSoundData,14);
pResult[10]=0x00;
pResult[11]=0x01;
//Copy the Track Data to the result
tSoundData=(LPBYTE) m_pSoundData;
tSoundData+=start;
memcpy(pResult+14,tSoundData,count);
//Create a new MIDI pointor contain the selected track
tMidi = new CMIDI();
tMidi->Create(pResult,count+14);
return tMidi;
}


The_east_key 2000-12-29
  • 打赏
  • 举报
回复
建议您访问www.etechbase.net/tech,里面有很多资料,也许可以解决您的问题。
访问http://168.168.18.11:81/etechbase/advsearch.php将您的问题输入查询内容框,选择不同的精确程度,即可以找到你所需要的答案。效果还是可以的。
Jimy 2000-12-29
  • 打赏
  • 举报
回复
OptiMay,
看了你回的文章!
谢谢,
但是在API中有MIDIStreamOut函数,我们如果能够直接用不是更好吗?
我想试图使用它现成的,如果用你所提供的办法去分析MIDI的文件格式
总觉得太繁琐了点
不过你至少也给我提供了方向,
再次感谢!
greentape 2000-09-04
  • 打赏
  • 举报
回复
我也正在试图解决这个问题.
你找到方法了么?

如果用MCI functions的话
可以做到么?
《计算机应用》第一章~第二章练习题 一、填空 1、通常,存储一个汉字占用( )字节,一个字节(Byte)由 ( )个二进制位组成。 2、计算机发展的阶段,通常是依据( )来划分的。 3、计算机有多种技术指标,而决定计算机的计算精度的则是( )。 4、在Windows XP系统英文输入法之间切换使用( )键。 5、同时选择一组相邻的文件或文件夹,可单击第一个要选取的文件或文件夹,按住( )键盘不放,再单击要选择的最后一个文件或文件夹。 6、在"资源管理器"剪切一个文件后,该文件被放到( )。 7、在不同驱动器间移动文件夹,须在鼠标选并拖拽至目标位置的同时要按下( )键。 8、在Windows XP 窗口,选末尾带有省略号(…)的菜单意味着( )。 9、计算机进行信息处理时,控制器从( )按顺序取出指令。 10、通常人们所说的计算机系统是由( )和( )两部分组成的。 11、在Windows XP ,任务栏通常处于屏幕的( )。 12、显示和隐藏工具栏可以使用( )菜单。 13、在Windows资源管理器的目录图标上,有"+"号表示目录下还有( )没列出。 14、对话框的元素有按钮、文本框、列表框、单选按钮、复选按钮、组合框和( ) 15、Windows界面窗口右上角的表示( )按钮,表示( )按钮。 16、预定义的、用来代替某些菜单命名的特殊组合称为( )。 17、Windows提供的大部分开发工具和实用程序可以在( )找到。 18、在Windows可以通过( )来查看工作组的计算机和网络上的全部计算机。 19、退出Windows时不能简单地( ),否则,会造成数据丢失,或占用大量磁盘空间,而是在退出Windows前选择( )。 20、要添加或删除某个文输入法,应先启动"控制面板",再使用其的( )功能。 二、选择题 1、对计算机发展趋势的叙述,不准确的是( )。 A、体积越来越小 B、精确度越来越高 C、速度越来越快 D、容量越来越小 2、1946年,在( )国诞生了第一台计算机。 A、英 B、法 C、德 D、美 3、计算机最早的用途是进行( )。 A、科学计算 B、自动控制 C、系统仿真 D、辅助设计 4、冯.诺依曼提出的计算机工作原理又称为( )工作原理。 A、信息流 B、存储器 C、CPU D、存储程序 5、( )能使计算机系统资源得到充分共享。 A、巨型化 B、智能化 C、微型化 D、网络化 6、网上购物属于计算机在( )方面的应用。 A、科学计算 B、电子商务 C、人工智能 D、网络通信 7、在微型计算机,把数据传送到硬盘上称为( )。 A、写盘 B、读盘 C、输入 D、以上都不是 8、在计算机存储器,1K字节最多可存储( )个汉字。 A、1000 B、1024 C、500 D、512 9、下列软件,不属于系统软件的是( )。 A、Windows XP B、DOS C、UNIX D、Word 10、一台微机必须具备的软件是( )。 A、操作系统 B、字处理软件 C、数据库管理软件 D、打字练习软件 11、所谓"裸机"是指( )。 A、单片机 B、单板机 C、不装备任何软件的计算机 D、只装备操作系统的计算机 12、所谓的PC是指( )。 A、个人计算机 B、品牌机 C、兼容机 D、组装机 13、计算机系统资源的管理是由( )来完成的。 A、硬件 B、操作系统 C、算法 D、控制器 14、计算机系统由( )组成。 A、主机及外部设备 B、硬件系统和软件系统 C、系统软件和应用软件 D、主机、键盘、显示器和打印机 15、计算机存储器的基本单位是( )。 A、字节 B、频率 C、字长 D、符号 16、负责指挥与控制计算机的部件是( )。 A、输入设备 B、输出设备 C、存储器 D、央处理器 17、将计算机的内存储器与外存储器相比,内存储器的主要特点之一是( )。 A、价格更便宜 B、内存容量大 C、存取速度快 D、价格虽贵但容量大 18、内存储器是计算机系统的记忆设备,它主要用于( )。 A、存放数据 B、存放程序 C、存放数据和程序 D、存放地址 19、计算机使用的数制是( )。 A、十六进制 B、二进制 C、十进制 D、八进制 20、一般计算机系统必不可少的输出设备是 ( )。 A、绘图仪 B、音箱 C、显示器 D、打印机 21、下列哪些设备不属于输入设备( )。 A、键盘 B、鼠标 C、手写笔 D、打印机 22、常用的打印机类型有( )。 A、激光式打印机 B、喷墨式打印机 C、针式打印机 D、以上三种类型 23、声卡不具有( )作用。 A、数字音频 B、音乐合成 C、MIDI音频 D、文字处理 24、键盘的接口有( )。 A、PS/2、USB两种 B、PS/2、USB、

8,303

社区成员

发帖
与我相关
我的任务
社区描述
游戏开发相关内容讨论专区
社区管理员
  • 游戏开发
  • 呆呆敲代码的小Y
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧