parent
1201f569e7
commit
2ca6db0ccd
@ -0,0 +1,626 @@ |
||||
/*
|
||||
Programming MIDI: Parsing, Displaying (& Playing) MIDI Files |
||||
"Better get these done before im virused..." - javidx9 |
||||
|
||||
License (OLC-3) |
||||
~~~~~~~~~~~~~~~ |
||||
|
||||
Copyright 2018-2020 OneLoneCoder.com |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
1. Redistributions or derivations of source code must retain the above |
||||
copyright notice, this list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions or derivative works in binary form must reproduce |
||||
the above copyright notice. This list of conditions and the following |
||||
disclaimer must be reproduced in the documentation and/or other |
||||
materials provided with the distribution. |
||||
|
||||
3. Neither the name of the copyright holder nor the names of its |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
Relevant Video: https://youtu.be/040BKtnDdg0
|
||||
|
||||
Links |
||||
~~~~~ |
||||
YouTube: https://www.youtube.com/javidx9
|
||||
https://www.youtube.com/javidx9extra
|
||||
Discord: https://discord.gg/WhwHUMV
|
||||
Twitter: https://www.twitter.com/javidx9
|
||||
Twitch: https://www.twitch.tv/javidx9
|
||||
GitHub: https://www.github.com/onelonecoder
|
||||
Patreon: https://www.patreon.com/javidx9
|
||||
Homepage: https://www.onelonecoder.com
|
||||
|
||||
Community: https://community.onelonecoder.com
|
||||
|
||||
Author |
||||
~~~~~~ |
||||
David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 |
||||
*/ |
||||
|
||||
|
||||
#define OLC_PGE_APPLICATION |
||||
#include "olcPixelGameEngine.h" |
||||
|
||||
#include <fstream> |
||||
#include <array> |
||||
|
||||
//#pragma comment(lib, "winmm.lib")
|
||||
|
||||
|
||||
struct MidiEvent |
||||
{ |
||||
enum class Type |
||||
{ |
||||
NoteOff, |
||||
NoteOn, |
||||
Other |
||||
} event; |
||||
|
||||
uint8_t nKey = 0; |
||||
uint8_t nVelocity = 0; |
||||
uint32_t nDeltaTick = 0; |
||||
}; |
||||
|
||||
|
||||
struct MidiNote |
||||
{ |
||||
uint8_t nKey = 0; |
||||
uint8_t nVelocity = 0; |
||||
uint32_t nStartTime = 0; |
||||
uint32_t nDuration = 0; |
||||
}; |
||||
|
||||
struct MidiTrack |
||||
{ |
||||
std::string sName; |
||||
std::string sInstrument; |
||||
std::vector<MidiEvent> vecEvents; |
||||
std::vector<MidiNote> vecNotes; |
||||
uint8_t nMaxNote = 64; |
||||
uint8_t nMinNote = 64; |
||||
}; |
||||
|
||||
|
||||
class MidiFile |
||||
{ |
||||
public: |
||||
enum EventName : uint8_t |
||||
{
|
||||
VoiceNoteOff = 0x80, |
||||
VoiceNoteOn = 0x90, |
||||
VoiceAftertouch = 0xA0, |
||||
VoiceControlChange = 0xB0, |
||||
VoiceProgramChange = 0xC0, |
||||
VoiceChannelPressure = 0xD0, |
||||
VoicePitchBend = 0xE0, |
||||
SystemExclusive = 0xF0,
|
||||
}; |
||||
|
||||
enum MetaEventName : uint8_t |
||||
{ |
||||
MetaSequence = 0x00, |
||||
MetaText = 0x01, |
||||
MetaCopyright = 0x02, |
||||
MetaTrackName = 0x03, |
||||
MetaInstrumentName = 0x04, |
||||
MetaLyrics = 0x05, |
||||
MetaMarker = 0x06, |
||||
MetaCuePoint = 0x07, |
||||
MetaChannelPrefix = 0x20, |
||||
MetaEndOfTrack = 0x2F, |
||||
MetaSetTempo = 0x51, |
||||
MetaSMPTEOffset = 0x54, |
||||
MetaTimeSignature = 0x58, |
||||
MetaKeySignature = 0x59, |
||||
MetaSequencerSpecific = 0x7F, |
||||
}; |
||||
|
||||
public: |
||||
MidiFile() |
||||
{ |
||||
} |
||||
|
||||
MidiFile(const std::string& sFileName) |
||||
{ |
||||
ParseFile(sFileName); |
||||
} |
||||
|
||||
void Clear() |
||||
{ |
||||
|
||||
} |
||||
|
||||
bool ParseFile(const std::string& sFileName) |
||||
{ |
||||
// Open the MIDI File as a stream
|
||||
std::ifstream ifs; |
||||
ifs.open(sFileName, std::fstream::in | std::ios::binary); |
||||
if (!ifs.is_open()) |
||||
return false; |
||||
|
||||
|
||||
// Helper Utilities ====================
|
||||
|
||||
// Swaps byte order of 32-bit integer
|
||||
auto Swap32 = [](uint32_t n) |
||||
{ |
||||
return (((n >> 24) & 0xff) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | ((n << 24) & 0xff000000)); |
||||
}; |
||||
|
||||
// Swaps byte order of 16-bit integer
|
||||
auto Swap16 = [](uint16_t n) |
||||
{ |
||||
return ((n >> 8) | (n << 8)); |
||||
}; |
||||
|
||||
// Reads nLength bytes form file stream, and constructs a text string
|
||||
auto ReadString = [&ifs](uint32_t nLength) |
||||
{ |
||||
std::string s; |
||||
for (uint32_t i = 0; i < nLength; i++) s += ifs.get(); |
||||
return s; |
||||
}; |
||||
|
||||
// Reads a compressed MIDI value. This can be up to 32 bits long. Essentially if the first byte, first
|
||||
// bit is set to 1, that indicates that the next byte is required to construct the full word. Only
|
||||
// the bottom 7 bits of each byte are used to construct the final word value. Each successive byte
|
||||
// that has MSB set, indicates a further byte needs to be read.
|
||||
auto ReadValue = [&ifs]() |
||||
{ |
||||
uint32_t nValue = 0; |
||||
uint8_t nByte = 0; |
||||
|
||||
// Read byte
|
||||
nValue = ifs.get(); |
||||
|
||||
// Check MSB, if set, more bytes need reading
|
||||
if (nValue & 0x80) |
||||
{ |
||||
// Extract bottom 7 bits of read byte
|
||||
nValue &= 0x7F; |
||||
do |
||||
{ |
||||
// Read next byte
|
||||
nByte = ifs.get(); |
||||
|
||||
// Construct value by setting bottom 7 bits, then shifting 7 bits
|
||||
nValue = (nValue << 7) | (nByte & 0x7F); |
||||
}
|
||||
while (nByte & 0x80); // Loop whilst read byte MSB is 1
|
||||
} |
||||
|
||||
// Return final construction (always 32-bit unsigned integer internally)
|
||||
return nValue; |
||||
}; |
||||
|
||||
uint32_t n32 = 0; |
||||
uint16_t n16 = 0; |
||||
|
||||
// Read MIDI Header (Fixed Size)
|
||||
ifs.read((char*)&n32, sizeof(uint32_t)); |
||||
uint32_t nFileID = Swap32(n32); |
||||
ifs.read((char*)&n32, sizeof(uint32_t)); |
||||
uint32_t nHeaderLength = Swap32(n32); |
||||
ifs.read((char*)&n16, sizeof(uint16_t)); |
||||
uint16_t nFormat = Swap16(n16); |
||||
ifs.read((char*)&n16, sizeof(uint16_t)); |
||||
uint16_t nTrackChunks = Swap16(n16); |
||||
ifs.read((char*)&n16, sizeof(uint16_t)); |
||||
uint16_t nDivision = Swap16(n16); |
||||
|
||||
for (uint16_t nChunk = 0; nChunk < nTrackChunks; nChunk++) |
||||
{
|
||||
std::cout << "===== NEW TRACK" << std::endl; |
||||
// Read Track Header
|
||||
ifs.read((char*)&n32, sizeof(uint32_t)); |
||||
uint32_t nTrackID = Swap32(n32); |
||||
ifs.read((char*)&n32, sizeof(uint32_t)); |
||||
uint32_t nTrackLength = Swap32(n32); |
||||
|
||||
bool bEndOfTrack = false; |
||||
|
||||
vecTracks.push_back(MidiTrack()); |
||||
|
||||
uint32_t nWallTime = 0; |
||||
|
||||
uint8_t nPreviousStatus = 0; |
||||
|
||||
while (!ifs.eof() && !bEndOfTrack) |
||||
{ |
||||
// Fundamentally all MIDI Events contain a timecode, and a status byte*
|
||||
uint32_t nStatusTimeDelta = 0; |
||||
uint8_t nStatus = 0; |
||||
|
||||
|
||||
// Read Timecode from MIDI stream. This could be variable in length
|
||||
// and is the delta in "ticks" from the previous event. Of course this value
|
||||
// could be 0 if two events happen simultaneously.
|
||||
nStatusTimeDelta = ReadValue(); |
||||
|
||||
// Read first byte of message, this could be the status byte, or it could not...
|
||||
nStatus = ifs.get(); |
||||
|
||||
// All MIDI Status events have the MSB set. The data within a standard MIDI event
|
||||
// does not. A crude yet utilised form of compression is to omit sending status
|
||||
// bytes if the following sequence of events all refer to the same MIDI Status.
|
||||
// This is called MIDI Running Status, and is essential to succesful decoding of
|
||||
// MIDI streams and files.
|
||||
//
|
||||
// If the MSB of the read byte was not set, and on the whole we were expecting a
|
||||
// status byte, then Running Status is in effect, so we refer to the previous
|
||||
// confirmed status byte.
|
||||
if (nStatus < 0x80) |
||||
{ |
||||
// MIDI Running Status is happening, so refer to previous valid MIDI Status byte
|
||||
nStatus = nPreviousStatus; |
||||
|
||||
// We had to read the byte to assess if MIDI Running Status is in effect. But!
|
||||
// that read removed the byte form the stream, and that will desync all of the
|
||||
// following code because normally we would have read a status byte, but instead
|
||||
// we have read the data contained within a MIDI message. The simple solution is
|
||||
// to put the byte back :P
|
||||
ifs.seekg(-1, std::ios_base::cur); |
||||
} |
||||
|
||||
|
||||
|
||||
if ((nStatus & 0xF0) == EventName::VoiceNoteOff) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nNoteID = ifs.get(); |
||||
uint8_t nNoteVelocity = ifs.get(); |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOff, nNoteID, nNoteVelocity, nStatusTimeDelta }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoiceNoteOn) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nNoteID = ifs.get(); |
||||
uint8_t nNoteVelocity = ifs.get(); |
||||
if(nNoteVelocity == 0) |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOff, nNoteID, nNoteVelocity, nStatusTimeDelta }); |
||||
else |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOn, nNoteID, nNoteVelocity, nStatusTimeDelta }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoiceAftertouch) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nNoteID = ifs.get(); |
||||
uint8_t nNoteVelocity = ifs.get(); |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoiceControlChange) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nControlID = ifs.get(); |
||||
uint8_t nControlValue = ifs.get(); |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoiceProgramChange) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nProgramID = ifs.get();
|
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoiceChannelPressure) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nChannelPressure = ifs.get(); |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); |
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::VoicePitchBend) |
||||
{ |
||||
nPreviousStatus = nStatus; |
||||
uint8_t nChannel = nStatus & 0x0F; |
||||
uint8_t nLS7B = ifs.get(); |
||||
uint8_t nMS7B = ifs.get(); |
||||
vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); |
||||
|
||||
} |
||||
|
||||
else if ((nStatus & 0xF0) == EventName::SystemExclusive) |
||||
{ |
||||
nPreviousStatus = 0; |
||||
|
||||
if (nStatus == 0xFF) |
||||
{ |
||||
// Meta Message
|
||||
uint8_t nType = ifs.get(); |
||||
uint8_t nLength = ReadValue(); |
||||
|
||||
switch (nType) |
||||
{ |
||||
case MetaSequence: |
||||
std::cout << "Sequence Number: " << ifs.get() << ifs.get() << std::endl; |
||||
break; |
||||
case MetaText: |
||||
std::cout << "Text: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
case MetaCopyright: |
||||
std::cout << "Copyright: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
case MetaTrackName: |
||||
vecTracks[nChunk].sName = ReadString(nLength); |
||||
std::cout << "Track Name: " << vecTracks[nChunk].sName << std::endl;
|
||||
break; |
||||
case MetaInstrumentName: |
||||
vecTracks[nChunk].sInstrument = ReadString(nLength); |
||||
std::cout << "Instrument Name: " << vecTracks[nChunk].sInstrument << std::endl; |
||||
break; |
||||
case MetaLyrics: |
||||
std::cout << "Lyrics: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
case MetaMarker: |
||||
std::cout << "Marker: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
case MetaCuePoint: |
||||
std::cout << "Cue: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
case MetaChannelPrefix: |
||||
std::cout << "Prefix: " << ifs.get() << std::endl; |
||||
break; |
||||
case MetaEndOfTrack: |
||||
bEndOfTrack = true; |
||||
break; |
||||
case MetaSetTempo: |
||||
// Tempo is in microseconds per quarter note
|
||||
if (m_nTempo == 0) |
||||
{ |
||||
(m_nTempo |= (ifs.get() << 16)); |
||||
(m_nTempo |= (ifs.get() << 8)); |
||||
(m_nTempo |= (ifs.get() << 0)); |
||||
m_nBPM = (60000000 / m_nTempo); |
||||
std::cout << "Tempo: " << m_nTempo << " (" << m_nBPM << "bpm)" << std::endl; |
||||
} |
||||
break; |
||||
case MetaSMPTEOffset: |
||||
std::cout << "SMPTE: H:" << ifs.get() << " M:" << ifs.get() << " S:" << ifs.get() << " FR:" << ifs.get() << " FF:" << ifs.get() << std::endl; |
||||
break; |
||||
case MetaTimeSignature: |
||||
std::cout << "Time Signature: " << ifs.get() << "/" << (2 << ifs.get()) << std::endl; |
||||
std::cout << "ClocksPerTick: " << ifs.get() << std::endl; |
||||
|
||||
// A MIDI "Beat" is 24 ticks, so specify how many 32nd notes constitute a beat
|
||||
std::cout << "32per24Clocks: " << ifs.get() << std::endl; |
||||
break; |
||||
case MetaKeySignature: |
||||
std::cout << "Key Signature: " << ifs.get() << std::endl; |
||||
std::cout << "Minor Key: " << ifs.get() << std::endl; |
||||
break; |
||||
case MetaSequencerSpecific: |
||||
std::cout << "Sequencer Specific: " << ReadString(nLength) << std::endl; |
||||
break; |
||||
default: |
||||
std::cout << "Unrecognised MetaEvent: " << nType << std::endl; |
||||
} |
||||
} |
||||
|
||||
if (nStatus == 0xF0) |
||||
{ |
||||
// System Exclusive Message Begin
|
||||
std::cout << "System Exclusive Begin: " << ReadString(ReadValue()) << std::endl; |
||||
} |
||||
|
||||
if (nStatus == 0xF7) |
||||
{ |
||||
// System Exclusive Message Begin
|
||||
std::cout << "System Exclusive End: " << ReadString(ReadValue()) << std::endl; |
||||
} |
||||
}
|
||||
else |
||||
{ |
||||
std::cout << "Unrecognised Status Byte: " << nStatus << std::endl; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// Convert Time Events to Notes
|
||||
for (auto& track : vecTracks) |
||||
{ |
||||
uint32_t nWallTime = 0; |
||||
|
||||
std::list<MidiNote> listNotesBeingProcessed; |
||||
|
||||
for (auto& event : track.vecEvents) |
||||
{ |
||||
nWallTime += event.nDeltaTick; |
||||
|
||||
if (event.event == MidiEvent::Type::NoteOn) |
||||
{ |
||||
// New Note
|
||||
listNotesBeingProcessed.push_back({ event.nKey, event.nVelocity, nWallTime, 0 }); |
||||
} |
||||
|
||||
if (event.event == MidiEvent::Type::NoteOff) |
||||
{ |
||||
auto note = std::find_if(listNotesBeingProcessed.begin(), listNotesBeingProcessed.end(), [&](const MidiNote& n) { return n.nKey == event.nKey; }); |
||||
if (note != listNotesBeingProcessed.end()) |
||||
{ |
||||
note->nDuration = nWallTime - note->nStartTime; |
||||
track.vecNotes.push_back(*note); |
||||
track.nMinNote = std::min(track.nMinNote, note->nKey); |
||||
track.nMaxNote = std::max(track.nMaxNote, note->nKey); |
||||
listNotesBeingProcessed.erase(note); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public: |
||||
std::vector<MidiTrack> vecTracks; |
||||
uint32_t m_nTempo = 0; |
||||
uint32_t m_nBPM = 0; |
||||
|
||||
}; |
||||
|
||||
|
||||
class olcMIDIViewer : public olc::PixelGameEngine |
||||
{ |
||||
public: |
||||
olcMIDIViewer() |
||||
{ |
||||
sAppName = "MIDI File Viewer"; |
||||
} |
||||
|
||||
|
||||
MidiFile midi; |
||||
|
||||
//HMIDIOUT hInstrument;
|
||||
size_t nCurrentNote[16]{ 0 }; |
||||
|
||||
double dSongTime = 0.0; |
||||
double dRunTime = 0.0; |
||||
uint32_t nMidiClock = 0; |
||||
|
||||
|
||||
public: |
||||
bool OnUserCreate() override |
||||
{ |
||||
|
||||
midi.ParseFile("ff7_battle.mid"); |
||||
|
||||
/*
|
||||
int nMidiDevices = midiOutGetNumDevs(); |
||||
if (nMidiDevices > 0) |
||||
{ |
||||
if (midiOutOpen(&hInstrument, 2, NULL, 0, NULL) == MMSYSERR_NOERROR) |
||||
{ |
||||
std::cout << "Opened midi" << std::endl; |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
|
||||
return true; |
||||
} |
||||
|
||||
float nTrackOffset = 1000; |
||||
|
||||
bool OnUserUpdate(float fElapsedTime) override |
||||
{ |
||||
Clear(olc::BLACK); |
||||
uint32_t nTimePerColumn = 50; |
||||
uint32_t nNoteHeight = 2; |
||||
uint32_t nOffsetY = 0; |
||||
|
||||
if (GetKey(olc::Key::LEFT).bHeld) nTrackOffset -= 10000.0f * fElapsedTime; |
||||
if (GetKey(olc::Key::RIGHT).bHeld) nTrackOffset += 10000.0f * fElapsedTime; |
||||
|
||||
|
||||
for (auto& track : midi.vecTracks) |
||||
{ |
||||
if (!track.vecNotes.empty()) |
||||
{ |
||||
uint32_t nNoteRange = track.nMaxNote - track.nMinNote; |
||||
|
||||
FillRect(0, nOffsetY, ScreenWidth(), (nNoteRange + 1) * nNoteHeight, olc::DARK_GREY); |
||||
DrawString(1, nOffsetY + 1, track.sName); |
||||
|
||||
for (auto& note : track.vecNotes) |
||||
{ |
||||
FillRect((note.nStartTime - nTrackOffset) / nTimePerColumn, (nNoteRange - (note.nKey - track.nMinNote)) * nNoteHeight + nOffsetY, note.nDuration / nTimePerColumn, nNoteHeight, olc::WHITE); |
||||
} |
||||
|
||||
nOffsetY += (nNoteRange + 1) * nNoteHeight + 4; |
||||
} |
||||
} |
||||
|
||||
// BELOW - ABSOLUTELY HORRIBLE BODGE TO PLAY SOUND
|
||||
// DO NOT USE THIS CODE...
|
||||
|
||||
/*
|
||||
dRunTime += fElapsedTime; |
||||
uint32_t nTempo = 4; |
||||
int nTrack = 1; |
||||
while (dRunTime >= 1.0 / double(midi.m_nBPM * 8)) |
||||
{ |
||||
dRunTime -= 1.0 / double(midi.m_nBPM * 8); |
||||
|
||||
// Single MIDI Clock
|
||||
nMidiClock++; |
||||
|
||||
int i = 0; |
||||
int nTrack = 1; |
||||
//for (nTrack = 1; nTrack < 3; nTrack++)
|
||||
{ |
||||
if (nCurrentNote[nTrack] < midi.vecTracks[nTrack].vecEvents.size()) |
||||
{ |
||||
if (midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nDeltaTick == 0) |
||||
{ |
||||
uint32_t nStatus = 0; |
||||
uint32_t nNote = midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nKey; |
||||
uint32_t nVelocity = midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nVelocity; |
||||
|
||||
if (midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].event == MidiEvent::Type::NoteOn) |
||||
nStatus = 0x90; |
||||
else |
||||
nStatus = 0x80; |
||||
|
||||
midiOutShortMsg(hInstrument, (nVelocity << 16) | (nNote << 8) | nStatus); |
||||
nCurrentNote[nTrack]++; |
||||
} |
||||
else |
||||
midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nDeltaTick--; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (GetKey(olc::Key::SPACE).bPressed) |
||||
{ |
||||
midiOutShortMsg(hInstrument, 0x00403C90); |
||||
} |
||||
|
||||
if (GetKey(olc::Key::SPACE).bReleased) |
||||
{ |
||||
midiOutShortMsg(hInstrument, 0x00003C80); |
||||
} |
||||
*/ |
||||
|
||||
|
||||
return true; |
||||
} |
||||
|
||||
|
||||
}; |
||||
|
||||
int main() |
||||
{ |
||||
olcMIDIViewer demo; |
||||
if (demo.Construct(1280, 960, 1, 1)) |
||||
demo.Start(); |
||||
return 0; |
||||
} |
||||
|
Loading…
Reference in new issue