✨ basic sine command and out directive.
parent
2c8df5e493
commit
8959950c2c
4
Makefile
4
Makefile
|
@ -17,7 +17,9 @@ install: tests
|
||||||
|
|
||||||
check:
|
check:
|
||||||
@cppcheck --language=c++ --enable=all -q lib src tests \
|
@cppcheck --language=c++ --enable=all -q lib src tests \
|
||||||
--suppress=missingIncludeSystem
|
--suppress=missingIncludeSystem \
|
||||||
|
--suppress=missingInclude \
|
||||||
|
--suppress=unmatchedSuppression
|
||||||
|
|
||||||
doc:
|
doc:
|
||||||
mkdir -p build/doc/doxygen
|
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
|
Sound design with MuzGen
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
MuzGen is not stable and has probably a lot's of bugs.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
intro
|
|
||||||
install
|
install
|
||||||
|
quickstart
|
||||||
|
signals
|
||||||
|
directives
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
The simplest way to install MuzGen is by using the Makefile at the project root directory.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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
|
add_library(muz-lib OBJECT
|
||||||
|
# Audio
|
||||||
Signal.cpp
|
Signal.cpp
|
||||||
|
AudioEngine.cpp
|
||||||
|
AudioConf.cpp
|
||||||
|
Constant.cpp
|
||||||
|
Sine.cpp
|
||||||
|
|
||||||
|
# Language
|
||||||
|
Node.cpp
|
||||||
|
Lexer.cpp
|
||||||
|
Parser.cpp
|
||||||
|
Compiler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET muz-lib
|
set_property(TARGET muz-lib
|
||||||
PROPERTY CXX_STANDARD 17
|
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)
|
if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
target_compile_options(muz-lib
|
target_compile_options(muz-lib
|
||||||
PRIVATE --coverage
|
PRIVATE --coverage
|
||||||
|
@ -32,6 +55,7 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
COMMAND $<TARGET_FILE:muz-test>
|
COMMAND $<TARGET_FILE:muz-test>
|
||||||
COMMAND ${LCOV_PATH} -d . --capture -o cov.info
|
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 '/usr/include/*' -o cov.info
|
||||||
|
COMMAND ${LCOV_PATH} -r cov.info '*.hpp' -o cov.info
|
||||||
COMMAND ${GENHTML_PATH} --legend -o cov_html cov.info
|
COMMAND ${GENHTML_PATH} --legend -o cov_html cov.info
|
||||||
)
|
)
|
||||||
endif()
|
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
|
#ifndef muz_SIGNAL_HPP
|
||||||
#define muz_SIGNAL_HPP
|
#define muz_SIGNAL_HPP
|
||||||
|
|
||||||
|
#include "commons.hpp"
|
||||||
|
|
||||||
namespace muz
|
namespace muz
|
||||||
{
|
{
|
||||||
|
MUZ_ERROR(signal_error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio signal interface.
|
||||||
|
* @see Sine
|
||||||
|
* @see Constant
|
||||||
|
**/
|
||||||
class Signal
|
class Signal
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Signal();
|
explicit Signal();
|
||||||
virtual ~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:
|
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
|
#ifndef muz_COMMONS_HPP
|
||||||
#define muz_COMMONS_HPP
|
#define muz_COMMONS_HPP
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <mutex>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "conf.hpp"
|
#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
|
#endif
|
||||||
|
|
42
src/main.cpp
42
src/main.cpp
|
@ -1,8 +1,48 @@
|
||||||
|
#include "lib/AudioConf.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include <lib/commons.hpp>
|
#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)
|
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;
|
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)
|
find_package(Catch2 REQUIRED)
|
||||||
|
|
||||||
add_executable(muz-test
|
add_executable(muz-test
|
||||||
trivial.cpp
|
Lexer.cpp
|
||||||
|
Parser.cpp
|
||||||
|
AudioConf.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET muz-test
|
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