✨ basic sine command and out directive.
parent
2c8df5e493
commit
8959950c2c
4
Makefile
4
Makefile
|
@ -17,7 +17,9 @@ install: tests
|
|||
|
||||
check:
|
||||
@cppcheck --language=c++ --enable=all -q lib src tests \
|
||||
--suppress=missingIncludeSystem
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppress=missingInclude \
|
||||
--suppress=unmatchedSuppression
|
||||
|
||||
doc:
|
||||
mkdir -p build/doc/doxygen
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
PROG ::= INSTR*
|
||||
INSTR ::= DIR | CMD
|
||||
DIR ::= dir_ident CMD
|
||||
CMD ::= osquare ident ARG* csquare
|
||||
ARG ::= LITERAL | CMD
|
||||
LITERAL ::= num
|
|
@ -0,0 +1,24 @@
|
|||
==========
|
||||
Directives
|
||||
==========
|
||||
|
||||
A directive is a way to specify behaviors that are not directly
|
||||
related to signal manipulations.
|
||||
|
||||
@out
|
||||
----
|
||||
|
||||
The ``@out`` signal uses the default audio output device to play a
|
||||
given signal. For instance:
|
||||
|
||||
.. code-block::
|
||||
|
||||
# doesnt make sound
|
||||
[sine 230 1]
|
||||
|
||||
does nothing. To hear our sine we have to use ``@out``.
|
||||
|
||||
.. code-block::
|
||||
|
||||
# make sound
|
||||
@out [sine 230 1]
|
|
@ -1,7 +1,12 @@
|
|||
Sound design with MuzGen
|
||||
------------------------
|
||||
|
||||
.. warning::
|
||||
MuzGen is not stable and has probably a lot's of bugs.
|
||||
|
||||
.. toctree::
|
||||
|
||||
intro
|
||||
install
|
||||
quickstart
|
||||
signals
|
||||
directives
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Installation
|
||||
------------
|
||||
The simplest way to install MuzGen is by using the Makefile at the project root directory.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
Introduction
|
||||
------------
|
||||
|
||||
MuzGen is a programming language for sound designers.
|
|
@ -0,0 +1,27 @@
|
|||
Quick Start
|
||||
===========
|
||||
|
||||
Hello ! So you want to make some sound design ? MuzGen allows you to
|
||||
generate some sound using a language named **MuzScript**. Ok, let's
|
||||
start with a simple example.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@out [sine 440 1]
|
||||
|
||||
Here, ``@out`` is a *directive* and ``sine`` is a *command*.
|
||||
|
||||
Let start with commands. A command is kind of a function. It has a
|
||||
name followed by its parameters if any.
|
||||
|
||||
Here, the command ``sine`` takes two parameters: a frequency and an
|
||||
amplitude. Like its name suggests, ``sine`` is a signal composed of
|
||||
one sine wave with the given frequency and amplitude.
|
||||
|
||||
.. note::
|
||||
The parameters ``440`` and ``1`` are not simple numbers. In fact,
|
||||
their are signals too ! We call them constant signal.
|
||||
|
||||
In order to hear our sine wave, we have to specify how we want it to
|
||||
be played. That is what the directive ``@out`` does. It will play the
|
||||
followed signal on the default audio device.
|
|
@ -0,0 +1,41 @@
|
|||
=======
|
||||
Signals
|
||||
=======
|
||||
|
||||
Signal Types
|
||||
------------
|
||||
|
||||
MuzGen use different kind of signals for sound design.
|
||||
|
||||
|
||||
Constant
|
||||
^^^^^^^^
|
||||
|
||||
The simplest signal is the **constant signal**. It
|
||||
returns the same frame everytime. It is used mostly as argument of
|
||||
commands. In MuzScript, every numbers are constant signals.
|
||||
|
||||
.. code-block::
|
||||
|
||||
# constant signal
|
||||
142
|
||||
|
||||
|
||||
Sine
|
||||
^^^^
|
||||
|
||||
The sine wave signal is the fundamental signal in sound theory.
|
||||
It takes two arguments: a frequency and an amplitude.
|
||||
Mathematically we can define our sine using the following formula:
|
||||
|
||||
.. math::
|
||||
|
||||
amplitude * sin(2 * pi * frequency / samplerate * time)
|
||||
|
||||
|
||||
To generate a sine, we can use the ``sine`` command.
|
||||
|
||||
.. code-block::
|
||||
|
||||
# sine signal with a frequency of 440 and an amplitude of 1
|
||||
[sine 440 1]
|
|
@ -0,0 +1,17 @@
|
|||
#include "AudioConf.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ AudioConf::AudioConf(int channels,
|
||||
unsigned long frames_per_buffer,
|
||||
unsigned samplerate)
|
||||
: m_channels { channels }
|
||||
, m_frames_per_buffer { frames_per_buffer }
|
||||
, m_samplerate { samplerate }
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ AudioConf::~AudioConf()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef muz_AUDIOCONF_HPP
|
||||
#define muz_AUDIOCONF_HPP
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/**
|
||||
* A configuration of an audio device.
|
||||
* @param channels number of audio channel (2 by default).
|
||||
* @param frames_per_buffer number of frames per buffer (256 by default).
|
||||
* @param samplerate the audio samplerate, (44100 by default).
|
||||
* @see AudioEngine
|
||||
**/
|
||||
class AudioConf
|
||||
{
|
||||
public:
|
||||
explicit AudioConf(int channels = 2,
|
||||
unsigned long frames_per_buffer = 256,
|
||||
unsigned samplerate = 44100);
|
||||
|
||||
virtual ~AudioConf();
|
||||
|
||||
int channels() const { return m_channels; }
|
||||
|
||||
unsigned long frames_per_buffer() const
|
||||
{ return m_frames_per_buffer; }
|
||||
|
||||
unsigned samplerate() const { return m_samplerate; }
|
||||
|
||||
private:
|
||||
int m_channels = 0;
|
||||
unsigned long m_frames_per_buffer;
|
||||
unsigned m_samplerate;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,116 @@
|
|||
#include "AudioEngine.hpp"
|
||||
#include "Signal.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ AudioEngine::AudioEngine(AudioConf const& conf)
|
||||
: m_conf { conf }
|
||||
{
|
||||
check_error(Pa_Initialize());
|
||||
}
|
||||
|
||||
/*virtual*/ AudioEngine::~AudioEngine()
|
||||
{
|
||||
check_error(Pa_Terminate());
|
||||
}
|
||||
|
||||
void AudioEngine::init()
|
||||
{
|
||||
PaError err = Pa_OpenDefaultStream(&m_stream,
|
||||
0 /*input*/,
|
||||
m_conf.channels() /*output*/,
|
||||
paFloat32,
|
||||
m_conf.samplerate(),
|
||||
m_conf.frames_per_buffer() ,
|
||||
&AudioEngine::callback,
|
||||
this);
|
||||
check_error(err);
|
||||
}
|
||||
|
||||
void AudioEngine::run()
|
||||
{
|
||||
if (!m_stream)
|
||||
{
|
||||
throw audio_error {"audio engine not initialized"};
|
||||
}
|
||||
|
||||
Pa_StartStream(m_stream);
|
||||
|
||||
while (!m_sig_queue.empty())
|
||||
{
|
||||
std::cin.get();
|
||||
pop_signal();
|
||||
}
|
||||
|
||||
Pa_StopStream(m_stream);
|
||||
}
|
||||
|
||||
void AudioEngine::push_signal(std::unique_ptr<Signal> signal)
|
||||
{
|
||||
std::lock_guard<std::mutex> mtx(m_sig_mtx);
|
||||
|
||||
m_sig_queue.push_back(std::move(signal));
|
||||
}
|
||||
|
||||
void AudioEngine::pop_signal()
|
||||
{
|
||||
std::lock_guard<std::mutex> mtx(m_sig_mtx);
|
||||
|
||||
if (!m_sig_queue.empty())
|
||||
{
|
||||
m_sig_queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::check_error(PaError err)
|
||||
{
|
||||
if (err != paNoError)
|
||||
{
|
||||
std::string msg = Pa_GetErrorText(err);
|
||||
throw audio_error {"cannot initalize portaudio: " + msg};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> AudioEngine::next()
|
||||
{
|
||||
std::lock_guard<std::mutex> mtx(m_sig_mtx);
|
||||
|
||||
if (m_sig_queue.empty())
|
||||
{
|
||||
return {0.0f, 0.0f};
|
||||
}
|
||||
|
||||
auto frame = m_sig_queue.back()->next();
|
||||
|
||||
if (frame.empty())
|
||||
{
|
||||
pop_signal();
|
||||
return next();
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/*static*/ int AudioEngine::callback(void const*, void* output,
|
||||
unsigned long frames_per_buffer,
|
||||
PaStreamCallbackTimeInfo const*,
|
||||
PaStreamCallbackFlags,
|
||||
void* data)
|
||||
{
|
||||
AudioEngine* engine = static_cast<AudioEngine*>(data);
|
||||
float* out = static_cast<float*>(output);
|
||||
size_t k = 0;
|
||||
|
||||
for (size_t i=0; i<frames_per_buffer; i++)
|
||||
{
|
||||
auto frame = engine->next();
|
||||
|
||||
for (float val: frame)
|
||||
{
|
||||
out[k++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef muz_AUDIOENGINE_HPP
|
||||
#define muz_AUDIOENGINE_HPP
|
||||
|
||||
#include <portaudio.h>
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "AudioConf.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
class Signal;
|
||||
|
||||
MUZ_ERROR(audio_error);
|
||||
|
||||
/**
|
||||
* Make sound from signals.
|
||||
* @see Signal
|
||||
**/
|
||||
class AudioEngine
|
||||
{
|
||||
public:
|
||||
explicit AudioEngine(AudioConf const& conf);
|
||||
virtual ~AudioEngine();
|
||||
|
||||
/**
|
||||
* Initialize the engine opening audio device.
|
||||
**/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Run the engine, making sound.
|
||||
**/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Push a new signal in the signals queue.
|
||||
*/
|
||||
void push_signal(std::unique_ptr<Signal> signal);
|
||||
|
||||
/**
|
||||
* Pop the current signal or does nothing if the queue is empty.
|
||||
**/
|
||||
void pop_signal();
|
||||
|
||||
private:
|
||||
AudioConf m_conf;
|
||||
PaStream* m_stream = nullptr;
|
||||
std::vector<std::unique_ptr<Signal>> m_sig_queue;
|
||||
std::mutex m_sig_mtx;
|
||||
|
||||
/**
|
||||
* Throws an audio_error exception if err is an error.
|
||||
**/
|
||||
void check_error(PaError err);
|
||||
|
||||
/**
|
||||
* Gives the next sample of the current signal.
|
||||
* @see Signal
|
||||
**/
|
||||
std::vector<float> next();
|
||||
|
||||
/**
|
||||
* Portaudio audio callback.
|
||||
**/
|
||||
static int callback(void const* input, void* output,
|
||||
unsigned long frames_per_buffer,
|
||||
PaStreamCallbackTimeInfo const* time,
|
||||
PaStreamCallbackFlags flags,
|
||||
void* data);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -8,13 +8,36 @@ configure_file(
|
|||
)
|
||||
|
||||
add_library(muz-lib OBJECT
|
||||
# Audio
|
||||
Signal.cpp
|
||||
AudioEngine.cpp
|
||||
AudioConf.cpp
|
||||
Constant.cpp
|
||||
Sine.cpp
|
||||
|
||||
# Language
|
||||
Node.cpp
|
||||
Lexer.cpp
|
||||
Parser.cpp
|
||||
Compiler.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET muz-lib
|
||||
PROPERTY CXX_STANDARD 17
|
||||
)
|
||||
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(PortAudio portaudio-2.0 IMPORTED_TARGET REQUIRED)
|
||||
|
||||
target_compile_options(muz-lib
|
||||
PUBLIC -Wall -Wextra
|
||||
)
|
||||
|
||||
target_link_libraries(muz-lib
|
||||
PUBLIC PkgConfig::PortAudio
|
||||
)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
target_compile_options(muz-lib
|
||||
PRIVATE --coverage
|
||||
|
@ -32,6 +55,7 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
|||
COMMAND $<TARGET_FILE:muz-test>
|
||||
COMMAND ${LCOV_PATH} -d . --capture -o cov.info
|
||||
COMMAND ${LCOV_PATH} -r cov.info '/usr/include/*' -o cov.info
|
||||
COMMAND ${LCOV_PATH} -r cov.info '*.hpp' -o cov.info
|
||||
COMMAND ${GENHTML_PATH} --legend -o cov_html cov.info
|
||||
)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
#include "Compiler.hpp"
|
||||
#include "Constant.hpp"
|
||||
#include "Sine.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Compiler::Compiler(AudioConf const& conf)
|
||||
: m_conf { conf }
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Compiler::~Compiler()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Signal>>
|
||||
Compiler::compile(std::shared_ptr<Node> node)
|
||||
{
|
||||
compile_node(node);
|
||||
|
||||
return std::move(m_outputs);
|
||||
}
|
||||
|
||||
void Compiler::compile_node(std::shared_ptr<Node> node)
|
||||
{
|
||||
switch (node->type())
|
||||
{
|
||||
case NODE_NUM: {
|
||||
float value = std::stof(node->value());
|
||||
push(std::make_unique<Constant>(m_conf, value));
|
||||
} break;
|
||||
|
||||
case NODE_CMD: {
|
||||
std::string name = node->child(0)->value();
|
||||
|
||||
for (size_t i=1; i<node->size(); i++)
|
||||
{
|
||||
compile_node(node->child(i));
|
||||
}
|
||||
|
||||
if (name == "sine")
|
||||
{
|
||||
check_cmd_arity(*node, 2);
|
||||
|
||||
auto one = pop();
|
||||
auto signal = std::make_unique<Sine>(m_conf,
|
||||
std::move(pop()),
|
||||
std::move(one));
|
||||
push(std::move(signal));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw compile_error {
|
||||
std::string()
|
||||
+ "cannot compile unknown command '" + name + "'."
|
||||
};
|
||||
}
|
||||
} break;
|
||||
|
||||
case NODE_DIR: {
|
||||
std::string name = node->child(0)->value();
|
||||
|
||||
if (name == "@out")
|
||||
{
|
||||
compile_node(node->child(1));
|
||||
m_outputs.push_back(std::move(pop()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw compile_error {
|
||||
std::string()
|
||||
+ "cannot compile unknown directive '" + name + "'."
|
||||
};
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
default:
|
||||
for (size_t i=0; i<node->size(); i++)
|
||||
{
|
||||
compile_node(node->child(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Compiler::push(std::unique_ptr<Signal> signal)
|
||||
{
|
||||
m_signals.push_back(std::move(signal));
|
||||
}
|
||||
|
||||
std::unique_ptr<Signal> Compiler::pop()
|
||||
{
|
||||
auto signal = std::move(m_signals.back());
|
||||
m_signals.pop_back();
|
||||
return signal;
|
||||
}
|
||||
|
||||
void Compiler::check_cmd_arity(Node const& node, int arity)
|
||||
{
|
||||
if (node.size() - 1 != arity)
|
||||
{
|
||||
throw compile_error {
|
||||
std::string()
|
||||
+ "arity mismatch for '"
|
||||
+ node.child(0)->value()
|
||||
+ "': expected <"
|
||||
+ std::to_string(arity)
|
||||
+ ">, got <"
|
||||
+ std::to_string(node.size() - 1)
|
||||
+ ">."};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef muz_COMPILER_HPP
|
||||
#define muz_COMPILER_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "Node.hpp"
|
||||
#include "AudioConf.hpp"
|
||||
#include "Signal.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
MUZ_ERROR(compile_error);
|
||||
|
||||
/**
|
||||
* Create a signal given an abstract syntax tree.
|
||||
* @see Signal
|
||||
* @see Parser
|
||||
* @see Node
|
||||
**/
|
||||
class Compiler
|
||||
{
|
||||
public:
|
||||
explicit Compiler(AudioConf const& conf);
|
||||
virtual ~Compiler();
|
||||
|
||||
std::vector<std::unique_ptr<Signal>> compile(std::shared_ptr<Node> node);
|
||||
void compile_node(std::shared_ptr<Node> node);
|
||||
|
||||
private:
|
||||
AudioConf const& m_conf;
|
||||
std::vector<std::unique_ptr<Signal>> m_signals;
|
||||
std::vector<std::unique_ptr<Signal>> m_outputs;
|
||||
|
||||
// signal stack
|
||||
// ------------
|
||||
void push(std::unique_ptr<Signal> signal);
|
||||
std::unique_ptr<Signal> pop();
|
||||
|
||||
void check_cmd_arity(Node const& node, int arity);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
#include "Constant.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Constant::Constant(AudioConf const& conf, float value)
|
||||
: m_conf { conf }
|
||||
, m_value { value }
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Constant::~Constant()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<float> Constant::next() /*override*/
|
||||
{
|
||||
std::vector<float> out;
|
||||
|
||||
for (int i=0; i<m_conf.channels(); i++)
|
||||
{
|
||||
out.push_back(m_value);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef muz_CONSTANT_HPP
|
||||
#define muz_CONSTANT_HPP
|
||||
|
||||
#include "Signal.hpp"
|
||||
#include "AudioConf.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/**
|
||||
* A constant signal mainly used as input for more complex signals.
|
||||
**/
|
||||
class Constant: public Signal
|
||||
{
|
||||
public:
|
||||
explicit Constant(AudioConf const& conf, float value=0.0f);
|
||||
virtual ~Constant();
|
||||
|
||||
std::vector<float> next() override;
|
||||
private:
|
||||
AudioConf m_conf;
|
||||
float m_value;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,215 @@
|
|||
#include "Lexer.hpp"
|
||||
#include "Node.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Lexer::Lexer()
|
||||
: m_seps {
|
||||
{'[', ']'}
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Lexer::~Lexer()
|
||||
{
|
||||
}
|
||||
|
||||
void Lexer::scan(std::string const& source)
|
||||
{
|
||||
m_source = source;
|
||||
m_cursor = 0;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Node>> Lexer::all()
|
||||
{
|
||||
std::vector<std::shared_ptr<Node>> res;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto tok = next();
|
||||
|
||||
if (tok)
|
||||
{
|
||||
res.push_back(tok);
|
||||
}
|
||||
else
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Lexer::next()
|
||||
{
|
||||
// consume spaces
|
||||
while (m_cursor < m_source.size()
|
||||
&& isspace(m_source[m_cursor]))
|
||||
{
|
||||
m_cursor++;
|
||||
}
|
||||
|
||||
// check word
|
||||
auto tok_info = next_word();
|
||||
|
||||
auto try_node = [&](NodeType type,
|
||||
bool (Lexer::*fn)(std::string const&) const)
|
||||
-> std::shared_ptr<Node>
|
||||
{
|
||||
auto f = std::bind(fn, this, std::placeholders::_1);
|
||||
|
||||
if (tok_info && f(tok_info->value))
|
||||
{
|
||||
auto node = std::make_shared<Node>(type, tok_info->value);
|
||||
m_cursor = tok_info->position;
|
||||
return node;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
|
||||
if (tok_info && tok_info->value == "[")
|
||||
{
|
||||
auto node = std::make_shared<Node>(NODE_OSQUARE);
|
||||
m_cursor = tok_info->position;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (tok_info && tok_info->value == "]")
|
||||
{
|
||||
auto node = std::make_shared<Node>(NODE_CSQUARE);
|
||||
m_cursor = tok_info->position;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (auto res = try_node(NODE_NUM, &Lexer::is_num);
|
||||
res)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (auto res = try_node(NODE_IDENT, &Lexer::is_ident);
|
||||
res)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (auto res = try_node(NODE_DIR_IDENT, &Lexer::is_dir_ident);
|
||||
res)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<TokenInfo> Lexer::next_word()
|
||||
{
|
||||
size_t cursor = m_cursor;
|
||||
std::string value;
|
||||
|
||||
// consume spaces
|
||||
while (cursor < m_source.size()
|
||||
&& isspace(m_source[cursor]))
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (is_sep(cursor) && !isspace(m_source[cursor]))
|
||||
{
|
||||
value = std::string(1, m_source[cursor]);
|
||||
cursor++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// read next word
|
||||
while (!is_sep(cursor))
|
||||
{
|
||||
value += m_source[cursor];
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.size() > 0)
|
||||
{
|
||||
return TokenInfo {
|
||||
cursor,
|
||||
NODE_UNDEFINED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Lexer::is_sep(size_t index) const
|
||||
{
|
||||
if (index >= m_source.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isspace(m_source[index]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::any_of(std::begin(m_seps), std::end(m_seps), [&](char c){
|
||||
return c == m_source[index];
|
||||
});
|
||||
}
|
||||
|
||||
bool Lexer::is_num(std::string const& word) const
|
||||
{
|
||||
auto beg = std::begin(word);
|
||||
|
||||
if (word.size() > 0 && word[0] == '-')
|
||||
{
|
||||
beg++;
|
||||
}
|
||||
|
||||
int count_dot = 0;
|
||||
|
||||
return std::all_of(beg, std::end(word), [&](char c){
|
||||
|
||||
if (c == '.')
|
||||
{
|
||||
count_dot++;
|
||||
}
|
||||
|
||||
return isdigit(c) || c == '.';
|
||||
}) && count_dot <= 1;
|
||||
}
|
||||
|
||||
bool Lexer::is_ident(std::string const& word) const
|
||||
{
|
||||
if (word.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (word[0] == '@') { return false; }
|
||||
if (isdigit(word[0])) { return false; }
|
||||
|
||||
return std::all_of(std::begin(word), std::end(word), [&](char c){
|
||||
return isalnum(c) || c == '_';
|
||||
});
|
||||
}
|
||||
|
||||
bool Lexer::is_dir_ident(std::string const& word) const
|
||||
{
|
||||
if (word.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (word[0] != '@') { return false; }
|
||||
|
||||
return std::all_of(std::begin(word), std::end(word), [&](char c){
|
||||
return isalnum(c) || c == '_' || c == '@';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef muz_LEXER_HPP
|
||||
#define muz_LEXER_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "Node.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
struct TokenInfo
|
||||
{
|
||||
size_t position;
|
||||
NodeType type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan a text and gives corresponding tokens.
|
||||
* @see Node
|
||||
**/
|
||||
class Lexer
|
||||
{
|
||||
public:
|
||||
explicit Lexer();
|
||||
virtual ~Lexer();
|
||||
|
||||
void scan(std::string const& source);
|
||||
std::vector<std::shared_ptr<Node>> all();
|
||||
std::shared_ptr<Node> next();
|
||||
|
||||
private:
|
||||
std::string m_source;
|
||||
size_t m_cursor = 0;
|
||||
std::vector<char> m_seps;
|
||||
|
||||
std::optional<TokenInfo> next_word();
|
||||
bool is_sep(size_t index) const;
|
||||
bool is_num(std::string const& word) const;
|
||||
bool is_ident(std::string const& word) const;
|
||||
bool is_dir_ident(std::string const& word) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,56 @@
|
|||
#include "Node.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Node::Node(NodeType type,
|
||||
std::string const& value)
|
||||
: m_type { type }
|
||||
, m_value { value }
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Node::~Node()
|
||||
{
|
||||
}
|
||||
|
||||
void Node::add_child(std::shared_ptr<Node> child)
|
||||
{
|
||||
m_children.push_back(child);
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Node::child(size_t index) const
|
||||
{
|
||||
if (index >= m_children.size())
|
||||
{
|
||||
throw node_error {"cannot get node child: bad index"};
|
||||
}
|
||||
|
||||
return m_children[index];
|
||||
}
|
||||
|
||||
std::string Node::string() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << NodeTypeStr[type()] + strlen("NODE_");
|
||||
|
||||
if (m_value.empty() == false)
|
||||
{
|
||||
ss << "[" << m_value << "]";
|
||||
}
|
||||
|
||||
if (m_children.empty() == false)
|
||||
{
|
||||
std::string sep;
|
||||
|
||||
ss << "(";
|
||||
for (auto& c: m_children)
|
||||
{
|
||||
ss << sep << c->string();
|
||||
sep = ",";
|
||||
}
|
||||
ss << ")";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef muz_NODE_HPP
|
||||
#define muz_NODE_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
|
||||
#define NODE_TYPE(G) \
|
||||
G(NODE_UNDEFINED), \
|
||||
G(NODE_NUM), G(NODE_IDENT), G(NODE_DIR_IDENT), \
|
||||
G(NODE_OSQUARE), G(NODE_CSQUARE), \
|
||||
G(NODE_PROG), G(NODE_DIR), G(NODE_CMD)
|
||||
|
||||
namespace muz
|
||||
{
|
||||
MUZ_ENUM(NodeType, NODE_TYPE);
|
||||
MUZ_ERROR(node_error);
|
||||
|
||||
/**
|
||||
* Represents a node of the abstract syntax tree.
|
||||
**/
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
explicit Node(NodeType type,
|
||||
std::string const& value="");
|
||||
virtual ~Node();
|
||||
|
||||
// properties
|
||||
// ----------
|
||||
inline NodeType type() const { return m_type; }
|
||||
inline std::string value() const { return m_value; }
|
||||
|
||||
// children
|
||||
// --------
|
||||
void add_child(std::shared_ptr<Node> child);
|
||||
std::shared_ptr<Node> child(size_t index) const;
|
||||
inline size_t size() const { return m_children.size(); }
|
||||
|
||||
std::string string() const;
|
||||
|
||||
private:
|
||||
NodeType m_type;
|
||||
std::string m_value;
|
||||
std::vector<std::shared_ptr<Node>> m_children;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,122 @@
|
|||
#include "Parser.hpp"
|
||||
#include "Node.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Parser::Parser()
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Parser::~Parser()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse(Lexer& lexer)
|
||||
{
|
||||
m_tokens = lexer.all();
|
||||
m_cursor = 0;
|
||||
|
||||
return parse_prog();
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::consume(std::optional<NodeType> type)
|
||||
{
|
||||
if (m_cursor >= m_tokens.size())
|
||||
{
|
||||
std::string ty_desired = NodeTypeStr[*type] + strlen("NODE_");
|
||||
throw syntax_error {"unexpected end: expected <"
|
||||
+ ty_desired
|
||||
+ ">, got nothing."};
|
||||
}
|
||||
|
||||
auto node = m_tokens[m_cursor];
|
||||
|
||||
if (type && node->type() != *type)
|
||||
{
|
||||
std::string ty_got = NodeTypeStr[node->type()] + strlen("NODE_");
|
||||
std::string ty_desired = NodeTypeStr[*type] + strlen("NODE_");
|
||||
throw syntax_error {"expected <"
|
||||
+ ty_desired
|
||||
+ ">, got <"
|
||||
+ ty_got + ">."};
|
||||
}
|
||||
|
||||
m_cursor++;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
NodeType Parser::peek(size_t lookahead) const
|
||||
{
|
||||
return m_tokens[m_cursor + lookahead]->type();
|
||||
}
|
||||
|
||||
bool Parser::next_is(NodeType type, size_t lookahead) const
|
||||
{
|
||||
if (m_cursor + lookahead >= m_tokens.size()) { return false; }
|
||||
return peek(lookahead) == type;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_prog()
|
||||
{
|
||||
auto node = std::make_shared<Node>(NODE_PROG);
|
||||
|
||||
while (m_cursor < m_tokens.size())
|
||||
{
|
||||
node->add_child(parse_instr());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_instr()
|
||||
{
|
||||
if (next_is(NODE_DIR_IDENT))
|
||||
{
|
||||
return parse_dir();
|
||||
}
|
||||
|
||||
return parse_cmd();
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_dir()
|
||||
{
|
||||
auto node = std::make_shared<Node>(NODE_DIR);
|
||||
node->add_child(consume(NODE_DIR_IDENT));
|
||||
node->add_child(parse_cmd());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_cmd()
|
||||
{
|
||||
consume(NODE_OSQUARE);
|
||||
|
||||
auto node = std::make_shared<Node>(NODE_CMD);
|
||||
node->add_child(consume(NODE_IDENT));
|
||||
|
||||
while (!next_is(NODE_CSQUARE))
|
||||
{
|
||||
node->add_child(parse_arg());
|
||||
}
|
||||
|
||||
consume(NODE_CSQUARE);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_arg()
|
||||
{
|
||||
if (next_is(NODE_OSQUARE))
|
||||
{
|
||||
return parse_cmd();
|
||||
}
|
||||
|
||||
return parse_literal();
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Parser::parse_literal()
|
||||
{
|
||||
return consume(NODE_NUM);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef muz_PARSER_HPP
|
||||
#define muz_PARSER_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "Lexer.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
MUZ_ERROR(syntax_error);
|
||||
|
||||
/**
|
||||
* Build an AST given a token list.
|
||||
* @see Node
|
||||
* @see Lexer
|
||||
**/
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
explicit Parser();
|
||||
virtual ~Parser();
|
||||
|
||||
std::shared_ptr<Node> parse(Lexer& lexer);
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Node>> m_tokens;
|
||||
size_t m_cursor = 0;
|
||||
|
||||
std::shared_ptr<Node> consume(std::optional<NodeType> type=std::nullopt);
|
||||
NodeType peek(size_t lookahead=0) const;
|
||||
bool next_is(NodeType type, size_t lookahead=0) const;
|
||||
|
||||
std::shared_ptr<Node> parse_prog();
|
||||
std::shared_ptr<Node> parse_instr();
|
||||
std::shared_ptr<Node> parse_dir();
|
||||
std::shared_ptr<Node> parse_cmd();
|
||||
std::shared_ptr<Node> parse_arg();
|
||||
std::shared_ptr<Node> parse_literal();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,14 +1,29 @@
|
|||
#ifndef muz_SIGNAL_HPP
|
||||
#define muz_SIGNAL_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
MUZ_ERROR(signal_error);
|
||||
|
||||
/**
|
||||
* Audio signal interface.
|
||||
* @see Sine
|
||||
* @see Constant
|
||||
**/
|
||||
class Signal
|
||||
{
|
||||
public:
|
||||
explicit Signal();
|
||||
virtual ~Signal();
|
||||
|
||||
/**
|
||||
* Get the next sample.
|
||||
* @return std::vector<float> of size N for a sample of N channels or an empty vector at end.
|
||||
*
|
||||
**/
|
||||
virtual std::vector<float> next() = 0;
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#include "Sine.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/*explicit*/ Sine::Sine(AudioConf const& conf,
|
||||
std::unique_ptr<Signal> freq,
|
||||
std::unique_ptr<Signal> amplitude)
|
||||
: m_conf { conf }
|
||||
, m_freq { std::move(freq) }
|
||||
, m_amplitude { std::move(amplitude) }
|
||||
{
|
||||
for (size_t i=0; i<static_cast<size_t>(m_conf.channels()); i++)
|
||||
{
|
||||
m_phases.push_back(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/*virtual*/ Sine::~Sine()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<float> Sine::next() /*override*/
|
||||
{
|
||||
assert(m_freq);
|
||||
assert(m_amplitude);
|
||||
|
||||
std::vector<float> out;
|
||||
auto freqs = m_freq->next();
|
||||
auto amps = m_amplitude->next();
|
||||
|
||||
if (freqs.size() != amps.size()
|
||||
|| freqs.size() != m_phases.size())
|
||||
{
|
||||
throw signal_error {"cannot generate sine: channel number mismatch"};
|
||||
}
|
||||
|
||||
for (size_t i=0; i<static_cast<size_t>(m_conf.channels()); i++)
|
||||
{
|
||||
float const value = amps[i] * std::sin(m_phases[i]);
|
||||
|
||||
m_phases[i] += 2 * M_PI * freqs[i]
|
||||
/ static_cast<float>(m_conf.samplerate());
|
||||
|
||||
out.push_back(value);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef muz_SINE_HPP
|
||||
#define muz_SINE_HPP
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "Signal.hpp"
|
||||
#include "AudioConf.hpp"
|
||||
|
||||
namespace muz
|
||||
{
|
||||
/**
|
||||
* Sinusoid signal with an amplitude and a frequency.
|
||||
**/
|
||||
class Sine: public Signal
|
||||
{
|
||||
public:
|
||||
explicit Sine(AudioConf const& conf,
|
||||
std::unique_ptr<Signal> freq,
|
||||
std::unique_ptr<Signal> amplitude);
|
||||
virtual ~Sine();
|
||||
|
||||
std::vector<float> next() override;
|
||||
private:
|
||||
AudioConf const& m_conf;
|
||||
std::unique_ptr<Signal> m_freq;
|
||||
std::unique_ptr<Signal> m_amplitude;
|
||||
std::vector<float> m_phases;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,7 +1,33 @@
|
|||
#ifndef muz_COMMONS_HPP
|
||||
#define muz_COMMONS_HPP
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
#include "conf.hpp"
|
||||
|
||||
#define MUZ_ERROR(NAME) \
|
||||
struct NAME : public std::runtime_error { \
|
||||
explicit NAME(std::string const& what): std::runtime_error(what) {} \
|
||||
}
|
||||
|
||||
#define MUZ_ENUM_IDENT(X) X
|
||||
#define MUZ_ENUM_STRING(X) #X
|
||||
|
||||
#define MUZ_ENUM(Prefix, Macro) \
|
||||
enum Prefix {Macro(MUZ_ENUM_IDENT)}; \
|
||||
constexpr char const* Prefix ## Str [] = {Macro(MUZ_ENUM_STRING)};
|
||||
|
||||
|
||||
#endif
|
||||
|
|
42
src/main.cpp
42
src/main.cpp
|
@ -1,8 +1,48 @@
|
|||
#include "lib/AudioConf.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <lib/commons.hpp>
|
||||
#include <lib/Sine.hpp>
|
||||
#include <lib/Constant.hpp>
|
||||
#include <lib/AudioEngine.hpp>
|
||||
|
||||
#include <lib/Lexer.hpp>
|
||||
#include <lib/Parser.hpp>
|
||||
#include <lib/Compiler.hpp>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::cout << "muzgen " << MUZ_VERSION << std::endl;
|
||||
muz::AudioConf conf;
|
||||
|
||||
muz::AudioEngine engine {conf};
|
||||
|
||||
engine.init();
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
std::ifstream file {argv[1]};
|
||||
std::stringstream ss;
|
||||
ss << file.rdbuf();
|
||||
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(ss.str());
|
||||
|
||||
muz::Parser parser;
|
||||
auto node = parser.parse(lexer);
|
||||
|
||||
muz::Compiler compiler {conf};
|
||||
auto signals = compiler.compile(node);
|
||||
|
||||
while (signals.size() > 0)
|
||||
{
|
||||
engine.push_signal(std::move(signals.back()));
|
||||
signals.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
engine.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#include <catch2/catch.hpp>
|
||||
#include "../lib/AudioConf.hpp"
|
||||
|
||||
class AudioConfTest
|
||||
{
|
||||
public:
|
||||
explicit AudioConfTest() {}
|
||||
virtual ~AudioConfTest() {}
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(AudioConfTest, "AudioConf_default")
|
||||
{
|
||||
muz::AudioConf conf;
|
||||
REQUIRE(2 == conf.channels());
|
||||
REQUIRE(256 == conf.frames_per_buffer());
|
||||
REQUIRE(44100 == conf.samplerate());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(AudioConfTest, "AudioConf_custom")
|
||||
{
|
||||
muz::AudioConf conf {1, 128, 88200};
|
||||
|
||||
REQUIRE(1 == conf.channels());
|
||||
REQUIRE(128 == conf.frames_per_buffer());
|
||||
REQUIRE(88200 == conf.samplerate());
|
||||
}
|
|
@ -5,7 +5,9 @@ project(MuzGenTest)
|
|||
find_package(Catch2 REQUIRED)
|
||||
|
||||
add_executable(muz-test
|
||||
trivial.cpp
|
||||
Lexer.cpp
|
||||
Parser.cpp
|
||||
AudioConf.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET muz-test
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
#include <catch2/catch.hpp>
|
||||
#include <lib/Lexer.hpp>
|
||||
|
||||
class LexerTest
|
||||
{
|
||||
public:
|
||||
explicit LexerTest() {}
|
||||
virtual ~LexerTest() {}
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
static std::string next_val(muz::Lexer& lexer)
|
||||
{
|
||||
auto tok = lexer.next();
|
||||
|
||||
if (tok)
|
||||
{
|
||||
return tok->string();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LexerTest, "Lexer_num")
|
||||
{
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(" 34 2.9 -7 -3.14 .1 1.");
|
||||
|
||||
REQUIRE("NUM[34]" == next_val(lexer));
|
||||
REQUIRE("NUM[2.9]" == next_val(lexer));
|
||||
REQUIRE("NUM[-7]" == next_val(lexer));
|
||||
REQUIRE("NUM[-3.14]" == next_val(lexer));
|
||||
REQUIRE("NUM[.1]" == next_val(lexer));
|
||||
REQUIRE("NUM[1.]" == next_val(lexer));
|
||||
|
||||
REQUIRE("" == next_val(lexer));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LexerTest, "Lexer_ident")
|
||||
{
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(" hello hello_world @hello");
|
||||
|
||||
REQUIRE("IDENT[hello]" == next_val(lexer));
|
||||
REQUIRE("IDENT[hello_world]" == next_val(lexer));
|
||||
REQUIRE("DIR_IDENT[@hello]" == next_val(lexer));
|
||||
|
||||
REQUIRE("" == next_val(lexer));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LexerTest, "Lexer_commands")
|
||||
{
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(" [[]");
|
||||
|
||||
REQUIRE("OSQUARE" == next_val(lexer));
|
||||
REQUIRE("OSQUARE" == next_val(lexer));
|
||||
REQUIRE("CSQUARE" == next_val(lexer));
|
||||
|
||||
REQUIRE("" == next_val(lexer));
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
#include <catch2/catch.hpp>
|
||||
#include "../lib/Parser.hpp"
|
||||
#include "../lib/Lexer.hpp"
|
||||
|
||||
class ParserTest
|
||||
{
|
||||
public:
|
||||
explicit ParserTest() {}
|
||||
virtual ~ParserTest() {}
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
static void test_parser(std::string const& oracle,
|
||||
std::string const& source)
|
||||
{
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(source);
|
||||
|
||||
muz::Parser parser;
|
||||
auto node = parser.parse(lexer);
|
||||
REQUIRE(oracle == node->string());
|
||||
}
|
||||
|
||||
static void test_parser_err(std::string const& source)
|
||||
{
|
||||
muz::Lexer lexer;
|
||||
lexer.scan(source);
|
||||
|
||||
muz::Parser parser;
|
||||
REQUIRE_THROWS_AS(parser.parse(lexer), muz::syntax_error);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(ParserTest, "Parser_commands")
|
||||
{
|
||||
test_parser_err("[hello");
|
||||
test_parser_err("hello]");
|
||||
test_parser_err("12");
|
||||
|
||||
test_parser("PROG(CMD(IDENT[hello]),CMD(IDENT[world]))",
|
||||
"[hello] [world]");
|
||||
|
||||
test_parser("PROG(CMD(IDENT[hello_world]))",
|
||||
"[hello_world]");
|
||||
|
||||
test_parser("PROG(CMD(IDENT[sine],NUM[440]))",
|
||||
"[sine 440]");
|
||||
|
||||
test_parser("PROG(CMD(IDENT[sine],NUM[440],NUM[217]))",
|
||||
"[sine 440 217]");
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(ParserTest, "Parser_directives")
|
||||
{
|
||||
test_parser("PROG(DIR(DIR_IDENT[@bim],CMD(IDENT[hello_world])))",
|
||||
"@bim [hello_world]");
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
class trivialTest
|
||||
{
|
||||
public:
|
||||
explicit trivialTest() {}
|
||||
virtual ~trivialTest() {}
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(trivialTest, "trivial_test")
|
||||
{
|
||||
REQUIRE(true);
|
||||
}
|
Loading…
Reference in New Issue