--- /dev/null
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <ast/composition.hpp>
+#include <exceptions/messageexception.hpp>
+#include <ir2midi/context.hpp>
+#include <message/message.hpp>
+
+namespace YAMML
+{
+
+namespace IR2MIDI
+{
+
+class ICommandProcessor
+{
+public:
+ virtual ~ICommandProcessor() = default;
+
+ virtual void Process(const AST::Command& ast) = 0;
+
+protected:
+ virtual IIR2MIDICompiler* GetCompiler() = 0;
+
+ void ThrowMessage(Message::MessageID id, const AST::SourceLocation& location, const std::vector<std::string>& args)
+ {
+ throw Exceptions::MessageException(
+ Message::MessageItem{
+ Message::MessageKind::Error,
+ id,
+ GetCompiler()->GetSourceName(),
+ location,
+ args
+ }
+ );
+ }
+};
+
+} // namespace IR2MIDI
+
+} // namespace YAMML
#pragma once
+#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <ast/composition.hpp>
#include <compiler/base.hpp>
#include <ir/module.hpp>
+#include <ir2midi/command.hpp>
#include <ir2midi/context.hpp>
#include <midi/file.hpp>
public:
explicit IR2MIDICompiler(const IR::Module& ir) : m_IR(ir)
{
+ InitializeCommandProcessors();
}
template<typename T>
IR2MIDICompiler(const IR::Module& ir, T func) : CompilerBase(func), m_IR(ir)
{
+ InitializeCommandProcessors();
}
+ IR2MIDICompiler(const IR2MIDICompiler&) = delete;
+ IR2MIDICompiler& operator=(const IR2MIDICompiler&) = delete;
+
virtual ~IR2MIDICompiler() = default;
bool Compile(const std::string& entryPoint);
virtual TrackCompilerContext& GetTrackContext(int trackNumber) override;
private:
+ void InitializeCommandProcessors();
bool CompileTrackBlock(const std::string& trackBlockName);
void CompileBlock(int trackNumber, IR::BlockReference blockRef);
void Finalize();
IR::Module m_IR;
MIDI::MIDIFile m_MIDI;
std::vector<TrackCompilerContext> m_Contexts;
+ std::unordered_map<std::string, std::unique_ptr<ICommandProcessor>> m_CommandProcessors;
};
} // namespace IR2MIDI
WrongNumberOfCommandArguments,
WrongTypeOfCommandArgument,
- InvalidTempo
+ InvalidTempo,
+ InvalidProgram
};
} // namespace Message
--- /dev/null
+
+#pragma once
+
+namespace YAMML
+{
+
+namespace MIDI
+{
+
+constexpr int TickPerQuarter = 960;
+constexpr int TrackNumberLimit = 16;
+constexpr int TrackNumberSafeLimit = 256;
+
+} // namespace MIDI
+
+} // namespace YAMML
#include <exceptions/messageexception.hpp>
#include <message/message.hpp>
+#include <midi/limits.hpp>
#include "composition2ir.hpp"
#include "containerutil.hpp"
namespace AST2IR
{
-constexpr int TrackNumberLimit = 16;
-
Composition2IRCompiler::Composition2IRCompiler(Compiler::CompilerBase& parentCompiler, IR::Module& ir)
: NestedCompilerBase(parentCompiler), m_IR(ir)
{
IR::Track Composition2IRCompiler::Compile(const AST::TrackBlock& ast)
{
- if (!(0 <= ast.TrackNumber && ast.TrackNumber < TrackNumberLimit))
+ if (!(0 <= ast.TrackNumber && ast.TrackNumber < MIDI::TrackNumberLimit))
{
throw Exceptions::MessageException(
Message::MessageItem{
Message::MessageID::TrackNumberIsOutOfPreferredRange,
m_IR.Name,
ast.Location,
- {std::to_string(ast.TrackNumber), std::to_string(TrackNumberLimit)}
+ {std::to_string(ast.TrackNumber), std::to_string(MIDI::TrackNumberLimit)}
}
);
}
#include <functional>
#include <iterator>
#include <string>
+#include <typeinfo>
#include <utility>
#include <vector>
#include <deque>
#include <exception>
#include <string>
+#include <typeinfo>
#include <vector>
#include <boost/optional.hpp>
#include <message/message.hpp>
#include <ir/block.hpp>
#include <ir/module.hpp>
+#include <midi/limits.hpp>
#include "containerutil.hpp"
#include "phrase2ir.hpp"
namespace AST2IR
{
-constexpr int TickPerQuarter = 960;
-
class DurationCalculator final : public boost::static_visitor<int>
{
public:
if (ast.Modifier.value().type() == typeid(AST::SimpleDurationModifierDots))
{
long pow2 = std::lround(std::pow(2, boost::get<AST::SimpleDurationModifierDots>(ast.Modifier.value()).Count));
- return TickPerQuarter * 4 / (ast.Base.Number / 2) - TickPerQuarter * 4 / (ast.Base.Number * pow2);
+ return MIDI::TickPerQuarter * 4 / (ast.Base.Number / 2) - MIDI::TickPerQuarter * 4 / (ast.Base.Number * pow2);
}
else
{
- return TickPerQuarter * 4 / (ast.Base.Number / 2) / boost::get<AST::SimpleDurationModifier>(ast.Modifier.value()).Number;
+ return MIDI::TickPerQuarter * 4 / (ast.Base.Number / 2) / boost::get<AST::SimpleDurationModifier>(ast.Modifier.value()).Number;
}
}
else
{
- return TickPerQuarter * 4 / ast.Base.Number;
+ return MIDI::TickPerQuarter * 4 / ast.Base.Number;
}
}
std::vector<IR::Block::EventType> Phrase2IRCompiler::operator()(const AST::NoteSequenceStatement& ast)
{
- m_DefaultDuration = TickPerQuarter;
+ m_DefaultDuration = MIDI::TickPerQuarter;
m_DefaultOctave = 5;
if (ast.Attributes.empty())
{Message::MessageID::WrongNumberOfCommandArguments, "wrong number of arguments passed to command '{0}'; {2} expected, {1} found"},
{Message::MessageID::WrongTypeOfCommandArgument, "command argument {1} has a wrong type; expecting '{2}' here"},
- {Message::MessageID::InvalidTempo, "invalid tempo value '{0}'"}
+ {Message::MessageID::InvalidTempo, "invalid tempo value '{0}'"},
+ {Message::MessageID::InvalidProgram, "invalid program name"}
},
m_pStdErrWriter{pStdErrWriter}
{
set(IR2MIDIHeaders
+ ../../include/ir2midi/command.hpp
../../include/ir2midi/context.hpp
../../include/ir2midi/ir2midi.hpp
+ command_program.hpp
command_tempo.hpp
)
set(IR2MIDISources
+ command_program.cpp
command_tempo.cpp
context.cpp
ir2midi.cpp
--- /dev/null
+
+#include <memory>
+#include <string>
+#include <typeinfo>
+
+#include <boost/variant.hpp>
+
+#include <exceptions/invalidarg.hpp>
+#include <ir2midi/command.hpp>
+#include <message/id.hpp>
+#include <midi/event.hpp>
+#include <midi/limits.hpp>
+
+#include "command_program.hpp"
+
+namespace YAMML
+{
+
+namespace IR2MIDI
+{
+
+class ProgramCommandProcessor final : public ICommandProcessor, public boost::static_visitor<int>
+{
+public:
+ explicit ProgramCommandProcessor(IIR2MIDICompiler* pCompiler) : m_pCompiler(pCompiler)
+ {
+ }
+
+ virtual ~ProgramCommandProcessor() = default;
+
+ virtual IIR2MIDICompiler* GetCompiler() override
+ {
+ return m_pCompiler;
+ }
+
+ virtual void Process(const AST::Command& ast) override
+ {
+ ValidateArguments(ast);
+
+ auto channel = boost::get<long>(ast.Arguments[0].Value);
+
+ try
+ {
+ GetCompiler()->GetTrackContext(channel).PushEvent(0, MIDI::ProgramChange{channel, ast.Arguments[1].Value.apply_visitor(*this)});
+ }
+ catch (const Exceptions::InvalidArgumentException&)
+ {
+ ThrowMessage(
+ Message::MessageID::InvalidProgram,
+ ast.Location,
+ {}
+ );
+ }
+ }
+
+ void ValidateArguments(const AST::Command& ast)
+ {
+ if (ast.Arguments.size() != 2)
+ {
+ ThrowMessage(
+ Message::MessageID::WrongNumberOfCommandArguments,
+ ast.Location,
+ {"program", std::to_string(ast.Arguments.size()), "2"}
+ );
+ }
+
+ if (ast.Arguments[0].Value.type() != typeid(long))
+ {
+ ThrowMessage(
+ Message::MessageID::WrongTypeOfCommandArgument,
+ ast.Location,
+ {"program", "1", "int"}
+ );
+ }
+
+ if ((ast.Arguments[1].Value.type() != typeid(long)) && (ast.Arguments[1].Value.type() != typeid(std::string)))
+ {
+ ThrowMessage(
+ Message::MessageID::WrongTypeOfCommandArgument,
+ ast.Location,
+ {"program", "2", "int/string"}
+ );
+ }
+
+ auto channel = boost::get<long>(ast.Arguments[0].Value);
+
+ if (!(0 <= channel && channel < MIDI::TrackNumberLimit))
+ {
+ ThrowMessage(
+ Message::MessageID::TrackNumberIsOutOfPreferredRange,
+ ast.Location,
+ {std::to_string(channel), std::to_string(MIDI::TrackNumberLimit)}
+ );
+ }
+ }
+
+ int operator()(const long& n)
+ {
+ if ((0 <= n) && (n < 128))
+ {
+ return static_cast<int>(n);
+ }
+ else
+ {
+ throw Exceptions::InvalidArgumentException("IR2MIDI::ProgramCommandProcessor::operator()(const long&)");
+ }
+ }
+
+ int operator()(const double&)
+ {
+ throw Exceptions::InvalidArgumentException("IR2MIDI::ProgramCommandProcessor::operator()(const double&)");
+ }
+
+ int operator()(const std::string&)
+ {
+ // TODO
+ throw Exceptions::InvalidArgumentException("IR2MIDI::ProgramCommandProcessor::operator()(const std::string&)");
+ }
+
+private:
+ IIR2MIDICompiler* m_pCompiler;
+};
+
+std::unique_ptr<ICommandProcessor> CreateProgramCommandProcessor(IIR2MIDICompiler* pCompiler)
+{
+ return std::make_unique<ProgramCommandProcessor>(pCompiler);
+}
+
+} // namespace IR2MIDI
+
+} // namespace YAMML
--- /dev/null
+
+#pragma once
+
+#include <memory>
+
+#include <ir2midi/command.hpp>
+#include <ir2midi/context.hpp>
+
+namespace YAMML
+{
+
+namespace IR2MIDI
+{
+
+std::unique_ptr<ICommandProcessor> CreateProgramCommandProcessor(IIR2MIDICompiler* pCompiler);
+
+} // namespace IR2MIDI
+
+} // namespace YAMML
-#include <boost/variant.hpp>
+#include <memory>
+#include <string>
+#include <typeinfo>
-#include "command_tempo.hpp"
+#include <boost/variant.hpp>
+#include <ir2midi/command.hpp>
+#include <message/id.hpp>
#include <midi/event.hpp>
+#include "command_tempo.hpp"
+
namespace YAMML
{
namespace IR2MIDI
{
-std::vector<Message::MessageItem> ProcessTempo(IIR2MIDICompiler* pCompiler, const AST::Command& ast)
+class TempoCommandProcessor final : public ICommandProcessor
{
- std::vector<Message::MessageItem> messages;
+public:
+ explicit TempoCommandProcessor(IIR2MIDICompiler* pCompiler) : m_pCompiler(pCompiler)
+ {
+ }
- if (ast.Arguments.size() != 1)
+ virtual ~TempoCommandProcessor() = default;
+
+ virtual IIR2MIDICompiler* GetCompiler() override
{
- messages.push_back(
- Message::MessageItem{
- Message::MessageKind::Error,
- Message::MessageID::WrongNumberOfCommandArguments,
- pCompiler->GetSourceName(),
- ast.Location,
- {"tempo", std::to_string(ast.Arguments.size()), "1"}
+ return m_pCompiler;
+ }
+
+ virtual void Process(const AST::Command& ast) override
+ {
+ ValidateArguments(ast);
+
+ unsigned int usecPerQuater = 60 * 1'000'000 / boost::get<long>(ast.Arguments[0].Value);
+
+ GetCompiler()->GetTrackContext(0).PushEvent(
+ 0,
+ MIDI::MetaEvent{
+ MIDI::MetaEventKind::SetTempo,
+ {
+ static_cast<std::uint8_t>((usecPerQuater & 0xFF0000) >> 16),
+ static_cast<std::uint8_t>((usecPerQuater & 0xFF00) >> 8),
+ static_cast<std::uint8_t>(usecPerQuater & 0xFF)
+ }
}
);
-
- return messages;
}
- if (ast.Arguments[0].Value.type() != typeid(long))
+ void ValidateArguments(const AST::Command& ast)
{
- messages.push_back(
- Message::MessageItem{
- Message::MessageKind::Error,
+ if (ast.Arguments.size() != 1)
+ {
+ ThrowMessage(
+ Message::MessageID::WrongNumberOfCommandArguments,
+ ast.Location,
+ {"tempo", std::to_string(ast.Arguments.size()), "1"}
+ );
+ }
+
+ if (ast.Arguments[0].Value.type() != typeid(long))
+ {
+ ThrowMessage(
Message::MessageID::WrongTypeOfCommandArgument,
- pCompiler->GetSourceName(),
ast.Location,
{"tempo", "1", "int"}
- }
- );
-
- return messages;
- }
+ );
+ }
- auto tempo = boost::get<long>(ast.Arguments[0].Value);
+ auto tempo = boost::get<long>(ast.Arguments[0].Value);
- if (tempo <= 0)
- {
- messages.push_back(
- Message::MessageItem{
- Message::MessageKind::Error,
+ if (tempo <= 0)
+ {
+ ThrowMessage(
Message::MessageID::InvalidTempo,
- pCompiler->GetSourceName(),
ast.Location,
{std::to_string(tempo)}
- }
- );
-
- return messages;
+ );
+ }
}
- unsigned int usecPerQuater = 60 * 1'000'000 / tempo;
+private:
+ IIR2MIDICompiler* m_pCompiler;
+};
- pCompiler->GetTrackContext(0).PushEvent(
- 0,
- MIDI::MetaEvent{
- MIDI::MetaEventKind::SetTempo,
- {
- static_cast<std::uint8_t>((usecPerQuater & 0xFF0000) >> 16),
- static_cast<std::uint8_t>((usecPerQuater & 0xFF00) >> 8),
- static_cast<std::uint8_t>(usecPerQuater & 0xFF)
- }
- }
- );
-
- return messages;
+std::unique_ptr<ICommandProcessor> CreateTempoCommandProcessor(IIR2MIDICompiler* pCompiler)
+{
+ return std::make_unique<TempoCommandProcessor>(pCompiler);
}
} // namespace IR2MIDI
#pragma once
-#include <string>
-#include <vector>
+#include <memory>
-#include <ast/composition.hpp>
+#include <ir2midi/command.hpp>
#include <ir2midi/context.hpp>
-#include <message/message.hpp>
namespace YAMML
{
namespace IR2MIDI
{
-std::vector<Message::MessageItem> ProcessTempo(IIR2MIDICompiler* pCompiler, const AST::Command& ast);
+std::unique_ptr<ICommandProcessor> CreateTempoCommandProcessor(IIR2MIDICompiler* pCompiler);
} // namespace IR2MIDI
#include <exceptions/messageexception.hpp>
#include <ir2midi/ir2midi.hpp>
#include <message/message.hpp>
+#include <midi/limits.hpp>
+#include "command_program.hpp"
#include "command_tempo.hpp"
namespace YAMML
namespace IR2MIDI
{
-constexpr int TrackNumberSafeLimit = 256;
-
class EventConverter final : public boost::static_visitor<>
{
public:
void IR2MIDICompiler::operator()(const AST::Command& ast)
{
- if (ast.Name == "tempo")
- {
- AddMessages(ProcessTempo(this, ast));
- }
- else
+ auto itProc = m_CommandProcessors.find(ast.Name);
+
+ if (itProc == m_CommandProcessors.end())
{
AddMessage(
Message::MessageItem{
}
);
}
+ else
+ {
+ try
+ {
+ itProc->second->Process(ast);
+ }
+ catch (const Exceptions::MessageException& e)
+ {
+ AddMessage(e.Item);
+ }
+ }
}
void IR2MIDICompiler::operator()(int trackNumber, const IR::Event& ev)
CompileBlock(trackNumber, blockRef);
}
+void IR2MIDICompiler::InitializeCommandProcessors()
+{
+ m_CommandProcessors["program"] = CreateProgramCommandProcessor(this);
+ m_CommandProcessors["tempo"] = CreateTempoCommandProcessor(this);
+}
+
bool IR2MIDICompiler::CompileTrackBlock(const std::string& trackBlockName)
{
auto itTrack = m_IR.TrackBlockNameMap.find(trackBlockName);
void IR2MIDICompiler::EnsureTrackInitialized(int number)
{
- if (!(0 <= number && number < TrackNumberSafeLimit))
+ if (!(0 <= number && number < MIDI::TrackNumberSafeLimit))
{
throw Exceptions::MessageException(
Message::MessageItem{
Message::MessageID::TrackNumberIsOutOfSafeRange,
m_IR.Name,
{0, 0},
- {std::to_string(number), std::to_string(TrackNumberSafeLimit)}
+ {std::to_string(number), std::to_string(MIDI::TrackNumberSafeLimit)}
}
);
}
#include <algorithm>
#include <exception>
+#include <memory>
#include <string>
+#include <typeinfo>
+#include <unordered_map>
#include <vector>
+#include <boost/optional.hpp>
#include <boost/variant.hpp>