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.

  1. download and install Visual Studio Code from the official website.
  2. Start Visual Studio Code, and open the Extensions menu (Cmd+Shift+X).
  3. search for “mimium” in the search field and install it.
  4. Create a file with the name of `hello.mmm’, copy the following code snippet, save it, and open it again in Visual Studio Code.
  5. A pop-up window will appear asking you to install the latest version of the mimium binary.
  6. 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.
  7. 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/Linuxbrew1.

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.

mimium --help

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.

mimium hello.mmm

Conguraturations!

You can read further explanation such as a grammer of the language and available functions on Making Sound page.


  1. Currently, we can not provide a binary package for macOS 11.0 thus it tries to build from the source. Installing Xcode and some additional software are required. ↩︎

1.1 - Examples

The entry point for the beginners of mimium - step by step example.
  1. dsp
  2. “self” example
  3. a simple effect
  4. composition
  5. sol-fa
  6. 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 sample1.

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. mimium currently does not provide a way to get a samplerate of the audio driver. In the future, this will be realized by introducing environment-variables(runtime-defined variables). ↩︎

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

  1. Prepare a MMM file(e.g. sin.mmm). If you haven’t installed mimium at this point, see Installation.
  2. 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) }
  1. Using your command line tool (e.g. bash), run the previous code.
$ mimium sin.mmm

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.

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 

Configure CMake

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

cmake --build build -j

-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

2 - Language Specification

2.1 - Grammar Definition

This page is about the definition of mimium grammer.

This page is about the rule of mimium grammer.

Comment out

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).

myvar:float = 100

Assigning to a different type will result in a compile-time error, as shown below.

myvar:string = 100

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

  1. pass it to the printstr function for debugging purposes. 2.

  2. pass it to the loadwav function to load an audio file. 3. pass it to include to include it.

  3. pass it to include to load other source files.

  4. 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.

mytup = (100,200,300)

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.

one,two,three = mytup

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)

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.

add = |x,y|{x+y}

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.

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)
}

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

2.2 - Built-in Function

This section describes the built-in function in mimium.

This section describes the built-in function in mimium.

delay(input:float,time:float)->float

Returns delayed input value with the duration of time(unit: samples).

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.

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.