Improve audio and midi support.
This commit is contained in:
parent
9bcc0ae88e
commit
8b5f485d1e
47 changed files with 1446 additions and 634 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
.cproject
|
.cproject
|
||||||
.project
|
.project
|
||||||
.settings/
|
.settings/
|
||||||
|
/Debug/
|
||||||
|
|
|
@ -6,20 +6,14 @@
|
||||||
class AudioDevice
|
class AudioDevice
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string mName;
|
|
||||||
unsigned mSampleRate;
|
|
||||||
unsigned mNumChannels;
|
|
||||||
unsigned mPeriod;
|
|
||||||
std::size_t mBufferSize;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AudioDevice();
|
AudioDevice();
|
||||||
|
|
||||||
~AudioDevice();
|
~AudioDevice();
|
||||||
|
|
||||||
|
static std::unique_ptr<AudioDevice> Create();
|
||||||
|
|
||||||
void SetSampleRate(unsigned rate);
|
void SetSampleRate(unsigned rate);
|
||||||
|
|
||||||
unsigned GetSampleRate() const;
|
unsigned GetSampleRate() const;
|
||||||
|
@ -40,7 +34,13 @@ public:
|
||||||
|
|
||||||
std::string GetName() const;
|
std::string GetName() const;
|
||||||
|
|
||||||
static std::unique_ptr<AudioDevice> 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<AudioDevice>;
|
using AudioDevicePtr = std::unique_ptr<AudioDevice>;
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IAudioInterface.h"
|
||||||
|
#include "AudioDevice.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "audio_interfaces/IAudioInterface.h"
|
|
||||||
#include "AudioDevice.h"
|
|
||||||
|
|
||||||
class AudioManager
|
class AudioManager
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::vector<AudioDevicePtr> mAudioDevices;
|
|
||||||
IAudioInterfaceUPtr mAudioInterface;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AudioManager();
|
AudioManager();
|
||||||
|
@ -31,6 +26,10 @@ public:
|
||||||
IAudioInterface* GetAudioInterface();
|
IAudioInterface* GetAudioInterface();
|
||||||
|
|
||||||
void Play();
|
void Play();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<AudioDevicePtr> mAudioDevices;
|
||||||
|
IAudioInterfaceUPtr mAudioInterface;
|
||||||
};
|
};
|
||||||
|
|
||||||
using AudioManagerUPtr = std::unique_ptr<AudioManager>;
|
using AudioManagerUPtr = std::unique_ptr<AudioManager>;
|
||||||
|
|
47
src/audio/AudioSample.cpp
Normal file
47
src/audio/AudioSample.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#include "AudioSample.h"
|
||||||
|
|
||||||
|
AudioSample::AudioSample()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioSample> AudioSample::Create()
|
||||||
|
{
|
||||||
|
return std::make_unique<AudioSample>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<short>& data, std::size_t channel)
|
||||||
|
{
|
||||||
|
if (mData.size() == channel)
|
||||||
|
{
|
||||||
|
mData.push_back(data);
|
||||||
|
}
|
||||||
|
else if(mData.size() > channel)
|
||||||
|
{
|
||||||
|
mData[channel] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<short> AudioSample::GetChannelData(std::size_t channel) const
|
||||||
|
{
|
||||||
|
if(mData.size() > channel)
|
||||||
|
{
|
||||||
|
return mData[channel];
|
||||||
|
}
|
||||||
|
return std::vector<short>();
|
||||||
|
}
|
27
src/audio/AudioSample.h
Normal file
27
src/audio/AudioSample.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class AudioSample
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AudioSample();
|
||||||
|
|
||||||
|
static std::unique_ptr<AudioSample> Create();
|
||||||
|
|
||||||
|
std::vector<short> GetChannelData(std::size_t channel) const;
|
||||||
|
void SetChannelData(const std::vector<short>& 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<std::vector<short> > mData;
|
||||||
|
};
|
||||||
|
|
||||||
|
using AudioSamplePtr = std::unique_ptr<AudioSample>;
|
37
src/audio/AudioSynth.cpp
Normal file
37
src/audio/AudioSynth.cpp
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#include "AudioSynth.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <limits>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
AudioSynth::AudioSynth()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSamplePtr AudioSynth::GetConstant(unsigned amplitude, unsigned duration)
|
||||||
|
{
|
||||||
|
auto sample = AudioSample::Create();
|
||||||
|
auto num_samples = duration * sample->GetSampleRate();
|
||||||
|
sample->SetChannelData(std::vector<short>(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<short> data(num_samples, 0);
|
||||||
|
|
||||||
|
double tick_duration = 1.0/sample_rate;
|
||||||
|
double pi_2 = 2.0 * M_PI;
|
||||||
|
short max_short = std::numeric_limits<short>::max();
|
||||||
|
for(unsigned idx=0; idx<num_samples; idx++)
|
||||||
|
{
|
||||||
|
const auto t = double(idx)*tick_duration;
|
||||||
|
data[idx] = max_short*std::sin(pi_2*freq*t);
|
||||||
|
}
|
||||||
|
sample->SetChannelData(data, 0);
|
||||||
|
return sample;
|
||||||
|
}
|
16
src/audio/AudioSynth.h
Normal file
16
src/audio/AudioSynth.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AudioSample.h"
|
||||||
|
|
||||||
|
class AudioSynth
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AudioSynth();
|
||||||
|
|
||||||
|
AudioSamplePtr GetConstant(unsigned amplitude, unsigned duration);
|
||||||
|
|
||||||
|
AudioSamplePtr GetSineWave(double freq, unsigned duration);
|
||||||
|
};
|
6
src/audio/AudioTrack.cpp
Normal file
6
src/audio/AudioTrack.cpp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include "AudioTrack.h"
|
||||||
|
|
||||||
|
AudioTrack::AudioTrack()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
8
src/audio/AudioTrack.h
Normal file
8
src/audio/AudioTrack.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class AudioTrack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AudioTrack();
|
||||||
|
};
|
58
src/audio/AudioWriter.cpp
Normal file
58
src/audio/AudioWriter.cpp
Normal file
|
@ -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<int>(handle, content_size);
|
||||||
|
handle->write("WAVE", 4);
|
||||||
|
|
||||||
|
/* write fmt subchunk */
|
||||||
|
handle->write("fmt ", 4);
|
||||||
|
BinaryFile::Write<int>(handle, 16); // SubChunk1Size
|
||||||
|
BinaryFile::Write<short>(handle, 1); // PCM Format
|
||||||
|
BinaryFile::Write<short>(handle, num_channels);
|
||||||
|
BinaryFile::Write<int>(handle, sample_rate);
|
||||||
|
BinaryFile::Write<int>(handle, byte_rate);
|
||||||
|
BinaryFile::Write<short>(handle, num_channels*bytes_per_sample); // block align
|
||||||
|
BinaryFile::Write<short>(handle, 8*bytes_per_sample); // bits/sample
|
||||||
|
|
||||||
|
/* write data subchunk */
|
||||||
|
handle->write("data", 4);
|
||||||
|
BinaryFile::Write<int>(handle, bytes_per_sample* num_samples*num_channels); // bits/sample
|
||||||
|
|
||||||
|
handle->write(reinterpret_cast<const char*>(&data[0]), data.size()*sizeof(short));
|
||||||
|
outfile.Close();
|
||||||
|
}
|
21
src/audio/AudioWriter.h
Normal file
21
src/audio/AudioWriter.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class AudioSample;
|
||||||
|
using AudioSamplePtr = std::unique_ptr<AudioSample>;
|
||||||
|
|
||||||
|
class AudioWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioWriter();
|
||||||
|
|
||||||
|
void SetPath(const std::string& path);
|
||||||
|
|
||||||
|
void Write(const AudioSamplePtr& sample);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string mPath;
|
||||||
|
};
|
|
@ -9,10 +9,15 @@ list(APPEND linux_HEADERS
|
||||||
list(APPEND audio_HEADERS
|
list(APPEND audio_HEADERS
|
||||||
AudioDevice.h
|
AudioDevice.h
|
||||||
AudioManager.h
|
AudioManager.h
|
||||||
|
AudioTrack.h
|
||||||
|
AudioSample.h
|
||||||
|
AudioSynth.h
|
||||||
|
AudioWriter.h
|
||||||
audio_interfaces/IAudioInterface.h
|
audio_interfaces/IAudioInterface.h
|
||||||
midi/MidiReader.h
|
midi/reader/MidiReader.h
|
||||||
midi/MidiTrack.h
|
midi/MidiTrack.h
|
||||||
midi/MidiDocument.h
|
midi/MidiDocument.h
|
||||||
|
midi/MidiElements.h
|
||||||
midi/MidiEvent.h
|
midi/MidiEvent.h
|
||||||
midi/MetaMidiEvent.h
|
midi/MetaMidiEvent.h
|
||||||
midi/MidiChannelEvent.h)
|
midi/MidiChannelEvent.h)
|
||||||
|
@ -24,7 +29,14 @@ list(APPEND linux_INCLUDES
|
||||||
list(APPEND audio_LIB_INCLUDES
|
list(APPEND audio_LIB_INCLUDES
|
||||||
AudioDevice.cpp
|
AudioDevice.cpp
|
||||||
AudioManager.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/MidiTrack.cpp
|
||||||
midi/MidiDocument.cpp
|
midi/MidiDocument.cpp
|
||||||
midi/MidiEvent.cpp
|
midi/MidiEvent.cpp
|
||||||
|
@ -38,6 +50,7 @@ target_include_directories(audio PUBLIC
|
||||||
"${PROJECT_SOURCE_DIR}/src/core/loggers"
|
"${PROJECT_SOURCE_DIR}/src/core/loggers"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/audio_interfaces"
|
"${CMAKE_CURRENT_SOURCE_DIR}/audio_interfaces"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/midi"
|
"${CMAKE_CURRENT_SOURCE_DIR}/midi"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/midi/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND linux_LIBS
|
list(APPEND linux_LIBS
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#include "AlsaInterface.h"
|
#include "AlsaInterface.h"
|
||||||
#include "FileLogger.h"
|
#include "FileLogger.h"
|
||||||
|
|
||||||
|
#include "AudioSynth.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
AlsaInterface::AlsaInterface()
|
AlsaInterface::AlsaInterface()
|
||||||
:mHandle(),
|
:mHandle(),
|
||||||
mHardwareParams(),
|
mHardwareParams(),
|
||||||
|
@ -118,20 +122,17 @@ void AlsaInterface::SetChannelNumber(const AudioDevicePtr& device)
|
||||||
void AlsaInterface::Play(const AudioDevicePtr& device)
|
void AlsaInterface::Play(const AudioDevicePtr& device)
|
||||||
{
|
{
|
||||||
MLOG_INFO("Playing audio");
|
MLOG_INFO("Playing audio");
|
||||||
unsigned char *data = (unsigned char *)malloc(mPeriodSize);
|
AudioSynth synth;
|
||||||
int frames = mPeriodSize >> 2;
|
const unsigned duration = 100;
|
||||||
for(int count = 0; count < 100; count++)
|
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++)
|
const auto offset= count*4*numFrames;
|
||||||
{
|
unsigned char frame[4] = {data[offset], data[offset+1], data[offset+2], data[offset+3]};
|
||||||
short s1 = (l2 % (512-count)) * 100 - 5000;
|
while ((snd_pcm_writei(mHandle, frame, numFrames)) < 0)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
snd_pcm_prepare(mHandle);
|
snd_pcm_prepare(mHandle);
|
||||||
MLOG_ERROR("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>");
|
MLOG_ERROR("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>");
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IAudioInterface.h"
|
||||||
|
#include "AudioDevice.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include "IAudioInterface.h"
|
|
||||||
|
|
||||||
#include "AudioDevice.h"
|
|
||||||
|
|
||||||
class AlsaInterface : public IAudioInterface
|
class AlsaInterface : public IAudioInterface
|
||||||
{
|
{
|
||||||
snd_pcm_t* mHandle;
|
snd_pcm_t* mHandle;
|
||||||
|
|
|
@ -9,9 +9,69 @@ MetaMidiEvent::MetaMidiEvent()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MetaMidiEvent> MetaMidiEvent::Create()
|
std::unique_ptr<MetaMidiEvent> MetaMidiEvent::Create()
|
||||||
{
|
{
|
||||||
return std::make_shared<MetaMidiEvent>();
|
return std::make_unique<MetaMidiEvent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
void MetaMidiEvent::SetValue(int value)
|
||||||
|
@ -19,37 +79,22 @@ void MetaMidiEvent::SetValue(int value)
|
||||||
mValue = 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)
|
void MetaMidiEvent::SetLabel(const std::string& label)
|
||||||
{
|
{
|
||||||
mLabel = 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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "MidiEvent.h"
|
#include "MidiEvent.h"
|
||||||
|
#include "MidiElements.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -10,40 +11,65 @@ public:
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
UNSET,
|
UNSET,
|
||||||
|
UNKNOWN,
|
||||||
|
SEQ_NUM,
|
||||||
|
TEXT,
|
||||||
|
COPYRIGHT,
|
||||||
TRACK_NAME,
|
TRACK_NAME,
|
||||||
|
INSTRUMENT_NAME,
|
||||||
|
LYRIC,
|
||||||
|
MARKER,
|
||||||
|
CUE_POINT,
|
||||||
|
CHANNEL_PREFIX,
|
||||||
|
END_TRACK,
|
||||||
SET_TEMPO,
|
SET_TEMPO,
|
||||||
TIME_SIGNATURE
|
SMPTE_OFFSET,
|
||||||
|
TIME_SIG,
|
||||||
|
KEY_SIG,
|
||||||
|
SEQ_CUSTOM
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
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_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
|
static const int META_TIME_SIG = 0x58; // 0101 1000
|
||||||
Type mType;
|
static const int META_KEY_SIG = 0x59; // 0101 1001
|
||||||
std::string mLabel;
|
|
||||||
int mValue;
|
static const int META_SEQ_CUSTOM = 0x7F;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
MetaMidiEvent();
|
MetaMidiEvent();
|
||||||
static std::shared_ptr<MetaMidiEvent> Create();
|
static std::unique_ptr<MetaMidiEvent> Create();
|
||||||
|
|
||||||
void SetValue(int value);
|
void SetValue(int value);
|
||||||
|
void SetType(char c);
|
||||||
void SetIsTrackName();
|
Type GetType() const;
|
||||||
|
|
||||||
void SetIsSetTempo();
|
|
||||||
|
|
||||||
void SetIsTimeSignature();
|
|
||||||
|
|
||||||
void SetLabel(const std::string& label);
|
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);
|
private:
|
||||||
|
Type mType {Type::UNSET};
|
||||||
static bool IsSetTempo(char c);
|
char mUnKnownMarker{0};
|
||||||
|
std::string mLabel;
|
||||||
static bool IsTimeSignature(char c);
|
int mValue { 0 };
|
||||||
|
MidiTimeSignature mTimeSig;
|
||||||
|
MidiSmtpeTimecode mTimecode;
|
||||||
|
MidiKeySignature mKeySig;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MetaMidiEventPtr = std::shared_ptr<MetaMidiEvent>;
|
using MetaMidiEventPtr = std::unique_ptr<MetaMidiEvent>;
|
||||||
|
|
|
@ -9,16 +9,16 @@ MidiChannelEvent::MidiChannelEvent()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MidiChannelEvent> MidiChannelEvent::Create()
|
std::unique_ptr<MidiChannelEvent> MidiChannelEvent::Create()
|
||||||
{
|
{
|
||||||
return std::make_shared<MidiChannelEvent>();
|
return std::make_unique<MidiChannelEvent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MidiChannelEvent::SetType(Type type)
|
void MidiChannelEvent::SetType(Type type)
|
||||||
{
|
{
|
||||||
mType = type;
|
mType = type;
|
||||||
}
|
}
|
||||||
MidiChannelEvent::Type MidiChannelEvent::GetType()
|
MidiChannelEvent::Type MidiChannelEvent::GetType() const
|
||||||
{
|
{
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
@ -27,54 +27,88 @@ void MidiChannelEvent::SetValues(int value0, int value1)
|
||||||
mValue0 = value0;
|
mValue0 = value0;
|
||||||
mValue1 = value1;
|
mValue1 = value1;
|
||||||
}
|
}
|
||||||
int MidiChannelEvent::GetValue0()
|
int MidiChannelEvent::GetValue0() const
|
||||||
{
|
{
|
||||||
return mValue0;
|
return mValue0;
|
||||||
}
|
}
|
||||||
int MidiChannelEvent::GetValue1()
|
int MidiChannelEvent::GetValue1() const
|
||||||
{
|
{
|
||||||
return mValue1;
|
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)
|
const bool isSystemMessage = event_type == 0xF;
|
||||||
|
if(!isSystemMessage)
|
||||||
{
|
{
|
||||||
return (c & NOTE_ON) == NOTE_ON;
|
mChannel = (c & second_four_bits) >> 4;
|
||||||
}
|
switch(event_type)
|
||||||
|
|
||||||
bool MidiChannelEvent::IsNoteOffEvent(int c)
|
|
||||||
{
|
{
|
||||||
return (c & NOTE_OFF) == NOTE_OFF;
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bool MidiChannelEvent::IsNoteAfterTouchEvent(int c)
|
else
|
||||||
{
|
{
|
||||||
return (c & NOTE_AFTERTOUCH) == NOTE_AFTERTOUCH;
|
const int subType = (c & second_four_bits) >> 4;
|
||||||
}
|
switch(subType)
|
||||||
|
|
||||||
bool MidiChannelEvent::IsProgramEvent(int c)
|
|
||||||
{
|
{
|
||||||
return (c & PROGRAM) == PROGRAM;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,32 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "MidiEvent.h"
|
#include "MidiEvent.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class MidiChannelEvent : public MidiEvent
|
class MidiChannelEvent : public MidiEvent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class Type{
|
enum class Type{
|
||||||
NONE, NOTE_OFF, NOTE_ON, NOTE_AFTERTOUCH, CONTROLLER,
|
NONE,
|
||||||
PROGRAM, CHANNEL_AFTERTOUCH, PITCH_BEND
|
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:
|
private:
|
||||||
|
@ -18,35 +37,36 @@ private:
|
||||||
static const int PROGRAM = 0xC;
|
static const int PROGRAM = 0xC;
|
||||||
static const int CHANNEL_AFTERTOUCH = 0xD;
|
static const int CHANNEL_AFTERTOUCH = 0xD;
|
||||||
static const int PITCH_BEND = 0xE;
|
static const int PITCH_BEND = 0xE;
|
||||||
Type mType;
|
|
||||||
int mValue0;
|
static const int SYS_EXCLUSIVE = 0x0;
|
||||||
int mValue1;
|
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:
|
public:
|
||||||
MidiChannelEvent();
|
MidiChannelEvent();
|
||||||
static std::shared_ptr<MidiChannelEvent> Create();
|
static std::unique_ptr<MidiChannelEvent> Create();
|
||||||
|
|
||||||
|
void SetTypeAndChannel(char c);
|
||||||
void SetType(Type type);
|
void SetType(Type type);
|
||||||
Type GetType();
|
Type GetType() const;
|
||||||
|
|
||||||
void SetValues(int value0, int value1);
|
void SetValues(int value0, int value1);
|
||||||
int GetValue0();
|
int GetValue0() const;
|
||||||
int GetValue1();
|
int GetValue1() const;
|
||||||
|
|
||||||
static bool IsControllerEvent(int c);
|
private:
|
||||||
|
Type mType;
|
||||||
static bool IsNoteOnEvent(int c);
|
int mChannel {0};
|
||||||
|
int mValue0 {0};
|
||||||
static bool IsNoteOffEvent(int c);
|
int mValue1 {1};
|
||||||
|
|
||||||
static bool IsNoteAfterTouchEvent(int c);
|
|
||||||
|
|
||||||
static bool IsProgramEvent(int c);
|
|
||||||
|
|
||||||
static bool IsChannelAftertouchEvent(int c);
|
|
||||||
|
|
||||||
static bool IsPitchbendEvent(int c);
|
|
||||||
|
|
||||||
static bool IsStatusByte(int c);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using MidiChannelEventPtr = std::shared_ptr<MidiChannelEvent>;
|
using MidiChannelEventPtr = std::unique_ptr<MidiChannelEvent>;
|
||||||
|
|
|
@ -1,6 +1,50 @@
|
||||||
#include "MidiDocument.h"
|
#include "MidiDocument.h"
|
||||||
|
|
||||||
|
#include "MidiTrack.h"
|
||||||
|
|
||||||
MidiDocument::MidiDocument()
|
MidiDocument::MidiDocument()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MidiDocument> MidiDocument::Create()
|
||||||
|
{
|
||||||
|
return std::make_unique<MidiDocument>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,34 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "MidiTrack.h"
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "MidiElements.h"
|
||||||
|
|
||||||
|
class MidiTrack;
|
||||||
|
using MidiTrackPtr = std::unique_ptr<MidiTrack>;
|
||||||
|
|
||||||
class MidiDocument
|
class MidiDocument
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
MidiDocument();
|
||||||
|
|
||||||
|
static std::unique_ptr<MidiDocument> 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 mFormatType = 0;
|
||||||
int mExpectedTracks = 0;
|
int mExpectedTracks = 0;
|
||||||
bool mUseFps = true;
|
MidiTimeDivision mTimeDivision;
|
||||||
int mFps = 0;
|
std::vector<MidiTrackPtr> mTracks;
|
||||||
int ticksPerFrame = 0;
|
|
||||||
std::vector<MidiTrack> mTracks;
|
|
||||||
MidiDocument();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using MidiDocumentPtr = std::unique_ptr<MidiDocument>;
|
||||||
|
|
31
src/audio/midi/MidiElements.h
Normal file
31
src/audio/midi/MidiElements.h
Normal file
|
@ -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};
|
||||||
|
};
|
|
@ -6,16 +6,16 @@ MidiEvent::MidiEvent()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MidiEvent> MidiEvent::Create()
|
||||||
|
{
|
||||||
|
return std::make_unique<MidiEvent>();
|
||||||
|
}
|
||||||
|
|
||||||
void MidiEvent::SetTimeDelta(int delta)
|
void MidiEvent::SetTimeDelta(int delta)
|
||||||
{
|
{
|
||||||
mTimeDelta = delta;
|
mTimeDelta = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MidiEvent> MidiEvent::Create()
|
|
||||||
{
|
|
||||||
return std::make_shared<MidiEvent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiEvent::IsMetaEvent(char c)
|
bool MidiEvent::IsMetaEvent(char c)
|
||||||
{
|
{
|
||||||
return (c & META_EVENT) == META_EVENT;
|
return (c & META_EVENT) == META_EVENT;
|
||||||
|
|
|
@ -7,20 +7,23 @@ class MidiEvent
|
||||||
static const int META_EVENT = 0xFF; // 1111 1111
|
static const int META_EVENT = 0xFF; // 1111 1111
|
||||||
static const int SYSEX_EVENT = 0xF0;
|
static const int SYSEX_EVENT = 0xF0;
|
||||||
static const int DIVIDED_SYSEX_EVENT = 0xF7;
|
static const int DIVIDED_SYSEX_EVENT = 0xF7;
|
||||||
int mTimeDelta;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
MidiEvent();
|
MidiEvent();
|
||||||
virtual ~MidiEvent() = default;
|
virtual ~MidiEvent() = default;
|
||||||
|
|
||||||
static std::shared_ptr<MidiEvent> Create();
|
static std::unique_ptr<MidiEvent> Create();
|
||||||
|
|
||||||
void SetTimeDelta(int delta);
|
void SetTimeDelta(int delta);
|
||||||
|
|
||||||
static bool IsMetaEvent(char c);
|
static bool IsMetaEvent(char c);
|
||||||
static bool IsSysExEvent(char c);
|
static bool IsSysExEvent(char c);
|
||||||
static bool IsNormalSysExEvent(char c);
|
static bool IsNormalSysExEvent(char c);
|
||||||
static bool IsDividedSysExEvent(char c);
|
static bool IsDividedSysExEvent(char c);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mTimeDelta{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
using MidiEventPtr = std::shared_ptr<MidiEvent>;
|
using MidiEventPtr = std::unique_ptr<MidiEvent>;
|
||||||
|
|
|
@ -1,379 +0,0 @@
|
||||||
#include "MidiReader.h"
|
|
||||||
|
|
||||||
#include "MidiDocument.h"
|
|
||||||
#include "ByteUtils.h"
|
|
||||||
#include "MidiTrack.h"
|
|
||||||
#include "BinaryFile.h"
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstring>
|
|
||||||
#include <bitset>
|
|
||||||
|
|
||||||
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 <<std::endl;
|
|
||||||
event.SetLabel(name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessSetTempoMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
|
||||||
{
|
|
||||||
event.SetIsSetTempo();
|
|
||||||
|
|
||||||
int length = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, length);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
std::string buffer;
|
|
||||||
BinaryFile::GetNextNBytes(file, buffer.data(), length);
|
|
||||||
mTrackByteCount += length;
|
|
||||||
|
|
||||||
int tempo = ByteUtils::ToInt(buffer.data(), length);
|
|
||||||
const int MICROSECONDS_PER_MINUTE = 60000000;
|
|
||||||
std::cout << "Got tempo "<< tempo << "|" << MICROSECONDS_PER_MINUTE/tempo<< std::endl;
|
|
||||||
event.SetValue(tempo);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool MidiReader::ProcessTimeSignatureMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
|
||||||
{
|
|
||||||
event.SetIsTimeSignature();
|
|
||||||
|
|
||||||
int length = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, length);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
int numer = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, numer);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
int denom = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, denom);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
int metro = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, metro);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
int f32s = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, f32s);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
std::cout << "Time sig " << length << "|" << numer << "|" << denom << "|" << metro << "|" << f32s << std::endl;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessMetaEvent(std::ifstream& file, MetaMidiEvent& event)
|
|
||||||
{
|
|
||||||
char c;
|
|
||||||
file.get(c);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
if(MetaMidiEvent::IsTrackName(c))
|
|
||||||
{
|
|
||||||
ProcessTrackNameMetaEvent(file, event);
|
|
||||||
}
|
|
||||||
else if(MetaMidiEvent::IsSetTempo(c))
|
|
||||||
{
|
|
||||||
ProcessSetTempoMetaEvent(file, event);
|
|
||||||
}
|
|
||||||
else if(MetaMidiEvent::IsTimeSignature(c))
|
|
||||||
{
|
|
||||||
ProcessTimeSignatureMetaEvent(file, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "something else" << std::endl;
|
|
||||||
std::cout << "Next " << std::bitset<8>(c) << "|" <<std::endl;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event, char c)
|
|
||||||
{
|
|
||||||
int value0 = int(c);
|
|
||||||
int value1 = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, value1);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
event.SetValues(value0, value1);
|
|
||||||
std::cout << "sdata: " << value0 << "|" << value1 << "|" << std::endl;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessMidiEventData(std::ifstream& file, MidiChannelEvent& event)
|
|
||||||
{
|
|
||||||
int value0 = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, value0);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
|
|
||||||
int value1 = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, value1);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
event.SetValues(value0, value1);
|
|
||||||
std::cout << "data: " << value0 << "|" << value1 << "|" << std::endl;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessMidiChannelEvent(std::ifstream& file, char firstByte, MidiChannelEvent& event)
|
|
||||||
{
|
|
||||||
int first_four_bits = 0xF0;
|
|
||||||
int second_four_bits = 0xF;
|
|
||||||
int event_type = (firstByte & first_four_bits) >> 4;
|
|
||||||
int midi_channel = (firstByte & second_four_bits) >> 4;
|
|
||||||
std::cout << "Channel: " << midi_channel << std::endl;
|
|
||||||
|
|
||||||
if(MidiChannelEvent::IsStatusByte(event_type))
|
|
||||||
{
|
|
||||||
if(MidiChannelEvent::IsControllerEvent(event_type))
|
|
||||||
{
|
|
||||||
event.SetType(MidiChannelEvent::Type::CONTROLLER);
|
|
||||||
mLastChannelEventType = MidiChannelEvent::Type::CONTROLLER;
|
|
||||||
std::cout << "Controller Event"<< std::endl;
|
|
||||||
ProcessMidiEventData(file, event);
|
|
||||||
}
|
|
||||||
else if(MidiChannelEvent::IsProgramEvent(event_type))
|
|
||||||
{
|
|
||||||
event.SetType(MidiChannelEvent::Type::PROGRAM);
|
|
||||||
mLastChannelEventType = MidiChannelEvent::Type::PROGRAM;
|
|
||||||
std::cout << "Program Event"<< std::endl;
|
|
||||||
int value0 = 0;
|
|
||||||
BinaryFile::GetNextByteAsInt(file, value0);
|
|
||||||
mTrackByteCount ++;
|
|
||||||
std::cout << "value " << value0 << std::endl;
|
|
||||||
}
|
|
||||||
else if(MidiChannelEvent::IsNoteOnEvent(event_type))
|
|
||||||
{
|
|
||||||
event.SetType(MidiChannelEvent::Type::NOTE_ON);
|
|
||||||
mLastChannelEventType = MidiChannelEvent::Type::NOTE_ON;
|
|
||||||
std::cout << "Note on Event"<< std::endl;
|
|
||||||
ProcessMidiEventData(file, event);
|
|
||||||
}
|
|
||||||
else if(MidiChannelEvent::IsNoteOffEvent(event_type))
|
|
||||||
{
|
|
||||||
event.SetType(MidiChannelEvent::Type::NOTE_OFF);
|
|
||||||
mLastChannelEventType = MidiChannelEvent::Type::NOTE_OFF;
|
|
||||||
std::cout << "Note off Event"<< std::endl;
|
|
||||||
ProcessMidiEventData(file, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Unknown status event: " << std::bitset<8>(firstByte) << "|" << event_type <<std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(mLastChannelEventType == MidiChannelEvent::Type::CONTROLLER)
|
|
||||||
{
|
|
||||||
std::cout << "Controller running event: " <<std::endl;
|
|
||||||
ProcessMidiEventData(file, event, firstByte);
|
|
||||||
}
|
|
||||||
else if(mLastChannelEventType == MidiChannelEvent::Type::NOTE_ON)
|
|
||||||
{
|
|
||||||
std::cout << "Note on running event: " <<std::endl;
|
|
||||||
ProcessMidiEventData(file, event, firstByte);
|
|
||||||
}
|
|
||||||
else if(mLastChannelEventType == MidiChannelEvent::Type::NOTE_OFF)
|
|
||||||
{
|
|
||||||
std::cout << "Note off running event: " <<std::endl;
|
|
||||||
ProcessMidiEventData(file, event, firstByte);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Unknown running event: " <<std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessEvent(std::ifstream& file, MidiTrack& track)
|
|
||||||
{
|
|
||||||
char c;
|
|
||||||
int timeDelta = ProcessTimeDelta(file);
|
|
||||||
|
|
||||||
file.get(c);
|
|
||||||
std::cout << std::bitset<8>(c) << std::endl;
|
|
||||||
mTrackByteCount ++;
|
|
||||||
if(MidiEvent::IsMetaEvent(c))
|
|
||||||
{
|
|
||||||
MetaMidiEvent event;
|
|
||||||
event.SetTimeDelta(timeDelta);
|
|
||||||
std::cout << "Meta event " <<std::endl;
|
|
||||||
ProcessMetaEvent(file, event);
|
|
||||||
track.AddEvent(event);
|
|
||||||
}
|
|
||||||
else if(MidiEvent::IsSysExEvent(c))
|
|
||||||
{
|
|
||||||
std::cout << "Sysex event" << std::endl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // Midi event
|
|
||||||
MidiChannelEvent event;
|
|
||||||
event.SetTimeDelta(timeDelta);
|
|
||||||
std::cout << "Midi event" << std::endl;
|
|
||||||
ProcessMidiChannelEvent(file, c, event);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MidiReader::ProcessTrackChunk(std::ifstream& file, bool debug)
|
|
||||||
{
|
|
||||||
if(!BinaryFile::CheckNextDWord(file, TrackChunkLabel))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int chunkSize = 0;
|
|
||||||
if(!BinaryFile::GetNextDWord(file, chunkSize))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::cout << "Chunk size: "<< chunkSize << std::endl;
|
|
||||||
|
|
||||||
mTrackByteCount = 0;
|
|
||||||
MidiTrack track;
|
|
||||||
unsigned iter_count = 0;
|
|
||||||
while(mTrackByteCount < unsigned(chunkSize))
|
|
||||||
{
|
|
||||||
std::cout << "-------------" << std::endl;
|
|
||||||
ProcessEvent(file, track);
|
|
||||||
std::cout << "Track byte count: "<< mTrackByteCount << std::endl;
|
|
||||||
if(debug && iter_count == 25)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
iter_count ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
mDocument.mTracks.push_back(track);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MidiReader::Read(const std::string& path)
|
|
||||||
{
|
|
||||||
std::ifstream file(path);
|
|
||||||
if(!ProcessHeader(file))
|
|
||||||
{
|
|
||||||
std::cout << "Problem processing header" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int trackCount = 0;
|
|
||||||
if(!ProcessTrackChunk(file))
|
|
||||||
{
|
|
||||||
std::cout << "Problem processing track chunk" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackCount ++;
|
|
||||||
|
|
||||||
std::cout << "****************************************" << std::endl;
|
|
||||||
std::cout << "Track 2" << std::endl;
|
|
||||||
|
|
||||||
if(!ProcessTrackChunk(file, true))
|
|
||||||
{
|
|
||||||
std::cout << "Problem processing track chunk" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackCount ++;
|
|
||||||
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "MidiDocument.h"
|
|
||||||
#include "MetaMidiEvent.h"
|
|
||||||
#include "MidiChannelEvent.h"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
|
@ -1,11 +1,35 @@
|
||||||
#include "MidiTrack.h"
|
#include "MidiTrack.h"
|
||||||
|
|
||||||
|
#include "MidiEvent.h"
|
||||||
|
|
||||||
MidiTrack::MidiTrack()
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include "MidiEvent.h"
|
#include "MidiEvent.h"
|
||||||
|
|
||||||
|
class MidiEvent;
|
||||||
|
using MidiEventPtr = std::unique_ptr<MidiEvent>;
|
||||||
|
|
||||||
class MidiTrack
|
class MidiTrack
|
||||||
{
|
{
|
||||||
std::vector<MidiEvent> mEvents;
|
|
||||||
|
|
||||||
public:
|
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<MidiEventPtr> mEvents;
|
||||||
};
|
};
|
||||||
|
|
77
src/audio/midi/reader/MidiChannelEventAdapter.cpp
Normal file
77
src/audio/midi/reader/MidiChannelEventAdapter.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#include "MidiChannelEventAdapter.h"
|
||||||
|
|
||||||
|
#include "BinaryFile.h"
|
||||||
|
#include "ByteUtils.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
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<int>(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 <<std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiChannelEventAdapter::ReadEventData(std::ifstream* file, MidiChannelEvent* event, char c)
|
||||||
|
{
|
||||||
|
int value0 = int(c);
|
||||||
|
int value1 = 0;
|
||||||
|
BinaryFile::GetNextByteAsInt(file, value1);
|
||||||
|
event->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;
|
||||||
|
}
|
14
src/audio/midi/reader/MidiChannelEventAdapter.h
Normal file
14
src/audio/midi/reader/MidiChannelEventAdapter.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MidiChannelEvent.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
190
src/audio/midi/reader/MidiMetaEventAdapter.cpp
Normal file
190
src/audio/midi/reader/MidiMetaEventAdapter.cpp
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
#include "MidiMetaEventAdapter.h"
|
||||||
|
|
||||||
|
#include "BinaryFile.h"
|
||||||
|
#include "ByteUtils.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
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<<std::endl;
|
||||||
|
|
||||||
|
switch (event->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) << "|" <<std::endl;
|
||||||
|
byteCount += ReadUnknownEvent(file);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiMetaEventAdapter::ReadUnknownEvent(std::ifstream* file)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
BinaryFile::GetNextByteAsInt(file, length);
|
||||||
|
|
||||||
|
char c;
|
||||||
|
for(unsigned idx=0; idx<length; idx++)
|
||||||
|
{
|
||||||
|
file->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; idx<length-4; idx++)
|
||||||
|
{
|
||||||
|
file->get(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;
|
||||||
|
}
|
21
src/audio/midi/reader/MidiMetaEventAdapter.h
Normal file
21
src/audio/midi/reader/MidiMetaEventAdapter.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MetaMidiEvent.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
153
src/audio/midi/reader/MidiReader.cpp
Normal file
153
src/audio/midi/reader/MidiReader.cpp
Normal file
|
@ -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 <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
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<MetaMidiEvent>();
|
||||||
|
event->SetTimeDelta(timeDelta);
|
||||||
|
std::cout << "Meta event " <<std::endl;
|
||||||
|
byteCount += MidiMetaEventAdapter::ReadEvent(mFile->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<MidiChannelEvent>();
|
||||||
|
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<MidiTrack>();
|
||||||
|
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<File>(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();
|
||||||
|
}
|
35
src/audio/midi/reader/MidiReader.h
Normal file
35
src/audio/midi/reader/MidiReader.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MidiDocument.h"
|
||||||
|
#include "MetaMidiEvent.h"
|
||||||
|
#include "MidiTrack.h"
|
||||||
|
#include "MidiChannelEvent.h"
|
||||||
|
#include "File.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<File> mFile;
|
||||||
|
MidiDocumentPtr mDocument;
|
||||||
|
int mLastMidiChannel {0};
|
||||||
|
MidiChannelEvent::Type mLastChannelEventType;
|
||||||
|
};
|
66
src/audio/midi/reader/MidiTimeAdapter.cpp
Normal file
66
src/audio/midi/reader/MidiTimeAdapter.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include "MidiTimeAdapter.h"
|
||||||
|
|
||||||
|
#include "BinaryFile.h"
|
||||||
|
#include "ByteUtils.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
13
src/audio/midi/reader/MidiTimeAdapter.h
Normal file
13
src/audio/midi/reader/MidiTimeAdapter.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MidiElements.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
class MidiTimeAdapter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static int ReadEventTimeDelta(std::ifstream* file, int& delta);
|
||||||
|
|
||||||
|
static int ReadTimeDivision(std::ifstream* file, MidiTimeDivision& division);
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
class ByteUtils
|
class ByteUtils
|
||||||
{
|
{
|
||||||
|
@ -7,6 +8,11 @@ public:
|
||||||
using Word = int;
|
using Word = int;
|
||||||
using DWord = int;
|
using DWord = int;
|
||||||
|
|
||||||
|
static bool MSBIsOne(char c)
|
||||||
|
{
|
||||||
|
return c & (1 << 7);
|
||||||
|
}
|
||||||
|
|
||||||
static int GetWordFirstBit(const Word word)
|
static int GetWordFirstBit(const Word word)
|
||||||
{
|
{
|
||||||
return word & ByteUtils::WORD_FIRST_BIT;
|
return word & ByteUtils::WORD_FIRST_BIT;
|
||||||
|
@ -17,22 +23,29 @@ public:
|
||||||
return word & ByteUtils::WORD_LAST_BYTE;
|
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; idx<size; idx++)
|
for(unsigned idx=0; idx<targetSize; idx++)
|
||||||
|
{
|
||||||
|
if (idx < size)
|
||||||
{
|
{
|
||||||
reverse[idx] = buffer[size - 1 -idx];
|
reverse[idx] = buffer[size - 1 -idx];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reverse[idx] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ToInt(char* buffer, const unsigned size, bool reverse = true)
|
static int ToInt(char* buffer, const unsigned size, bool reverse = true)
|
||||||
{
|
{
|
||||||
int result;
|
int result {0};
|
||||||
if(reverse)
|
if(reverse)
|
||||||
{
|
{
|
||||||
std::string reversed;
|
char reversed[sizeof(int)];
|
||||||
ReverseBuffer(buffer, reversed.data(), size);
|
ReverseBuffer(buffer, reversed, size, sizeof(int));
|
||||||
std::memcpy(&result, reversed.data(), sizeof(int));
|
std::memcpy(&result, reversed, sizeof(int));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
#include "BinaryFile.h"
|
#include "BinaryFile.h"
|
||||||
#include "ByteUtils.h"
|
#include "ByteUtils.h"
|
||||||
|
|
||||||
bool BinaryFile::GetNextByteAsInt(std::ifstream& file, int& target)
|
bool BinaryFile::GetNextByteAsInt(std::ifstream* file, int& target)
|
||||||
{
|
{
|
||||||
char c;
|
char c;
|
||||||
file.get(c);
|
file->get(c);
|
||||||
target = int(c);
|
target = int(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BinaryFile::GetNextNBytes(std::ifstream& file, char* buffer, unsigned number)
|
bool BinaryFile::GetNextNBytes(std::ifstream* file, char* buffer, unsigned number)
|
||||||
{
|
{
|
||||||
char c;
|
char c;
|
||||||
for(unsigned idx=0; idx<number; idx++)
|
for(unsigned idx=0; idx<number; idx++)
|
||||||
{
|
{
|
||||||
if(file.get(c))
|
if(file->get(c))
|
||||||
{
|
{
|
||||||
buffer[idx] = c;
|
buffer[idx] = c;
|
||||||
}
|
}
|
||||||
|
@ -26,17 +26,17 @@ bool BinaryFile::GetNextNBytes(std::ifstream& file, char* buffer, unsigned numbe
|
||||||
return true;
|
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);
|
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);
|
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];
|
char buffer[ByteUtils::BYTES_PER_WORD];
|
||||||
if(!BinaryFile::GetNextWord(file, buffer))
|
if(!BinaryFile::GetNextWord(file, buffer))
|
||||||
|
@ -47,7 +47,7 @@ bool BinaryFile::GetNextWord(std::ifstream& file, int& target, bool reverse)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BinaryFile::GetNextDWord(std::ifstream& file, int& target)
|
bool BinaryFile::GetNextDWord(std::ifstream* file, int& target)
|
||||||
{
|
{
|
||||||
char buffer[ByteUtils::BYTES_PER_DWORD];
|
char buffer[ByteUtils::BYTES_PER_DWORD];
|
||||||
if(!BinaryFile::GetNextDWord(file, buffer))
|
if(!BinaryFile::GetNextDWord(file, buffer))
|
||||||
|
@ -58,7 +58,7 @@ bool BinaryFile::GetNextDWord(std::ifstream& file, int& target)
|
||||||
return true;
|
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];
|
char buffer[ByteUtils::BYTES_PER_DWORD];
|
||||||
if(!BinaryFile::GetNextDWord(file, buffer))
|
if(!BinaryFile::GetNextDWord(file, buffer))
|
||||||
|
@ -73,12 +73,12 @@ bool BinaryFile::CheckNextDWord(std::ifstream& file, const char* target)
|
||||||
return true;
|
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;
|
char c;
|
||||||
for(unsigned idx=0; idx<numBytes; idx++)
|
for(unsigned idx=0; idx<numBytes; idx++)
|
||||||
{
|
{
|
||||||
if(!file.get(c))
|
if(!file->get(c))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,26 @@ class BinaryFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static bool GetNextByteAsInt(std::ifstream& file, int& target);
|
template<typename T>
|
||||||
|
static bool Write(std::ofstream* file, T data)
|
||||||
|
{
|
||||||
|
file->write(reinterpret_cast<char*>(&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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,17 +31,27 @@ std::ofstream* File::GetOutHandle() const
|
||||||
return mOutHandle.get();
|
return mOutHandle.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Open()
|
void File::Open(bool asBinary)
|
||||||
{
|
{
|
||||||
if(mAccessMode == AccessMode::Read)
|
if(mAccessMode == AccessMode::Read)
|
||||||
{
|
{
|
||||||
|
auto flags = std::ifstream::in;
|
||||||
|
if (asBinary)
|
||||||
|
{
|
||||||
|
flags |= std::ifstream::binary;
|
||||||
|
}
|
||||||
mInHandle = std::make_unique<std::ifstream>();
|
mInHandle = std::make_unique<std::ifstream>();
|
||||||
mInHandle->open(mFullPath, std::ifstream::in);
|
mInHandle->open(mFullPath, flags);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
auto flags = std::ofstream::out;
|
||||||
|
if (asBinary)
|
||||||
|
{
|
||||||
|
flags |= std::ofstream::binary;
|
||||||
|
}
|
||||||
mOutHandle = std::make_unique<std::ofstream>();
|
mOutHandle = std::make_unique<std::ofstream>();
|
||||||
mOutHandle->open(mFullPath, std::ofstream::out);
|
mOutHandle->open(mFullPath, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ public:
|
||||||
|
|
||||||
void SetAccessMode(AccessMode mode);
|
void SetAccessMode(AccessMode mode);
|
||||||
|
|
||||||
void Open();
|
void Open(bool asBinary = false);
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@ FileFormat::ExtensionMap FileFormat::mExtensions = []
|
||||||
ExtensionMap ret;
|
ExtensionMap ret;
|
||||||
ret[Format::Markdown] = ".md";
|
ret[Format::Markdown] = ".md";
|
||||||
ret[Format::Html] = ".html";
|
ret[Format::Html] = ".html";
|
||||||
|
ret[Format::Wav] = ".wav";
|
||||||
return ret;
|
return ret;
|
||||||
}();
|
}();
|
||||||
|
|
|
@ -1,6 +1,25 @@
|
||||||
add_executable(test_runner test_runner.cpp)
|
add_library(test_utils SHARED
|
||||||
target_include_directories(test_runner PUBLIC
|
test_utils/TestCase.h
|
||||||
"${PROJECT_SOURCE_DIR}/test/"
|
test_utils/TestCaseRunner.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(test_runner PUBLIC core
|
|
||||||
network database geometry audio graphics web)
|
target_include_directories(test_utils PUBLIC
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
34
test/audio/TestAudioWriter.cpp
Normal file
34
test/audio/TestAudioWriter.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "AudioSample.h"
|
||||||
|
#include "AudioSynth.h"
|
||||||
|
#include "AudioWriter.h"
|
||||||
|
|
||||||
|
#include "TestCase.h"
|
||||||
|
#include "TestCaseRunner.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<TestWriteWav>());
|
||||||
|
|
||||||
|
const auto testsPassed = runner.Run();
|
||||||
|
return testsPassed ? 0 : -1;
|
||||||
|
}
|
31
test/audio/TestMidiReader.cpp
Normal file
31
test/audio/TestMidiReader.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "MidiReader.h"
|
||||||
|
|
||||||
|
#include "TestCase.h"
|
||||||
|
#include "TestCaseRunner.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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<TestReadMidi>());
|
||||||
|
|
||||||
|
const auto testsPassed = runner.Run();
|
||||||
|
return testsPassed ? 0 : -1;
|
||||||
|
}
|
13
test/test_utils/TestCase.h
Normal file
13
test/test_utils/TestCase.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class TestCase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TestCase(){};
|
||||||
|
virtual ~TestCase() = default;
|
||||||
|
TestCase(const TestCase&) = delete;
|
||||||
|
virtual bool Run() {return false;};
|
||||||
|
};
|
||||||
|
using TestCasePtr = std::unique_ptr<TestCase>;
|
26
test/test_utils/TestCaseRunner.cpp
Normal file
26
test/test_utils/TestCaseRunner.cpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#include "TestCaseRunner.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
18
test/test_utils/TestCaseRunner.h
Normal file
18
test/test_utils/TestCaseRunner.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TestCase.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class TestCaseRunner
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void AddTestCase(const std::string& label, TestCasePtr testCase);
|
||||||
|
|
||||||
|
bool Run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
using TestInstance = std::pair<std::string, TestCasePtr>;
|
||||||
|
std::vector<TestInstance> mCases;
|
||||||
|
};
|
Loading…
Reference in a new issue