diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 46532dd..d9c9037 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -6,12 +6,14 @@ list(APPEND audio_LIB_INCLUDES midi/MidiTrack.cpp midi/MidiDocument.cpp midi/MidiEvent.cpp - midi/MetaMidiEvent.cpp) + midi/MetaMidiEvent.cpp + midi/MidiChannelEvent.cpp) add_library(audio SHARED ${audio_LIB_INCLUDES}) target_include_directories(audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src/core/file_utilities" "${PROJECT_SOURCE_DIR}/src/core/loggers" "${CMAKE_CURRENT_SOURCE_DIR}/audio_interfaces" "${CMAKE_CURRENT_SOURCE_DIR}/midi" diff --git a/src/audio/midi/MetaMidiEvent.cpp b/src/audio/midi/MetaMidiEvent.cpp index 9c3cf80..e8bb111 100644 --- a/src/audio/midi/MetaMidiEvent.cpp +++ b/src/audio/midi/MetaMidiEvent.cpp @@ -1,7 +1,55 @@ #include "MetaMidiEvent.h" MetaMidiEvent::MetaMidiEvent() - : MidiEvent() + : MidiEvent(), + mType(Type::UNSET), + mLabel(), + mValue(0) { } + +std::shared_ptr MetaMidiEvent::Create() +{ + return std::make_shared(); +} + +void MetaMidiEvent::SetValue(int value) +{ + mValue = value; +} + +bool MetaMidiEvent::IsTrackName(char c) +{ + return (c & META_TRACK_NAME) == META_TRACK_NAME; +} + +bool MetaMidiEvent::IsSetTempo(char c) +{ + return (c & META_SET_TEMPO) == META_SET_TEMPO; +} + +bool MetaMidiEvent::IsTimeSignature(char c) +{ + return (c & META_TIME_SIG) == META_TIME_SIG; +} + +void MetaMidiEvent::SetIsSetTempo() +{ + mType = Type::SET_TEMPO; +} + +void MetaMidiEvent::SetIsTrackName() +{ + mType = Type::TRACK_NAME; +} + +void MetaMidiEvent::SetIsTimeSignature() +{ + mType = Type::TIME_SIGNATURE; +} + +void MetaMidiEvent::SetLabel(const std::string& label) +{ + mLabel = label; +} diff --git a/src/audio/midi/MetaMidiEvent.h b/src/audio/midi/MetaMidiEvent.h index ce38a78..c062897 100644 --- a/src/audio/midi/MetaMidiEvent.h +++ b/src/audio/midi/MetaMidiEvent.h @@ -1,26 +1,49 @@ #pragma once -#include - #include "MidiEvent.h" +#include +#include class MetaMidiEvent : public MidiEvent { public: - enum class Type - { - UNSET, - TRACK_NAME - }; + enum class Type + { + UNSET, + TRACK_NAME, + SET_TEMPO, + TIME_SIGNATURE + }; - Type mType = Type::UNSET; - std::string mLabel; - MetaMidiEvent(); +private: - static bool IsTrackName(char c) - { - return (c & META_TRACK_NAME) == META_TRACK_NAME; - } + static const int META_SET_TEMPO = 0x51; // 0101 0001 + static const int META_TRACK_NAME = 0x3; // 0000 0011 + static const int META_TIME_SIG = 0x58; // 0101 1000 + Type mType; + std::string mLabel; + int mValue; - static const int META_TRACK_NAME = 0x3; // 0000 0011 +public: + + MetaMidiEvent(); + static std::shared_ptr Create(); + + void SetValue(int value); + + void SetIsTrackName(); + + void SetIsSetTempo(); + + void SetIsTimeSignature(); + + void SetLabel(const std::string& label); + + static bool IsTrackName(char c); + + static bool IsSetTempo(char c); + + static bool IsTimeSignature(char c); }; + +using MetaMidiEventPtr = std::shared_ptr; diff --git a/src/audio/midi/MidiChannelEvent.cpp b/src/audio/midi/MidiChannelEvent.cpp new file mode 100644 index 0000000..f0a3dce --- /dev/null +++ b/src/audio/midi/MidiChannelEvent.cpp @@ -0,0 +1,80 @@ +#include "MidiChannelEvent.h" + +MidiChannelEvent::MidiChannelEvent() + : MidiEvent(), + mType(Type::NONE), + mValue0(0), + mValue1(0) +{ + +} + +std::shared_ptr MidiChannelEvent::Create() +{ + return std::make_shared(); +} + +void MidiChannelEvent::SetType(Type type) +{ + mType = type; +} +MidiChannelEvent::Type MidiChannelEvent::GetType() +{ + return mType; +} +void MidiChannelEvent::SetValues(int value0, int value1) +{ + mValue0 = value0; + mValue1 = value1; +} +int MidiChannelEvent::GetValue0() +{ + return mValue0; +} +int MidiChannelEvent::GetValue1() +{ + return mValue1; +} + +bool MidiChannelEvent::IsControllerEvent(int c) +{ + return (c & CONTROLLER) == CONTROLLER; +} + +bool MidiChannelEvent::IsNoteOnEvent(int c) +{ + return (c & NOTE_ON) == NOTE_ON; +} + +bool MidiChannelEvent::IsNoteOffEvent(int c) +{ + return (c & NOTE_OFF) == NOTE_OFF; +} + +bool MidiChannelEvent::IsNoteAfterTouchEvent(int c) +{ + return (c & NOTE_AFTERTOUCH) == NOTE_AFTERTOUCH; +} + +bool MidiChannelEvent::IsProgramEvent(int c) +{ + return (c & PROGRAM) == PROGRAM; +} + +bool MidiChannelEvent::IsChannelAftertouchEvent(int c) +{ + return (c & CHANNEL_AFTERTOUCH) == CHANNEL_AFTERTOUCH; +} + +bool MidiChannelEvent::IsPitchbendEvent(int c) +{ + return (c & PITCH_BEND) == PITCH_BEND; +} + +bool MidiChannelEvent::IsStatusByte(int c) +{ + return IsControllerEvent(c) || IsNoteOnEvent(c) || + IsNoteOffEvent(c) || IsNoteAfterTouchEvent(c) || + IsProgramEvent(c) || IsChannelAftertouchEvent(c) || + IsPitchbendEvent(c); +} diff --git a/src/audio/midi/MidiChannelEvent.h b/src/audio/midi/MidiChannelEvent.h new file mode 100644 index 0000000..6546fc2 --- /dev/null +++ b/src/audio/midi/MidiChannelEvent.h @@ -0,0 +1,52 @@ +#pragma once + +#include "MidiEvent.h" + +class MidiChannelEvent : public MidiEvent +{ +public: + enum class Type{ + NONE, NOTE_OFF, NOTE_ON, NOTE_AFTERTOUCH, CONTROLLER, + PROGRAM, CHANNEL_AFTERTOUCH, PITCH_BEND + }; + +private: + static const int NOTE_OFF = 0x8; + static const int NOTE_ON = 0x9; + static const int NOTE_AFTERTOUCH = 0xA; + static const int CONTROLLER = 0xB; + static const int PROGRAM = 0xC; + static const int CHANNEL_AFTERTOUCH = 0xD; + static const int PITCH_BEND = 0xE; + Type mType; + int mValue0; + int mValue1; + +public: + MidiChannelEvent(); + static std::shared_ptr Create(); + + void SetType(Type type); + Type GetType(); + void SetValues(int value0, int value1); + int GetValue0(); + int GetValue1(); + + static bool IsControllerEvent(int c); + + static bool IsNoteOnEvent(int c); + + static bool IsNoteOffEvent(int c); + + static bool IsNoteAfterTouchEvent(int c); + + static bool IsProgramEvent(int c); + + static bool IsChannelAftertouchEvent(int c); + + static bool IsPitchbendEvent(int c); + + static bool IsStatusByte(int c); +}; + +using MidiChannelEventPtr = std::shared_ptr; diff --git a/src/audio/midi/MidiEvent.cpp b/src/audio/midi/MidiEvent.cpp index 0b52bcb..58d2ebf 100644 --- a/src/audio/midi/MidiEvent.cpp +++ b/src/audio/midi/MidiEvent.cpp @@ -1,6 +1,37 @@ #include "MidiEvent.h" MidiEvent::MidiEvent() + : mTimeDelta(0) { } + +void MidiEvent::SetTimeDelta(int delta) +{ + mTimeDelta = delta; +} + +std::shared_ptr MidiEvent::Create() +{ + return std::make_shared(); +} + +bool MidiEvent::IsMetaEvent(char c) +{ + return (c & META_EVENT) == META_EVENT; +} + +bool MidiEvent::IsSysExEvent(char c) +{ + return IsNormalSysExEvent(c) || IsDividedSysExEvent(c); +} + +bool MidiEvent::IsNormalSysExEvent(char c) +{ + return (c & SYSEX_EVENT) == SYSEX_EVENT; +} + +bool MidiEvent::IsDividedSysExEvent(char c) +{ + return (c & DIVIDED_SYSEX_EVENT) == DIVIDED_SYSEX_EVENT; +} diff --git a/src/audio/midi/MidiEvent.h b/src/audio/midi/MidiEvent.h index e684319..78f060e 100644 --- a/src/audio/midi/MidiEvent.h +++ b/src/audio/midi/MidiEvent.h @@ -1,17 +1,26 @@ #pragma once +#include + class MidiEvent { + static const int META_EVENT = 0xFF; // 1111 1111 + static const int SYSEX_EVENT = 0xF0; + static const int DIVIDED_SYSEX_EVENT = 0xF7; + int mTimeDelta; public: - int mTimeDelta = 0; - MidiEvent(); - static bool IsMetaEvent(char c) - { - return (c & META_EVENT) == META_EVENT; - } + MidiEvent(); + virtual ~MidiEvent() = default; -public: - static const int META_EVENT = 0xFF; // 1111 1111 + static std::shared_ptr Create(); + + void SetTimeDelta(int delta); + static bool IsMetaEvent(char c); + static bool IsSysExEvent(char c); + static bool IsNormalSysExEvent(char c); + static bool IsDividedSysExEvent(char c); }; + +using MidiEventPtr = std::shared_ptr; diff --git a/src/audio/midi/MidiReader.cpp b/src/audio/midi/MidiReader.cpp index e7a0eb9..37d94f0 100644 --- a/src/audio/midi/MidiReader.cpp +++ b/src/audio/midi/MidiReader.cpp @@ -1,211 +1,379 @@ #include "MidiReader.h" + +#include "MidiDocument.h" +#include "ByteUtils.h" +#include "MidiTrack.h" +#include "BinaryFile.h" + #include #include #include #include -#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> 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 <> 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 <(c) << "|" <(c) << "|" <> 4; - std::cout << "Next " << std::bitset<8>(c) << "|" << event_type <> 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 <(c) << std::endl; + mTrackByteCount ++; + if(MidiEvent::IsMetaEvent(c)) + { + MetaMidiEvent event; + event.SetTimeDelta(timeDelta); + std::cout << "Meta event " < class MidiReader { - static constexpr const char TrackChunkLabel[] = "MTrk"; - static constexpr const char HeaderLabel[] = "MThd"; - MidiDocument mDocument; - unsigned mTrackByteCount; + static constexpr const char TrackChunkLabel[] = "MTrk"; + static constexpr const char HeaderLabel[] = "MThd"; + MidiDocument mDocument; + unsigned mTrackByteCount; + MidiChannelEvent::Type mLastChannelEventType; public: - MidiReader(); + MidiReader(); - void Read(const std::string& path); + void Read(const std::string& path); private: - bool ProcessHeader(std::ifstream& file); + bool ProcessHeader(std::ifstream& file); - bool ProcessTrackChunk(std::ifstream& file); + bool ProcessTimeDivision(std::ifstream& file); - bool ProcessEvent(std::ifstream& file); + bool ProcessTrackChunk(std::ifstream& file, bool debug=false); - bool ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event); + bool ProcessEvent(std::ifstream& file, MidiTrack& track); - int ProcessTimeDelta(std::ifstream& file); + bool ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event, char c); + bool ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event); + + bool ProcessMidiChannelEvent(std::ifstream& file, char firstByte, MidiChannelEvent& event); + bool ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event); + + int ProcessTimeDelta(std::ifstream& file); + + bool ProcessTrackNameMetaEvent(std::ifstream& file, MetaMidiEvent& event); + bool ProcessSetTempoMetaEvent(std::ifstream& file, MetaMidiEvent& event); + bool ProcessTimeSignatureMetaEvent(std::ifstream& file, MetaMidiEvent& event); }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3ebb043..4f09cf3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -3,6 +3,7 @@ list(APPEND core_LIB_INCLUDES Color.cpp CommandLineArgs.cpp loggers/FileLogger.cpp + file_utilities/BinaryFile.cpp StringUtils.cpp http/HttpResponse.cpp) diff --git a/src/core/file_utilities/BinaryFile.cpp b/src/core/file_utilities/BinaryFile.cpp new file mode 100644 index 0000000..cf06acd --- /dev/null +++ b/src/core/file_utilities/BinaryFile.cpp @@ -0,0 +1,88 @@ +#include "BinaryFile.h" +#include "ByteUtils.h" + +bool BinaryFile::GetNextByteAsInt(std::ifstream& file, int& target) +{ + char c; + file.get(c); + target = int(c); + return true; +} + +bool BinaryFile::GetNextNBytes(std::ifstream& file, char* buffer, unsigned number) +{ + char c; + for(unsigned idx=0; idx +#include + +class BinaryFile +{ +public: + + static bool GetNextByteAsInt(std::ifstream& file, int& target); + + static bool GetNextNBytes(std::ifstream& file, char* buffer, unsigned numBytes); + + static bool GetNextWord(std::ifstream& file, char* buffer); + + static bool GetNextDWord(std::ifstream& file, char* buffer); + + static bool GetNextWord(std::ifstream& file, int& target, bool reverse = true); + + static bool GetNextDWord(std::ifstream& file, int& target); + + static bool CheckNextDWord(std::ifstream& file, const char* target); + + static bool GetNextString(std::ifstream& file, std::string& target, unsigned numBytes); +};