Added MIDI Video
This commit is contained in:
parent
1201f569e7
commit
2ca6db0ccd
626
Videos/OneLoneCoder_PGE_MIDI.cpp
Normal file
626
Videos/OneLoneCoder_PGE_MIDI.cpp
Normal file
@ -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…
x
Reference in New Issue
Block a user