From 8b5f485d1e43d13c86b62d3258b5ad2668a9a800 Mon Sep 17 00:00:00 2001 From: jmsgrogan Date: Sun, 23 May 2021 21:02:38 +0100 Subject: [PATCH] Improve audio and midi support. --- .gitignore | 1 + src/audio/AudioDevice.h | 18 +- src/audio/AudioManager.h | 15 +- src/audio/AudioSample.cpp | 47 +++ src/audio/AudioSample.h | 27 ++ src/audio/AudioSynth.cpp | 37 ++ src/audio/AudioSynth.h | 16 + src/audio/AudioTrack.cpp | 6 + src/audio/AudioTrack.h | 8 + src/audio/AudioWriter.cpp | 58 +++ src/audio/AudioWriter.h | 21 + src/audio/CMakeLists.txt | 17 +- src/audio/audio_interfaces/AlsaInterface.cpp | 27 +- src/audio/audio_interfaces/AlsaInterface.h | 7 +- src/audio/midi/MetaMidiEvent.cpp | 109 +++-- src/audio/midi/MetaMidiEvent.h | 62 ++- src/audio/midi/MidiChannelEvent.cpp | 122 ++++-- src/audio/midi/MidiChannelEvent.h | 70 ++-- src/audio/midi/MidiDocument.cpp | 44 ++ src/audio/midi/MidiDocument.h | 34 +- src/audio/midi/MidiElements.h | 31 ++ src/audio/midi/MidiEvent.cpp | 10 +- src/audio/midi/MidiEvent.h | 9 +- src/audio/midi/MidiReader.cpp | 379 ------------------ src/audio/midi/MidiReader.h | 43 -- src/audio/midi/MidiTrack.cpp | 28 +- src/audio/midi/MidiTrack.h | 23 +- .../midi/reader/MidiChannelEventAdapter.cpp | 77 ++++ .../midi/reader/MidiChannelEventAdapter.h | 14 + .../midi/reader/MidiMetaEventAdapter.cpp | 190 +++++++++ src/audio/midi/reader/MidiMetaEventAdapter.h | 21 + src/audio/midi/reader/MidiReader.cpp | 153 +++++++ src/audio/midi/reader/MidiReader.h | 35 ++ src/audio/midi/reader/MidiTimeAdapter.cpp | 66 +++ src/audio/midi/reader/MidiTimeAdapter.h | 13 + src/core/ByteUtils.h | 27 +- src/core/file_utilities/BinaryFile.cpp | 22 +- src/core/file_utilities/BinaryFile.h | 23 +- src/core/file_utilities/File.cpp | 16 +- src/core/file_utilities/File.h | 2 +- src/core/file_utilities/FileFormats.cpp | 1 + test/CMakeLists.txt | 29 +- test/audio/TestAudioWriter.cpp | 34 ++ test/audio/TestMidiReader.cpp | 31 ++ test/test_utils/TestCase.h | 13 + test/test_utils/TestCaseRunner.cpp | 26 ++ test/test_utils/TestCaseRunner.h | 18 + 47 files changed, 1446 insertions(+), 634 deletions(-) create mode 100644 src/audio/AudioSample.cpp create mode 100644 src/audio/AudioSample.h create mode 100644 src/audio/AudioSynth.cpp create mode 100644 src/audio/AudioSynth.h create mode 100644 src/audio/AudioTrack.cpp create mode 100644 src/audio/AudioTrack.h create mode 100644 src/audio/AudioWriter.cpp create mode 100644 src/audio/AudioWriter.h create mode 100644 src/audio/midi/MidiElements.h delete mode 100644 src/audio/midi/MidiReader.cpp delete mode 100644 src/audio/midi/MidiReader.h create mode 100644 src/audio/midi/reader/MidiChannelEventAdapter.cpp create mode 100644 src/audio/midi/reader/MidiChannelEventAdapter.h create mode 100644 src/audio/midi/reader/MidiMetaEventAdapter.cpp create mode 100644 src/audio/midi/reader/MidiMetaEventAdapter.h create mode 100644 src/audio/midi/reader/MidiReader.cpp create mode 100644 src/audio/midi/reader/MidiReader.h create mode 100644 src/audio/midi/reader/MidiTimeAdapter.cpp create mode 100644 src/audio/midi/reader/MidiTimeAdapter.h create mode 100644 test/audio/TestAudioWriter.cpp create mode 100644 test/audio/TestMidiReader.cpp create mode 100644 test/test_utils/TestCase.h create mode 100644 test/test_utils/TestCaseRunner.cpp create mode 100644 test/test_utils/TestCaseRunner.h diff --git a/.gitignore b/.gitignore index 8c6bc44..be28466 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .cproject .project .settings/ +/Debug/ diff --git a/src/audio/AudioDevice.h b/src/audio/AudioDevice.h index e213a0d..c28297e 100644 --- a/src/audio/AudioDevice.h +++ b/src/audio/AudioDevice.h @@ -6,20 +6,14 @@ class AudioDevice { -private: - - std::string mName; - unsigned mSampleRate; - unsigned mNumChannels; - unsigned mPeriod; - std::size_t mBufferSize; - public: AudioDevice(); ~AudioDevice(); + static std::unique_ptr Create(); + void SetSampleRate(unsigned rate); unsigned GetSampleRate() const; @@ -40,7 +34,13 @@ public: std::string GetName() const; - static std::unique_ptr Create(); +private: + + std::string mName {"unset"}; + unsigned mSampleRate {44100}; + unsigned mNumChannels {1}; + unsigned mPeriod {2}; + std::size_t mBufferSize{0}; }; using AudioDevicePtr = std::unique_ptr; diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h index 2140c50..b1b6bcd 100644 --- a/src/audio/AudioManager.h +++ b/src/audio/AudioManager.h @@ -1,19 +1,14 @@ #pragma once +#include "IAudioInterface.h" +#include "AudioDevice.h" + #include #include -#include "audio_interfaces/IAudioInterface.h" -#include "AudioDevice.h" - class AudioManager { -private: - - std::vector mAudioDevices; - IAudioInterfaceUPtr mAudioInterface; - public: AudioManager(); @@ -31,6 +26,10 @@ public: IAudioInterface* GetAudioInterface(); void Play(); + +private: + std::vector mAudioDevices; + IAudioInterfaceUPtr mAudioInterface; }; using AudioManagerUPtr = std::unique_ptr; diff --git a/src/audio/AudioSample.cpp b/src/audio/AudioSample.cpp new file mode 100644 index 0000000..a301e61 --- /dev/null +++ b/src/audio/AudioSample.cpp @@ -0,0 +1,47 @@ +#include "AudioSample.h" + +AudioSample::AudioSample() +{ + +} + +std::unique_ptr AudioSample::Create() +{ + return std::make_unique(); +} + +std::size_t AudioSample::GetNumChannels() const +{ + return mData.size(); +} + +unsigned AudioSample::GetSampleRate() const +{ + return mSampleRate; +} + +unsigned AudioSample::GetBitDepth() const +{ + return mBitDepth; +} + +void AudioSample::SetChannelData(const std::vector& data, std::size_t channel) +{ + if (mData.size() == channel) + { + mData.push_back(data); + } + else if(mData.size() > channel) + { + mData[channel] = data; + } +} + +std::vector AudioSample::GetChannelData(std::size_t channel) const +{ + if(mData.size() > channel) + { + return mData[channel]; + } + return std::vector(); +} diff --git a/src/audio/AudioSample.h b/src/audio/AudioSample.h new file mode 100644 index 0000000..66aba07 --- /dev/null +++ b/src/audio/AudioSample.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +class AudioSample +{ +public: + + AudioSample(); + + static std::unique_ptr Create(); + + std::vector GetChannelData(std::size_t channel) const; + void SetChannelData(const std::vector& data, std::size_t channel); + + std::size_t GetNumChannels() const; + unsigned GetSampleRate() const; + unsigned GetBitDepth() const; + +private: + unsigned mSampleRate { 44100 }; + unsigned mBitDepth{ 16 }; + std::vector > mData; +}; + +using AudioSamplePtr = std::unique_ptr; diff --git a/src/audio/AudioSynth.cpp b/src/audio/AudioSynth.cpp new file mode 100644 index 0000000..a62a09d --- /dev/null +++ b/src/audio/AudioSynth.cpp @@ -0,0 +1,37 @@ +#include "AudioSynth.h" +#include +#include +#include + +AudioSynth::AudioSynth() +{ + +} + +AudioSamplePtr AudioSynth::GetConstant(unsigned amplitude, unsigned duration) +{ + auto sample = AudioSample::Create(); + auto num_samples = duration * sample->GetSampleRate(); + sample->SetChannelData(std::vector(num_samples, amplitude), 0); + return sample; +} + +AudioSamplePtr AudioSynth::GetSineWave(double freq, unsigned duration) +{ + auto sample = AudioSample::Create(); + const auto sample_rate = sample->GetSampleRate(); + const auto num_samples = sample_rate*duration; + + std::vector data(num_samples, 0); + + double tick_duration = 1.0/sample_rate; + double pi_2 = 2.0 * M_PI; + short max_short = std::numeric_limits::max(); + for(unsigned idx=0; idxSetChannelData(data, 0); + return sample; +} diff --git a/src/audio/AudioSynth.h b/src/audio/AudioSynth.h new file mode 100644 index 0000000..5500786 --- /dev/null +++ b/src/audio/AudioSynth.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "AudioSample.h" + +class AudioSynth +{ +public: + + AudioSynth(); + + AudioSamplePtr GetConstant(unsigned amplitude, unsigned duration); + + AudioSamplePtr GetSineWave(double freq, unsigned duration); +}; diff --git a/src/audio/AudioTrack.cpp b/src/audio/AudioTrack.cpp new file mode 100644 index 0000000..70083fe --- /dev/null +++ b/src/audio/AudioTrack.cpp @@ -0,0 +1,6 @@ +#include "AudioTrack.h" + +AudioTrack::AudioTrack() +{ + +} diff --git a/src/audio/AudioTrack.h b/src/audio/AudioTrack.h new file mode 100644 index 0000000..05fc3e1 --- /dev/null +++ b/src/audio/AudioTrack.h @@ -0,0 +1,8 @@ +#pragma once + +class AudioTrack +{ +public: + + AudioTrack(); +}; diff --git a/src/audio/AudioWriter.cpp b/src/audio/AudioWriter.cpp new file mode 100644 index 0000000..5c29258 --- /dev/null +++ b/src/audio/AudioWriter.cpp @@ -0,0 +1,58 @@ +#include "AudioWriter.h" + +#include "File.h" +#include "BinaryFile.h" +#include "AudioSample.h" + +AudioWriter::AudioWriter() +{ + +} + +void AudioWriter::SetPath(const std::string& path) +{ + mPath = path; +} + +void AudioWriter::Write(const AudioSamplePtr& sample) +{ + if (mPath.empty()) + { + return; + } + + const auto sample_rate = sample->GetSampleRate(); + const auto num_channels = sample->GetNumChannels(); + const auto bytes_per_sample = sample->GetBitDepth() / 8; + unsigned byte_rate = sample_rate*num_channels*bytes_per_sample; + + File outfile(mPath); + outfile.SetAccessMode(File::AccessMode::Write); + outfile.Open(true); + auto handle = outfile.GetOutHandle(); + + handle->write("RIFF", 4); + auto data = sample->GetChannelData(0); + const auto num_samples = data.size(); + unsigned content_size = 36 + bytes_per_sample* num_samples*num_channels; + + BinaryFile::Write(handle, content_size); + handle->write("WAVE", 4); + + /* write fmt subchunk */ + handle->write("fmt ", 4); + BinaryFile::Write(handle, 16); // SubChunk1Size + BinaryFile::Write(handle, 1); // PCM Format + BinaryFile::Write(handle, num_channels); + BinaryFile::Write(handle, sample_rate); + BinaryFile::Write(handle, byte_rate); + BinaryFile::Write(handle, num_channels*bytes_per_sample); // block align + BinaryFile::Write(handle, 8*bytes_per_sample); // bits/sample + + /* write data subchunk */ + handle->write("data", 4); + BinaryFile::Write(handle, bytes_per_sample* num_samples*num_channels); // bits/sample + + handle->write(reinterpret_cast(&data[0]), data.size()*sizeof(short)); + outfile.Close(); +} diff --git a/src/audio/AudioWriter.h b/src/audio/AudioWriter.h new file mode 100644 index 0000000..e55f33f --- /dev/null +++ b/src/audio/AudioWriter.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class AudioSample; +using AudioSamplePtr = std::unique_ptr; + +class AudioWriter +{ +public: + AudioWriter(); + + void SetPath(const std::string& path); + + void Write(const AudioSamplePtr& sample); + +private: + + std::string mPath; +}; diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index a3f641e..44a0dea 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -9,10 +9,15 @@ list(APPEND linux_HEADERS list(APPEND audio_HEADERS AudioDevice.h AudioManager.h + AudioTrack.h + AudioSample.h + AudioSynth.h + AudioWriter.h audio_interfaces/IAudioInterface.h - midi/MidiReader.h + midi/reader/MidiReader.h midi/MidiTrack.h midi/MidiDocument.h + midi/MidiElements.h midi/MidiEvent.h midi/MetaMidiEvent.h midi/MidiChannelEvent.h) @@ -24,7 +29,14 @@ list(APPEND linux_INCLUDES list(APPEND audio_LIB_INCLUDES AudioDevice.cpp AudioManager.cpp - midi/MidiReader.cpp + AudioSample.cpp + AudioSynth.cpp + AudioTrack.cpp + AudioWriter.cpp + midi/reader/MidiReader.cpp + midi/reader/MidiTimeAdapter.cpp + midi/reader/MidiMetaEventAdapter.cpp + midi/reader/MidiChannelEventAdapter.cpp midi/MidiTrack.cpp midi/MidiDocument.cpp midi/MidiEvent.cpp @@ -38,6 +50,7 @@ target_include_directories(audio PUBLIC "${PROJECT_SOURCE_DIR}/src/core/loggers" "${CMAKE_CURRENT_SOURCE_DIR}/audio_interfaces" "${CMAKE_CURRENT_SOURCE_DIR}/midi" + "${CMAKE_CURRENT_SOURCE_DIR}/midi/reader" ) list(APPEND linux_LIBS diff --git a/src/audio/audio_interfaces/AlsaInterface.cpp b/src/audio/audio_interfaces/AlsaInterface.cpp index 6375a4c..de413a5 100644 --- a/src/audio/audio_interfaces/AlsaInterface.cpp +++ b/src/audio/audio_interfaces/AlsaInterface.cpp @@ -1,6 +1,10 @@ #include "AlsaInterface.h" #include "FileLogger.h" +#include "AudioSynth.h" + +#include + AlsaInterface::AlsaInterface() :mHandle(), mHardwareParams(), @@ -118,20 +122,17 @@ void AlsaInterface::SetChannelNumber(const AudioDevicePtr& device) void AlsaInterface::Play(const AudioDevicePtr& device) { MLOG_INFO("Playing audio"); - unsigned char *data = (unsigned char *)malloc(mPeriodSize); - int frames = mPeriodSize >> 2; - for(int count = 0; count < 100; count++) + AudioSynth synth; + const unsigned duration = 100; + double freq = 440; + auto data = synth.GetSineWave(freq, duration)->GetChannelData(0); + + int numFrames = mPeriodSize >> 2; + for(int count = 0; count < duration; count++) { - for(int l2 = 0; l2 < frames; l2++) - { - short s1 = (l2 % (512-count)) * 100 - 5000; - short s2 = (l2 % (256-count)) * 100 - 5000; - data[4*l2] = (unsigned char)s1; - data[4*l2+1] = s1 >> 8; - data[4*l2+2] = (unsigned char)s2; - data[4*l2+3] = s2 >> 8; - } - while ((snd_pcm_writei(mHandle, data, frames)) < 0) + const auto offset= count*4*numFrames; + unsigned char frame[4] = {data[offset], data[offset+1], data[offset+2], data[offset+3]}; + while ((snd_pcm_writei(mHandle, frame, numFrames)) < 0) { snd_pcm_prepare(mHandle); MLOG_ERROR("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>"); diff --git a/src/audio/audio_interfaces/AlsaInterface.h b/src/audio/audio_interfaces/AlsaInterface.h index f78645d..fe0eb2c 100644 --- a/src/audio/audio_interfaces/AlsaInterface.h +++ b/src/audio/audio_interfaces/AlsaInterface.h @@ -1,12 +1,11 @@ #pragma once +#include "IAudioInterface.h" +#include "AudioDevice.h" + #include #include -#include "IAudioInterface.h" - -#include "AudioDevice.h" - class AlsaInterface : public IAudioInterface { snd_pcm_t* mHandle; diff --git a/src/audio/midi/MetaMidiEvent.cpp b/src/audio/midi/MetaMidiEvent.cpp index e8bb111..0790cc0 100644 --- a/src/audio/midi/MetaMidiEvent.cpp +++ b/src/audio/midi/MetaMidiEvent.cpp @@ -9,9 +9,69 @@ MetaMidiEvent::MetaMidiEvent() } -std::shared_ptr MetaMidiEvent::Create() +std::unique_ptr MetaMidiEvent::Create() { - return std::make_shared(); + return std::make_unique(); +} + +void MetaMidiEvent::SetType(char c) +{ + switch (c) + { + case META_SEQ_NUM: + mType = Type::SEQ_NUM; + break; + case META_TEXT: + mType = Type::TEXT; + break; + case META_COPYRIGHT: + mType = Type::COPYRIGHT; + break; + case META_TRACK_NAME: + mType = Type::TRACK_NAME; + break; + case META_INSTRUMENT_NAME: + mType = Type::INSTRUMENT_NAME; + break; + case META_LYRIC: + mType = Type::LYRIC; + break; + case META_MARKER: + mType = Type::MARKER; + break; + case META_CUE_POINT: + mType = Type::CUE_POINT; + break; + case META_CHANNEL_PREFIX: + mType = Type::CHANNEL_PREFIX; + break; + case META_END_TRACK: + mType = Type::END_TRACK; + break; + case META_SET_TEMPO: + mType = Type::SET_TEMPO; + break; + case META_SMPTE_OFFSET: + mType = Type::SMPTE_OFFSET; + break; + case META_TIME_SIG: + mType = Type::TIME_SIG; + break; + case META_KEY_SIG: + mType = Type::KEY_SIG; + break; + case META_SEQ_CUSTOM: + mType = Type::SEQ_CUSTOM; + break; + default: + mType = Type::UNKNOWN; + mUnKnownMarker = c; + } +} + +MetaMidiEvent::Type MetaMidiEvent::GetType() const +{ + return mType; } void MetaMidiEvent::SetValue(int value) @@ -19,37 +79,22 @@ 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; } + +void MetaMidiEvent::SetTimeSignature(const MidiTimeSignature& timeSig) +{ + mTimeSig = timeSig; +} + +void MetaMidiEvent::SetTimeCode(const MidiSmtpeTimecode& timeCode) +{ + mTimecode = timeCode; +} + +void MetaMidiEvent::SetKeySignature(const MidiKeySignature& keySig) +{ + mKeySig = keySig; +} diff --git a/src/audio/midi/MetaMidiEvent.h b/src/audio/midi/MetaMidiEvent.h index c062897..4b003cd 100644 --- a/src/audio/midi/MetaMidiEvent.h +++ b/src/audio/midi/MetaMidiEvent.h @@ -1,6 +1,7 @@ #pragma once #include "MidiEvent.h" +#include "MidiElements.h" #include #include @@ -10,40 +11,65 @@ public: enum class Type { UNSET, + UNKNOWN, + SEQ_NUM, + TEXT, + COPYRIGHT, TRACK_NAME, + INSTRUMENT_NAME, + LYRIC, + MARKER, + CUE_POINT, + CHANNEL_PREFIX, + END_TRACK, SET_TEMPO, - TIME_SIGNATURE + SMPTE_OFFSET, + TIME_SIG, + KEY_SIG, + SEQ_CUSTOM }; private: + static const int META_SEQ_NUM = 0x0; + static const int META_TEXT = 0x1; + static const int META_COPYRIGHT = 0x2; + static const int META_TRACK_NAME = 0x3; // 0000 0011 + static const int META_INSTRUMENT_NAME = 0x4; + static const int META_LYRIC = 0x5; + static const int META_MARKER = 0x6; + static const int META_CUE_POINT = 0x7; + static const int META_CHANNEL_PREFIX = 0x20; + static const int META_END_TRACK = 0x2F; static const int META_SET_TEMPO = 0x51; // 0101 0001 - static const int META_TRACK_NAME = 0x3; // 0000 0011 + static const int META_SMPTE_OFFSET = 0x54; static const int META_TIME_SIG = 0x58; // 0101 1000 - Type mType; - std::string mLabel; - int mValue; + static const int META_KEY_SIG = 0x59; // 0101 1001 + + static const int META_SEQ_CUSTOM = 0x7F; public: MetaMidiEvent(); - static std::shared_ptr Create(); + static std::unique_ptr Create(); void SetValue(int value); - - void SetIsTrackName(); - - void SetIsSetTempo(); - - void SetIsTimeSignature(); + void SetType(char c); + Type GetType() const; void SetLabel(const std::string& label); + void SetTimeSignature(const MidiTimeSignature& timeSig); + void SetTimeCode(const MidiSmtpeTimecode& timeCode); + void SetKeySignature(const MidiKeySignature& keySig); - static bool IsTrackName(char c); - - static bool IsSetTempo(char c); - - static bool IsTimeSignature(char c); +private: + Type mType {Type::UNSET}; + char mUnKnownMarker{0}; + std::string mLabel; + int mValue { 0 }; + MidiTimeSignature mTimeSig; + MidiSmtpeTimecode mTimecode; + MidiKeySignature mKeySig; }; -using MetaMidiEventPtr = std::shared_ptr; +using MetaMidiEventPtr = std::unique_ptr; diff --git a/src/audio/midi/MidiChannelEvent.cpp b/src/audio/midi/MidiChannelEvent.cpp index f0a3dce..cf0826f 100644 --- a/src/audio/midi/MidiChannelEvent.cpp +++ b/src/audio/midi/MidiChannelEvent.cpp @@ -9,16 +9,16 @@ MidiChannelEvent::MidiChannelEvent() } -std::shared_ptr MidiChannelEvent::Create() +std::unique_ptr MidiChannelEvent::Create() { - return std::make_shared(); + return std::make_unique(); } void MidiChannelEvent::SetType(Type type) { mType = type; } -MidiChannelEvent::Type MidiChannelEvent::GetType() +MidiChannelEvent::Type MidiChannelEvent::GetType() const { return mType; } @@ -27,54 +27,88 @@ void MidiChannelEvent::SetValues(int value0, int value1) mValue0 = value0; mValue1 = value1; } -int MidiChannelEvent::GetValue0() +int MidiChannelEvent::GetValue0() const { return mValue0; } -int MidiChannelEvent::GetValue1() +int MidiChannelEvent::GetValue1() const { return mValue1; } -bool MidiChannelEvent::IsControllerEvent(int c) +void MidiChannelEvent::SetTypeAndChannel(char c) { - return (c & CONTROLLER) == CONTROLLER; -} + const int first_four_bits = 0xF0; + const int second_four_bits = 0xF; + const int event_type = (c & first_four_bits) >> 4; -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); + const bool isSystemMessage = event_type == 0xF; + if(!isSystemMessage) + { + mChannel = (c & second_four_bits) >> 4; + switch(event_type) + { + case NOTE_OFF: + mType = Type::NOTE_OFF; + break; + case NOTE_ON: + mType = Type::NOTE_ON; + break; + case NOTE_AFTERTOUCH: + mType = Type::NOTE_AFTERTOUCH; + break; + case CONTROLLER: + mType = Type::CONTROLLER; + break; + case PROGRAM: + mType = Type::PROGRAM; + break; + case CHANNEL_AFTERTOUCH: + mType = Type::CHANNEL_AFTERTOUCH; + break; + case PITCH_BEND: + mType = Type::PITCH_BEND; + break; + } + } + else + { + const int subType = (c & second_four_bits) >> 4; + switch(subType) + { + case SYS_EXCLUSIVE: + mType = Type::SYS_EXCLUSIVE; + break; + case SYS_SONG_POS: + mType = Type::SYS_SONG_POS; + break; + case SYS_SONG_SELECT: + mType = Type::SYS_SONG_SELECT; + break; + case SYS_TUNE_REQUEST: + mType = Type::SYS_TUNE_REQUEST; + break; + case SYS_END_EXCLUSIVE: + mType = Type::SYS_END_EXCLUSIVE; + break; + case SYS_TIMING_CLOCK: + mType = Type::SYS_TIMING_CLOCK; + break; + case SYS_START: + mType = Type::SYS_START; + break; + case SYS_CONTINUE: + mType = Type::SYS_CONTINUE; + break; + case SYS_STOP: + mType = Type::SYS_STOP; + break; + case SYS_ACTIVE_SENSING: + mType = Type::SYS_ACTIVE_SENSING; + break; + case SYS_RESET: + mType = Type::SYS_RESET; + break; + } + } } diff --git a/src/audio/midi/MidiChannelEvent.h b/src/audio/midi/MidiChannelEvent.h index 6546fc2..75f790b 100644 --- a/src/audio/midi/MidiChannelEvent.h +++ b/src/audio/midi/MidiChannelEvent.h @@ -1,13 +1,32 @@ #pragma once #include "MidiEvent.h" +#include class MidiChannelEvent : public MidiEvent { public: enum class Type{ - NONE, NOTE_OFF, NOTE_ON, NOTE_AFTERTOUCH, CONTROLLER, - PROGRAM, CHANNEL_AFTERTOUCH, PITCH_BEND + NONE, + NOTE_OFF, + NOTE_ON, + NOTE_AFTERTOUCH, + CONTROLLER, + PROGRAM, + CHANNEL_AFTERTOUCH, + PITCH_BEND, + CHANNEL_MODE, + SYS_EXCLUSIVE, + SYS_SONG_POS, + SYS_SONG_SELECT, + SYS_TUNE_REQUEST, + SYS_END_EXCLUSIVE, + SYS_TIMING_CLOCK, + SYS_START, + SYS_CONTINUE, + SYS_STOP, + SYS_ACTIVE_SENSING, + SYS_RESET }; private: @@ -18,35 +37,36 @@ private: static const int PROGRAM = 0xC; static const int CHANNEL_AFTERTOUCH = 0xD; static const int PITCH_BEND = 0xE; - Type mType; - int mValue0; - int mValue1; + + static const int SYS_EXCLUSIVE = 0x0; + static const int SYS_SONG_POS = 0x2; + static const int SYS_SONG_SELECT = 0x3; + static const int SYS_TUNE_REQUEST = 0x6; + static const int SYS_END_EXCLUSIVE = 0x7; + static const int SYS_TIMING_CLOCK = 0x8; + static const int SYS_START = 0xA; + static const int SYS_CONTINUE = 0xB; + static const int SYS_STOP = 0xC; + static const int SYS_ACTIVE_SENSING = 0xE; + static const int SYS_RESET = 0xF; public: MidiChannelEvent(); - static std::shared_ptr Create(); + static std::unique_ptr Create(); + void SetTypeAndChannel(char c); void SetType(Type type); - Type GetType(); + Type GetType() const; + void SetValues(int value0, int value1); - int GetValue0(); - int GetValue1(); + int GetValue0() const; + int GetValue1() const; - 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); +private: + Type mType; + int mChannel {0}; + int mValue0 {0}; + int mValue1 {1}; }; -using MidiChannelEventPtr = std::shared_ptr; +using MidiChannelEventPtr = std::unique_ptr; diff --git a/src/audio/midi/MidiDocument.cpp b/src/audio/midi/MidiDocument.cpp index f6af36a..775a71e 100644 --- a/src/audio/midi/MidiDocument.cpp +++ b/src/audio/midi/MidiDocument.cpp @@ -1,6 +1,50 @@ #include "MidiDocument.h" +#include "MidiTrack.h" + MidiDocument::MidiDocument() { } + +std::unique_ptr MidiDocument::Create() +{ + return std::make_unique(); +} + +void MidiDocument::AddTrack(MidiTrackPtr track) +{ + mTracks.push_back(std::move(track)); +} + +void MidiDocument::SetFormatType(int format) +{ + mFormatType = format; +} + +void MidiDocument::SetExpectedTracks(int expected) +{ + mExpectedTracks = expected; +} + +void MidiDocument::SetTimeDivision(const MidiTimeDivision& timeDivision) +{ + mTimeDivision = timeDivision; +} + +std::string MidiDocument::Serialize() const +{ + std::string output = "MidiDocument\n"; + output += "Format type: " + std::to_string(mFormatType) + "\n"; + output += "Expected Tracks: " + std::to_string(mExpectedTracks) + "\n"; + output += "Use fps: " + std::to_string(mTimeDivision.mUseFps) + "\n"; + output += "fps: " + std::to_string(mTimeDivision.mFps) + "\n"; + output += "Ticks per frame: " + std::to_string(mTimeDivision.mTicks) + "\n"; + output += "Num tracks: " + std::to_string(mTracks.size()) + "\n"; + for(const auto& track : mTracks) + { + output += track->Serialize(); + output += "------------------------\n"; + } + return output; +} diff --git a/src/audio/midi/MidiDocument.h b/src/audio/midi/MidiDocument.h index c3f4d93..24888ba 100644 --- a/src/audio/midi/MidiDocument.h +++ b/src/audio/midi/MidiDocument.h @@ -1,16 +1,34 @@ #pragma once #include -#include "MidiTrack.h" +#include +#include + +#include "MidiElements.h" + +class MidiTrack; +using MidiTrackPtr = std::unique_ptr; class MidiDocument { public: - int mFormatType = 0; - int mExpectedTracks = 0; - bool mUseFps = true; - int mFps = 0; - int ticksPerFrame = 0; - std::vector mTracks; - MidiDocument(); + MidiDocument(); + + static std::unique_ptr Create(); + + void AddTrack(MidiTrackPtr track); + + void SetFormatType(int format); + void SetExpectedTracks(int expected); + void SetTimeDivision(const MidiTimeDivision& timeDivision); + + std::string Serialize() const; + +private: + int mFormatType = 0; + int mExpectedTracks = 0; + MidiTimeDivision mTimeDivision; + std::vector mTracks; }; + +using MidiDocumentPtr = std::unique_ptr; diff --git a/src/audio/midi/MidiElements.h b/src/audio/midi/MidiElements.h new file mode 100644 index 0000000..3d01003 --- /dev/null +++ b/src/audio/midi/MidiElements.h @@ -0,0 +1,31 @@ +#pragma once + +struct MidiTimeSignature +{ + int mNumer {4}; + int mDenom {4}; + int mMetro {1}; + int mF32 {1}; +}; + +struct MidiTimeDivision +{ + bool mUseFps {false}; + int mFps { 0 }; + int mTicks { 1 }; +}; + +struct MidiSmtpeTimecode +{ + int mHr{0}; + int mMin{0}; + int mSec{0}; + int mFrame{0}; + int mFrameFrac{0}; +}; + +struct MidiKeySignature +{ + int mSharpsFlats{0}; + int mMinor{0}; +}; diff --git a/src/audio/midi/MidiEvent.cpp b/src/audio/midi/MidiEvent.cpp index 58d2ebf..dfc867d 100644 --- a/src/audio/midi/MidiEvent.cpp +++ b/src/audio/midi/MidiEvent.cpp @@ -6,16 +6,16 @@ MidiEvent::MidiEvent() } +std::unique_ptr MidiEvent::Create() +{ + return std::make_unique(); +} + 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; diff --git a/src/audio/midi/MidiEvent.h b/src/audio/midi/MidiEvent.h index 78f060e..5130540 100644 --- a/src/audio/midi/MidiEvent.h +++ b/src/audio/midi/MidiEvent.h @@ -7,20 +7,23 @@ 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: MidiEvent(); virtual ~MidiEvent() = default; - static std::shared_ptr Create(); + static std::unique_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); + +private: + int mTimeDelta{0}; }; -using MidiEventPtr = std::shared_ptr; +using MidiEventPtr = std::unique_ptr; diff --git a/src/audio/midi/MidiReader.cpp b/src/audio/midi/MidiReader.cpp deleted file mode 100644 index 6a59c9e..0000000 --- a/src/audio/midi/MidiReader.cpp +++ /dev/null @@ -1,379 +0,0 @@ -#include "MidiReader.h" - -#include "MidiDocument.h" -#include "ByteUtils.h" -#include "MidiTrack.h" -#include "BinaryFile.h" - -#include -#include -#include -#include - -MidiReader::MidiReader() - :mDocument(), - mTrackByteCount(0), - mLastChannelEventType(MidiChannelEvent::Type::NONE) -{ - -} - -bool MidiReader::ProcessHeader(std::ifstream& file) -{ - if(!BinaryFile::CheckNextDWord(file, HeaderLabel)) - { - return false; - } - - int length = 0; - if(!BinaryFile::GetNextDWord(file, length)) - { - return false; - } - - 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); - 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 <(c) << "|" <> 4; - int midi_channel = (firstByte & second_four_bits) >> 4; - std::cout << "Channel: " << midi_channel << 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; - MidiChannelEvent::Type mLastChannelEventType; - -public: - - MidiReader(); - - void Read(const std::string& path); - -private: - - bool ProcessHeader(std::ifstream& file); - - bool ProcessTimeDivision(std::ifstream& file); - - bool ProcessTrackChunk(std::ifstream& file, bool debug=false); - - bool ProcessEvent(std::ifstream& file, MidiTrack& track); - - 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/audio/midi/MidiTrack.cpp b/src/audio/midi/MidiTrack.cpp index 7817728..2bd4a25 100644 --- a/src/audio/midi/MidiTrack.cpp +++ b/src/audio/midi/MidiTrack.cpp @@ -1,11 +1,35 @@ #include "MidiTrack.h" +#include "MidiEvent.h" + MidiTrack::MidiTrack() { } -void MidiTrack::AddEvent(const MidiEvent& event) +MidiTrack::~MidiTrack() { - mEvents.push_back(event); + +} + +void MidiTrack::AddEvent(MidiEventPtr event) +{ + mEvents.push_back(std::move(event)); +} + +MidiEvent* MidiTrack::GetEvent(std::size_t idx) const +{ + return mEvents[idx].get(); +} + +std::size_t MidiTrack::GetNumEvents() +{ + return mEvents.size(); +} + +std::string MidiTrack::Serialize() const +{ + std::string output = "MidiTrack\n"; + output += "Num Events: " + std::to_string(mEvents.size()) + "\n"; + return output; } diff --git a/src/audio/midi/MidiTrack.h b/src/audio/midi/MidiTrack.h index ac69101..888313c 100644 --- a/src/audio/midi/MidiTrack.h +++ b/src/audio/midi/MidiTrack.h @@ -1,15 +1,30 @@ #pragma once #include +#include +#include #include "MidiEvent.h" +class MidiEvent; +using MidiEventPtr = std::unique_ptr; + class MidiTrack { - std::vector mEvents; - public: - MidiTrack(); + MidiTrack(); - void AddEvent(const MidiEvent& event); + ~MidiTrack(); + + void AddEvent(MidiEventPtr event); + + MidiEvent* GetEvent(std::size_t idx) const; + + std::size_t GetNumEvents(); + + std::string Serialize() const; + +private: + + std::vector mEvents; }; diff --git a/src/audio/midi/reader/MidiChannelEventAdapter.cpp b/src/audio/midi/reader/MidiChannelEventAdapter.cpp new file mode 100644 index 0000000..1c176fe --- /dev/null +++ b/src/audio/midi/reader/MidiChannelEventAdapter.cpp @@ -0,0 +1,77 @@ +#include "MidiChannelEventAdapter.h" + +#include "BinaryFile.h" +#include "ByteUtils.h" + +#include +#include + +int MidiChannelEventAdapter::ReadEvent(std::ifstream* file, char firstByte, MidiChannelEvent* event, MidiChannelEvent::Type& lastEventType) +{ + 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; + unsigned byteCount = 0; + std::cout << "Channel: " << midi_channel << std::endl; + + const bool isStatusByte = ByteUtils::MSBIsOne(firstByte); + if(isStatusByte) + { + event->SetTypeAndChannel(firstByte); + lastEventType = event->GetType(); + } + else + { + event->SetType(lastEventType); + } + std::cout << "MC Type " << static_cast(event->GetType()) << std::endl; + switch(event->GetType()) + { + case MidiChannelEvent::Type::NOTE_ON: + case MidiChannelEvent::Type::NOTE_OFF: + case MidiChannelEvent::Type::CONTROLLER: + { + if (isStatusByte) + { + byteCount += ReadEventData(file, event); + } + else + { + byteCount += ReadEventData(file, event, firstByte); + } + break; + } + case MidiChannelEvent::Type::PROGRAM: + { + int value0 = 0; + BinaryFile::GetNextByteAsInt(file, value0); + byteCount ++; + break; + } + default: + std::cout << "Unknown status event: " << std::bitset<8>(firstByte) << "|" << event_type <SetValues(value0, value1); + return 1; +} + +int MidiChannelEventAdapter::ReadEventData(std::ifstream* file, MidiChannelEvent* event) +{ + int value0 = 0; + BinaryFile::GetNextByteAsInt(file, value0); + + int value1 = 0; + BinaryFile::GetNextByteAsInt(file, value1); + event->SetValues(value0, value1); + return 2; +} diff --git a/src/audio/midi/reader/MidiChannelEventAdapter.h b/src/audio/midi/reader/MidiChannelEventAdapter.h new file mode 100644 index 0000000..39d7c9a --- /dev/null +++ b/src/audio/midi/reader/MidiChannelEventAdapter.h @@ -0,0 +1,14 @@ +#pragma once + +#include "MidiChannelEvent.h" + +#include + +class MidiChannelEventAdapter +{ +public: + static int ReadEvent(std::ifstream* file, char firstByte, MidiChannelEvent* event, MidiChannelEvent::Type& lastEventType); + + static int ReadEventData(std::ifstream* file, MidiChannelEvent* event, char c); + static int ReadEventData(std::ifstream* file, MidiChannelEvent* event); +}; diff --git a/src/audio/midi/reader/MidiMetaEventAdapter.cpp b/src/audio/midi/reader/MidiMetaEventAdapter.cpp new file mode 100644 index 0000000..7437c47 --- /dev/null +++ b/src/audio/midi/reader/MidiMetaEventAdapter.cpp @@ -0,0 +1,190 @@ +#include "MidiMetaEventAdapter.h" + +#include "BinaryFile.h" +#include "ByteUtils.h" + +#include +#include + +int MidiMetaEventAdapter::ReadEvent(std::ifstream* file, MetaMidiEvent* event, int& lastMidiChannel) +{ + unsigned byteCount = 0; + char c; + file->get(c); + byteCount++; + + event->SetType(c); + + std::cout << "Meta event type: " << std::hex << int(c) << std::dec<GetType()) + { + case MetaMidiEvent::Type::SEQ_NUM: + byteCount += ReadIntEvent(file, event, 2); + break; + case MetaMidiEvent::Type::TEXT: + case MetaMidiEvent::Type::COPYRIGHT: + case MetaMidiEvent::Type::TRACK_NAME: + case MetaMidiEvent::Type::INSTRUMENT_NAME: + case MetaMidiEvent::Type::MARKER: + case MetaMidiEvent::Type::CUE_POINT: + byteCount += ReadStringEvent(file, event); + break; + case MetaMidiEvent::Type::CHANNEL_PREFIX: + byteCount += ReadChannelPrefixEvent(file, event, lastMidiChannel); + break; + case MetaMidiEvent::Type::END_TRACK: + { + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount ++; + break; + } + case MetaMidiEvent::Type::SMPTE_OFFSET: + byteCount += ReadTimeCodeEvent(file, event); + break; + case MetaMidiEvent::Type::SET_TEMPO: + byteCount += ReadIntEvent(file, event); + break; + case MetaMidiEvent::Type::TIME_SIG: + byteCount += ReadTimeSignatureEvent(file, event); + break; + case MetaMidiEvent::Type::KEY_SIG: + byteCount += ReadKeySignatureEvent(file, event); + break; + case MetaMidiEvent::Type::SEQ_CUSTOM: + break; + case MetaMidiEvent::Type::UNKNOWN: + std::cout << "Unknown meta event " << std::bitset<8>(c) << "|" <get(c); + } + return length; +} + +int MidiMetaEventAdapter::ReadStringEvent(std::ifstream* file, MetaMidiEvent* event) +{ + unsigned byteCount = 0; + + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount++; + + std::string name; + BinaryFile::GetNextString(file, name, length); + byteCount += length; + event->SetLabel(name); + return byteCount; +} + +int MidiMetaEventAdapter::ReadIntEvent(std::ifstream* file, MetaMidiEvent* event, int lengthIn) +{ + unsigned byteCount = 0; + int length = 0; + if(lengthIn > -1) + { + length = lengthIn; + } + else + { + BinaryFile::GetNextByteAsInt(file, length); + byteCount ++; + } + + std::string buffer; + BinaryFile::GetNextNBytes(file, buffer.data(), length); + byteCount += length; + + const int value = ByteUtils::ToInt(buffer.data(), length); + event->SetValue(value); + return byteCount; +} + +int MidiMetaEventAdapter::ReadChannelPrefixEvent(std::ifstream* file, MetaMidiEvent* event, int& lastMidiChannel) +{ + unsigned byteCount = 0; + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount ++; + + std::string buffer; + BinaryFile::GetNextNBytes(file, buffer.data(), length); + byteCount += length; + + const int value = ByteUtils::ToInt(buffer.data(), length); + event->SetValue(value); + lastMidiChannel = value; + return byteCount; +} + +int MidiMetaEventAdapter::ReadTimeSignatureEvent(std::ifstream* file, MetaMidiEvent* event) +{ + unsigned byteCount = 0; + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount++; + + MidiTimeSignature timeSig; + BinaryFile::GetNextByteAsInt(file, timeSig.mNumer); + BinaryFile::GetNextByteAsInt(file, timeSig.mDenom); + BinaryFile::GetNextByteAsInt(file, timeSig.mMetro); + BinaryFile::GetNextByteAsInt(file, timeSig.mF32); + byteCount +=4; + + if (length > 4) + { + char c; + for(unsigned idx=0; idxget(c); + byteCount++; + } + } + return byteCount; +} + +int MidiMetaEventAdapter::ReadKeySignatureEvent(std::ifstream* file, MetaMidiEvent* event) +{ + unsigned byteCount = 0; + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount++; + + MidiKeySignature keySig; + BinaryFile::GetNextByteAsInt(file, keySig.mSharpsFlats); + BinaryFile::GetNextByteAsInt(file, keySig.mMinor); + byteCount +=2; + return byteCount; +} + +int MidiMetaEventAdapter::ReadTimeCodeEvent(std::ifstream* file, MetaMidiEvent* event) +{ + unsigned byteCount = 0; + int length = 0; + BinaryFile::GetNextByteAsInt(file, length); + byteCount++; + + MidiSmtpeTimecode timeCode; + BinaryFile::GetNextByteAsInt(file, timeCode.mHr); + BinaryFile::GetNextByteAsInt(file, timeCode.mMin); + BinaryFile::GetNextByteAsInt(file, timeCode.mSec); + BinaryFile::GetNextByteAsInt(file, timeCode.mFrame); + BinaryFile::GetNextByteAsInt(file, timeCode.mFrameFrac); + byteCount +=5; + return byteCount; +} diff --git a/src/audio/midi/reader/MidiMetaEventAdapter.h b/src/audio/midi/reader/MidiMetaEventAdapter.h new file mode 100644 index 0000000..75ef289 --- /dev/null +++ b/src/audio/midi/reader/MidiMetaEventAdapter.h @@ -0,0 +1,21 @@ +#pragma once + +#include "MetaMidiEvent.h" +#include + + +class MidiMetaEventAdapter +{ +public: + + static int ReadEvent(std::ifstream* file, MetaMidiEvent* event, int& lastMidiChannel); + + static int ReadIntEvent(std::ifstream* file, MetaMidiEvent* event, int length=-1); + static int ReadStringEvent(std::ifstream* file, MetaMidiEvent* event); + + static int ReadChannelPrefixEvent(std::ifstream* file, MetaMidiEvent* event, int& lastMidiChannel); + static int ReadTimeSignatureEvent(std::ifstream* file, MetaMidiEvent* event); + static int ReadKeySignatureEvent(std::ifstream* file, MetaMidiEvent* event); + static int ReadTimeCodeEvent(std::ifstream* file, MetaMidiEvent* event); + static int ReadUnknownEvent(std::ifstream* file); +}; diff --git a/src/audio/midi/reader/MidiReader.cpp b/src/audio/midi/reader/MidiReader.cpp new file mode 100644 index 0000000..124eb19 --- /dev/null +++ b/src/audio/midi/reader/MidiReader.cpp @@ -0,0 +1,153 @@ +#include "MidiReader.h" + +#include "MidiDocument.h" +#include "ByteUtils.h" +#include "MidiTrack.h" +#include "BinaryFile.h" +#include "FileLogger.h" +#include "MidiElements.h" +#include "MidiTimeAdapter.h" +#include "MidiMetaEventAdapter.h" +#include "MidiChannelEventAdapter.h" + +#include +#include +#include +#include + +MidiReader::MidiReader() + : mDocument(MidiDocument::Create()), + mLastChannelEventType(MidiChannelEvent::Type::NONE) +{ + +} + +MidiDocument* MidiReader::GetDocument() const +{ + return mDocument.get(); +} + +bool MidiReader::ProcessHeader() +{ + if(!BinaryFile::CheckNextDWord(mFile->GetInHandle(), HeaderLabel)) + { + return false; + } + + int length = 0; + if(!BinaryFile::GetNextDWord(mFile->GetInHandle(), length)) + { + return false; + } + + int formatType { 0 }; + if(!BinaryFile::GetNextWord(mFile->GetInHandle(), formatType)) + { + return false; + } + mDocument->SetFormatType(formatType); + + int expectedTracks { 0 }; + if(!BinaryFile::GetNextWord(mFile->GetInHandle(), expectedTracks)) + { + return false; + } + mDocument->SetExpectedTracks(expectedTracks); + + MidiTimeDivision timeDivision; + MidiTimeAdapter::ReadTimeDivision(mFile->GetInHandle(), timeDivision); + mDocument->SetTimeDivision(timeDivision); + return true; +} + +int MidiReader::ProcessEvent(MidiTrack* track) +{ + int timeDelta {0}; + unsigned byteCount {0}; + byteCount += MidiTimeAdapter::ReadEventTimeDelta(mFile->GetInHandle(), timeDelta); + + char c; + mFile->GetInHandle()->get(c); + std::cout << "Event check: " << std::bitset<8>(c) << std::endl; + byteCount++; + if(MidiEvent::IsMetaEvent(c)) + { + auto event = std::make_unique(); + event->SetTimeDelta(timeDelta); + std::cout << "Meta event " <GetInHandle(), event.get(), mLastMidiChannel); + track->AddEvent(std::move(event)); + } + else if(MidiEvent::IsSysExEvent(c)) + { + std::cout << "Sysex event" << std::endl; + } + else + { // Midi event + auto event = std::make_unique(); + event->SetTimeDelta(timeDelta); + std::cout << "Midi event" << std::endl; + byteCount += MidiChannelEventAdapter::ReadEvent(mFile->GetInHandle(), c, event.get(), mLastChannelEventType); + track->AddEvent(std::move(event)); + } + return byteCount; +} + +bool MidiReader::ProcessTrackChunk(bool debug) +{ + if(!BinaryFile::CheckNextDWord(mFile->GetInHandle(), TrackChunkLabel)) + { + return false; + } + + int chunkSize = 0; + if(!BinaryFile::GetNextDWord(mFile->GetInHandle(), chunkSize)) + { + return false; + } + + unsigned byteCount = 0; + auto track = std::make_unique(); + unsigned iter_count = 0; + while(byteCount < unsigned(chunkSize)) + { + std::cout << "-------------" << std::endl; + byteCount += ProcessEvent(track.get()); + std::cout << "Track byte count: " << byteCount << " of " << chunkSize << std::endl; + if(debug && iter_count == 40) + { + return true; + } + iter_count ++; + } + mDocument->AddTrack(std::move(track)); + return true; +} + +void MidiReader::Read(const std::string& path) +{ + mFile = std::make_unique(path); + mFile->Open(true); + if(!ProcessHeader()) + { + MLOG_ERROR("Problem processing header"); + return; + } + + int trackCount = 0; + if(!ProcessTrackChunk(false)) + { + MLOG_ERROR("Problem processing track chunk"); + return; + } + trackCount++; + + if(!ProcessTrackChunk(true)) + { + MLOG_ERROR("Problem processing track chunk"); + return; + } + trackCount++; + + mFile->Close(); +} diff --git a/src/audio/midi/reader/MidiReader.h b/src/audio/midi/reader/MidiReader.h new file mode 100644 index 0000000..57db097 --- /dev/null +++ b/src/audio/midi/reader/MidiReader.h @@ -0,0 +1,35 @@ +#pragma once + +#include "MidiDocument.h" +#include "MetaMidiEvent.h" +#include "MidiTrack.h" +#include "MidiChannelEvent.h" +#include "File.h" +#include + +class MidiReader +{ + static constexpr const char TrackChunkLabel[] = "MTrk"; + static constexpr const char HeaderLabel[] = "MThd"; + +public: + + MidiReader(); + + void Read(const std::string& path); + + MidiDocument* GetDocument() const; + +private: + + bool ProcessHeader(); + bool ProcessTrackChunk(bool debug=false); + int ProcessEvent(MidiTrack* track); + +private: + + std::unique_ptr mFile; + MidiDocumentPtr mDocument; + int mLastMidiChannel {0}; + MidiChannelEvent::Type mLastChannelEventType; +}; diff --git a/src/audio/midi/reader/MidiTimeAdapter.cpp b/src/audio/midi/reader/MidiTimeAdapter.cpp new file mode 100644 index 0000000..e4bee2f --- /dev/null +++ b/src/audio/midi/reader/MidiTimeAdapter.cpp @@ -0,0 +1,66 @@ +#include "MidiTimeAdapter.h" + +#include "BinaryFile.h" +#include "ByteUtils.h" + +#include +#include + +int MidiTimeAdapter::ReadEventTimeDelta(std::ifstream* file, int& delta) +{ + unsigned byteCount = 0; + char c; + file->get(c); + byteCount++; + + if(!ByteUtils::MSBIsOne(c)) + { + delta = int(c); + std::cout << "Time delta final: " << delta << std::endl; + return byteCount; + } + + 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); + byteCount++; + 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)); + + delta = int(final_c); + std::cout << "Time delta final: " << delta << "|" << std::bitset<16>(final_c)<< std::endl; + return byteCount; +} + +int MidiTimeAdapter::ReadTimeDivision(std::ifstream* file, MidiTimeDivision& division) +{ + int time_division; + if(!BinaryFile::GetNextWord(file, time_division, false)) + { + return -1; + } + + division.mUseFps = ByteUtils::GetWordFirstBit(time_division); + if (division.mUseFps) + { + const int TOP_7_BITS = 0x7F00; // 0111 1111 - 0000 0000 + division.mFps = ((~time_division & TOP_7_BITS) >> 8) - 1; // Reverse 2complement of next 7 bits + } + + division.mTicks = ByteUtils::GetWordLastByte(time_division); + return 2; // Bytes advanced +} diff --git a/src/audio/midi/reader/MidiTimeAdapter.h b/src/audio/midi/reader/MidiTimeAdapter.h new file mode 100644 index 0000000..4e20cf8 --- /dev/null +++ b/src/audio/midi/reader/MidiTimeAdapter.h @@ -0,0 +1,13 @@ +#pragma once + +#include "MidiElements.h" + +#include + +class MidiTimeAdapter +{ +public: + static int ReadEventTimeDelta(std::ifstream* file, int& delta); + + static int ReadTimeDivision(std::ifstream* file, MidiTimeDivision& division); +}; diff --git a/src/core/ByteUtils.h b/src/core/ByteUtils.h index 63ab3bb..6de6a22 100644 --- a/src/core/ByteUtils.h +++ b/src/core/ByteUtils.h @@ -1,5 +1,6 @@ #pragma once #include +#include class ByteUtils { @@ -7,6 +8,11 @@ public: using Word = int; using DWord = int; + static bool MSBIsOne(char c) + { + return c & (1 << 7); + } + static int GetWordFirstBit(const Word word) { return word & ByteUtils::WORD_FIRST_BIT; @@ -17,22 +23,29 @@ public: return word & ByteUtils::WORD_LAST_BYTE; } - static void ReverseBuffer(char* buffer, char* reverse, unsigned size) + static void ReverseBuffer(char* buffer, char* reverse, unsigned size, unsigned targetSize) { - for(unsigned idx=0; idxget(c); target = int(c); return true; } -bool BinaryFile::GetNextNBytes(std::ifstream& file, char* buffer, unsigned number) +bool BinaryFile::GetNextNBytes(std::ifstream* file, char* buffer, unsigned number) { char c; for(unsigned idx=0; idxget(c)) { buffer[idx] = c; } @@ -26,17 +26,17 @@ bool BinaryFile::GetNextNBytes(std::ifstream& file, char* buffer, unsigned numbe return true; } -bool BinaryFile::GetNextWord(std::ifstream& file, char* buffer) +bool BinaryFile::GetNextWord(std::ifstream* file, char* buffer) { return GetNextNBytes(file, buffer, ByteUtils::BYTES_PER_WORD); } -bool BinaryFile::GetNextDWord(std::ifstream& file, char* buffer) +bool BinaryFile::GetNextDWord(std::ifstream* file, char* buffer) { return GetNextNBytes(file, buffer, ByteUtils::BYTES_PER_DWORD); } -bool BinaryFile::GetNextWord(std::ifstream& file, int& target, bool reverse) +bool BinaryFile::GetNextWord(std::ifstream* file, int& target, bool reverse) { char buffer[ByteUtils::BYTES_PER_WORD]; if(!BinaryFile::GetNextWord(file, buffer)) @@ -47,7 +47,7 @@ bool BinaryFile::GetNextWord(std::ifstream& file, int& target, bool reverse) return true; } -bool BinaryFile::GetNextDWord(std::ifstream& file, int& target) +bool BinaryFile::GetNextDWord(std::ifstream* file, int& target) { char buffer[ByteUtils::BYTES_PER_DWORD]; if(!BinaryFile::GetNextDWord(file, buffer)) @@ -58,7 +58,7 @@ bool BinaryFile::GetNextDWord(std::ifstream& file, int& target) return true; } -bool BinaryFile::CheckNextDWord(std::ifstream& file, const char* target) +bool BinaryFile::CheckNextDWord(std::ifstream* file, const char* target) { char buffer[ByteUtils::BYTES_PER_DWORD]; if(!BinaryFile::GetNextDWord(file, buffer)) @@ -73,12 +73,12 @@ bool BinaryFile::CheckNextDWord(std::ifstream& file, const char* target) return true; } -bool BinaryFile::GetNextString(std::ifstream& file, std::string& target, unsigned numBytes) +bool BinaryFile::GetNextString(std::ifstream* file, std::string& target, unsigned numBytes) { char c; for(unsigned idx=0; idxget(c)) { return false; } diff --git a/src/core/file_utilities/BinaryFile.h b/src/core/file_utilities/BinaryFile.h index 50973f4..50ac0f2 100644 --- a/src/core/file_utilities/BinaryFile.h +++ b/src/core/file_utilities/BinaryFile.h @@ -7,19 +7,26 @@ class BinaryFile { public: - static bool GetNextByteAsInt(std::ifstream& file, int& target); + template + static bool Write(std::ofstream* file, T data) + { + file->write(reinterpret_cast(&data), sizeof(data)); + return true; + } - static bool GetNextNBytes(std::ifstream& file, char* buffer, unsigned numBytes); + static bool GetNextByteAsInt(std::ifstream* file, int& target); - static bool GetNextWord(std::ifstream& file, char* buffer); + static bool GetNextNBytes(std::ifstream* file, char* buffer, unsigned numBytes); - static bool GetNextDWord(std::ifstream& file, char* buffer); + static bool GetNextWord(std::ifstream* file, char* buffer); - static bool GetNextWord(std::ifstream& file, int& target, bool reverse = true); + static bool GetNextDWord(std::ifstream* file, char* buffer); - static bool GetNextDWord(std::ifstream& file, int& target); + static bool GetNextWord(std::ifstream* file, int& target, bool reverse = true); - static bool CheckNextDWord(std::ifstream& file, const char* target); + static bool GetNextDWord(std::ifstream* file, int& target); - static bool GetNextString(std::ifstream& file, std::string& target, unsigned numBytes); + static bool CheckNextDWord(std::ifstream* file, const char* target); + + static bool GetNextString(std::ifstream* file, std::string& target, unsigned numBytes); }; diff --git a/src/core/file_utilities/File.cpp b/src/core/file_utilities/File.cpp index 636f11c..f5fb3a8 100644 --- a/src/core/file_utilities/File.cpp +++ b/src/core/file_utilities/File.cpp @@ -31,17 +31,27 @@ std::ofstream* File::GetOutHandle() const return mOutHandle.get(); } -void File::Open() +void File::Open(bool asBinary) { if(mAccessMode == AccessMode::Read) { + auto flags = std::ifstream::in; + if (asBinary) + { + flags |= std::ifstream::binary; + } mInHandle = std::make_unique(); - mInHandle->open(mFullPath, std::ifstream::in); + mInHandle->open(mFullPath, flags); } else { + auto flags = std::ofstream::out; + if (asBinary) + { + flags |= std::ofstream::binary; + } mOutHandle = std::make_unique(); - mOutHandle->open(mFullPath, std::ofstream::out); + mOutHandle->open(mFullPath, flags); } } diff --git a/src/core/file_utilities/File.h b/src/core/file_utilities/File.h index da4c101..9b698d6 100644 --- a/src/core/file_utilities/File.h +++ b/src/core/file_utilities/File.h @@ -40,7 +40,7 @@ public: void SetAccessMode(AccessMode mode); - void Open(); + void Open(bool asBinary = false); void Close(); diff --git a/src/core/file_utilities/FileFormats.cpp b/src/core/file_utilities/FileFormats.cpp index e75ca09..3bb927b 100644 --- a/src/core/file_utilities/FileFormats.cpp +++ b/src/core/file_utilities/FileFormats.cpp @@ -5,5 +5,6 @@ FileFormat::ExtensionMap FileFormat::mExtensions = [] ExtensionMap ret; ret[Format::Markdown] = ".md"; ret[Format::Html] = ".html"; + ret[Format::Wav] = ".wav"; return ret; }(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d7cfa30..7f2faff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,25 @@ -add_executable(test_runner test_runner.cpp) -target_include_directories(test_runner PUBLIC - "${PROJECT_SOURCE_DIR}/test/" +add_library(test_utils SHARED + test_utils/TestCase.h + test_utils/TestCaseRunner.cpp + ) + +target_include_directories(test_utils PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/test_utils" ) -target_link_libraries(test_runner PUBLIC core - network database geometry audio graphics web) \ No newline at end of file + +list(APPEND TestFiles + audio/TestAudioWriter.cpp + audio/TestMidiReader.cpp) + +list(APPEND TestNames + TestAudioWriter + TestMidiReader) + +foreach(TestFile TestName IN ZIP_LISTS TestFiles TestNames) + add_executable(${TestName} ${TestFile}) + target_link_libraries(${TestName} PUBLIC core network database geometry audio graphics web test_utils) +endforeach() + + +add_executable(test_runner test_runner.cpp) +target_link_libraries(test_runner PUBLIC core network database geometry audio graphics web) \ No newline at end of file diff --git a/test/audio/TestAudioWriter.cpp b/test/audio/TestAudioWriter.cpp new file mode 100644 index 0000000..28a1dd6 --- /dev/null +++ b/test/audio/TestAudioWriter.cpp @@ -0,0 +1,34 @@ +#include "AudioSample.h" +#include "AudioSynth.h" +#include "AudioWriter.h" + +#include "TestCase.h" +#include "TestCaseRunner.h" + +#include +#include + +class TestWriteWav : public TestCase +{ +public: + bool Run() override + { + AudioWriter writer; + writer.SetPath("test.wav"); + + AudioSynth synth; + auto sample = synth.GetSineWave(240, 5); + + writer.Write(sample); + return true; + } +}; + +int main() +{ + TestCaseRunner runner; + runner.AddTestCase("TestWriteNav", std::make_unique()); + + const auto testsPassed = runner.Run(); + return testsPassed ? 0 : -1; +} diff --git a/test/audio/TestMidiReader.cpp b/test/audio/TestMidiReader.cpp new file mode 100644 index 0000000..86c06a5 --- /dev/null +++ b/test/audio/TestMidiReader.cpp @@ -0,0 +1,31 @@ +#include "MidiReader.h" + +#include "TestCase.h" +#include "TestCaseRunner.h" + +#include +#include +#include + +class TestReadMidi : public TestCase +{ +public: + bool Run() override + { + MidiReader reader; + reader.Read("/home/jmsgrogan/Downloads/test.mid"); + + auto document = reader.GetDocument(); + std::cout << document->Serialize() << std::endl; + return true; + } +}; + +int main() +{ + TestCaseRunner runner; + runner.AddTestCase("TestReadMidi", std::make_unique()); + + const auto testsPassed = runner.Run(); + return testsPassed ? 0 : -1; +} diff --git a/test/test_utils/TestCase.h b/test/test_utils/TestCase.h new file mode 100644 index 0000000..6105350 --- /dev/null +++ b/test/test_utils/TestCase.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class TestCase +{ +public: + TestCase(){}; + virtual ~TestCase() = default; + TestCase(const TestCase&) = delete; + virtual bool Run() {return false;}; +}; +using TestCasePtr = std::unique_ptr; diff --git a/test/test_utils/TestCaseRunner.cpp b/test/test_utils/TestCaseRunner.cpp new file mode 100644 index 0000000..f588948 --- /dev/null +++ b/test/test_utils/TestCaseRunner.cpp @@ -0,0 +1,26 @@ +#include "TestCaseRunner.h" + +#include +#include + +void TestCaseRunner::AddTestCase(const std::string& label, TestCasePtr testCase) +{ + mCases.push_back({label, std::move(testCase)}); +} + +bool TestCaseRunner::Run() +{ + bool testsPassed = true; + for(const auto& test : mCases) + { + std::cout << "Running " << test.first << std::endl; + const auto result = test.second->Run(); + if (!result) + { + std::cout << test.first << " Failed" << std::endl; + testsPassed = false; + break; + } + } + return testsPassed; +} diff --git a/test/test_utils/TestCaseRunner.h b/test/test_utils/TestCaseRunner.h new file mode 100644 index 0000000..b776e8f --- /dev/null +++ b/test/test_utils/TestCaseRunner.h @@ -0,0 +1,18 @@ +#pragma once + +#include "TestCase.h" + +#include + +class TestCaseRunner +{ +public: + + void AddTestCase(const std::string& label, TestCasePtr testCase); + + bool Run(); + +private: + using TestInstance = std::pair; + std::vector mCases; +};