Further midi file support.
This commit is contained in:
parent
36826fa1d4
commit
4d5ca4d654
12 changed files with 739 additions and 200 deletions
|
@ -1,211 +1,379 @@
|
|||
#include "MidiReader.h"
|
||||
|
||||
#include "MidiDocument.h"
|
||||
#include "ByteUtils.h"
|
||||
#include "MidiTrack.h"
|
||||
#include "BinaryFile.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <bitset>
|
||||
#include "MidiDocument.h"
|
||||
#include "ByteUtils.h"
|
||||
#include "MidiTrack.h"
|
||||
|
||||
MidiReader::MidiReader()
|
||||
:mDocument(),
|
||||
mTrackByteCount(0),
|
||||
mLastChannelEventType(MidiChannelEvent::Type::NONE)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool GetBytes(std::ifstream& file, char* buffer, unsigned number)
|
||||
{
|
||||
char c;
|
||||
for(unsigned idx=0; idx<number; idx++)
|
||||
{
|
||||
if(file.get(c))
|
||||
{
|
||||
buffer[idx] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetWord(std::ifstream& file, char* buffer)
|
||||
{
|
||||
return GetBytes(file, buffer, 2);
|
||||
}
|
||||
|
||||
bool GetDWord(std::ifstream& file, char* buffer)
|
||||
{
|
||||
return GetBytes(file, buffer, 4);
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessHeader(std::ifstream& file)
|
||||
{
|
||||
char c;
|
||||
unsigned bufferSize = ByteUtils::BYTES_PER_DWORD;
|
||||
char buffer[bufferSize];
|
||||
|
||||
if(!GetDWord(file, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!ByteUtils::CompareDWords(buffer, HeaderLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!GetDWord(file, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = ByteUtils::ToDWord(buffer);
|
||||
|
||||
if(!GetWord(file, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
mDocument.mFormatType = ByteUtils::ToWord(buffer);
|
||||
|
||||
if(!GetWord(file, buffer))
|
||||
if(!BinaryFile::CheckNextDWord(file, HeaderLabel))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
mDocument.mExpectedTracks = ByteUtils::ToWord(buffer);
|
||||
|
||||
if(!GetWord(file, buffer))
|
||||
int length = 0;
|
||||
if(!BinaryFile::GetNextDWord(file, length))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
int time_division;
|
||||
std::memcpy(&time_division, buffer, sizeof(int));
|
||||
|
||||
const int TOP_7_BITS = 0x7F00; // 0111 1111 - 0000 0000
|
||||
mDocument.mUseFps = ByteUtils::GetWordFirstBit(time_division);
|
||||
mDocument.mFps = ((~time_division & TOP_7_BITS) >> 8) - 1; // Reverse 2complement of next 7 bits
|
||||
mDocument.ticksPerFrame = ByteUtils::GetWordLastByte(time_division);
|
||||
return true;
|
||||
if(!BinaryFile::GetNextWord(file, mDocument.mFormatType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!BinaryFile::GetNextWord(file, mDocument.mExpectedTracks))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessTimeDivision(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessTimeDivision(std::ifstream& file)
|
||||
{
|
||||
int time_division;
|
||||
if(!BinaryFile::GetNextWord(file, time_division, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int TOP_7_BITS = 0x7F00; // 0111 1111 - 0000 0000
|
||||
mDocument.mUseFps = ByteUtils::GetWordFirstBit(time_division);
|
||||
mDocument.mFps = ((~time_division & TOP_7_BITS) >> 8) - 1; // Reverse 2complement of next 7 bits
|
||||
mDocument.ticksPerFrame = ByteUtils::GetWordLastByte(time_division);
|
||||
return true;
|
||||
}
|
||||
|
||||
int MidiReader::ProcessTimeDelta(std::ifstream& file)
|
||||
{
|
||||
char c;
|
||||
file.get(c);
|
||||
bool needs_next = c & ByteUtils::BYTE_FIRST_BIT;
|
||||
int time_offset = 0;
|
||||
if(!needs_next)
|
||||
{
|
||||
time_offset = int(c);
|
||||
mTrackByteCount ++;
|
||||
}
|
||||
std::cout << "Next " << needs_next <<std::endl;
|
||||
std::cout << "Offset " << time_offset <<std::endl;
|
||||
return time_offset;
|
||||
char c;
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
|
||||
if(unsigned(c >> 7) == 0)
|
||||
{
|
||||
int delta = int(c);
|
||||
std::cout << "Time delta final: " << delta << "|" << std::bitset<16>(c)<< std::endl;
|
||||
return delta;
|
||||
}
|
||||
|
||||
int working_c = c;
|
||||
int final_c = 0;
|
||||
unsigned count = 0;
|
||||
std::cout << "Working " << std::bitset<8>(working_c) << std::endl;
|
||||
while(unsigned(working_c >> 7) != 0)
|
||||
{
|
||||
char corrected = (working_c &= ~(1UL << 7));
|
||||
final_c <<= 7;
|
||||
final_c |= (corrected << 7*count);
|
||||
char file_c;
|
||||
file.get(file_c);
|
||||
mTrackByteCount ++;
|
||||
working_c = int(file_c);
|
||||
std::cout << "Working " << std::bitset<8>(working_c) << std::endl;
|
||||
count ++;
|
||||
}
|
||||
std::cout << "Time delta start: " << std::bitset<16>(final_c) << std::endl;
|
||||
final_c <<= 7;
|
||||
std::cout << "Time delta pre: " << std::bitset<16>(final_c) << std::endl;
|
||||
final_c |= (working_c << 7*(count-1));
|
||||
|
||||
int delta = int(final_c);
|
||||
std::cout << "Time delta final: " << delta << "|" << std::bitset<16>(final_c)<< std::endl;
|
||||
return delta;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessTrackNameMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
||||
{
|
||||
event.SetIsTrackName();
|
||||
|
||||
int length = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, length);
|
||||
mTrackByteCount ++;
|
||||
|
||||
std::string name;
|
||||
BinaryFile::GetNextString(file, name, length);
|
||||
mTrackByteCount += length;
|
||||
std::cout << "Track name: " << name << "|" << length <<std::endl;
|
||||
event.SetLabel(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessSetTempoMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
||||
{
|
||||
event.SetIsSetTempo();
|
||||
|
||||
int length = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, length);
|
||||
mTrackByteCount ++;
|
||||
|
||||
char buffer[length];
|
||||
BinaryFile::GetNextNBytes(file, buffer, length);
|
||||
mTrackByteCount += length;
|
||||
|
||||
int tempo = ByteUtils::ToInt(buffer, length);
|
||||
const int MICROSECONDS_PER_MINUTE = 60000000;
|
||||
std::cout << "Got tempo "<< tempo << "|" << MICROSECONDS_PER_MINUTE/tempo<< std::endl;
|
||||
event.SetValue(tempo);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool MidiReader::ProcessTimeSignatureMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
||||
{
|
||||
event.SetIsTimeSignature();
|
||||
|
||||
int length = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, length);
|
||||
mTrackByteCount ++;
|
||||
|
||||
int numer = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, numer);
|
||||
mTrackByteCount ++;
|
||||
|
||||
int denom = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, denom);
|
||||
mTrackByteCount ++;
|
||||
|
||||
int metro = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, metro);
|
||||
mTrackByteCount ++;
|
||||
|
||||
int f32s = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, f32s);
|
||||
mTrackByteCount ++;
|
||||
|
||||
std::cout << "Time sig " << length << "|" << numer << "|" << denom << "|" << metro << "|" << f32s << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
||||
{
|
||||
char c;
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
if(MetaMidiEvent::IsTrackName(c))
|
||||
{
|
||||
event.mType = MetaMidiEvent::Type::TRACK_NAME;
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
int length = int(c);
|
||||
std::string name;
|
||||
std::cout << "Track name: " << length <<std::endl;
|
||||
|
||||
for(unsigned idx=0; idx<length; idx++)
|
||||
{
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
name += c;
|
||||
}
|
||||
std::cout << name << std::endl;
|
||||
event.mLabel = "Track name";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "something else" << std::endl;
|
||||
std::cout << "Next " << std::bitset<8>(c) << "|" <<std::endl;
|
||||
}
|
||||
return true;
|
||||
char c;
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
if(MetaMidiEvent::IsTrackName(c))
|
||||
{
|
||||
ProcessTrackNameMetaEvent(file, event);
|
||||
}
|
||||
else if(MetaMidiEvent::IsSetTempo(c))
|
||||
{
|
||||
ProcessSetTempoMetaEvent(file, event);
|
||||
}
|
||||
else if(MetaMidiEvent::IsTimeSignature(c))
|
||||
{
|
||||
ProcessTimeSignatureMetaEvent(file, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "something else" << std::endl;
|
||||
std::cout << "Next " << std::bitset<8>(c) << "|" <<std::endl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessEvent(std::ifstream& file)
|
||||
bool MidiReader::ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event, char c)
|
||||
{
|
||||
char c;
|
||||
int timeDelta = ProcessTimeDelta(file);
|
||||
|
||||
file.get(c);
|
||||
mTrackByteCount ++;
|
||||
if(MidiEvent::IsMetaEvent(c))
|
||||
{
|
||||
MetaMidiEvent event;
|
||||
event.mTimeDelta = timeDelta;
|
||||
std::cout << "Meta event " <<std::endl;
|
||||
ProcessMetaEvent(file, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
int first_byte = 0xF0;
|
||||
int event_type = (c & first_byte) >> 4;
|
||||
std::cout << "Next " << std::bitset<8>(c) << "|" << event_type <<std::endl;
|
||||
std::cout << event_type << std::endl;
|
||||
}
|
||||
return true;
|
||||
int value0 = int(c);
|
||||
int value1 = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, value1);
|
||||
mTrackByteCount ++;
|
||||
event.SetValues(value0, value1);
|
||||
std::cout << "sdata: " << value0 << "|" << value1 << "|" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiReader::ProcessTrackChunk(std::ifstream& file)
|
||||
bool MidiReader::ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event)
|
||||
{
|
||||
char c;
|
||||
unsigned bufferSize = ByteUtils::BYTES_PER_DWORD;
|
||||
char buffer[bufferSize];
|
||||
int value0 = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, value0);
|
||||
mTrackByteCount ++;
|
||||
|
||||
if(!GetDWord(file, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int value1 = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, value1);
|
||||
mTrackByteCount ++;
|
||||
event.SetValues(value0, value1);
|
||||
std::cout << "data: " << value0 << "|" << value1 << "|" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!ByteUtils::CompareDWords(buffer, TrackChunkLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool MidiReader::ProcessMidiChannelEvent(std::ifstream& file, char firstByte, MidiChannelEvent& event)
|
||||
{
|
||||
int first_four_bits = 0xF0;
|
||||
int second_four_bits = 0xF;
|
||||
int event_type = (firstByte & first_four_bits) >> 4;
|
||||
int midi_channel = (firstByte & second_four_bits) >> 4;
|
||||
std::cout << "Channel: " << midi_channel << std::endl;
|
||||
|
||||
if(!GetDWord(file, buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int chunkSize = ByteUtils::ToDWord(buffer);
|
||||
std::cout << "Chunk size: "<< chunkSize << std::endl;
|
||||
if(MidiChannelEvent::IsStatusByte(event_type))
|
||||
{
|
||||
if(MidiChannelEvent::IsControllerEvent(event_type))
|
||||
{
|
||||
event.SetType(MidiChannelEvent::Type::CONTROLLER);
|
||||
mLastChannelEventType = MidiChannelEvent::Type::CONTROLLER;
|
||||
std::cout << "Controller Event"<< std::endl;
|
||||
ProcessMidiEventData(file, event);
|
||||
}
|
||||
else if(MidiChannelEvent::IsProgramEvent(event_type))
|
||||
{
|
||||
event.SetType(MidiChannelEvent::Type::PROGRAM);
|
||||
mLastChannelEventType = MidiChannelEvent::Type::PROGRAM;
|
||||
std::cout << "Program Event"<< std::endl;
|
||||
int value0 = 0;
|
||||
BinaryFile::GetNextByteAsInt(file, value0);
|
||||
mTrackByteCount ++;
|
||||
std::cout << "value " << value0 << std::endl;
|
||||
}
|
||||
else if(MidiChannelEvent::IsNoteOnEvent(event_type))
|
||||
{
|
||||
event.SetType(MidiChannelEvent::Type::NOTE_ON);
|
||||
mLastChannelEventType = MidiChannelEvent::Type::NOTE_ON;
|
||||
std::cout << "Note on Event"<< std::endl;
|
||||
ProcessMidiEventData(file, event);
|
||||
}
|
||||
else if(MidiChannelEvent::IsNoteOffEvent(event_type))
|
||||
{
|
||||
event.SetType(MidiChannelEvent::Type::NOTE_OFF);
|
||||
mLastChannelEventType = MidiChannelEvent::Type::NOTE_OFF;
|
||||
std::cout << "Note off Event"<< std::endl;
|
||||
ProcessMidiEventData(file, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Unknown status event: " << std::bitset<8>(firstByte) << "|" << event_type <<std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mLastChannelEventType == MidiChannelEvent::Type::CONTROLLER)
|
||||
{
|
||||
std::cout << "Controller running event: " <<std::endl;
|
||||
ProcessMidiEventData(file, event, firstByte);
|
||||
}
|
||||
else if(mLastChannelEventType == MidiChannelEvent::Type::NOTE_ON)
|
||||
{
|
||||
std::cout << "Note on running event: " <<std::endl;
|
||||
ProcessMidiEventData(file, event, firstByte);
|
||||
}
|
||||
else if(mLastChannelEventType == MidiChannelEvent::Type::NOTE_OFF)
|
||||
{
|
||||
std::cout << "Note off running event: " <<std::endl;
|
||||
ProcessMidiEventData(file, event, firstByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Unknown running event: " <<std::endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mTrackByteCount = 0;
|
||||
MidiTrack track;
|
||||
ProcessEvent(file);
|
||||
std::cout << "Track byte count: "<< mTrackByteCount << std::endl;
|
||||
bool MidiReader::ProcessEvent(std::ifstream& file, MidiTrack& track)
|
||||
{
|
||||
char c;
|
||||
int timeDelta = ProcessTimeDelta(file);
|
||||
|
||||
ProcessEvent(file);
|
||||
file.get(c);
|
||||
std::cout << std::bitset<8>(c) << std::endl;
|
||||
mTrackByteCount ++;
|
||||
if(MidiEvent::IsMetaEvent(c))
|
||||
{
|
||||
MetaMidiEvent event;
|
||||
event.SetTimeDelta(timeDelta);
|
||||
std::cout << "Meta event " <<std::endl;
|
||||
ProcessMetaEvent(file, event);
|
||||
track.AddEvent(event);
|
||||
}
|
||||
else if(MidiEvent::IsSysExEvent(c))
|
||||
{
|
||||
std::cout << "Sysex event" << std::endl;
|
||||
}
|
||||
else
|
||||
{ // Midi event
|
||||
MidiChannelEvent event;
|
||||
event.SetTimeDelta(timeDelta);
|
||||
std::cout << "Midi event" << std::endl;
|
||||
ProcessMidiChannelEvent(file, c, event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mDocument.mTracks.push_back(track);
|
||||
return true;
|
||||
bool MidiReader::ProcessTrackChunk(std::ifstream& file, bool debug)
|
||||
{
|
||||
if(!BinaryFile::CheckNextDWord(file, TrackChunkLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int chunkSize = 0;
|
||||
if(!BinaryFile::GetNextDWord(file, chunkSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::cout << "Chunk size: "<< chunkSize << std::endl;
|
||||
|
||||
mTrackByteCount = 0;
|
||||
MidiTrack track;
|
||||
unsigned iter_count = 0;
|
||||
while(mTrackByteCount < chunkSize)
|
||||
{
|
||||
std::cout << "-------------" << std::endl;
|
||||
ProcessEvent(file, track);
|
||||
std::cout << "Track byte count: "<< mTrackByteCount << std::endl;
|
||||
if(debug && iter_count == 25)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
iter_count ++;
|
||||
}
|
||||
|
||||
mDocument.mTracks.push_back(track);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiReader::Read(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path);
|
||||
if(!ProcessHeader(file))
|
||||
{
|
||||
std::cout << "Problem processing header" << std::endl;
|
||||
}
|
||||
std::ifstream file(path);
|
||||
if(!ProcessHeader(file))
|
||||
{
|
||||
std::cout << "Problem processing header" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!ProcessTrackChunk(file))
|
||||
{
|
||||
std::cout << "Problem processing track chunk" << std::endl;
|
||||
}
|
||||
int trackCount = 0;
|
||||
if(!ProcessTrackChunk(file))
|
||||
{
|
||||
std::cout << "Problem processing track chunk" << std::endl;
|
||||
return;
|
||||
}
|
||||
trackCount ++;
|
||||
|
||||
std::cout << "****************************************" << std::endl;
|
||||
std::cout << "Track 2" << std::endl;
|
||||
|
||||
if(!ProcessTrackChunk(file, true))
|
||||
{
|
||||
std::cout << "Problem processing track chunk" << std::endl;
|
||||
return;
|
||||
}
|
||||
trackCount ++;
|
||||
|
||||
|
||||
file.close();
|
||||
file.close();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue