From 36826fa1d4dfc34d242bd713821988fab4163377 Mon Sep 17 00:00:00 2001 From: jmsgrogan Date: Sun, 3 May 2020 07:56:27 +0100 Subject: [PATCH] Start midi file processing. --- README.md | 10 + apps/CMakeLists.txt | 4 +- apps/console-main.cpp | 29 ++- src/audio/CMakeLists.txt | 11 +- src/audio/audio_interfaces/AlsaInterface.cpp | 27 +-- src/audio/midi/MetaMidiEvent.cpp | 7 + src/audio/midi/MetaMidiEvent.h | 26 +++ src/audio/midi/MidiDocument.cpp | 6 + src/audio/midi/MidiDocument.h | 16 ++ src/audio/midi/MidiEvent.cpp | 6 + src/audio/midi/MidiEvent.h | 17 ++ src/audio/midi/MidiReader.cpp | 211 +++++++++++++++++++ src/audio/midi/MidiReader.h | 31 +++ src/audio/midi/MidiTrack.cpp | 11 + src/audio/midi/MidiTrack.h | 15 ++ src/console/CMakeLists.txt | 1 + src/console/MainApplication.cpp | 23 +- src/console/MainApplication.h | 1 - src/core/ByteUtils.h | 81 +++++++ src/core/CMakeLists.txt | 3 +- src/core/loggers/FileLogger.cpp | 15 +- src/core/loggers/FileLogger.h | 21 +- 22 files changed, 528 insertions(+), 44 deletions(-) create mode 100644 README.md create mode 100644 src/audio/midi/MetaMidiEvent.cpp create mode 100644 src/audio/midi/MetaMidiEvent.h create mode 100644 src/audio/midi/MidiDocument.cpp create mode 100644 src/audio/midi/MidiDocument.h create mode 100644 src/audio/midi/MidiEvent.cpp create mode 100644 src/audio/midi/MidiEvent.h create mode 100644 src/audio/midi/MidiReader.cpp create mode 100644 src/audio/midi/MidiReader.h create mode 100644 src/audio/midi/MidiTrack.cpp create mode 100644 src/audio/midi/MidiTrack.h create mode 100644 src/core/ByteUtils.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..893050e --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# MediaTool +Hobby project - the idea is to build a set of multimedia tools 'from scratch' in C++ for fun/practice. + +```bash +mkdir build +cd build +cmake $PATH_TO_SOURCE +make +apps/$NAME_OF_APP +``` diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index b9565e6..df8d63f 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -19,9 +19,9 @@ target_link_libraries(sample_console PUBLIC console core network add_executable(xml_practice xml-practice.cpp) target_include_directories(xml_practice PUBLIC "${PROJECT_SOURCE_DIR}/src/core" - "${PROJECT_SOURCE_DIR}/src/core/xml" + "${PROJECT_SOURCE_DIR}/src/web/xml" ) -target_link_libraries(xml_practice PUBLIC core) +target_link_libraries(xml_practice PUBLIC core web) # Markdown practice add_executable(markdown_practice markdown-practice.cpp) diff --git a/apps/console-main.cpp b/apps/console-main.cpp index c8a99c4..d6e96db 100644 --- a/apps/console-main.cpp +++ b/apps/console-main.cpp @@ -1,16 +1,37 @@ #include #include - +#include +#include "CommandLineArgs.h" #include "MainApplication.h" -int main() +int main(int argc, char *argv[]) { + CommandLineArgs command_line_args; + command_line_args.Process(argc, argv); + + std::string program_type; + if(command_line_args.GetNumberOfArgs() > 1) + { + program_type = command_line_args.GetArg(1); + } + // Start the main app auto main_app = MainApplication::Create(); main_app->Initialize(std::filesystem::current_path()); - //main_app->RunServer(); - main_app->PlayAudio(); + if(program_type == "server") + { + main_app->RunServer(); + } + else if(program_type == "audio") + { + main_app->PlayAudio(); + } + else + { + std::cerr << "Unknown program type" << std::endl; + } + main_app->ShutDown(); return 0; } diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index a115389..46532dd 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -1,13 +1,20 @@ list(APPEND audio_LIB_INCLUDES AudioDevice.cpp AudioManager.cpp - audio_interfaces/AlsaInterface.cpp) + audio_interfaces/AlsaInterface.cpp + midi/MidiReader.cpp + midi/MidiTrack.cpp + midi/MidiDocument.cpp + midi/MidiEvent.cpp + midi/MetaMidiEvent.cpp) add_library(audio SHARED ${audio_LIB_INCLUDES}) target_include_directories(audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src/core/loggers" "${CMAKE_CURRENT_SOURCE_DIR}/audio_interfaces" + "${CMAKE_CURRENT_SOURCE_DIR}/midi" ) -target_link_libraries(audio PUBLIC asound) \ No newline at end of file +target_link_libraries(audio PUBLIC core asound) \ No newline at end of file diff --git a/src/audio/audio_interfaces/AlsaInterface.cpp b/src/audio/audio_interfaces/AlsaInterface.cpp index 51eacee..fc8fc67 100644 --- a/src/audio/audio_interfaces/AlsaInterface.cpp +++ b/src/audio/audio_interfaces/AlsaInterface.cpp @@ -1,6 +1,5 @@ #include "AlsaInterface.h" - -#include +#include "FileLogger.h" AlsaInterface::AlsaInterface() :mHandle(), @@ -22,17 +21,18 @@ std::shared_ptr AlsaInterface::Create() void AlsaInterface::OpenDevice(AudioDevicePtr device) { + MLOG_INFO("Opening Device"); snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; if (snd_pcm_open(&mHandle, device->GetName().c_str(), stream, 0) < 0) { - std::cerr << "Error opening PCM device: " << device->GetName() << std::endl; + MLOG_ERROR("Error opening PCM device: " + device->GetName()); return; } snd_pcm_hw_params_alloca(&mHardwareParams); if (snd_pcm_hw_params_any(mHandle, mHardwareParams) < 0) { - std::cerr << "Can not configure this PCM device.\n" << std::endl; + MLOG_ERROR("Can not configure this PCM device."); return; } @@ -46,7 +46,7 @@ void AlsaInterface::OpenDevice(AudioDevicePtr device) /* Apply HW parameter settings to */ /* PCM device and prepare device */ if (snd_pcm_hw_params(mHandle, mHardwareParams) < 0) { - std::cerr << "Error setting HW params." << std::endl; + MLOG_ERROR("Error setting HW params."); return; } } @@ -54,7 +54,7 @@ void AlsaInterface::OpenDevice(AudioDevicePtr device) void AlsaInterface::SetAccessType(AudioDevicePtr device) { if (snd_pcm_hw_params_set_access(mHandle, mHardwareParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { - std::cerr << "Error setting device access." << std::endl; + MLOG_ERROR("Error setting device access."); return; } } @@ -63,7 +63,7 @@ void AlsaInterface::SetSampleFormat(AudioDevicePtr device) { /* Set sample format */ if (snd_pcm_hw_params_set_format(mHandle, mHardwareParams, SND_PCM_FORMAT_S16_LE) < 0) { - std::cerr << "Error setting format. " << std::endl; + MLOG_ERROR("Error setting format. "); return; } } @@ -74,11 +74,11 @@ void AlsaInterface::SetSampleRate(AudioDevicePtr device) unsigned exact_rate = rate; if (snd_pcm_hw_params_set_rate_near(mHandle, mHardwareParams, &exact_rate, 0) < 0) { - std::cerr << "Error setting rate. " << std::endl; + MLOG_ERROR("Error setting rate. "); return; } if (rate != exact_rate) { - std::cerr << "The rate is not supported by your hardware." << std::endl; + MLOG_ERROR("The rate is not supported by your hardware."); } } @@ -87,7 +87,7 @@ void AlsaInterface::SetPeriod(AudioDevicePtr device) /* Set number of periods. Periods used to be called fragments. */ if (snd_pcm_hw_params_set_periods(mHandle, mHardwareParams, device->GetPeriod(), 0) < 0) { - std::cerr << "Error setting periods. " << std::endl; + MLOG_ERROR("Error setting periods. "); return; } } @@ -101,7 +101,7 @@ void AlsaInterface::SetBufferSize(AudioDevicePtr device) /* latency = periodsize * periods / (rate * bytes_per_frame) */ if (snd_pcm_hw_params_set_buffer_size(mHandle, mHardwareParams, (mPeriodSize * periods)>>2) < 0) { - std::cerr << "Error setting buffersize. " << std::endl; + MLOG_ERROR("Error setting buffersize. "); return; } } @@ -111,13 +111,14 @@ void AlsaInterface::SetChannelNumber(AudioDevicePtr device) /* Set number of channels */ if (snd_pcm_hw_params_set_channels(mHandle, mHardwareParams, device->GetNumChannels()) < 0) { - std::cout << "Error setting channels" << std::endl; + MLOG_ERROR("Error setting channels"); return; } } void AlsaInterface::Play(AudioDevicePtr device) { + MLOG_INFO("Playing audio"); int num_frames = 10; unsigned char *data = (unsigned char *)malloc(mPeriodSize); int frames = mPeriodSize >> 2; @@ -135,7 +136,7 @@ void AlsaInterface::Play(AudioDevicePtr device) while ((snd_pcm_writei(mHandle, data, frames)) < 0) { snd_pcm_prepare(mHandle); - fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n"); + MLOG_ERROR("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>"); } } } diff --git a/src/audio/midi/MetaMidiEvent.cpp b/src/audio/midi/MetaMidiEvent.cpp new file mode 100644 index 0000000..9c3cf80 --- /dev/null +++ b/src/audio/midi/MetaMidiEvent.cpp @@ -0,0 +1,7 @@ +#include "MetaMidiEvent.h" + +MetaMidiEvent::MetaMidiEvent() + : MidiEvent() +{ + +} diff --git a/src/audio/midi/MetaMidiEvent.h b/src/audio/midi/MetaMidiEvent.h new file mode 100644 index 0000000..ce38a78 --- /dev/null +++ b/src/audio/midi/MetaMidiEvent.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "MidiEvent.h" + +class MetaMidiEvent : public MidiEvent +{ +public: + enum class Type + { + UNSET, + TRACK_NAME + }; + + Type mType = Type::UNSET; + std::string mLabel; + MetaMidiEvent(); + + static bool IsTrackName(char c) + { + return (c & META_TRACK_NAME) == META_TRACK_NAME; + } + + static const int META_TRACK_NAME = 0x3; // 0000 0011 +}; diff --git a/src/audio/midi/MidiDocument.cpp b/src/audio/midi/MidiDocument.cpp new file mode 100644 index 0000000..f6af36a --- /dev/null +++ b/src/audio/midi/MidiDocument.cpp @@ -0,0 +1,6 @@ +#include "MidiDocument.h" + +MidiDocument::MidiDocument() +{ + +} diff --git a/src/audio/midi/MidiDocument.h b/src/audio/midi/MidiDocument.h new file mode 100644 index 0000000..c3f4d93 --- /dev/null +++ b/src/audio/midi/MidiDocument.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "MidiTrack.h" + +class MidiDocument +{ +public: + int mFormatType = 0; + int mExpectedTracks = 0; + bool mUseFps = true; + int mFps = 0; + int ticksPerFrame = 0; + std::vector mTracks; + MidiDocument(); +}; diff --git a/src/audio/midi/MidiEvent.cpp b/src/audio/midi/MidiEvent.cpp new file mode 100644 index 0000000..0b52bcb --- /dev/null +++ b/src/audio/midi/MidiEvent.cpp @@ -0,0 +1,6 @@ +#include "MidiEvent.h" + +MidiEvent::MidiEvent() +{ + +} diff --git a/src/audio/midi/MidiEvent.h b/src/audio/midi/MidiEvent.h new file mode 100644 index 0000000..e684319 --- /dev/null +++ b/src/audio/midi/MidiEvent.h @@ -0,0 +1,17 @@ +#pragma once + +class MidiEvent +{ + +public: + int mTimeDelta = 0; + MidiEvent(); + + static bool IsMetaEvent(char c) + { + return (c & META_EVENT) == META_EVENT; + } + +public: + static const int META_EVENT = 0xFF; // 1111 1111 +}; diff --git a/src/audio/midi/MidiReader.cpp b/src/audio/midi/MidiReader.cpp new file mode 100644 index 0000000..e7a0eb9 --- /dev/null +++ b/src/audio/midi/MidiReader.cpp @@ -0,0 +1,211 @@ +#include "MidiReader.h" +#include +#include +#include +#include +#include "MidiDocument.h" +#include "ByteUtils.h" +#include "MidiTrack.h" + +MidiReader::MidiReader() +{ + +} + +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; +} + +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 <(c) << "|" <> 4; + std::cout << "Next " << std::bitset<8>(c) << "|" << event_type < + +class MidiReader +{ + static constexpr const char TrackChunkLabel[] = "MTrk"; + static constexpr const char HeaderLabel[] = "MThd"; + MidiDocument mDocument; + unsigned mTrackByteCount; + +public: + + MidiReader(); + + void Read(const std::string& path); + +private: + + bool ProcessHeader(std::ifstream& file); + + bool ProcessTrackChunk(std::ifstream& file); + + bool ProcessEvent(std::ifstream& file); + + bool ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event); + + int ProcessTimeDelta(std::ifstream& file); +}; diff --git a/src/audio/midi/MidiTrack.cpp b/src/audio/midi/MidiTrack.cpp new file mode 100644 index 0000000..7817728 --- /dev/null +++ b/src/audio/midi/MidiTrack.cpp @@ -0,0 +1,11 @@ +#include "MidiTrack.h" + +MidiTrack::MidiTrack() +{ + +} + +void MidiTrack::AddEvent(const MidiEvent& event) +{ + mEvents.push_back(event); +} diff --git a/src/audio/midi/MidiTrack.h b/src/audio/midi/MidiTrack.h new file mode 100644 index 0000000..ac69101 --- /dev/null +++ b/src/audio/midi/MidiTrack.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "MidiEvent.h" + +class MidiTrack +{ + std::vector mEvents; + +public: + + MidiTrack(); + + void AddEvent(const MidiEvent& event); +}; diff --git a/src/console/CMakeLists.txt b/src/console/CMakeLists.txt index dfa026e..e222248 100644 --- a/src/console/CMakeLists.txt +++ b/src/console/CMakeLists.txt @@ -9,5 +9,6 @@ target_include_directories(console PUBLIC "${PROJECT_SOURCE_DIR}/src/network" "${PROJECT_SOURCE_DIR}/src/network/sockets" "${PROJECT_SOURCE_DIR}/src/audio" + "${PROJECT_SOURCE_DIR}/src/audio/midi" "${PROJECT_SOURCE_DIR}/src/audio/audio_interfaces" ) \ No newline at end of file diff --git a/src/console/MainApplication.cpp b/src/console/MainApplication.cpp index b3501f1..0fb4776 100644 --- a/src/console/MainApplication.cpp +++ b/src/console/MainApplication.cpp @@ -1,8 +1,9 @@ #include "MainApplication.h" +#include "FileLogger.h" +#include "MidiReader.h" MainApplication::MainApplication() - :mLogger(), - mDatabaseManager() + : mDatabaseManager() { } @@ -14,10 +15,9 @@ MainApplication::~MainApplication() void MainApplication::Initialize(const std::filesystem::path& workDir) { - mLogger = FileLogger::Create(); - mLogger->SetWorkDirectory(workDir.string()); - mLogger->Open(); - mLogger->LogLine("Launched"); + FileLogger::GetInstance().SetWorkDirectory(workDir.string()); + FileLogger::GetInstance().Open(); + MLOG_INFO("Launched"); mDatabaseManager = DatabaseManager::Create(); mDatabaseManager->CreateDatabase(workDir.string() + "/database.db"); @@ -34,16 +34,19 @@ void MainApplication::RunServer() void MainApplication::PlayAudio() { - auto device = AudioDevice::Create(); - mAudioManager->GetAudioInterface()->OpenDevice(device); - mAudioManager->GetAudioInterface()->Play(device); + MidiReader reader; + reader.Read("/home/james/sample.mid"); +// auto device = AudioDevice::Create(); +// mAudioManager->GetAudioInterface()->OpenDevice(device); +// mAudioManager->GetAudioInterface()->Play(device); } void MainApplication::ShutDown() { - mLogger->Close(); mDatabaseManager->OnShutDown(); mNetworkManager->ShutDown(); + MLOG_INFO("Shut down"); + FileLogger::GetInstance().Close(); } std::shared_ptr MainApplication::Create() diff --git a/src/console/MainApplication.h b/src/console/MainApplication.h index c3a0fbe..275caa8 100644 --- a/src/console/MainApplication.h +++ b/src/console/MainApplication.h @@ -12,7 +12,6 @@ class MainApplication { private: - FileLoggerPtr mLogger; DatabaseManagerPtr mDatabaseManager; NetworkManagerPtr mNetworkManager; AudioManagerPtr mAudioManager; diff --git a/src/core/ByteUtils.h b/src/core/ByteUtils.h new file mode 100644 index 0000000..f1ec1ad --- /dev/null +++ b/src/core/ByteUtils.h @@ -0,0 +1,81 @@ +#pragma once +#include + +class ByteUtils +{ +public: + using Word = int; + using DWord = int; + + static int GetWordFirstBit(const Word word) + { + return word & ByteUtils::WORD_FIRST_BIT; + }; + + static int GetWordLastByte(const Word word) + { + return word & ByteUtils::WORD_LAST_BYTE; + } + + static void ReverseBuffer(char* buffer, char* reverse, unsigned size) + { + for(unsigned idx=0; idx +#include -FileLogger::FileLogger() - :mWorkDirectory(), - mFileName("MT_Log.txt"), - mFileStream() -{ - -} FileLogger::~FileLogger() { @@ -38,7 +33,9 @@ void FileLogger::LogLine(const std::string& line) mFileStream << line << std::endl; } -std::shared_ptr FileLogger::Create() +void FileLogger::LogLine(const std::string& logType, const std::string& line, const std::string& fileName, const std::string& functionName, int lineNumber) { - return std::make_shared(); + std::time_t t = std::time(nullptr); + const std::string cleanedFileName = fileName.substr(fileName.find_last_of("/\\") + 1); + mFileStream << logType << "|" << std::put_time(std::gmtime(&t), "%T") << "|" << cleanedFileName << "::" << functionName << "::" << lineNumber << "|" << line << std::endl; } diff --git a/src/core/loggers/FileLogger.h b/src/core/loggers/FileLogger.h index 0f23688..2f9294a 100644 --- a/src/core/loggers/FileLogger.h +++ b/src/core/loggers/FileLogger.h @@ -1,4 +1,6 @@ #pragma once +#define MLOG_INFO(msg) FileLogger::GetInstance().LogLine("Info", msg, __FILE__, __FUNCTION__, __LINE__); +#define MLOG_ERROR(msg) FileLogger::GetInstance().LogLine("Error", msg, __FILE__, __FUNCTION__, __LINE__); #include #include @@ -12,9 +14,24 @@ class FileLogger std::ofstream mFileStream; + FileLogger() + :mWorkDirectory(), + mFileName("MT_Log.txt"), + mFileStream() + { + + } + public: - FileLogger(); + static FileLogger& GetInstance() + { + static FileLogger instance; + return instance; + } + + FileLogger(FileLogger const&) = delete; + void operator=(FileLogger const&) = delete; ~FileLogger(); @@ -28,7 +45,7 @@ public: void LogLine(const std::string& line); - static std::shared_ptr Create(); + void LogLine(const std::string& logType, const std::string& line, const std::string& fileName = "", const std::string& functionName = "", int lineNumber=-1); };