Start midi file processing.

This commit is contained in:
jmsgrogan 2020-05-03 07:56:27 +01:00
parent 59c6161fdb
commit 36826fa1d4
22 changed files with 528 additions and 44 deletions

10
README.md Normal file
View file

@ -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
```

View file

@ -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)

View file

@ -1,16 +1,37 @@
#include <filesystem>
#include <unistd.h>
#include <iostream>
#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();
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;
}

View file

@ -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)
target_link_libraries(audio PUBLIC core asound)

View file

@ -1,6 +1,5 @@
#include "AlsaInterface.h"
#include <iostream>
#include "FileLogger.h"
AlsaInterface::AlsaInterface()
:mHandle(),
@ -22,17 +21,18 @@ std::shared_ptr<AlsaInterface> 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 >>>>>>>>>>>>>>>");
}
}
}

View file

@ -0,0 +1,7 @@
#include "MetaMidiEvent.h"
MetaMidiEvent::MetaMidiEvent()
: MidiEvent()
{
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <string>
#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
};

View file

@ -0,0 +1,6 @@
#include "MidiDocument.h"
MidiDocument::MidiDocument()
{
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <vector>
#include "MidiTrack.h"
class MidiDocument
{
public:
int mFormatType = 0;
int mExpectedTracks = 0;
bool mUseFps = true;
int mFps = 0;
int ticksPerFrame = 0;
std::vector<MidiTrack> mTracks;
MidiDocument();
};

View file

@ -0,0 +1,6 @@
#include "MidiEvent.h"
MidiEvent::MidiEvent()
{
}

View file

@ -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
};

View file

@ -0,0 +1,211 @@
#include "MidiReader.h"
#include <fstream>
#include <iostream>
#include <cstring>
#include <bitset>
#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<number; idx++)
{
if(file.get(c))
{
buffer[idx] = c;
}
else
{
return false;
}
}
return true;
}
bool GetWord(std::ifstream& file, char* buffer)
{
return GetBytes(file, buffer, 2);
}
bool GetDWord(std::ifstream& file, char* buffer)
{
return GetBytes(file, buffer, 4);
}
bool MidiReader::ProcessHeader(std::ifstream& file)
{
char c;
unsigned bufferSize = ByteUtils::BYTES_PER_DWORD;
char buffer[bufferSize];
if(!GetDWord(file, buffer))
{
return false;
}
if(!ByteUtils::CompareDWords(buffer, HeaderLabel))
{
return false;
}
if(!GetDWord(file, buffer))
{
return false;
}
int length = ByteUtils::ToDWord(buffer);
if(!GetWord(file, buffer))
{
return false;
}
mDocument.mFormatType = ByteUtils::ToWord(buffer);
if(!GetWord(file, buffer))
{
return false;
}
mDocument.mExpectedTracks = ByteUtils::ToWord(buffer);
if(!GetWord(file, buffer))
{
return false;
}
int time_division;
std::memcpy(&time_division, buffer, sizeof(int));
const int TOP_7_BITS = 0x7F00; // 0111 1111 - 0000 0000
mDocument.mUseFps = ByteUtils::GetWordFirstBit(time_division);
mDocument.mFps = ((~time_division & TOP_7_BITS) >> 8) - 1; // Reverse 2complement of next 7 bits
mDocument.ticksPerFrame = ByteUtils::GetWordLastByte(time_division);
return true;
}
int MidiReader::ProcessTimeDelta(std::ifstream& file)
{
char c;
file.get(c);
bool needs_next = c & ByteUtils::BYTE_FIRST_BIT;
int time_offset = 0;
if(!needs_next)
{
time_offset = int(c);
mTrackByteCount ++;
}
std::cout << "Next " << needs_next <<std::endl;
std::cout << "Offset " << time_offset <<std::endl;
return time_offset;
}
bool MidiReader::ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event)
{
char c;
file.get(c);
mTrackByteCount ++;
if(MetaMidiEvent::IsTrackName(c))
{
event.mType = MetaMidiEvent::Type::TRACK_NAME;
file.get(c);
mTrackByteCount ++;
int length = int(c);
std::string name;
std::cout << "Track name: " << length <<std::endl;
for(unsigned idx=0; idx<length; idx++)
{
file.get(c);
mTrackByteCount ++;
name += c;
}
std::cout << name << std::endl;
event.mLabel = "Track name";
}
else
{
std::cout << "something else" << std::endl;
std::cout << "Next " << std::bitset<8>(c) << "|" <<std::endl;
}
return true;
}
bool MidiReader::ProcessEvent(std::ifstream& file)
{
char c;
int timeDelta = ProcessTimeDelta(file);
file.get(c);
mTrackByteCount ++;
if(MidiEvent::IsMetaEvent(c))
{
MetaMidiEvent event;
event.mTimeDelta = timeDelta;
std::cout << "Meta event " <<std::endl;
ProcessMetaEvent(file, event);
}
else
{
int first_byte = 0xF0;
int event_type = (c & first_byte) >> 4;
std::cout << "Next " << std::bitset<8>(c) << "|" << event_type <<std::endl;
std::cout << event_type << std::endl;
}
return true;
}
bool MidiReader::ProcessTrackChunk(std::ifstream& file)
{
char c;
unsigned bufferSize = ByteUtils::BYTES_PER_DWORD;
char buffer[bufferSize];
if(!GetDWord(file, buffer))
{
return false;
}
if(!ByteUtils::CompareDWords(buffer, TrackChunkLabel))
{
return false;
}
if(!GetDWord(file, buffer))
{
return false;
}
int chunkSize = ByteUtils::ToDWord(buffer);
std::cout << "Chunk size: "<< chunkSize << std::endl;
mTrackByteCount = 0;
MidiTrack track;
ProcessEvent(file);
std::cout << "Track byte count: "<< mTrackByteCount << std::endl;
ProcessEvent(file);
mDocument.mTracks.push_back(track);
return true;
}
void MidiReader::Read(const std::string& path)
{
std::ifstream file(path);
if(!ProcessHeader(file))
{
std::cout << "Problem processing header" << std::endl;
}
if(!ProcessTrackChunk(file))
{
std::cout << "Problem processing track chunk" << std::endl;
}
file.close();
}

View file

@ -0,0 +1,31 @@
#pragma once
#include "MidiDocument.h"
#include "MetaMidiEvent.h"
#include <string>
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);
};

View file

@ -0,0 +1,11 @@
#include "MidiTrack.h"
MidiTrack::MidiTrack()
{
}
void MidiTrack::AddEvent(const MidiEvent& event)
{
mEvents.push_back(event);
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <vector>
#include "MidiEvent.h"
class MidiTrack
{
std::vector<MidiEvent> mEvents;
public:
MidiTrack();
void AddEvent(const MidiEvent& event);
};

View file

@ -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"
)

View file

@ -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> MainApplication::Create()

View file

@ -12,7 +12,6 @@ class MainApplication
{
private:
FileLoggerPtr mLogger;
DatabaseManagerPtr mDatabaseManager;
NetworkManagerPtr mNetworkManager;
AudioManagerPtr mAudioManager;

81
src/core/ByteUtils.h Normal file
View file

@ -0,0 +1,81 @@
#pragma once
#include <cstring>
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<size; idx++)
{
reverse[idx] = buffer[size - 1 -idx];
}
}
static int ToInt(char* buffer, unsigned size, bool reverse = true)
{
int result;
if(reverse)
{
char reversed[size];
ReverseBuffer(buffer, reversed, size);
std::memcpy(&result, reversed, sizeof(int));
}
else
{
std::memcpy(&result, buffer, sizeof(int));
}
return result;
}
static Word ToWord(char* buffer, bool reverse = true)
{
return ToInt(buffer, BYTES_PER_WORD, reverse);
}
static DWord ToDWord(char* buffer, bool reverse = true)
{
return ToInt(buffer, BYTES_PER_DWORD, reverse);
}
static bool Compare(char* buffer, const char* tag, unsigned size)
{
for(unsigned idx=0; idx<size; idx++)
{
if(tag[idx] != buffer[idx])
{
return false;
}
}
return true;
}
static bool CompareDWords(char* buffer, const char* tag)
{
return Compare(buffer, tag, BYTES_PER_DWORD);
}
static bool CompareWords(char* buffer, const char* tag)
{
return Compare(buffer, tag, BYTES_PER_WORD);
}
static const int BYTES_PER_WORD = 2;
static const int BYTES_PER_DWORD = 4;
static const int BYTE_FIRST_BIT = 0x40; // 1000 0000
static const int WORD_FIRST_BIT = 0x8000; // 1000 0000 - 0000 0000
static const int WORD_LAST_BYTE = 0x00FF; // 0000 0000 - 1111 1111
};

View file

@ -3,7 +3,8 @@ list(APPEND core_LIB_INCLUDES
Color.cpp
CommandLineArgs.cpp
loggers/FileLogger.cpp
StringUtils.cpp)
StringUtils.cpp
http/HttpResponse.cpp)
# add the executable
add_library(core SHARED ${core_LIB_INCLUDES})

View file

@ -1,12 +1,7 @@
#include "FileLogger.h"
#include <ctime>
#include <iomanip>
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> 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<FileLogger>();
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;
}

View file

@ -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 <memory>
#include <string>
@ -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<FileLogger> Create();
void LogLine(const std::string& logType, const std::string& line, const std::string& fileName = "", const std::string& functionName = "", int lineNumber=-1);
};