Documentation
Overview for mimium
Documentation is under preparation! Seeking for people who support documentations and translations.
mimium(MInimal-Musical-medIUM) is a domain specific programming language for describing/generating sound and music.
With this language, you can write a low-level audio processing with an easy expression and high-performance powered by LLVM.
fn lpf(input:float,fb:float){
return (1-fb)*input + fb*self
}
A special keyword self
can be used in function, which is a last return value of the function.
This enables an easy and clean expression of feedback connection of signal chain, inspired by Faust.
you can also write a note-level processing by using a temporal recursion, inspired by Extempore.
fn noteloop()->void{
freq = (freq+1200)%4000
noteloop()@(now + 48000)
}
Calling function with @
specifies the time when the function will be executed.
An event scheduling for this mechanism is driven by a clock from an audio driver thus have a sample-accuracy.
1 - Users Guide
1.1 - Getting Started
Let’s start your mimium experiences
Prerequisites
Currently, mimium can be used on the following environments.
- macOS (x86)
- Linux(tested on Ubuntu, uses ALSA as backend)
- Windows 10
Getting started with an extension for Visual Studio Code
The easiest way to get started is to use Visual Studio Code, a free text editor/IDE.
- download and install Visual Studio Code from the official website.
- Start Visual Studio Code, and open the Extensions menu (Cmd+Shift+X).
- search for “mimium” in the search field and install it.
- Create a file with the name of `hello.mmm’, copy the following code snippet, save it, and open it again in Visual Studio Code.
- A pop-up window will appear asking you to install the latest version of the mimium binary.
- Cmd+Shift+P to open the command palette, search for “mimium”, and execute the command “Run currently opening file” to run the file currently open in the editor from the terminal.
- If you want to stop the sound, press Ctrl+C in the terminal.
// hello.mmm
fn dsp(){
output = sin(now*440*2*3.141595/48000)
return (output,output)
}
Other ways to install
You can download latest binaries from GitHub Release. Copy bin/mimium
to appropariate path (for example, /usr/loca/bin
on macOS/Linux).
On macOS/Linux, you can easily install mimium by using Homebrew/Linuxbrew.
brew install mimium-org/mimium/mimium
for more detailed information such as building from source, check Installation page.
Run the command
You can run mimium by running mimium
command. If the binary is correctly installed, you can see the help menu with the following command.
Make text file with the name of hello.mmm
on current working directory and paste the code snippet above.
Then, type the following command to run the file. (Take care the volume of speakers.) You will hear the sine wave sound of 440Hz.
Conguraturations!
You can read further explanation such as a grammer of the language and available functions on Making Sound page.
1.1.1 - Examples
The entry point for the beginners of mimium - step by step example.
- dsp
- “self” example
- a simple effect
- composition
- sol-fa
- rhythm pattern
Example: sol-fa
In the example below, the system’s audio driver sample rate is 48000 Hz, and it will play sol-fa sounds every second.
notes = [440,495,260.7,293.3,330,347.7,391.1]
index = 0
fn updateIndex(){
index = (index+1)%7
updateIndex()@(now+48000)
}
updateIndex()@48000
fn dsp(){
vol = 0.2
octave = 1
sec = now/48000
freq = notes[index]
out = vol * sin(octave*freq*3.14*2*sec)
return (0,out)
}
Point 1: Arrays.
notes = [440,495,260.7,293.3,330,347.7,391.1] // 1st line
In mimium, you can define an array. Arrays are defined using []
. The beginning of the index is 0.
In this example, the first line of the array that creates the sol-fa sounds note contains the frequencies of the A-B-C-D-E-F-G notes, which are used in subsequent processing.
- A: 440Hz
- B: 495Hz
- C: 260.7Hz
- D: 293.3Hz
- E: 330Hz
- F: 347.7Hz
- G: 391.1Hz
The way to use arrays is to use array_name[index]
, as in line 12.
freq = notes[index] // line 12
.
Point 2: Temporal Recursion
// Lines 3~7
fn updateIndex(){
index = (index+1)%7
updateIndex()@(now+48000)
}
updateIndex()@48000
V0.3.0 does not adopt the for-loop statement that most languages have. However, you can describe repetitive executions of the function by using a design pattern called temporal recursion as shown in lines 3~6.
In the sol-fa example, after executing updateIndex()
at 48000 samples in line 7, the now
and @
keywords are used together in the function to execute updateIndex()
at 48000 samples from the current time (line 5).
Point 3: Using the now
and @
keywords together
updateIndex()@(now+48000) // line 5
In mimium, the current number of samples can be retrieved using now
. Users should note that in the v0.3.0 current specification, the now
keyword does not represent real-time. The unit is sample.
Also, mimium has a @
keyword, which means “execute the function before @
when the number of samples is the number of values resulting from the calculation of the expression after @
.
The @
time indicates the absolute time since the audio driver was started, so if you write updateIndex()@48000
as in line 7, it will always execute updateIndex()
once 48000 samples after it was started.
In the fifth line of the example, by connecting now
and 48000
with the +
keyword, we can determine the sample point in time from the current time, and by using @
, we can execute the function at that sample point.
Point 4: Octave
// Lines 8~15
fn dsp(){
vol = 0.2
octave = 1
sec = now/48000
freq = notes[index]
out = vol *sin(octave*freq*3.14*2*sec)
return (0,out)
}
In the musical scale, there is a relationship between doubling the frequency (Hz) and going up an octave, and conversely, halving the frequency and going down an octave. In lines 8 to 15 of the example, the value is fixed at octave = 1
, so it plays a sol-fa sounds from 260.7Hz to 495Hz, but if you change this value to 2, for example, you can express a scale that goes up an octave.
1.1.2 - Making Sound
Here you will learn how to make the basic sound using the mimium.
Documentation is under preparation! We are seeking for people who support documentations and translations.
Making 440Hz Sine wave sound
- Prepare a MMM file(e.g. sin.mmm). If you haven’t installed mimium at this point, see Installation.
- Write the following code and saving the file.
fn dsp(time){
return sin(now*440*3.14*2/48000)
}
Otherwise you can write one-liner code snippets.
dsp = |t|{ sin(now*440*3.14*2/48000) }
- Using your command line tool (e.g. bash), run the previous code.
1.1.3 - Installation
Install mimium to your computer
Documentation is under preparation! We are seeking for people who support documentations and translations.
Install through homebrew
For easier installation, using Homebrew, a package manager tool for macOS and Linux is recommended.
If you have not installed homebrew, open terminal app and copy & paste next line and hit Enter key.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
If you got homebrew, you can install mimium by typing
brew install mimium-org/mimium/mimium
That’s it! You can check if you have successfully installed or not by
mimium --version # will return mimium version:x.x.x
Install manually
You can download built binary from GitHub Release Page.
mimium-vx.x.x-Darwin.zip is for macOS, mimium-vx.x.x-Linux.zip is for Linux and mimium-vx.x.x-Windows.zip is for Windows.
After finished downloading and extracting zip files, copy mimium
inside bin
folder into /usr/local/bin
, all files inside lib
into /usr/local/lib
. On GNU/Linux, note that the directory /usr/local/lib
is not in library path by default, so don’t forget to add this directory to /etc/ld.so.conf.d/
and run ldconfig
as root.
Build from source manually
mimium compiler & runtime are written in C++ thus you need C++ compiler.
Xcode clang on macOS(you can install by just typing xcode-select --install
) and GCC >= 9 is recommended on Linux.
You also need several libraries and build tools below.
on macOS, git and cmake are installed if you got xcode clang.
Also the codes depends on RtAudio(a cross-platform audio driver library) library but it will be downloaded & built automatically by cmake at a configure step.
You can of course install these dependencies via brew
, or apt-get
.
At Ubuntu 18.04(Bionic), a version of bison from apt is 3.0.4, which will not work. Please install latest version manually. Also,
Github Actions Workflow for automatic build & test contains all the informations for dependencies & build steps including further steps described below.
Get a source code from GitHub repository
git clone https://github.com/mimium-org/mimium.git
cd mimium
# 'master' branch is a stable version. You can get the development version from 'dev' branch.
git checkout master
mkdir build && cd build
cmake ..
At this step, CMake will download and build RtAudio Library. You can pass generic options for CMake configuration option, for example,
-DCMAKE_INSTALL_PREFIX=/your/directory
specifies installation path.-DCMAKE_BUILD_TYPE=Debug
specifies build optimization level. You can choose from ‘Debug’, ‘Release’, ‘MinSizeRel’ , ‘RelWithDebinfo’-DCMAKE_CXX_COMPILER=/path/to/compiler
specifies C++ compiler.-DBUILD_SHARED_LIBS=ON
Build libraries as dynamic-link library.(Not well tested on Linux and Windows)。-DBUILD_TEST=ON
Include tests to build targets.-DENABLE_COVERAGE=ON
Enable compiler option to calculate code-cvoerage using GCov.
Build
-j
option controls maximum number of parallel CPU threads usage(e.g. -j8
means up to 8 threads). -j
with no number means possible maximum number on your platform.
Install
cmake --build build target=install
Uninstall
#(on build folder)
cmake --build build --target uninstall
This uninstall target uses information on build/install_manifest.txt
generated by CMake at installation steps. If you fail to remove files, check this file and try build , installation and uninstall steps sequentialy again.
Syntax Highlight for Visual Studio Code
Currently, we have a syntax highlight for Visual Studio Code, a cross platform text editor/development environment.
You can get by searching mimium-language
on an extension panel on VS Code or you can get from the link below.
https://marketplace.visualstudio.com/items?itemName=mimium-org.mimium-language
1.2 - Language Specification
1.2.1 - Grammar Definition
This page is about the definition of mimium grammer.
This page is about the rule of mimium grammer.
As in C++ and JavaScript, anything to the right of //
in a line is treated as a comment.
You can also comment out multiple lines at once by enclosing them as /* */
.
Variable declaration and assignment
In mimium, when a value is assigned with the =
operator, a new variable is created if the variable with the specified name does not exist yet.
There is no need to use keywords such as ``let’’ when declaring variables.
``rust
mynumber = 1000
The variables are all modifiable (mutable).
All variables are mutable. All variables are mutable, i.e., you can assign new values to variables that have already been declared.
mynumber = 1000 // variable is declared, and 1000 is assigned
mynumber = 2000 // 2000 is newly assigned to mynumber
type
Type is a concept to distinguish between variables and other data, such as numbers and strings, depending on their purpose.
mimium is a statically typed language, which means that all types are determined at compile time (before the actual sound is made).
Statically typed languages generally have an advantage in terms of execution speed over languages that check types during execution. On the other hand, there is a disadvantage of manual type specification, which tends to lead to long descriptions. mimium has a feature called Type Inference, which allows you to omit type annotations if the type can be automatically determined from the context, thus keeping your code concise.
There are two types: primitive types, which are the smallest unit that cannot be further decomposed, and aggregate types, which are made by combining multiple types.
Explicit annotation of types is possible at the time of variable declaration and function declaration.
In the parameters of variables and functions, the type can be specified by writing the name of the type followed by a :
(colon).
Assigning to a different type will result in a compile-time error, as shown below.
In function type declarations, the return value can be specified by following the parameter parentheses with a ->
between them.
fn add(x:float,y:float)->float{
return x + y
}
```.
In the case of this add function, we can predict that x and y are floats from the context [^binaryop], so we can omit it as follows.
```rust
fn add(x,y){
return x+y
}
Primitive types
The only primitive types in mimium are float
, string
and void
.
The only numeric type in mimium is float
(internally a 64bit float).
Integers can be used with the round
, ceil
, and floor
functions.
Values of type string
can be generated from double-quoted string literals, such as "hoge"
.
Currently, it does not support string cutting or merging, and its usage is basically the same as
pass it to the printstr
function for debugging purposes. 2.
pass it to the loadwav
function to load an audio file. 3. pass it to include
to include it.
pass it to include
to load other source files.
pass to include
to load other source files.
The void
type has no value and is used to indicate that there is no return value for the function.
Composite types
Arrays
An array is a type that can store multiple values of the same type in succession. It can be generated by comma-separated values enclosed in []
(angle brackets).
myarr = [1,2,3,4,5,6,7,8,9,10]
You can retrieve the value of an array type by specifying a zero-based index with angle brackets, such as myarr[0]
.
arr_content = myarr[0] //arr_content should be 1
You can also rewrite the contents of the array by using angle brackets on the left side value as well.
myarr[4] = 20 //myarr becomes [1,2,3,4,20,6,7,8,9,10].
**The size of the array is fixed. The size of the array is fixed, so you can’t add values to the back of the array. There is no bounds check, so out-of-range access will cause a crash. ** The size of the array is fixed.
Automatic interpolation
Indexes are automatically linearly interpolated when accessed with decimal values.
arr_content = myarr[1.5] //should be 2.5
There is no automatic rounding to integers, so if you want to avoid interpolation, you need to round the indices using the round
function or something similar.
Tuples
A tuple is a value that combines different types into one. They can be generated by enclosing variables in ()
(round brackets) and inserting comma-separated variables.
Tuples are also similar to arrays, but each element can have a different type.
You can retrieve the value of a tuple by placing a comma-separated variable at the left-hand side value. There is no need to separate them with parentheses in this case.
In the future, we will implement a notation to extract by index, like mytup.1
, instead of just decomposing by left side value.
Tuples are typically used in mimium to group together channels of audio signals such as stereo and multi-channel in signal processing.
Type Alias
Because type annotation for tuple can be redundant, it can be shorten using type alias semantics.
type FilterCoeffs = (float,float,float,float,float)
Struct(Record Type)
Struct has similar functionality to Tuple type but it can have field names for each type. Struct type cannot be anonymous type. Thus user needs to declare a type alias before initializing value and construct a variable with TypeName{val1,val2...}
.
The values can be extracted by dot operator like expr.field
.
type MyBigStruct = {"field1":float,"field2":FilterCoeffs,"field3":string}
mystr = MyBigStruct{100,(0.0,1.0,1.2,0.8,0.4),"test"}
println(mystr.field1)
printlnstr(mystr.field3)
Destructive assignment for struct type variable like mystr.fst = 111
is under implementation。
Functions
A function is a collection of reusable procedures that take multiple values and return a new value.
As an example, consider the add function, which just adds two values together and returns them.
To store it as a variable with an explicit type in mimium, write the following
fn add(x,y){
return x+y
}
In mimium, functions can be treated as first-class values. This means that you can assign a function to a variable or take it as a parameter of a function.
For example, the type annotation of the previous add function is (float,float)->float
. To assign the previous add function to a variable, you can write the following If you want to assign the function as a function parameter, see the section on higher-order functions.
my_function:(float,float)->float = add
Anonymous functions (lambda expressions)
The previous function declaration is actually an alias to the syntax for storing anonymous functions in variables, as shown below.
add = |x:float,y:float|->float{return x+y}
It is also possible to call such a function directly without assigning it to a variable.
println(|x,y|{return x + y}(1,2)) //print "3"
Also, as will be explained in the block syntax section, the last line of the block can be substituted for return by simply placing an expression in place of return. In other words, the add
function, combined with type inference, can be omitted until the following example.
Pipe (|>
) operators
In mimium, you can use the pipe operator |>
to rewrite nested function calls like a(b(c(d)))
as d |> c |> b |> a
.
Currently, the pipe operator can only be used with functions that have a single parameter. It will be possible to use it in functions with more than one parameter by automatically expanding the tuple type value with a feature such as the parameter pack.
Looping by recursion
Named functions can also call themselves.
The fact function to compute the factorial can be defined as follows
fn fact(input:float){
if(input>0){
return 1
}else{
return input * fact(input-1)
}
}
Use recursive functions with care, as they can cause infinite loops.
self
In a function, you can use a special keyword called self
.
self
is a variable that can refer to the last value returned by a function. For example, the following function will return a value that increases by increment
each time it is called.
fn counter(increment){
return self+increment
}
Self is basically only available for functions that originate from the dsp
function. self is initialized to 0 when the audio engine is started, and a separate value is created and managed for each calling context.
For example, in the following example, the counter
function is given a different increment for each of its functions, which internally allocates two pieces of memory for self, with lch increasing by 0.01 samples every time it crosses 1 and resetting to 0, and rch increasing by 0.05 samples every time it crosses 1.
fn dsp()->(float,float){
lch = counter(0.01)%1
rch = counter(0.05)%1
return (lch,rch)
}
Self is currently always initialized with 0. We are currently working on a way to change this initial value.
Scope of variables
mimium is a lexical-scoped language, which means that it is possible to refer to variables defined outside of a function.
TBD.
Expressions, statements, and blocks
A collection of statements enclosed in curly braces {}
used in a function, etc. is a unit called a block.
A statement almost always consists of a syntax for assigning expressions, such as a = b
.
expression is a unit consisting of numbers like 1000
, variable symbols like mynumber
, arithmetic expressions like 1+2*3
, and function calls with return values like add(x,y)
.
Block is actually one of the expressions.
You can put multiple statements in a block, and the last line can use return
to specify the value to be returned. The return
keyword in the last line can also be omitted and just putting expression is allowed.
For example, the following syntax is grammatically correct. (*As of v0.3.0, this syntax is implemented incorrectly and does not work. *)
//mynumber should be 6
mynumber = {
x = 2
y = 4
return x+y
}
Conditional
Conditional in mimium has the syntax if (condition) then_expression else else_expression
.
condition
, then_expression
, and else_expression
are all expressions.
If the value of condition
is greater than zero, the then_expression
part is evaluated, otherwise the else_expression
is evaluated.
If the then/else part is expressed as a block, it can be written in a C-like way as follows.
fn fact(input:float){
if(input>0){
return 1
}else{
return input * fact(input-1)
}
}
On the other hand, the if statement itself can be treated as an expression, so the same syntax can be rewritten as follows.
Note that the parentheses in the conditional part cannot be omitted.
fn fact(input:float){
return if (input>0) 1 else input * fact(input-1)
}
Deferred execution with @
operator
You can defer the execution of a function by following the function call with @
followed by a value of numeric type.
The unit of time is samples.
For example, the following example writes 100 and 200 to the standard output at the 0th and 48000th samples after starting the audio driver.
println(100)@0
println(200)@48000
Currently, the @
operator can only be used for functions of type void
(which have no return value).
By delaying the execution of a recursive function with @
, it is also possible to repeat certain operations at regular intervals.
For example, the following example will increment the number from 0 to 1 at 48000 sample intervals and write it to the standard output.
fn loopprint(input)->void{
println(input)
loopprint(input+1)@(now+48000)
}
loopprint(0)@0
include
The syntax include("path/to/file.mmm")
allows you to include other files in the file.
The file path can be an absolute path or a path relative to the file.
Currently, there is no namespace division, and the include statement is purely replaced by the text of the file (but once a file is loaded, it will not be loaded more than once).
Syntax definition by BNF, operator precedence, etc.
TBD
1.2.2 - Built-in Function
This section describes the built-in function in mimium.
This section describes the built-in function in mimium.
Returns delayed input
value with the duration of time
(unit: samples).
The maximum value of time is currently fixed at 44100 samples, and there is always enough memory to store 44100 samples regardless of the actual delay time. This will be improved in the future with the implementation of compile-time constant functionality.
For example, the delay can be combined with self to create a feedback delay as shown below.
fn fbdelay(input:float,time:float,feedback:float){
return delay(input*self*feedback,time)
}
random()->float
Returns random value in a range of -1 to 1。An acutual implementation on C++ uses rand() function in C standard library like below.
(double)rand() / RAND_MAX) * 2 - 1
Basic mathematical functions
Listed functions in math.h of C language is included in mimium by default.
Takes one float and returns one float if it has no explanation.
sin
cos
tan
asin
acos
atan
atan2
(x,y)sinh
cosh
tanh
log
log10
exp
pow
(x,y)sqrt
abs
ceil
floor
trunc
round
fmod
(x,y) %
operator is an alias to this function.remainder
(x,y)min
(x,y) alias to fmin
in C language.max
(x,y) alias to fmax
in C language.
print / println / printstr
Mainly used for debugging purpose.
Print values to stdout.
print
,println
accept number type as parameter.
println
output value with newline.
You can output string type values by using printstr
.
loadwav(path:string)->[float x 0] / loadwavsize(path:string)->float
Load audio files by using LibSndFile.
Both take the file path of the audio file (.wav, .aiff, .flac, etc.) as a parameter.
If the path is not absolute, it is interpreted as relative to the location of the source file.
loadwavsize(path)
returns the number of samples of the audio file.
loadwav(path)
returns the audio file as a read array.
If you access the file with an index larger than the file size, it will crash, so you need to use the value of loadwavsize to limit the value.
File loading is a temporary implementation, so only 1-channel audio files can be used.
In the future, with the introduction of the structure, the specification will be changed so that you can get the number of samples, channels, sample rate, and arrays to each channel all in one function.
2 - Developers Guide
Overall structure, C++ coding style and guideline for compiler&runtime development.
Development
2.1 - Setup development environments
How to install dependency libraries, build source codes, debug and test.
Dependencies
- cmake
- bison 3.3~
- flex
- llvm 11~
- libsndfile
- rtaudio(cmake downloads automatically)
Optionally, Ninja is recommended for a fast build.
macOS
Install Xcode, and Xcode command line tools with the following command.
Install Homebrew by the instruction on the website.
Install dependencies with the following command.
brew tap mimium-org/mimium
brew install mimium -s --only-dependencies
Linux(Ubuntu)
If you are using Homebrew on Linux, you can use same installation command in the section of macOS.
If you want to install dependencies with apt, use the following command.
pushd /tmp && wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh && popd
sudo apt-get install libalsa-ocaml-dev libfl-dev libbison-dev libz-dev libvorbis-dev libsndfile-dev libopus-dev gcc-9 ninja-build
Windows (on MSYS2)
Currently, build on Windows is realized by using MSYS2(https://www.msys2.org/). Follow the instruction on MSYS2 website to install it.
Open MSYS MinGW64 terminal and install dependencies with the following command..
pacman -Syu --noconfirm git flex bison mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-libsndfile mingw64/mingw-w64-x86_64-opus mingw-w64-x86_64-ninja mingw-w64-x86_64-llvm
Clone the repository
Clone the repository of mimium with the git command.
git clone --recursive https://github.com/mimium-org/mimium.git
Editor
For the development of mimium, using Visual Studio Code is recommended.
Open mimium.code-workspace
with VSCode.
Recommended Extensions
When you open the workspace, the pop-up menu will be shown to install recommended extensions if they are not installed.
- cmake-tools
- clangd
- CodeLLDB
- Coverage Gutter
Especially, CMake Tools is necessary to develop C++ project with VSCode.
When you open the workspace with the Cmake Tools installed, you will be asked which CMake kit you want to use (only at the first time).
If you are on macOS, choose /usr/bin/clang
. Otherwise, choose an appropriate compiler you installsed, for example, /usr/local/bin/g++
on Linux, and /mingw64/bin/g++
on MSYS2.
Build
Select the CMake Tools tab from the menu on the left side of VS Code, and select Configure All. A build directory will be created under build/
(RtAudio will be downloaded and built automatically at this time).
Right next to Configure All, press Build All to start building all projects.
Build Targets
src/mimium_exe
: Builds the main mimium
command.src/mimium
: Builds the main body of the library, libmimium
.test/Tests
: The target to build all tests (except Fuzzing).Lcov
LcovResetCounter
: Target available when -DENABLE_COVERAGE
is enabled. run the Lcov
target after building and running one or more tests, etc. to collect coverage information. If you select Show Coverage
from the Cmd+Shift+P command palette in this state, the actual execution of the code will be highlighted. After editing the source and rebuilding, you will need to run LcovResetCounter
because of coverage information conflicts.
Debugging on VSCode
Select the Run tab from the menu on the left side of VS Code.
Configurations can be selected from next to the Run button. There are two workspaces: one to launch the CMake target (“CMake Target Debug(Workspace)”), and another to run it with command line options (“Launch with Arg(edit this config)(workspace )”).
You can choose which CMake target to launch by clicking “Select the target to launch” in the bottom menu bar.
If you want to launch by passing options to the command line, select “Launch with Arg(edit this config)(workspace)” and then use the gear symbol to directly edit the mimium.code-workspace file. For example, use “args” to specify the file name as shown in the following example.
...
{
"name": "Launch with arg(edit this config)",
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": ["${workspaceFolder}/examples/gain.mmm"],
"cwd": "${workspaceFolder}/build",
},
...
You can also pass the input from stdio by specifying like "stdio": ["${workspaceFolder}/stdin.txt",null,null],
.
Test
There are three main types of tests, using Google Test.
Unit tests
This is mainly a test to see if each section of the compiler works correctly by starting it alone.
Regression Test.
This is a test to invoke the actual built mimium
binary from the std::system
function and check if there are any errors in other sections due to the addition of new features.
Since mimium does not currently have a syntax for verifying tests, we pipe the calculation results to standard output using the println
function, and Google Test captures it to verify that the answer is correct.
Also, the test which launches the audio driver is currently not implemented.
Fuzzing test
This is a fuzzing test that uses Clang’s libfuzzer.
Fuzzing tests are used to verify that a random input (in this case, a string of source code) is given in a gradually changing manner, and that if the syntax is correct, it will be processed correctly, and if it is an error, it will be treated as an error and an unexpected crash will not occur.
It is only validated on macOS and is not included in CI.
Run Ctest
You can execute test command by running ctest
command on build
directory, or you can execute Unit Test and Regressin test from the menu on right-bottom of VSCode.
2.2 - Coding Style
Coding styles used in a development of mimium
Basic Policy
- language specification (LS) conforms to C++17.The main reason is why Code Template Inference improve readability such as
if constexpr
beacuse mimium use actively std::variant
or std::optional
. - **If you are uncertain about readability or a slight improvement in execution speed, take readability.**In the first place C++ is guranteerd to be reasonably fast, so it doesn’t matter if it can be a little rich processing, that degree of hesitation is about same when optimized.
- Avoid using external libraries as much as possible(especialy for general purpose such as boost).And STL is actively used.
- The compiler depends on bison(yacc) and flex(lex) as parser.This is especially valuable as bison source document,so we plan to continue using in the future, but there are problems Unicode not being able to be loaded for flex, I may move to REflex etc. or switch to manual implementation.Under consideration.
- The runtime currently depends on libsndfile etc. for reading audio file, but we would like to separate it as a project structure later because it is only needed at runtime.
- Mimium does not use raw pointers.Basically mimium uses
std::shared_ptr<T>
.But llvm::Type*
in the LLVM library and llvm::Value*
are not limited to this because they implement their own reference counting. - In the conditional expression(e.g.if block),mimium does not use the fact the pointer variable is empty with
nullptr
.Even if it takes some effort,the document value of the source code itself is improved by using std::optional<std::shared_ptr<T>>
.
Dynamic Polymorphism
There are two types of polymorphism that switches indivisual processing for each type in C++: Static Polymorphism, which determines the type at compile time, and Dynamic Polymorphism, which determines the operation at runtime.
Static Polymorphism is mainly implemented by templates, and Dynamic Polymorphism is mainly implemented by inheritance and virtual function.
However, the mimium development basically does not use virtual function for Dynamic Polymorphism.Instead, use std::variant
introduced in STL since C++17.std::variant<T1,T2,T3...>
is a type to which a variable having any of multiple types T1 to Tn can be substituted, and by using std::get<T>
and std::visit()
, it is possible to dynamically divide the processing according to the type.This is a substitute for the type called sum type that is often seen in functional types, and std::visit
enables processing close to so-called pattern matching when combined processing division using templates and constexpr.In internal implementation, it is maximum memory of the type that can be taken and securing a tag (integer) of which type is currently held, so it is also called Tagged Union.
mimiumにおける具体的な型でいうと抽象構文木であるmimium::ast::Expr
やmimium::ast::Statement
、中間表現であるmimium::mir::Instruction
、(mimium言語における)型を表すmimium::types::Value
などがstd::variant
へのエイリアスです。
仮想関数よりもvariant
を用いるメリットはなんでしょうか?1つは、実行コストがかかることです。仮想関数は実行時に関数へのテーブルを保持し仮想関数が呼び出されるたびにそれを参照する必要があるため、std::variant
を用いた多相の方が実行速度では一般的に有利だとされています。
もう1つはアップキャストの問題です。抽象構文木のような木構造のデータを渡りながら処理をする時、どうしても
- 継承した個別の型を基底クラスのポインターでダウンキャストして受け取る
- 仮想関数を用いて型に応じた処理をする
- 処理したあと帰ってきたポインターを元の型に戻し(アップキャスト)、さらに処理を続ける
といったパターンが発生します。このアップキャストは一般的なコーディングでは、間違った型へキャストして終えば予測不可能な挙動が起きるので御法度とされています。仮想関数で用いている実行時型情報(RTTI)を用いるdynamic_cast
を使って動的に型検査をして安全にアップキャストする方法もありますが、記述が長くなりがちなども問題もあります。一方でstd:variant
を使用するとこのようなダウン→アップキャストの必要はないので型情報が明確に取り扱えます。
またこうした(型に応じた処理を複数種類)x(複数の型)と組み合わせる方法はデザインパターンの中ではビジターパターンとも呼ばれ、 std::visit(function_object,variant_variable)
ではこの形が引数としてシンプルに表されています。一方仮想関数を使ってのビジターパターンはデータ側にacceptと呼ばれるメソッドを実装しておく必要があるので、データはデータ、関数は関数というように構造を分離することが難しくなります。
再帰的データ構造
std::variant
にも難しい点はあります。そのうち重要な点は再帰的データ構造がそのままでは扱えないことです。
たとえば、types::Value
はtypes::Float
やtypes::Function
など取りうる型すべてを含んでいますが、ここでtypes::Function
のメンバ変数にはたとえば返り値を表す型としてtypes::Value
が含まれてしまっています。
std::variant
は通常の数値型のデータなどと同じように、取りうる型の最大値分だけメモリをスタック確保し、ヒープアロケーションは行わない仕様となっており、再帰的なデータ構造の場合はデータサイズを静的に決定できなくなってしまうのでコンパイルできなくなります。
これを回避するためには、再帰的な部分を持つデータについては実体の代わりにポインタを格納するなどの方法が考えられるのですが、初期化やメンバアクセスなどが統一されないためややこしくなるなどの問題があるため、mimiumでは次の記事を参考にしたヘルパークラスを利用しています。
Breaking Circular Dependencies in Recursive Union Types With C++17 - Don’t Compute In Public(last view:2020-08-17)
具体的には内部のT
の実体を要素数1のstd::vector<T>
に確保し、キャスト演算子としてT& ()
を実装しimplicitに元の型から構築、キャストして参照を受け取ることができるようにしています。std::vector
はT
の中身が不完全なままでもスタック上のデータサイズを確定させることができるのでコンパイルが通るようになるのです。
記事中のrecursive_wrapper<T>
を、mimium内部ではRec_Wrap<T>
と名付け、さらにこの型をrT
(たとえばFunction
に対してrFunction
といった形で)エイリアスしています。
using Value = std::variant<None, Void, Float, String, rRef, rTypeVar, rPointer, rFunction,rClosure, rArray, rStruct, rTuple, rAlias>;
/*~~~~~*/
using rFunction = Rec_Wrap<Function>;
/*~~~~~*/
struct Function : Aggregate {
Value ret_type;
std::vector<Value> arg_types;
};
たとえばstd::visit
でパターンマッチングする時にはビジターの関数オブジェクトのoperator()
オーバーロードを以下のようにします。
types::Value operator()(types::Function& f){
return someProcess(f);
}
/*~~~~~*/
template<typename T>
types::Value operator()(Rec_Wrap<T>& t){
return (*this)(static_cast<T&>(t));
}
Rec_Wrap<T>
を一度キャストして剥がしてもう一度自分自身を適用するテンプレート関数を使います。operator()(Rec_Wrap<types::Function>& rf)
を直接オーバーロードしても構わないのですが、このrf
は直接メンバアクセスができないため一度ローカル変数でtypes::Function&
にキャストしてやらないといけなかったりする二度手間が発生します。
エラー処理、例外
try
、throw
、catch
と言ったC++標準のエラー処理を積極的に利用します。throw
を使ったエラー処理は実行コストが高いというデメリットがあるものの、正常系で処理をしている間はコストがかからず、言語組み込みの仕様なのでエラー処理部の可読性が高まります。
optionalなどを活用したエラー処理クラスの実装は型シグネチャが複雑になり可読性が下がるなどのデメリットもあります。ただ内部的に使用しているLLVMのライブラリでは実行速度を重視してExpected
クラスなどでこの形式でエラー処理をしているため、こちらとtry
、catch
のエラー処理を混ぜるのはあまり効率がよくはない、混乱するといった問題点も存在しています。
現状ではコンパイラもランタイムもtry/catchのエラー処理を使用していますが、ランタイム側では実行時間にセンシティブである、今後組み込み向けなどに移植される可能性もあると言った事情を考えるとoptional等を活用した処理に変更するかもしれません。
基本的にtry
,catch
はコンパイラでの処理のはじめに行い深くネストすることはしません。どの道、ほとんどのケースでエラーが起きれば最終的にコンパイルエラーの形に帰着するためです。必要に応じてエラークラスを継承して種類分けし、catchしたところでまとめてエラーの型ごとにメッセージ等を個別に処理します。( エラークラスの定義は今後の課題)
3 - Contributing
Contributing for mimium
Code of Conduct
Contribution guide
Documentation guide
Community(gitter/twitter/etc)
3.1 - Code of Conduct
Purpose of the community and expected behavior as a participant
Purpose
A primary goal of all communities such as a development group, other(e.g. documentation, financial) contributors, users group, related events, and so on) is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible.
As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, nationality, language, age, ethnicity, socioeconomic status and religion (or lack thereof).
- Prioritize Diversity. We strive to be a community that welcomes and supports people of all backgrounds and identities.
- Considerate: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions.
- Respect other communities. mimium has been made under the influence of many preceding languages, projects, and music/sound artworks. mimium is not made for surpassing or making them obsolete. We strive to grow our community and other various communities got involved with us to be more valuable through repetetive communication.
- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
- Exercise consideration and respect in your speech and actions.
- Attempt collaboration before conflict.
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
- As the mimium community is a worldwide community, remember that people discussing with you may be using their non-primary language. Also, you need not hasitate speak your primary language if the language is not a major language used in the community.
- You are invited to use our community to share your own projects and events, commercial or noncommercial, that directly involve mimium.
- You may also share news of projects and events (for example, conferences, academic events, competitions) that are only topically related to mimium, provided that they are noncommercial.
- Avoid using the community as a platform to advertise commercial products that lack direct connections to mimium.
Unacceptable Behavior
Unacceptable behaviors include: intimidating, harassing, abusive, discriminatory, derogatory or demeaning speech or actions by any participant in our community online, at all related events and in one-on-one communications carried out in the context of community business. Community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
Harassment includes:
- harmful or prejudicial verbal or written comments related to gender, sexual orientation, race, religion, disability
- inappropriate use of nudity and/or sexual images (including presentation slides)
- inappropriate depictions of violence (including presentation slides)
- deliberate intimidation, stalking or following
- harassing photography or recording
- sustained disruption of talks or other events
- inappropriate physical contact, and unwelcome sexual attention.
Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
If You Witness or Are Subject to Unacceptable Behavior
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. You can find a list of organizers to contact for each of the supporters of this code of conduct at the bottom of this page. Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify one of the community organizers with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues—online and in-person—as well as in all one-on-one communications pertaining to community business.
License and attribution
This Code of Conduct is distributed under a Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)」.
Also, This code of conduct is based on the following references.
Updating this Code of Conduct
Participants of the community should continue considering and discussing whether the code of conduct is appropriate for the current community. As necessary, update this code of conduct so that the community becomes better.
History
- 2021/1/17 first version - mainly written by Tomoya Matsuura(@tomoyanonymous).
4 - Release Notes
Update/Deprecation infos on each update
v0.4.0(2021-04-03)
New Language Feature
Struct type and type alias are introduced(#56).
type stereo = (float,float)
type composite = {signal:stereo , id:float}
fn test2(){
ogya = composite{(100,200), 100}
ho,fu = ogya.signal
he = ogya.id
println(ho)
println(fu)
println(he)
}
test2()
Still, struct type has several limitations.
- Embedding function type variable onto struct (and tuple) cannot be used for closure.
- destructive assignment for struct variable by dot operator is not implemented yet(only a parser is implemented).
Bugfixes
- Fixed behavior of CLI when it could not find an input file path(#62,by @t-sin).
Refactoring
- Some internal helper functions such as recursive variants are refined.
v0.3.1(2021-02-25)
Bugfixes
- fixed a bug in code generator when if statement has void block(#51)
- added missing
==
and !=
infix operators (#53)
Refactoring
ExecutionEngine
class has been made by splitting from Runtime
class toward implementing interpreter backend and environment variables feature.
New Function
Though this release is a patch release, a subtle new feature is added.
mem
, a 1-sample delay function has been added. A simple example of biquad filter using this function is added to mimium-core/filter.mmm
(#44).
Other updates
A list of all contributers is added to readme by using all-contributors.
v0.3.0 (2021-02-03)
New language features
Tuple and Array type
New aggregate type, tuple and array have been added.
Tuple type can be constructed with parenthesis and comma.
Currently, the only way to get value of tuple type is like C++’s structural binding.
// Tuple Construction
triples = (100,200,300)//type signature is (float,float,float)
one,two,three = triples
In the future, dot access operator like triples.0
will be added.
Array type can be constructed with angle brackets and comma.
Currently All the array is mutable , fixed sized and declared as a private global variable in llvm module.
As a unique feature, an interpolation with floating pointer index is supported as same as reading audio files.
# Array Construction
myarr = [100,200,300]
internalv = myarr[0]//zero-based index
myarr[2] = 400//the array is now [100,200,400]
interp = myarr[0.5]//the result will be 150(linear interpolation)
Multichannel support for dsp function
Now mimium supports more than mono output, stereo and more many channels.
Numbers of inputs and outputs are determined by a type of dsp function
.
For example, stereo panning can be written like below.
fn panner(input:float,pan:float) -> (float,float){
return (input*pan,input*(1-pan))
}
fn dsp(input:(float,float))->(float,float){
src = random()*0.2
res = panner(src,sin(5*now/48000)+1*0.5)
return res
}
This is the breaking change because, 1. input parameter for dsp
was time
before v0.2 (this was a temporary solution before now
was implemented), 2. output type for dsp
was float
but now the type for dsp
should be (tuple of float)->(tuple of float)
, a function that takes 1 variable with tuple of floats and returns tuple of floats.
Even if you want to process mono, the output type should be a tuple of 1 float.
Some examples were rewritten to match with this language spec.
Using mimium as C++ library
Dependencies of library headers and frontend(application instance and CLI) were tidied up.
We can now use mimium as C++ Library.
You can import mimium easily by using CMake.
A minimal example is on https://github.com/mimium-org/mimium-libimport-example.
other changes
- Supported version of LLVM is now 11.
- Code_Of_Conduct.md was added.
v0.2.0 (2020-12-24)
Improvements
- Windows build(on MSYS2) is ready. Check GitHub Actions workflow if you want to build.
delay(input,delaytime)
primitive function has been added.- maximum delay time is currently fixed to 44100 samples.
- (experimental) a simple macro preprocessor has been added.
- you can now include other source files with
include "otherfile.mmm"
put on a global context.
- Refactored Mid-Level internal representation.
v0.1.5 (2020-09-28)
Fixed bugs
- fixed a compile error when if statement contains only a single fcall inside braces(Thanks @t-sin).
Improvements
- Automatically copies changelog to release body in GitHub actions
Other
Now new features such as delay primitive function which will be included in v0.2.0 are under development.
v0.1.4 (2020-09-11)
Fixed bugs
- fixed compile error with a combination of clang & libstdc++.
- fixed crashes on returning if as expression in function.
Improvement
- introduced address sanitizer and simple fuzzing test in develop environments.
- memory allocations on user-code are freed on a destructor call of runtime.
- heap allocations by user-code was not freed in previous versions but it’s not a serious problem because a program itself are exited when a destructor of runtime is called. This improvement makes sense mostly for a fuzzing test that iterates compilations many times in same process.
v0.1.3 (2020-09-09)
fixed bugs
- fixed unusual crash when if-statement used on linux
- fixed operator precedence of if statement when used as expression
// mathmatical operators have higher precedence than if or else!
myvar = if(cond) 100+20 else 200*50
v0.1.2 (2020-09-07)
fixed bugs
- Previous release did not contain llvm generation for if statement. Now if statement/expression works correctly.
- fixed a bug that comment-out which ends with a line break could not be parsed correctly.
Refactoring
MIR(Mid-Level Representation)-related classes’ implementation has been made more simplified.
As like ast classes refined in v0.1.0, data does not have member functions and is initiallized with aggregate initialization.
v0.1.1 (2020-09-05)
Fixed Bug
This release mainly fixed a parser.
Now, a specification of block , statements inside curly-brackets is similar to Rust.
The block can be treated as just an expression, like function call or infix operations, when you put a single expression on last line of statements in block, or put return
statement. That value becomes the block’s value itself.
Thus, all examples below is valid.
fn test(x,y){
localvar = x*y+100
localvar //last line is expression.
}
fn test(x,y){
localvar = x*y+100
return localvar //last line is return statement.
}
testvar = { velylocal = 100
verylocal*2004} * 300 // this is also valid...
By this change, if
statement can be both of an expression and a statement. When the contents of then/else statement do not have return value, it is just treated as like function of void
type.
v0.1.0 (2020-08-21)
This release is mostly refactoring of internal processings, especialy AST(Abstract Syntax Tree)-related classes which was written in very early stage of development and is completely different style of implementation compared to newly introduced class such as MIR and Type related classes. A dynamic polymorphism in this compiler development is unified to the way using std::variant
, not an class inheritance.
The release does neither contains new features nor breaking changes but fixed many instabilities, particularly incorrectness of type inference.
Also, build on Ubuntu has prepared. You can check dependencies from github action workflow page. Seeking people who can test on windows!
v0.0.0 (2020-02-15)
first release! no arrays, no structs, no include/modules. This is a “proof of concept” version of mimium.
known bugs
- Calling same functions that uses
self
more than twice, instantiation of self will not be performed properly. (can avoid this behaviour by defining alias of function) - in
dsp
function, at least 1 global variable should be used.
limitations
if
can have only a expression, not multiple statements(it’s like conditional operator((cond)? then_expr : else_expr
) in other languages.)- array access
[]
can be used only for audio files loaded by using loadwav(path)
. No boundary checks for array access. You can get a size of the array by loadwavsize(path)
.