├── .github └── workflows │ ├── ci.yaml │ ├── ci_web.yaml │ ├── release.yml │ └── trigger_release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── Development.md ├── LICENSE ├── Readme.md ├── Roadmap.md ├── dist-workspace.toml ├── mimium-audiodriver ├── Cargo.toml ├── src │ ├── backends.rs │ ├── backends │ │ ├── cpal.rs │ │ ├── csv.rs │ │ └── local_buffer.rs │ ├── driver.rs │ ├── lib.rs │ └── runtime_fn.rs └── tests │ └── getnow.rs ├── mimium-cli ├── Cargo.toml ├── examples │ ├── 0b5vr.mmm │ ├── biquad.mmm │ ├── cascadeosc.mmm │ ├── fbdelay.mmm │ ├── getnow.mmm │ ├── midiin.mmm │ ├── multiosc.mmm │ ├── noise.mmm │ ├── reactive_f.mmm │ ├── reactive_sequencer.mmm │ ├── sinewave.mmm │ └── subtract_synth_demo.mmm ├── lib │ ├── core.mmm │ ├── delay.mmm │ ├── env.mmm │ ├── filter.mmm │ ├── math.mmm │ ├── noise.mmm │ ├── osc.mmm │ ├── reactive.mmm │ └── reverb.mmm └── src │ └── main.rs ├── mimium-guitools ├── Cargo.toml ├── examples │ ├── plot.mmm │ └── plot_and_midi.mmm ├── readme.md └── src │ ├── lib.rs │ ├── main.rs │ ├── plot_ui.rs │ └── plot_window.rs ├── mimium-lang ├── Cargo.toml ├── benches │ └── bench.rs ├── build.rs ├── examples │ └── multistage.mmm └── src │ ├── ast.rs │ ├── ast │ └── builder.rs │ ├── ast_interpreter.rs │ ├── compiler.rs │ ├── compiler │ ├── bytecodegen.rs │ ├── intrinsics.rs │ ├── mirgen.rs │ ├── mirgen │ │ ├── closure_convert.rs │ │ ├── convert_pronoun.rs │ │ ├── hir_solve_stage.rs │ │ ├── recursecheck.rs │ │ └── test.rs │ ├── parser.rs │ ├── parser │ │ ├── chumsky_test.rs │ │ ├── error.rs │ │ ├── lexer.rs │ │ ├── resolve_include.rs │ │ ├── statement.rs │ │ ├── test.rs │ │ └── token.rs │ ├── tests │ │ └── hirgen.rs │ └── typing.rs │ ├── interner.rs │ ├── lib.rs │ ├── mir.rs │ ├── mir │ └── print.rs │ ├── pattern.rs │ ├── plugin.rs │ ├── plugin │ └── system_plugin.rs │ ├── repl.rs │ ├── runtime.rs │ ├── runtime │ ├── builtin_fn.rs │ ├── vm.rs │ └── vm │ │ ├── builtin.rs │ │ ├── bytecode.rs │ │ ├── program.rs │ │ ├── ringbuffer.rs │ │ └── test.rs │ ├── types.rs │ ├── types │ └── builder.rs │ ├── utils.rs │ └── utils │ ├── environment.rs │ ├── error.rs │ ├── fileloader.cjs │ ├── fileloader.rs │ ├── half_float.rs │ ├── metadata.rs │ └── miniprint.rs ├── mimium-midi ├── Cargo.toml ├── build.rs ├── examples │ └── midiin.mmm ├── readme.md ├── src │ └── lib.rs └── tests │ ├── integration_test.rs │ └── mmm │ └── midi_test.mmm ├── mimium-rs.code-workspace ├── mimium-scheduler ├── Cargo.toml ├── benches │ └── bench.rs └── src │ ├── lib.rs │ └── scheduler.rs ├── mimium-symphonia ├── Cargo.toml ├── Readme.md ├── build.rs ├── src │ ├── filemanager.rs │ └── lib.rs └── tests │ ├── assets │ └── count_100_by_0_01_f32_48000Hz.wav │ ├── integration_test.rs │ └── mmm │ ├── loadwav.mmm │ └── loadwav_interp.mmm ├── mimium-test ├── Cargo.toml ├── Readme.md ├── build.rs ├── src │ └── lib.rs └── tests │ ├── intergration_test.rs │ ├── mmm │ ├── add.mmm │ ├── block_local_scope.mmm │ ├── block_local_scope_fail.mmm │ ├── cascade.mmm │ ├── closure_argument.mmm │ ├── closure_closed.mmm │ ├── closure_counter.mmm │ ├── closure_counter2.mmm │ ├── closure_counter_tuple.mmm │ ├── closure_escape_3nested.mmm │ ├── closure_open.mmm │ ├── closure_open_3nested.mmm │ ├── closure_open_inline.mmm │ ├── closure_tuple_escape.mmm │ ├── counter.mmm │ ├── delay.mmm │ ├── delay2.mmm │ ├── error_include_itself.mmm │ ├── fb_and_stateful_call.mmm │ ├── fb_mem.mmm │ ├── fb_mem2.mmm │ ├── fb_mem3.mmm │ ├── generic_id.mmm │ ├── hello.mmm │ ├── hof.mmm │ ├── hof_infer.mmm │ ├── hof_state.mmm │ ├── hof_typefail.mmm │ ├── if.mmm │ ├── if_parse.mmm │ ├── if_state.mmm │ ├── let_multi.mmm │ ├── let_tuple.mmm │ ├── let_tuple_nested.mmm │ ├── loopcounter.mmm │ ├── many_comments.mmm │ ├── many_errors.mmm │ ├── mem.mmm │ ├── mir_counter.mmm │ ├── mirtest.mmm │ ├── nested_if.mmm │ ├── osc.mmm │ ├── parse_fntest.mmm │ ├── parser_firstbreak.mmm │ ├── pipe.mmm │ ├── placeholder.mmm │ ├── primitive_log.mmm │ ├── primitive_sin.mmm │ ├── primitive_sqrt.mmm │ ├── recursion.mmm │ ├── scheduler_counter.mmm │ ├── scheduler_counter_indirect.mmm │ ├── scheduler_global_recursion.mmm │ ├── scheduler_invalid.mmm │ ├── scheduler_multiple_at_sametime.mmm │ ├── scheduler_reactive.mmm │ ├── shadowing.mmm │ ├── shadowing_assign.mmm │ ├── simple_stereo.mmm │ ├── state_tuple.mmm │ ├── statefn.mmm │ ├── statefn2.mmm │ ├── statefn2_same.mmm │ ├── stateful_closure.mmm │ ├── test_include.mmm │ ├── test_include_target.mmm │ ├── test_phase_reset.mmm │ ├── tuple_args.mmm │ └── typing_tuple_fail.mmm │ └── scheduler_test.rs ├── mimium-web ├── Cargo.toml ├── LICENSE └── src │ └── lib.rs └── mimium_logo_slant.svg /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Tests for Native App 2 | 3 | on: 4 | pull_request: 5 | branches: [main, dev] 6 | push: 7 | branches: [main, dev] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | cargo_test: 15 | name: "cargo test (OS: ${{ matrix.config.os }}" 16 | runs-on: ${{ matrix.config.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - { os: ubuntu-latest } 22 | - { os: macOS-latest } 23 | - { os: windows-latest } 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Set up Rust 29 | uses: dtolnay/rust-toolchain@master 30 | with: 31 | toolchain: stable 32 | - name: Install system requirements for Linux 33 | if: runner.os == 'Linux' 34 | run: | 35 | sudo apt update -y 36 | sudo apt install -y libasound2-dev 37 | - name: Cache cargo registry 38 | uses: actions/cache@v4 39 | with: 40 | path: | 41 | ~/.cargo/bin/ 42 | ~/.cargo/registry/index/ 43 | ~/.cargo/registry/cache/ 44 | ~/.cargo/git/db/ 45 | target/ 46 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 47 | 48 | - run: cargo test 49 | -------------------------------------------------------------------------------- /.github/workflows/ci_web.yaml: -------------------------------------------------------------------------------- 1 | name: Test for Web 2 | 3 | on: 4 | pull_request: 5 | branches: [main, dev] 6 | push: 7 | branches: [main, dev] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | cargo_test_setup: 15 | name: "setup test for web" 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up Rust 22 | uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: stable 25 | - name: Cache cargo registry 26 | uses: actions/cache@v4 27 | with: 28 | path: | 29 | ~/.cargo/bin/ 30 | ~/.cargo/registry/index/ 31 | ~/.cargo/registry/cache/ 32 | ~/.cargo/git/db/ 33 | ~/.cargo/.crates2.json 34 | ~/.cargo/.crates.toml 35 | target/ 36 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 37 | - name: Install wasm-pack 38 | run: which wasm-pack || cargo install wasm-pack 39 | - name: "mimium-test for web" 40 | run: wasm-pack test --node 41 | working-directory: mimium-test 42 | - name: "mimium-lang test for web" 43 | run: wasm-pack test --node 44 | working-directory: mimium-lang -------------------------------------------------------------------------------- /.github/workflows/trigger_release.yml: -------------------------------------------------------------------------------- 1 | # This action is triggered by a manual workflow_dispatch event. 2 | # It takes input of release version number like "2.3.4". 3 | # This action executes `cargo-release` and `wasm-pack publish` and pushes the changes to the repository. 4 | name: Trigger Release 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | release_version: 9 | description: "Release version number (You must write CHANGELOG.md before running this action. Also this action must be done only in main branch.)" 10 | required: true 11 | type: string 12 | skip-cargo-release: 13 | description: "Skip cargo-release" 14 | required: false 15 | type: boolean 16 | skip-wasm-pack-publish: 17 | description: "Skip wasm-pack publish" 18 | required: false 19 | type: boolean 20 | jobs: 21 | release: 22 | permissions: 23 | contents: write 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - name: Set up Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: stable 32 | override: true 33 | - name: Install system requirements for Linux 34 | run: | 35 | sudo apt update -y 36 | sudo apt install -y libasound2-dev 37 | - name: Setup .npmrc file to publish to npm 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: '20.x' 41 | registry-url: 'https://registry.npmjs.org' 42 | - name: Cache cargo registry 43 | uses: actions/cache@v4 44 | with: 45 | path: | 46 | ~/.cargo/bin/ 47 | ~/.cargo/registry/index/ 48 | ~/.cargo/registry/cache/ 49 | ~/.cargo/git/db/ 50 | target/ 51 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 52 | - name: Add git config 53 | run: | 54 | git config --global user.email "me@matsuuratomoya.com" 55 | git config --global user.name "GitHub Actions[Bot]" 56 | - name: Run cargo release 57 | if: ${{ !inputs.skip-cargo-release }} 58 | run: | 59 | which cargo-release || cargo install cargo-release 60 | cargo release ${{ inputs.release_version }} --execute --no-confirm 61 | env: 62 | CARGO_REGISTRY_TOKEN: ${{ secrets.CRATEIO_TOKEN }} 63 | - name: Run wasm-pack publish 64 | if: ${{ !inputs.skip-wasm-pack-publish }} 65 | run: | 66 | which wasm-pack || cargo install wasm-pack 67 | wasm-pack build --target web 68 | wasm-pack publish --target web 69 | working-directory: mimium-web 70 | env: 71 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | .DS_Store -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # mimium Code of Conduct 2 | 3 | [On website, translated versions are available](https://mimium.org/en/docs/contributing/code-of-conduct/). 4 | [日本語版はWebサイトから閲覧可能です](https://mimium.org/ja/docs/contributing/code-of-conduct/)。 5 | 6 | ## Purpose 7 | 8 | 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. 9 | 10 | 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). 11 | 12 | ## The mimium community strives to be: 13 | 14 | - Prioritize Diversity. We strive to be a community that welcomes and supports people of all backgrounds and identities. 15 | - 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. 16 | - 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. 17 | 18 | ## Expected Behavior as a member of the community 19 | 20 | - Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 21 | - Exercise consideration and respect in your speech and actions. 22 | - Attempt collaboration before conflict. 23 | - 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. 24 | - 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. 25 | 26 | ### Self-Promotion 27 | 28 | - You are invited to use our community to share your own projects and events, commercial or noncommercial, that directly involve mimium. 29 | - 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. 30 | - Avoid using the community as a platform to advertise commercial products that lack direct connections to mimium. 31 | 32 | ## Unacceptable Behavior 33 | 34 | 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. 35 | 36 | Harassment includes: 37 | 38 | - harmful or prejudicial verbal or written comments related to gender, sexual orientation, race, religion, disability 39 | - inappropriate use of nudity and/or sexual images (including presentation slides) 40 | - inappropriate depictions of violence (including presentation slides) 41 | - deliberate intimidation, stalking or following 42 | - harassing photography or recording 43 | - sustained disruption of talks or other events 44 | - inappropriate physical contact, and unwelcome sexual attention. 45 | 46 | ## Consequences of Unacceptable Behavior 47 | 48 | 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. 49 | 50 | 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). 51 | 52 | ## If You Witness or Are Subject to Unacceptable Behavior 53 | 54 | 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. 55 | 56 | 57 | ## Addressing Grievances 58 | 59 | 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. 60 | 61 | ## Scope 62 | 63 | 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. 64 | 65 | ## License and attribution 66 | 67 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/)」. 68 | 69 | Also, This code of conduct is based on the following references. 70 | 71 | - SuperCollider Code of Conduct 72 | - Berlin Code of Conduct 73 | - Contributers Covenant 74 | - School for Poetic Computation Code of Conduct 75 | - Algorave Code of Conduct https://github.com/Algorave/algoraveconduct 76 | 77 | ## Updating this Code of Conduct 78 | 79 | 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. 80 | 81 | ### History 82 | 83 | - 2021/1/17 first version - mainly written by Tomoya Matsuura(@tomoyanonymous). 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "mimium-lang", 5 | "mimium-audiodriver", 6 | "mimium-cli", 7 | "mimium-test", 8 | "mimium-scheduler", 9 | "mimium-symphonia", 10 | "mimium-midi", 11 | "mimium-guitools", 12 | "mimium-web", 13 | ] 14 | 15 | resolver = "2" 16 | 17 | [workspace.package] 18 | edition = "2021" 19 | license = "MPL-2.0" 20 | version = "2.2.7" 21 | repository = "https://github.com/mimium-org/mimium-rs/" 22 | readme = "Readme.md" 23 | authors = ["Tomoya Matsuura "] 24 | 25 | [workspace.dependencies] 26 | mimium-lang = { path = "mimium-lang", version = "2.2.7" } 27 | mimium-audiodriver = { path = "mimium-audiodriver", version = "2.2.7" } 28 | mimium-midi = { path = "mimium-midi", version = "2.2.7" } 29 | mimium-symphonia = { path = "mimium-symphonia", version = "2.2.7" } 30 | mimium-scheduler = { path = "mimium-scheduler", version = "2.2.7" } 31 | mimium-guitools = { path = "mimium-guitools", version = "2.2.7" } 32 | mimium-web = { path = "mimium-web", version = "2.2.7" } 33 | mimium-test = { path = "mimium-test" } 34 | itertools = "0.13.0" 35 | wasm-bindgen = "0.2.93" 36 | wasm-bindgen-test = "0.3" 37 | 38 | # do not add git tag for each crates on release. 39 | [workspace.metadata.release] 40 | tag-prefix = "" 41 | 42 | # The profile that 'cargo dist' will build with 43 | [profile.dist] 44 | inherits = "release" 45 | lto = "thin" 46 | -------------------------------------------------------------------------------- /Development.md: -------------------------------------------------------------------------------- 1 | # Document for Developers 2 | 3 | ## Prepare Development Environment 4 | 5 | On windows, you need to install git and Visual Studio. 6 | 7 | On macOS, you need to install XCode from AppStore and execute `xcode-select --install` on the terminal. 8 | 9 | ### Install Rust with rustup 10 | 11 | To install rust language toolchains, follow the instruction of this page. 12 | 13 | https://www.rust-lang.org/tools/install 14 | 15 | #### Install additional cargo tools. 16 | 17 | We uses several additional tools to improve developing experience and automate a deployment process. 18 | 19 | You can install them with the command below. 20 | 21 | ```sh 22 | cargo install clippy cargo-dist cargo-release 23 | ``` 24 | 25 | ### Install IDE 26 | 27 | Install Visual Studio Code. 28 | 29 | https://code.visualstudio.com/ 30 | 31 | ### Clone Repository 32 | 33 | ```sh 34 | git clone https://github.com/tomoyanonymous/mimium-rs.git 35 | ``` 36 | 37 | Open `mimium-rs.code-workspace` with VSCode. The workspace contains recommended extensions, notification to install them will come up when you open the workspace first time. 38 | 39 | - mimium-language(Syntax highlight for mimium language) 40 | - rust-analyzer (Rust Language Server) 41 | - CodeLLDB (Better debugger) 42 | - Even better TOML(Better language support for Cargo.toml) 43 | 44 | ## How to Run & Debug 45 | 46 | You can run built `mimium-cli` by running `cargo run -- targetfile.mmm`. Note that you need to be in `mimium-cli` directory. 47 | 48 | Note that the binary with debug configuration is slow, you may hear the glitch noise (because of audio driver underrun). You should try with release build when you are trying to check audio with `--release` option. 49 | 50 | You can also debug the binary with LLDB from the debug menu on the left sidebar of VSCode. It has several config options but mostly you use "Debug executable 'mimium-CLI'". You can change target `.mmm` file and optional arguments by modifying the line of `"args": ["mimium-cli/examples/sinewave.mmm"].`. 51 | 52 | (Do not commit this change.) 53 | 54 | 55 | ## How to bump release (for the core maintainer) 56 | 57 | Merge `dev` branch into `main` on your local repository, and write changelog. 58 | 59 | You should write the version at h2 level. It should be reflected on release note on succeeding action. 60 | 61 | You can trigger Github Actions Workflow to trigger release manually. 62 | 63 | It internally execute `cargo-release`. You have to specify newer version number like `2.0.0-alpha2` as a workflow input. 64 | 65 | ```sh 66 | cargo release 2.0.0-alpha2 --execute 67 | ``` 68 | 69 | The version should follow SemVer rule and do not require `v` prefix. 70 | 71 | Note that this command will modify the version in the root `Cargo.toml` and make a commit and tag for them, and pushes it onto the remote. 72 | 73 | Also it internally executes `cargo publish` to upload crates into crate.io, so make sure you have a write permission token to publish. (Set `CRATEIO_TOKEN` for repository secrets.) 74 | 75 | For the wasm build, it executes `wasm-pack publish --target web`. It also requires npm publish token. (Set `NPM_PUBLISH_TOKEN` secret.) 76 | 77 | If tagged commit is pushed to github, the another workflow is triggered. 78 | 79 | The workflow uses `cargo-dist` to publish binary on a github release. 80 | 81 | Do not forget re-merge commits on `main` into `dev` branch after main release is done. 82 | 83 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # mimium 2 | 3 | main: [![Test(main)](https://github.com/tomoyanonymous/mimium-rs/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/tomoyanonymous/mimium-rs/actions/workflows/ci.yaml) dev: [![Test(dev)](https://github.com/tomoyanonymous/mimium-rs/actions/workflows/ci.yaml/badge.svg?branch=dev)](https://github.com/tomoyanonymous/mimium-rs/actions/workflows/ci.yaml) 4 | 5 | A programming language as an infrastructure for sound and music. 6 | 7 |

8 | An icon of the mimium. The word “mimium” is written in small caps, white letters at an angle on a gray diamond-shaped background with a gradient. The vertical bars of the letters are evenly spaced, making it look like a pedestrian crossing. 9 |

10 | 11 | https://mimium.org (A documentation for v2 is under preparation!) 12 | 13 | --- 14 | 15 | mimium(*MInimal-Musical-medIUM*) is a programming language for sound and music. 16 | 17 | mimium is made to be an infrastructure for distributing music in a form of a source code, not only a tool for musicians and programmers. 18 | 19 | Its semantics are inspired from several modern programming languages for sound such as *[Faust](https://faust.grame.fr)*, *[Kronos](https://kronoslang.io/)* and *[Extempore](https://extemporelang.github.io/)*. 20 | 21 | A minimal example below generates a sinewave of 440Hz. 22 | 23 | ```rust 24 | // minimal.mmm 25 | let twopi = 3.141595*2.0 26 | fn dsp(){ 27 | sin(now * 440.0 * twopi / samplerate) 28 | } 29 | ``` 30 | 31 | A special keyword `self` can be used in function, which is a last return value of the function. 32 | This enables an easy and clean expression of feedback connection of signal chain. 33 | 34 | ```rust 35 | fn lpf(input,fb){ 36 | (1.0-fb)*input + fb*self 37 | } 38 | ``` 39 | 40 | Also, the language design is based on the call by value lambda calculus, so the higher-order functions are supported to express generative signal graph like replicatiing multiple oscillators. 41 | 42 | ```rust 43 | fn replicate(n,gen){ 44 | if (n>0.0){ 45 | let c = replicate(n - 1.0,gen) 46 | let g = gen() 47 | |x,rate| g(x,rate) + c(x+100.0,rate+0.1) 48 | }else{ 49 | |x,rate| 0 50 | } 51 | } 52 | ``` 53 | 54 | mimium is a statically typed language but the most of type annotations can be omitted by the type inference system. If you are interested in the theoritical background of mimium, see [the paper about mimium](https://matsuuratomoya.com/en/research/lambdammm-ifc-2024/). 55 | 56 | This repository is for a mimium *version 2*, all the code base is rewritten in Rust while the original was in C++, and semantics of the language was re-designed. The codes are still very under development. 57 | 58 | ## Installation 59 | 60 | An easy way to start mimium is using [Visual Studio Code Extension](https://github.com/mimium-org/mimium-language). You can run opening `.mmm` file from the command palette. 61 | 62 | Also you can download the latest CLI tool [mimium-cli](https://github.com/tomoyanonymous/mimium-rs/releases) from GitHub Release. 63 | 64 | ## Development 65 | 66 | See [Development](./Development) section. 67 | 68 | ## Contributing 69 | 70 | There's no concrete way for contributing to the mimium project for now but any type of contribution (bugfix, code refactoring, documentation, showing the usecases, etc). 71 | 72 | (However, because the mimium is still early stage of the development and there's much things to do, the proposal or request for new feature without Pull Request will not be accepted.) 73 | 74 | Take a look at [Code of Conduct](./CODE_OF_CONDUCT) before you make contribution. 75 | 76 | ## [License](LICENSE) 77 | 78 | ©️ the mimium development community. 79 | 80 | The source code is licensed under the [Mozilla Puclic License 2.0 (MPL2.0)](LICENSE). 81 | 82 | ## Original Author 83 | 84 | Tomoya Matsuura/松浦知也 85 | 86 | ## Acknowledgements 87 | 88 | This project is supported grants and scholarships as follows. 89 | 90 | - 2019 Exploratory IT Human Resources Project ([The MITOU Program](https://www.ipa.go.jp/jinzai/mitou/portal_index.html)) by IPA: INFORMATION-TECHNOLOGY PROMOTION AGENCY, Japan. 91 | - Kakehashi Foundation (2022) 92 | - JSPS Kakenhi 23K12059 "Civil Engineering of Music, as a Practice and Critics between Music and Engineering"(2023-2025) 93 | 94 | ### Contributers 95 | 96 | This list contains the contributers from v1 development, documentation and financial sponsors(via github sponsor). 97 | 98 | #### Source Code Contributions 99 | 100 | - [Hiroaki Yutani](https://github.com/yutannihilation) 101 | - [Shinichi Tanaka(t-sin)](https://github.com/t-sin) 102 | - [kyo](https://github.com/syougikakugenn) 103 | - [Inqb8tr-jp](https://github.com/Inqb8tr-jp) 104 | - [zakuro9715](https://github.com/zakuro9715) 105 | 106 | #### Other forms of Contributions 107 | 108 | - [Baku Hashimoto](https://baku89.com) 109 | - [Yuichi Yogo](https://github.com/yuichkun) 110 | - [Ayumu Nagamatsu](http://ayumu-nagamatsu.com/) 111 | - [zigen](https://horol.org/) 112 | 113 | 114 | ## Known Bugs 115 | 116 | See [GitHub Issues with "bug" tag](https://github.com/tomoyanonymous/mimium-rs/issues?q=is%3Aissue+is%3Aopen+label%3Abug). 117 | 118 | ## [Roadmap](./Roadmap.md) -------------------------------------------------------------------------------- /Roadmap.md: -------------------------------------------------------------------------------- 1 | 2 | # Roadmap 3 | 4 | ## For version 3 5 | 6 | - [ ] Web Platform 7 | - [ ] Multi-stage computation (Hygienic Macro) 8 | - [ ] Generics 9 | - [ ] Module System 10 | - [ ] Package Manager 11 | 12 | ## Further & Other Plans 13 | 14 | - [ ] Native & WASM backend with Cranelift 15 | - [ ] Transpilation to C++ using [Faust's Signal API](https://faustdoc.grame.fr/tutorials/signal-api/) 16 | - [ ] Dynamic loading of plugins 17 | - [ ] effect system for managing stateful function and IO 18 | 19 | --- 20 | 21 | ## Finished Topics 22 | 23 | ### For version 2 (Rust re-implementation) 24 | 25 | - [x] Basic Data Types 26 | - [x] AST 27 | - [x] MIR 28 | - [x] VM Instructions 29 | - [x] Aggregate Types 30 | - [x] Tuple (Vector) types 31 | - [x] Compilers 32 | - [x] Stateful Functions 33 | - [x] Feedback with `self` 34 | - [x] Delay and mem 35 | - [x] Parser 36 | - [x] MIR Generation 37 | - [x] Type Inference 38 | - [x] Code Generation 39 | - [x] VM Code Generation 40 | - [x] Runtime 41 | - [x] Audio Driver Backend 42 | - [x] CPAL implmentation 43 | - [x] Logical Scheduler 44 | - [x] auto de-allocation of unused closure 45 | - [x] destructive assignment of closure upvalues 46 | - [x] schedule (`@`) operator 47 | - [x] Runtime value 48 | - [x] `now` 49 | - [x] `samplerate` 50 | - [x] VM 51 | - [x] Closure upvalue implementation 52 | - [x] StateStorage implementation 53 | - [x] simple file include 54 | - [x] simple audio file reader function 55 | - [ ] ~~array(slice) type & automatic interporation~~ -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:."] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.25.1" 8 | # CI backends to support 9 | ci = "github" 10 | # The installers to generate for each app 11 | installers = [ 12 | ] # we don't use this because the installer script does not copy bundled assets. 13 | # Target platforms to build apps for (Rust target-triple syntax) 14 | targets = [ 15 | "aarch64-apple-darwin", 16 | "x86_64-apple-darwin", 17 | "x86_64-unknown-linux-gnu", 18 | "x86_64-pc-windows-msvc", 19 | ] 20 | # Extra static files to include in each App (path relative to this Cargo.toml's dir) 21 | include = ["./mimium-cli/lib/", "./mimium-cli/examples/", "Readme.md"] 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".zip" 24 | # The archive format to use for windows builds (defaults .zip) 25 | windows-archive = ".zip" 26 | 27 | [dist.dependencies.apt] 28 | libasound2-dev = "*" 29 | 30 | [dist.github-custom-runners] 31 | x86_64-unknown-linux-gnu = "ubuntu-latest" 32 | -------------------------------------------------------------------------------- /mimium-audiodriver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-audiodriver" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "audio driver plugin for mimium" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | # [lib] 11 | 12 | 13 | [dependencies] 14 | cpal = "0.15.3" 15 | num-traits = "0.2.19" 16 | ringbuf = "0.4.1" 17 | mimium-lang = { workspace = true } 18 | -------------------------------------------------------------------------------- /mimium-audiodriver/src/backends.rs: -------------------------------------------------------------------------------- 1 | pub mod csv; 2 | pub mod local_buffer; 3 | #[cfg(not(target_arch = "wasm32"))] 4 | pub mod cpal; 5 | -------------------------------------------------------------------------------- /mimium-audiodriver/src/backends/csv.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufWriter, Write}, 4 | path::Path, 5 | }; 6 | 7 | use mimium_lang::{compiler::IoChannelInfo, runtime::vm, ExecContext}; 8 | 9 | use crate::driver::Driver; 10 | 11 | use super::local_buffer::LocalBufferDriver; 12 | 13 | pub struct CsvDriver { 14 | driver: LocalBufferDriver, 15 | csv_file: Box, 16 | } 17 | 18 | impl CsvDriver { 19 | pub fn new>(times: usize, path: &Option

) -> Self { 20 | let csv_file: Box = if let Some(path) = path { 21 | let csv_file = File::create(path.as_ref()).unwrap(); 22 | Box::new(BufWriter::new(csv_file)) 23 | } else { 24 | Box::new(BufWriter::new(std::io::stdout())) 25 | }; 26 | 27 | Self { 28 | driver: LocalBufferDriver::new(times), 29 | csv_file, 30 | } 31 | } 32 | } 33 | 34 | impl Driver for CsvDriver { 35 | type Sample = ::Sample; 36 | fn get_runtimefn_infos(&self) -> Vec { 37 | self.driver.get_runtimefn_infos() 38 | } 39 | 40 | fn init( 41 | &mut self, 42 | ctx: ExecContext, 43 | sample_rate: Option, 44 | ) -> Option { 45 | let res = self.driver.init(ctx, sample_rate); 46 | 47 | let chunk_size = res.map_or(0, |io| io.output); 48 | let mut header = String::new(); 49 | for i in 0..chunk_size { 50 | header.push_str(&format!("ch{i}")); 51 | if i != chunk_size - 1 { 52 | header.push(','); 53 | } else { 54 | header.push('\n'); 55 | } 56 | } 57 | 58 | // TODO: these erros should be handled. The Driver interface will 59 | // probably return Result to propagate runtime errors (e.g. dsp() not 60 | // found). Let's revisit here after it happens. 61 | self.csv_file 62 | .write_all(header.as_bytes()) 63 | .expect("failed to write"); 64 | self.csv_file.flush().expect("failed to flush"); 65 | 66 | res 67 | } 68 | 69 | fn play(&mut self) -> bool { 70 | let res = self.driver.play(); 71 | 72 | let chunk_size = self.driver.vmdata.as_ref().map_or(0, |vmdata| { 73 | vmdata.vm.prog.iochannels.map_or(0, |io| io.output) 74 | }) as _; 75 | let mut line = String::new(); 76 | for sample in self.driver.get_generated_samples().chunks(chunk_size) { 77 | for (i, v) in sample.iter().enumerate() { 78 | // :? is to display "0" as "0.0" 79 | line.push_str(&format!("{v:?}")); 80 | if i != sample.len() - 1 { 81 | line.push(','); 82 | } else { 83 | line.push('\n'); 84 | } 85 | } 86 | } 87 | 88 | self.csv_file 89 | .write_all(line.as_bytes()) 90 | .expect("failed to write"); 91 | self.csv_file.flush().expect("failed to flush"); 92 | 93 | res 94 | } 95 | 96 | fn pause(&mut self) -> bool { 97 | self.driver.pause() 98 | } 99 | 100 | fn get_samplerate(&self) -> u32 { 101 | self.driver.get_samplerate() 102 | } 103 | 104 | fn get_current_sample(&self) -> mimium_lang::runtime::Time { 105 | self.driver.get_current_sample() 106 | } 107 | 108 | fn is_playing(&self) -> bool { 109 | self.driver.is_playing() 110 | } 111 | } 112 | 113 | pub fn csv_driver>(times: usize, path: &Option

) -> Box> { 114 | Box::new(CsvDriver::new(times, path)) 115 | } 116 | -------------------------------------------------------------------------------- /mimium-audiodriver/src/backends/local_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicU64, Ordering}, 3 | Arc, 4 | }; 5 | 6 | use mimium_lang::{ 7 | compiler::IoChannelInfo, 8 | interner::ToSymbol, 9 | runtime::{vm, Time}, 10 | ExecContext, 11 | }; 12 | 13 | use crate::driver::{Driver, RuntimeData, SampleRate}; 14 | 15 | /// Execute the program n times and write the result values to `localbuffer`. 16 | pub struct LocalBufferDriver { 17 | pub vmdata: Option, 18 | pub count: Arc, 19 | samplerate: SampleRate, 20 | localbuffer: Vec, 21 | times: usize, 22 | } 23 | 24 | impl Default for LocalBufferDriver { 25 | fn default() -> Self { 26 | let count = Arc::new(AtomicU64::new(0)); 27 | 28 | Self { 29 | vmdata: None, 30 | count, 31 | samplerate: SampleRate::from(48000), 32 | localbuffer: vec![], 33 | times: 0, 34 | } 35 | } 36 | } 37 | 38 | impl LocalBufferDriver { 39 | pub fn new(times: usize) -> Self { 40 | let count = Arc::new(AtomicU64::new(0)); 41 | 42 | Self { 43 | vmdata: None, 44 | count, 45 | samplerate: SampleRate::from(48000), 46 | localbuffer: vec![], 47 | times, 48 | } 49 | } 50 | 51 | pub fn get_generated_samples(&self) -> &[::Sample] { 52 | &self.localbuffer 53 | } 54 | } 55 | 56 | impl Driver for LocalBufferDriver { 57 | type Sample = f64; 58 | 59 | fn get_runtimefn_infos(&self) -> Vec { 60 | let getnow = crate::runtime_fn::gen_getnowfn(self.count.clone()); 61 | let getsamplerate = crate::runtime_fn::gen_getsampleratefn(self.samplerate.0.clone()); 62 | 63 | vec![getnow, getsamplerate] 64 | } 65 | 66 | fn init( 67 | &mut self, 68 | mut ctx: ExecContext, 69 | sample_rate: Option, 70 | ) -> Option { 71 | let iochannels = ctx.get_iochannel_count(); 72 | let vm = ctx.take_vm().expect("vm is not prepared yet"); 73 | let dsp_i = vm 74 | .prog 75 | .get_fun_index(&"dsp".to_symbol()) 76 | .expect("no dsp function found"); 77 | let (_, dsp_func) = &vm.prog.global_fn_table[dsp_i]; 78 | 79 | self.localbuffer = Vec::with_capacity(dsp_func.nret * self.times); 80 | self.samplerate = sample_rate.unwrap_or(SampleRate::from(48000)); 81 | 82 | self.vmdata = Some(RuntimeData::new( 83 | vm, 84 | ctx.get_system_plugins().cloned().collect(), 85 | )); 86 | iochannels 87 | } 88 | 89 | fn play(&mut self) -> bool { 90 | let vmdata = self.vmdata.as_mut().expect("Not initialized yet?"); 91 | let iochannels = vmdata.vm.prog.iochannels; 92 | let (_ichannels, ochannels) = iochannels.map_or((0, 0), |io| (io.input, io.output)); 93 | // let _ = vmdata.run_main(); 94 | self.localbuffer.clear(); 95 | for _ in 0..self.times { 96 | let now = self.count.load(Ordering::Relaxed); 97 | let _ = vmdata.run_dsp(Time(now)); 98 | let res = vm::Machine::get_as_array::<::Sample>( 99 | vmdata.vm.get_top_n(ochannels as _), 100 | ); 101 | self.localbuffer.extend_from_slice(res); 102 | //update current time. 103 | self.count.store(now + 1, Ordering::Relaxed); 104 | } 105 | false 106 | } 107 | 108 | fn pause(&mut self) -> bool { 109 | false 110 | } 111 | 112 | fn get_samplerate(&self) -> u32 { 113 | self.samplerate.get() 114 | } 115 | 116 | fn get_current_sample(&self) -> Time { 117 | Time(self.count.load(Ordering::Relaxed)) 118 | } 119 | 120 | fn is_playing(&self) -> bool { 121 | false 122 | } 123 | } 124 | 125 | pub fn local_buffer_driver(times: usize) -> Box> { 126 | Box::new(LocalBufferDriver::new(times)) 127 | } 128 | -------------------------------------------------------------------------------- /mimium-audiodriver/src/driver.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicU32, Ordering}, 3 | Arc, 4 | }; 5 | 6 | use mimium_lang::{ 7 | compiler::IoChannelInfo, interner::ToSymbol, plugin::{DynSystemPlugin, InstantPlugin}, runtime::{ 8 | vm::{self, ExtClsInfo, FuncProto, ReturnCode}, 9 | Time, 10 | }, ExecContext 11 | }; 12 | use num_traits::Float; 13 | 14 | // #[derive(Clone)] 15 | // pub struct PlaybackInfo { 16 | // pub sample_rate: u32, 17 | // pub current_time: usize, 18 | // pub frame_per_buffer: u64, 19 | // pub channels: u64, 20 | // } 21 | 22 | // impl PlaybackInfo { 23 | // pub fn get_current_realtime(&self) -> f32 { 24 | // self.current_time as f32 / self.sample_rate as f32 25 | // } 26 | // pub fn rewind(&mut self) { 27 | // self.current_time = 0; 28 | // } 29 | // } 30 | 31 | // pub trait Component { 32 | // type Sample: Float; 33 | // fn get_input_channels(&self) -> u64; 34 | // fn get_output_channels(&self) -> u64; 35 | // fn prepare_play(&mut self, info: &PlaybackInfo); 36 | // fn render(&mut self, input: &[Self::Sample], output: &mut [Self::Sample], info: &PlaybackInfo); 37 | // } 38 | 39 | #[derive(Clone)] 40 | pub struct SampleRate(pub Arc); 41 | impl From for SampleRate { 42 | fn from(value: u32) -> Self { 43 | Self(Arc::new(value.into())) 44 | } 45 | } 46 | impl SampleRate { 47 | pub fn get(&self) -> u32 { 48 | self.0.load(Ordering::Relaxed) 49 | } 50 | } 51 | 52 | /// Note: `Driver` trait doesn't have `new()` so that the struct can have its own 53 | /// `new()` with any parameters specific to the type. With this in mind, `init()` 54 | /// can accept only common parameters. 55 | pub trait Driver { 56 | type Sample: Float; 57 | fn get_runtimefn_infos(&self) -> Vec; 58 | /// Call ctx.run_main() before moving ctx to Driver with this function. 59 | fn init(&mut self, ctx: ExecContext, sample_rate: Option) -> Option; 60 | fn play(&mut self) -> bool; 61 | fn pause(&mut self) -> bool; 62 | fn get_samplerate(&self) -> u32; 63 | fn get_current_sample(&self) -> Time; 64 | fn is_playing(&self) -> bool; 65 | fn get_as_plugin(&self) -> InstantPlugin { 66 | InstantPlugin { 67 | extfns: vec![], 68 | extcls: self.get_runtimefn_infos(), 69 | } 70 | } 71 | } 72 | 73 | pub struct RuntimeData { 74 | pub vm: vm::Machine, 75 | pub sys_plugins: Vec, 76 | pub dsp_i: usize, 77 | } 78 | impl RuntimeData { 79 | pub fn new(vm: vm::Machine, sys_plugins: Vec) -> Self { 80 | //todo:error handling 81 | let dsp_i = vm.prog.get_fun_index(&"dsp".to_symbol()).unwrap_or(0); 82 | Self { 83 | vm, 84 | sys_plugins, 85 | dsp_i, 86 | } 87 | } 88 | 89 | /// warn: Currently duplicated with ExecContext::run_main. 90 | /// only LocalBufferDriver uses this function. 91 | pub fn run_main(&mut self) -> ReturnCode { 92 | self.sys_plugins.iter().for_each(|plug: &DynSystemPlugin| { 93 | //todo: encapsulate unsafety within SystemPlugin functionality 94 | let p = unsafe { plug.0.get().as_mut().unwrap_unchecked() }; 95 | let _ = p.on_init(&mut self.vm); 96 | }); 97 | let res = self.vm.execute_main(); 98 | self.sys_plugins.iter().for_each(|plug: &DynSystemPlugin| { 99 | //todo: encapsulate unsafety within SystemPlugin functionality 100 | let p = unsafe { plug.0.get().as_mut().unwrap_unchecked() }; 101 | let _ = p.after_main(&mut self.vm); 102 | }); 103 | res 104 | } 105 | pub fn get_dsp_fn(&self) -> &FuncProto { 106 | &self.vm.prog.global_fn_table[self.dsp_i].1 107 | } 108 | pub fn run_dsp(&mut self, time: Time) -> ReturnCode { 109 | self.sys_plugins.iter().for_each(|plug: &DynSystemPlugin| { 110 | //todo: encapsulate unsafety within SystemPlugin functionality 111 | let p = unsafe { plug.0.get().as_mut().unwrap_unchecked() }; 112 | let _ = p.on_sample(time, &mut self.vm); 113 | }); 114 | self.vm.execute_idx(self.dsp_i) 115 | } 116 | } -------------------------------------------------------------------------------- /mimium-audiodriver/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod backends; 2 | pub mod driver; 3 | pub mod runtime_fn; 4 | 5 | pub fn load_default_runtime() -> Box> { 6 | #[cfg(not(target_arch = "wasm32"))] 7 | { 8 | crate::backends::cpal::native_driver(4096) 9 | } 10 | #[cfg(target_arch = "wasm32")] 11 | { 12 | crate::backends::local_buffer::local_buffer_driver(4096) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mimium-audiodriver/src/runtime_fn.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | rc::Rc, 4 | sync::{ 5 | atomic::{AtomicU32, AtomicU64, Ordering}, 6 | Arc, 7 | }, 8 | }; 9 | 10 | use mimium_lang::{ 11 | function, 12 | interner::ToSymbol, 13 | numeric, 14 | runtime::vm::{ExtClsInfo, Machine}, 15 | types::{PType, Type}, 16 | }; 17 | 18 | pub fn gen_getnowfn(count: Arc) -> ExtClsInfo { 19 | let func = Rc::new(RefCell::new(move |machine: &mut Machine| { 20 | let count = count.load(Ordering::Relaxed) as f64; 21 | machine.set_stack(0, Machine::to_value(count)); 22 | 1 23 | })); 24 | ( 25 | "_mimium_getnow".to_symbol(), 26 | func, 27 | function!(vec![], numeric!()), 28 | ) 29 | } 30 | pub fn gen_getsampleratefn(samplerate: Arc) -> ExtClsInfo { 31 | let func = Rc::new(RefCell::new(move |machine: &mut Machine| { 32 | let count = samplerate.load(Ordering::Relaxed) as f64; 33 | machine.set_stack(0, Machine::to_value(count)); 34 | 1 35 | })); 36 | ( 37 | "_mimium_getsamplerate".to_symbol(), 38 | func, 39 | function!(vec![], numeric!()), 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /mimium-audiodriver/tests/getnow.rs: -------------------------------------------------------------------------------- 1 | use mimium_audiodriver::{ 2 | backends::local_buffer::LocalBufferDriver, 3 | driver::{Driver, SampleRate}, 4 | }; 5 | use mimium_lang::{ 6 | compiler::IoChannelInfo, 7 | function, 8 | interner::ToSymbol as _, 9 | numeric, 10 | plugin::Plugin, 11 | runtime::vm::{FuncProto, Instruction, Program}, 12 | types::{PType, Type}, 13 | Config, ExecContext, 14 | }; 15 | 16 | #[test] 17 | fn getnow_test() { 18 | // fn dsp(){ 19 | // now 20 | // } 21 | let inner_insts_main = vec![Instruction::Return0]; 22 | let main_f = FuncProto { 23 | bytecodes: inner_insts_main, 24 | constants: vec![], //cls, int 4 25 | ..Default::default() 26 | }; 27 | let inner_insts = vec![ 28 | Instruction::MoveConst(0, 0), //load constant 0 for closure index 29 | Instruction::CallExtFun(0, 0, 1), //call getnow 30 | Instruction::Return(0, 1), // return single value at 0 31 | ]; 32 | let dsp_f = FuncProto { 33 | nparam: 0, 34 | nret: 1, 35 | bytecodes: inner_insts, 36 | constants: vec![0], //cls, 37 | ..Default::default() 38 | }; 39 | let fns = vec![("main".to_symbol(), main_f), ("dsp".to_symbol(), dsp_f)]; 40 | 41 | let prog = Program { 42 | global_fn_table: fns, 43 | ext_fun_table: vec![("_mimium_getnow".to_symbol(), function!(vec![], numeric!()))], 44 | iochannels: Some(IoChannelInfo { 45 | input: 0, 46 | output: 1, 47 | }), 48 | ..Default::default() 49 | }; 50 | 51 | let times = 10; 52 | let mut driver = LocalBufferDriver::new(times); 53 | let p: Box = Box::new(driver.get_as_plugin()); 54 | let mut ctx = ExecContext::new([p].into_iter(), None, Config::default()); 55 | ctx.prepare_machine_with_bytecode(prog); 56 | let _iochannels = driver.init(ctx, Some(SampleRate::from(48000))); 57 | driver.play(); 58 | 59 | let res = driver.get_generated_samples(); 60 | let answer = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 61 | assert_eq!(res, answer); 62 | } 63 | -------------------------------------------------------------------------------- /mimium-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-cli" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | authors.workspace = true 8 | description = "mimium(minimal-musical-medium) an infrastructural programming language for sound and music. mimium-cli is a simple frontend interface for using mimium" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | # [lib] 12 | 13 | [dependencies] 14 | 15 | clap = { version = "4.5.7", features = ["derive"] } 16 | 17 | mimium-lang = { workspace = true } 18 | mimium-audiodriver = { workspace = true } 19 | mimium-midi = { workspace = true } 20 | mimium-symphonia = { workspace = true } 21 | mimium-scheduler = { workspace = true } 22 | mimium-guitools = { workspace = true } 23 | colog = "1.3.0" 24 | -------------------------------------------------------------------------------- /mimium-cli/examples/0b5vr.mmm: -------------------------------------------------------------------------------- 1 | let BPM = 130.0 2 | let B2T = 60.0 / BPM 3 | 4 | let E = 2.718281828459045 5 | let PI = 3.141592653589793 6 | let TAU = 2 * PI 7 | 8 | fn exp(x) { 9 | E ^ x 10 | } 11 | 12 | fn counter(x) { 13 | self + x 14 | } 15 | 16 | fn integ(x, reset) { 17 | if (reset) { 18 | 0 19 | } else { 20 | self + x 21 | } 22 | } 23 | 24 | fn lerp(a, b, t) { 25 | a * (1 - t) + b * t 26 | } 27 | 28 | fn clamp(x, a, b) { 29 | min(max(x, a), b) 30 | } 31 | 32 | fn saturate(x) { 33 | clamp(x, 0, 1) 34 | } 35 | 36 | fn linearstep(a, b, t) { 37 | saturate((t - a) / (b - a)) 38 | } 39 | 40 | fn smoothstep(a, b, t) { 41 | let x = linearstep(a, b, t) 42 | x * x * (3 - 2 * x) 43 | } 44 | 45 | let myprobe = make_probe("test") 46 | let myprobe2 = make_probe("test2") 47 | 48 | fn dsp() { 49 | let dest = 0.0 50 | let time = now / samplerate 51 | { 52 | let t = time % B2T 53 | let reset = t < 1 / samplerate 54 | 55 | let env = smoothstep(0.3, 0.1, t) 56 | env = env * exp(-100.0 * t) 57 | let freq = 50 58 | freq = freq * lerp(1, 3, exp(-40 * t)) 59 | 60 | let phase = integ(TAU * freq / samplerate, reset) 61 | if(reset){ 62 | probeln(phase) 63 | }else{ 64 | 0 65 | } 66 | dest = dest + env * sin(phase) 67 | } 68 | 69 | dest = myprobe(dest) 70 | 71 | (dest, dest) 72 | } -------------------------------------------------------------------------------- /mimium-cli/examples/biquad.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | include("filter.mmm") 3 | fn dsp(){ 4 | let f = phasor(0.2)*3000.0+200.0; 5 | let sig = saw(220.0,0.0); 6 | let r = lowpass(sig,f,10.0) 7 | (r, r) 8 | } -------------------------------------------------------------------------------- /mimium-cli/examples/cascadeosc.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | fn osc(freq){ 3 | sinwave(freq,0.0) 4 | } 5 | 6 | fn cascade(n,gen:()->(float)->float){ 7 | let g = gen() 8 | if (n>0.0){ 9 | let c = cascade(n - 1.0 ,gen) 10 | |rate| rate + g(rate/2.)* 0.5* rate*(1.0-(1.0/(n*2.0))) |> c 11 | }else{ 12 | |rate| g(rate) 13 | } 14 | } 15 | let n =40.0 16 | let myosc = cascade(n, | | osc); 17 | 18 | fn dsp(){ 19 | let r = myosc(200.0) 20 | (r,r) 21 | } -------------------------------------------------------------------------------- /mimium-cli/examples/fbdelay.mmm: -------------------------------------------------------------------------------- 1 | include ("osc.mmm") 2 | fn osc(freq){ 3 | sinwave(freq,0.0) 4 | } 5 | fn amosc(freq,rate){ 6 | osc(freq)*(1.0- phasor(rate)) 7 | } 8 | fn fbdelay(input,time,fb,mix){ 9 | input*mix + (1.0-mix) * delay(40001.0,(input+self*fb),time) 10 | } 11 | 12 | fn dsp(){ 13 | let f = phasor(1.0)*3000.0+50.0 14 | 15 | let sig = amosc(f,1.0) 16 | fbdelay(sig,40000.0,0.9,0.5) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /mimium-cli/examples/getnow.mmm: -------------------------------------------------------------------------------- 1 | fn dsp(){ 2 | let time = now * 440.0 / samplerate 3 | let nphase = time % 1.0 4 | sin(nphase*6.2831853) 5 | } -------------------------------------------------------------------------------- /mimium-cli/examples/midiin.mmm: -------------------------------------------------------------------------------- 1 | let _ = set_midi_port("from Max 1") 2 | let pi = 3.14159265359 3 | let sr = samplerate 4 | let probe1 = make_probe("gain") 5 | let probe2 = make_probe("out") 6 | fn phasor(freq){ 7 | (self + freq/sr)%1.0 8 | } 9 | fn osc(freq){ 10 | sin(phasor(freq)*pi*2.0) 11 | } 12 | fn midi_to_hz(note){ 13 | 440.0* (2.0 ^((note-69.0)/12.0)) 14 | } 15 | let boundval = bind_midi_note_mono(0.0,69.0,127.0); 16 | fn dsp(){ 17 | let (note,vel) = boundval(); 18 | let sig = note |> midi_to_hz |> osc 19 | let r = sig * probe1((vel /127.0)) 20 | |> probe2; 21 | (r,r) 22 | } -------------------------------------------------------------------------------- /mimium-cli/examples/multiosc.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | 3 | fn osc(freq){ 4 | sinwave(freq,0.0) 5 | } 6 | fn amosc(freq,rate){ 7 | osc(freq + osc(rate)*4000.0) 8 | } 9 | fn replicate(n,gen){ 10 | if (n>0.0){ 11 | let c = replicate(n - 1.0,gen) 12 | let g = gen() 13 | |x,rate| g(x,rate) + c(x+100.0,rate+0.1) 14 | }else{ 15 | |x,rate| 0 16 | } 17 | } 18 | let n = 20.0 19 | let multiosc = replicate(n,| | amosc); 20 | fn dsp(){ 21 | let res = multiosc(4000.0,0.5) / n 22 | res 23 | } -------------------------------------------------------------------------------- /mimium-cli/examples/noise.mmm: -------------------------------------------------------------------------------- 1 | include("noise.mmm") 2 | fn noise(){ 3 | gen_noise(27642.0) 4 | } 5 | fn noise2(){ 6 | gen_noise(12345.0) 7 | } 8 | 9 | fn dsp(){ 10 | let l = noise()*2.0-1.0 11 | let r = noise2()*2.0-1.0 12 | (l,r) 13 | } -------------------------------------------------------------------------------- /mimium-cli/examples/reactive_f.mmm: -------------------------------------------------------------------------------- 1 | include("core.mmm") 2 | 3 | include("osc.mmm") 4 | include("reactive.mmm") 5 | 6 | fn major(){ 7 | let prev = self 8 | let incr = if (prev==4 ||prev==11 || prev == 12) 1 else 2 9 | (prev+incr)%13 10 | } 11 | 12 | let mynote:()->float = metro(0.5*samplerate ,major); 13 | 14 | fn dsp(){ 15 | let r = sinwave(mynote()+60 |> midi_to_hz ,0.0) * 0.5 16 | (r,r) 17 | } -------------------------------------------------------------------------------- /mimium-cli/examples/reactive_sequencer.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | include("env.mmm") 3 | include("filter.mmm") 4 | include("delay.mmm") 5 | include("reverb.mmm") 6 | include("reactive.mmm") 7 | let myprobe = make_probe("test1") 8 | let myprobe2 = make_probe("test2") 9 | fn counter(max){ 10 | (self+1)%max 11 | } 12 | fn rhythm1(){ 13 | (counter(64) % 8) == 0 || (counter(32) % 2) == 0 || (counter(32) % 3)==0 14 | } 15 | fn rhythm2(){ 16 | (counter(64) % 2) == 0 || (counter(64) % 5) == 0 17 | } 18 | let bpm = 40; 19 | let beat1 = 8 * (bpm / 60) 20 | let beat2 = 16 * (bpm / 60) 21 | fn beat_to_interval(beat){ 22 | samplerate / beat 23 | } 24 | fn myreverb(input){ 25 | let damp = 0.3 26 | let roomsize = 0.8 27 | let width = 1.0 28 | freeverb_stereo(input,damp,roomsize,width,0.8) 29 | } 30 | fn mydelay(input){ 31 | pingpong_delay((input*0.7,input*0.3),0.3*samplerate,0.2,0.3) 32 | } 33 | let gate1:()->float = metro(beat1 |> beat_to_interval ,rhythm1); 34 | let gate2:()->float = metro(beat2 |> beat_to_interval ,rhythm2); 35 | fn synth(gate){ 36 | let g = gate > 0 37 | let f = 80 + adsr(0.001,0.2,0.0,0.1,g) * 30 38 | let cf = 100 + adsr(0.01,0.2,0.0,0.1,g) * 400 39 | let sig = lowpass(saw(f,0.0),cf,4.0) 40 | sig * adsr(0.0001,0.2,0.0,0.1,gate) 41 | } 42 | fn dsp(){ 43 | let gate = (gate1() * gate2()) |> myprobe 44 | let r = synth(gate)*0.5 |> myprobe2 |> mydelay |> myreverb ; 45 | (r,r) 46 | } 47 | 48 | -------------------------------------------------------------------------------- /mimium-cli/examples/sinewave.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | fn dsp(){ 3 | let r = sinwave(440.0,0.0)*0.2 4 | (r,r) 5 | } -------------------------------------------------------------------------------- /mimium-cli/examples/subtract_synth_demo.mmm: -------------------------------------------------------------------------------- 1 | include("osc.mmm") 2 | include("env.mmm") 3 | include("filter.mmm") 4 | fn midi_to_hz(note){ 5 | 440.0*(2.0^ ((note-69.0)/12.0) ) 6 | } 7 | // let _ = set_midi_port("from Max 1") 8 | let boundval = bind_midi_note_mono(0.0,69.0,127.0); 9 | let probe1 = make_probe("out") 10 | 11 | fn osc(freq){ 12 | saw(freq,0.0) 13 | } 14 | fn my_synth(note,vel,cutoff_base,fenv_depth){ 15 | let gain = vel/127 16 | let gain_env = adsr(0.5,0.2,0.5,2.0,gain) 17 | let cutfreq = cutoff_base + adsr(0.6,0.3,0.3,2.0,gain) * fenv_depth 18 | let sig = note 19 | |> midi_to_hz 20 | |> osc; 21 | lowpass(sig,cutfreq,5.0)*gain_env 22 | } 23 | fn dsp(){ 24 | let (note,vel) = boundval(); 25 | let r = my_synth(note,vel,400,3000) |> probe1 26 | (r,r) 27 | } -------------------------------------------------------------------------------- /mimium-cli/lib/core.mmm: -------------------------------------------------------------------------------- 1 | include("math.mmm") 2 | 3 | fn mix(gain,a,b){ 4 | gain*a + (1.0-gain)*b 5 | } 6 | fn switch(gate,a,b){ 7 | let gain = if (gate>0.0) 1.0 else 0.0 8 | mix(gain,a,b) 9 | } 10 | 11 | fn midi_to_hz(note){ 12 | 440.0*(2.0^ ((note-69.0)/12.0) ) 13 | } 14 | 15 | fn hz_to_midi(freq){ 16 | 12.0*log2(freq/440.0) + 69.0 17 | } 18 | 19 | fn dbtolinear(x){ 20 | pow(10.0, x/20.0) 21 | } 22 | 23 | fn linear2db(g) { 24 | 20.0*log10(g) 25 | } 26 | -------------------------------------------------------------------------------- /mimium-cli/lib/delay.mmm: -------------------------------------------------------------------------------- 1 | fn _delay(input,time){ 2 | delay(48000,input,time) 3 | } 4 | 5 | fn pingpong_delay_inner(input:(float,float),time,fb)->(float,float){ 6 | let (left_i,right_i) = input 7 | let (left_fb, right_fb) = self 8 | 9 | let left_res = _delay(left_i+right_fb*fb ,time) 10 | let right_res = _delay(right_i+left_fb*fb , time) 11 | (left_res,right_res) 12 | } 13 | fn pingpong_delay(input,time,fb,mix){ 14 | let (left_dry,right_dry) = input 15 | let (left_wet,right_wet) = pingpong_delay_inner(input,time,fb) 16 | let mix_inv = 1-mix 17 | (left_dry*mix_inv + left_wet*mix, right_dry*mix_inv + right_wet*mix ) 18 | } -------------------------------------------------------------------------------- /mimium-cli/lib/env.mmm: -------------------------------------------------------------------------------- 1 | fn sec2samp(sec){ 2 | sec*samplerate 3 | } 4 | fn countup(active){ 5 | let r =(self+1.0); 6 | let rr = if (active>0) r else 0.0 7 | rr 8 | } 9 | fn countupn(time,active){ 10 | let res = countup(active) 11 | let r = if(res0.0 16 | } 17 | fn adsr(attack,decay,sustain,release,input){ 18 | let s = self; 19 | let at_or_dec = hold(sec2samp(attack),input) 20 | let atsig = min(1.0,(s + 1.0/sec2samp(attack))) 21 | let decsig = max(sustain,(s-1.0/sec2samp(decay))) 22 | let releasesig =max(0.0,(s-1.0/sec2samp(release))) 23 | let at_dec_sus_sig = if (at_or_dec>0.0) atsig else decsig 24 | let res = if (input>0.0) at_dec_sus_sig else releasesig 25 | res 26 | } -------------------------------------------------------------------------------- /mimium-cli/lib/filter.mmm: -------------------------------------------------------------------------------- 1 | include("math.mmm") 2 | fn onepole(x, ratio){ 3 | x*(1.0-ratio) + self*ratio 4 | } 5 | 6 | fn smooth(x){ 7 | onepole(x, 0.95) 8 | } 9 | 10 | fn gen_hp_coeff(fc,q,fs){ 11 | let omega = 2.0 * PI* fc / fs 12 | let alpha = sin(omega)/(2.0* q) 13 | let norm = 1.0 + alpha 14 | let a1 = -2.0 * cos(omega) / norm 15 | let a2 = (1.0 - alpha)/ norm 16 | let btmp = 1.0 + cos(omega) 17 | let b0 = btmp / (2.0*norm) 18 | let b1 = -btmp / norm 19 | let b2 = b0 20 | (a1,a2,b0,b1,b2) 21 | } 22 | fn gen_lp_coeff(fc,q,fs){ 23 | let omega = 2.0 * PI* fc / fs 24 | let alpha = sin(omega)/(2.0* q) 25 | let norm = 1.0 + alpha 26 | let a1 = -2.0 * cos(omega) / norm 27 | let a2 = (1.0 - alpha)/ norm 28 | let btmp = 1.0 - cos(omega) 29 | let b0 = btmp / (2.0*norm) 30 | let b1 = btmp / norm 31 | let b2 = b0 32 | (a1,a2,b0,b1,b2) 33 | } 34 | 35 | fn gen_bp_coeff(fc,q,fs){ 36 | let omega = 2.0 * PI* fc / fs 37 | let alpha = sin(omega)/(2.0* q) 38 | let norm = 1.0 + alpha 39 | let a1 = -2.0 * cos(omega) / norm 40 | let a2 = (1.0 - alpha)/ norm 41 | let b0 = sin(omega) / (2.0*norm) 42 | let b1 = 0 43 | let b2 = -b0 44 | (a1,a2,b0,b1,b2) 45 | } 46 | 47 | fn gen_ap_coeff(fc,q,fs){ 48 | let omega = 2.0 * PI* fc / fs 49 | let alpha = sin(omega)/(2.0* q) 50 | let norm = 1.0 + alpha 51 | let a1 = -2.0 * cos(omega) / norm 52 | let a2 = (1.0 - alpha)/ norm 53 | let b0 = a2 54 | let b1 = a1 55 | let b2 = 1.0 56 | (a1,a2,b0,b1,b2) 57 | } 58 | 59 | fn gen_peaking_coeff(fc,gain_db,q,fs){ 60 | let A = 10 ^ (gain_db/40) 61 | let omega = 2.0 * PI* fc / fs 62 | let alpha = sin(omega)/(2.0* q) 63 | let norm = 1 + alpha/A 64 | let b0 = 1.0 65 | let b1 = -2*cos(omega)/norm 66 | let b2 = (1 - alpha * A) / norm 67 | let a1 = b1 68 | let a2 = (1 - alpha /A)/norm 69 | (a1,a2,b0,b1,b2) 70 | } 71 | 72 | fn _biquad_inner (x,a1,a2){ 73 | let (ws, wss, _wsss) = self 74 | let w = x - a1*ws - a2*wss 75 | (w, ws, wss) 76 | } 77 | fn biquad(x,coeffs){ 78 | let (a1,a2,b0,b1,b2) = coeffs; 79 | let (w,ws,wss) = _biquad_inner(x,a1,a2); 80 | b0*w + b1*ws + b2*wss 81 | } 82 | 83 | fn lowpass(x,fc,q){ 84 | biquad(x,gen_lp_coeff(fc,q,samplerate)) 85 | } 86 | 87 | fn highpass(x,fc,q){ 88 | biquad(x,gen_hp_coeff(fc,q,samplerate)) 89 | } 90 | fn bandpass(x,fc,q){ 91 | biquad(x,gen_bp_coeff(fc,q,samplerate)) 92 | } 93 | fn allpass(x,fc,q){ 94 | biquad(x,gen_ap_coeff(fc,q,samplerate)) 95 | } 96 | 97 | fn peakeq(x,fc,gaindb,q){ 98 | biquad(x,gen_peaking_coeff(fc,gaindb,q,samplerate)) 99 | } -------------------------------------------------------------------------------- /mimium-cli/lib/math.mmm: -------------------------------------------------------------------------------- 1 | //built-in "log" function means that the base is napier(x.ln() in rust). 2 | 3 | let PI = 3.14159265359 4 | fn log2(x){ 5 | log(x)/log(2.0) 6 | } 7 | 8 | fn log10(x){ 9 | log(x)/log(10.0) 10 | } -------------------------------------------------------------------------------- /mimium-cli/lib/noise.mmm: -------------------------------------------------------------------------------- 1 | let RANDMAX = 2147483647.0 2 | 3 | fn gen_rand(seed){ 4 | (self*48271.0) % (2.0^31.0 -1.0)+seed 5 | } 6 | fn gen_noise(seed){ 7 | gen_rand(seed)/RANDMAX 8 | } 9 | 10 | fn noise(){ 11 | gen_noise(1.0) 12 | } -------------------------------------------------------------------------------- /mimium-cli/lib/osc.mmm: -------------------------------------------------------------------------------- 1 | include("math.mmm") 2 | 3 | fn phasor(freq){ 4 | (self + freq/samplerate)%1.0 5 | } 6 | 7 | fn phasor_shift(freq,phase_shift){ 8 | (self + freq/samplerate + phase_shift)%1.0 9 | } 10 | 11 | fn saw(freq,phase){ 12 | phasor_shift(freq,phase) * 2.0 -1.0 13 | } 14 | 15 | fn triangle(freq,phase){ 16 | let p = phasor_shift(freq,phase); 17 | let raw = if (p < 0.25) p* -1.0+0.5 else if (p>0.75) p * -1.0+1.50 else p 18 | (raw-0.5)*4.0 19 | } 20 | 21 | fn sinwave(freq,phase){ 22 | phasor_shift(freq,phase)*2.0*PI |> sin 23 | } -------------------------------------------------------------------------------- /mimium-cli/lib/reactive.mmm: -------------------------------------------------------------------------------- 1 | fn metro(interval,sig:()->float)->()->float{ 2 | let v = 0.0 3 | letrec updater = | |{ 4 | let s:float =sig(); 5 | v = s 6 | let _ = updater@(now+interval); 7 | } 8 | let _ = updater@(now+1) 9 | | | {v} 10 | } -------------------------------------------------------------------------------- /mimium-cli/lib/reverb.mmm: -------------------------------------------------------------------------------- 1 | include("filter.mmm") 2 | 3 | //damp should be 0-1 4 | fn _damp_filter(x,damp){ 5 | let damp_scaled = 0.4; 6 | onepole(x,damp*damp_scaled) 7 | } 8 | //roomsize should be 0-1 9 | fn _lp_fb_comb(input,time,damp,roomsize){ 10 | let room_scaled = roomsize*0.28 + 0.7 11 | let sum = input+ room_scaled* _damp_filter(self,damp) 12 | delay(1800,input,time) 13 | } 14 | fn _lfc_bank(input,damp,roomsize){ 15 | _lp_fb_comb(input,1557,damp,roomsize) + 16 | _lp_fb_comb(input,1617,damp,roomsize) + 17 | _lp_fb_comb(input,1491,damp,roomsize) + 18 | _lp_fb_comb(input,1422,damp,roomsize) + 19 | _lp_fb_comb(input,1277,damp,roomsize) + 20 | _lp_fb_comb(input,1356,damp,roomsize) + 21 | _lp_fb_comb(input,1188,damp,roomsize) + 22 | _lp_fb_comb(input,1116,damp,roomsize) 23 | } 24 | 25 | fn allpass_delay(input,N){ 26 | delay(600,input,N) 27 | } 28 | fn _freeverb_allpass(input,gain,N){ 29 | let ffdelayed = allpass_delay(input + self*gain ,N) 30 | -gain * input + (1-gain*gain) * ffdelayed 31 | } 32 | fn _allpass1(input){ 33 | _freeverb_allpass(input,0.5,225) 34 | } 35 | fn _allpass2(input){ 36 | _freeverb_allpass(input,0.5,556) 37 | } 38 | fn _allpass3(input){ 39 | _freeverb_allpass(input,0.5,441) 40 | } 41 | fn _allpass4(input){ 42 | _freeverb_allpass(input,0.5,341) 43 | } 44 | fn freeverb_mono(input,damp,roomsize,mix:float)->float{ 45 | let lf = _lfc_bank(input,damp,roomsize) 46 | let out = lf |> _allpass1 |> _allpass2|> _allpass3 |> _allpass4 ; 47 | let r = input*(1-mix)+out*mix 48 | r 49 | } 50 | fn _scalewidth(x){ 51 | x * 0.5 + 0.5 52 | } 53 | fn freeverb_stereo(input:(float,float),damp,roomsize,width,mix_2){ 54 | let (left,right) = input 55 | let left_wet = freeverb_mono(left,damp,roomsize,1.0) 56 | let right_wet = freeverb_mono(right,damp,roomsize,1.0) 57 | let mix_inv = 1-mix_2 58 | let left_out = left*mix_inv + left_wet*_scalewidth(mix_2)+right_wet*_scalewidth(1-mix_2) 59 | let right_out = left*mix_inv + left_wet*_scalewidth(mix_2)+right_wet*_scalewidth(1-mix_2) 60 | 61 | (left_out,right_out) 62 | } -------------------------------------------------------------------------------- /mimium-guitools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-guitools" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "basic analyzer tool plugin with egui for mimium." 8 | 9 | [package.metadata.dist] 10 | dist = false 11 | 12 | 13 | [dependencies] 14 | egui = "0.29.1" 15 | egui_plot = "0.29.0" 16 | mimium-lang = { workspace = true } 17 | eframe = { version = "0.29.1", default-features = false, features = [ 18 | "default_fonts", # Embed the default egui fonts. 19 | "glow", # Use the glow rendering backend. Alternative: "wgpu". 20 | "persistence", # Enable restoring app state when restarting the app. 21 | ] } 22 | ringbuf = "0.4.7" 23 | -------------------------------------------------------------------------------- /mimium-guitools/examples/plot.mmm: -------------------------------------------------------------------------------- 1 | let pi = 3.14159265359 2 | let sr = 48000.0 3 | fn phasor(freq){ 4 | (self + freq/sr)%1.0 5 | } 6 | fn osc(freq){ 7 | sin(phasor(freq)*pi*2.0) 8 | } 9 | fn fmosc(freq,rate){ 10 | osc(freq + osc(rate)*4000.0) 11 | } 12 | fn amosc(input,rate){ 13 | input * (osc(rate)+1.0/ 2) 14 | } 15 | 16 | 17 | let probel = make_probe("left") 18 | let prober = make_probe("right") 19 | 20 | fn dsp(){ 21 | let l = probel(amosc(fmosc(440,0.02) , 0.2)) 22 | let r = prober(amosc(fmosc(220,0.03) , 0.3)) 23 | (l,r) 24 | } -------------------------------------------------------------------------------- /mimium-guitools/examples/plot_and_midi.mmm: -------------------------------------------------------------------------------- 1 | let pi = 3.14159265359 2 | let sr = 48000.0 3 | let _ = set_midi_port("from Max 1") 4 | fn sec2samp(sec){ 5 | sec*48000.0 6 | } 7 | fn countup(active){ 8 | let r =(self+1.0); 9 | let rr = if (active) r else 0.0 10 | rr 11 | } 12 | fn countupn(time,active){ 13 | let res = countup(active) 14 | let r = if(res0.0 19 | } 20 | fn get_gain(gate){ 21 | if (gate>0.1) gate else self 22 | } 23 | fn adsr(attack,decay,sustain,release,input){ 24 | let s = self 25 | let atsig = min(1.0,(s + 1.0/sec2samp(attack))) 26 | let decsig = max(sustain,(s-1.0/sec2samp(decay))) 27 | let releasesig =max(0.0,(s-1.0/sec2samp(release))) 28 | let at_or_dec = hold(sec2samp(attack),input>0.1) 29 | let at_dec_sus_sig = if (at_or_dec>0.1) atsig else decsig 30 | let res = if (input>0.1) at_dec_sus_sig else releasesig 31 | res 32 | } 33 | 34 | fn phasor(freq){ 35 | (self + freq/sr)%1.0 36 | } 37 | fn osc(freq){ 38 | sin(phasor(freq)*pi*2.0) 39 | } 40 | 41 | fn midi_to_hz(note){ 42 | 440.0* (2.0 ^((note-69.0)/12.0)) 43 | } 44 | fn myadsr(gate){ 45 | adsr(0.01,0.1,1.0,2.0,gate) *get_gain(gate) 46 | } 47 | fn synth(midiv){ 48 | let (note,vel) = midiv 49 | let sig = note |> midi_to_hz |> osc 50 | let gain = vel /127.0; 51 | sig * gain 52 | } 53 | 54 | let ch0 = bind_midi_note_mono(0.0,69.0,0.0); 55 | 56 | let probe = make_probe("sig") 57 | fn dsp(){ 58 | let l = probe(ch0() |> synth) 59 | (l,l) 60 | } -------------------------------------------------------------------------------- /mimium-guitools/readme.md: -------------------------------------------------------------------------------- 1 | # mimium-guitools 2 | 3 | This plugin provides various ways to interact gui through `egui` crate. Works only on native architecture. 4 | 5 | ## Plot signals 6 | 7 | ```rust 8 | fn osc(freq){ 9 | ... 10 | } 11 | let probe1 = make_probe("probe_label1")//when more than 1 probes are created, gui window will be launched on the startup. 12 | 13 | fn dsp(){ 14 | let r = probe1(osc(440)) // probe closure returns original value. 15 | (r,r) 16 | } 17 | 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /mimium-guitools/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use mimium_lang::{ 4 | function, 5 | interner::{ToSymbol, TypeNodeId}, 6 | log, numeric, 7 | plugin::{SysPluginSignature, SystemPlugin, SystemPluginFnType}, 8 | runtime::vm::{ExtClsInfo, Machine, ReturnCode}, 9 | string_t, 10 | types::{PType, Type}, 11 | }; 12 | use plot_window::PlotApp; 13 | use ringbuf::{ 14 | traits::{Producer, Split}, 15 | HeapRb, 16 | }; 17 | pub(crate) mod plot_ui; 18 | pub mod plot_window; 19 | 20 | pub struct GuiToolPlugin { 21 | window: Option, 22 | } 23 | 24 | impl Default for GuiToolPlugin { 25 | fn default() -> Self { 26 | Self { 27 | window: Some(PlotApp::default()), 28 | } 29 | } 30 | } 31 | 32 | impl GuiToolPlugin { 33 | fn get_closure_type() -> TypeNodeId { 34 | function!(vec![numeric!()], numeric!()) 35 | } 36 | 37 | /// This method is exposed as "make_probe(label:String)->(float)->float". 38 | pub fn make_probe(&mut self, vm: &mut Machine) -> ReturnCode { 39 | if let Some(app) = self.window.as_mut() { 40 | let idx = vm.get_stack(0); 41 | let probename = vm.prog.strings[idx as usize].as_str(); 42 | 43 | let (mut prod, cons) = HeapRb::::new(4096).split(); 44 | app.add_plot(probename, cons); 45 | let cb = move |vm: &mut Machine| -> ReturnCode { 46 | let v = Machine::get_as::(vm.get_stack(0)); 47 | let _ = prod.try_push(v); 48 | //do not modify any stack values 49 | 1 50 | }; 51 | let info: ExtClsInfo = ( 52 | "probegetter".to_symbol(), 53 | Rc::new(RefCell::new(cb)), 54 | Self::get_closure_type(), 55 | ); 56 | let cls = vm.wrap_extern_cls(info); 57 | vm.set_stack(0, Machine::to_value(cls)); 58 | } else { 59 | log::warn!("make_probe called other than global context."); 60 | } 61 | 1 62 | } 63 | } 64 | impl SystemPlugin for GuiToolPlugin { 65 | fn try_get_main_loop(&mut self) -> Option> { 66 | #[cfg(not(target_arch = "wasm32"))] 67 | { 68 | let make_window = self.window.as_ref().is_some_and(|w| !w.is_empty()); 69 | make_window 70 | .then(|| { 71 | self.window.take().map(|window| -> Box { 72 | Box::new(move || { 73 | let native_options = eframe::NativeOptions { 74 | viewport: egui::ViewportBuilder::default() 75 | .with_inner_size([400.0, 300.0]) 76 | .with_min_inner_size([300.0, 220.0]), // .with_icon( 77 | // // NOTE: Adding an icon is optional 78 | // eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) 79 | // .expect("Failed to load icon"),) 80 | ..Default::default() 81 | }; 82 | let _ = eframe::run_native( 83 | "mimium guitools", 84 | native_options, 85 | Box::new(|_cc| Ok(Box::new(window))), 86 | ) 87 | .inspect_err(|e| log::error!("{e}")); 88 | }) 89 | }) 90 | }) 91 | .flatten() 92 | } 93 | 94 | #[cfg(target_arch = "wasm32")] 95 | None 96 | } 97 | fn gen_interfaces(&self) -> Vec { 98 | let ty = function!(vec![string_t!()], Self::get_closure_type()); 99 | let fptr: SystemPluginFnType = Self::make_probe; 100 | let make_probe = SysPluginSignature::new("make_probe", fptr, ty); 101 | vec![make_probe] 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mimium-guitools/src/main.rs: -------------------------------------------------------------------------------- 1 | /// Standalone app for testing. 2 | /// 3 | use mimium_guitools::plot_window::PlotApp; 4 | 5 | // When compiling natively: 6 | 7 | #[derive(Debug)] 8 | struct Error(String); 9 | impl std::fmt::Display for Error { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | write!(f, "{}", self.0) 12 | } 13 | } 14 | impl std::error::Error for Error {} 15 | fn main() -> eframe::Result { 16 | // env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). 17 | #[cfg(not(target_arch = "wasm32"))] 18 | { 19 | let native_options = eframe::NativeOptions { 20 | viewport: egui::ViewportBuilder::default() 21 | .with_inner_size([400.0, 300.0]) 22 | .with_min_inner_size([300.0, 220.0]), // .with_icon( 23 | // // NOTE: Adding an icon is optional 24 | // eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) 25 | // .expect("Failed to load icon"),) 26 | ..Default::default() 27 | }; 28 | eframe::run_native( 29 | "mimium guitools", 30 | native_options, 31 | Box::new(|_cc| Ok(Box::new(PlotApp::new_test()))), 32 | ) 33 | } 34 | #[cfg(target_arch = "wasm32")] 35 | Err(eframe::Error::AppCreation(Box::new(Error( 36 | "mimium-guitools is not supported on wasm32.".to_string(), 37 | )))) 38 | } 39 | -------------------------------------------------------------------------------- /mimium-guitools/src/plot_ui.rs: -------------------------------------------------------------------------------- 1 | use egui::Color32; 2 | use egui_plot::{Line, LineStyle, PlotPoints}; 3 | use ringbuf::{traits::Consumer, HeapCons, HeapRb}; 4 | pub(crate) struct PlotUi { 5 | label: String, 6 | color: Color32, 7 | buf: HeapCons, 8 | local_buf: Vec, 9 | update_index: usize, 10 | } 11 | impl PlotUi { 12 | pub fn new(label: &str, buf: HeapCons, color: Color32) -> Self { 13 | let local_buf = (0..4096).map(|_| 0.0).collect(); 14 | 15 | Self { 16 | label: label.to_string(), 17 | color, 18 | buf, 19 | local_buf, 20 | update_index: 0, 21 | } 22 | } 23 | pub fn new_test(label: &str) -> Self { 24 | let local_buf = (0..4096) 25 | .map(|t| (std::f64::consts::PI * 2.0 * (t as f64 / 4096.0)).sin()) 26 | .collect(); 27 | Self { 28 | label: label.to_string(), 29 | color: Color32::from_rgb(255, 0, 0), 30 | buf: HeapCons::new(HeapRb::new(10).into()), 31 | local_buf, 32 | update_index: 0, 33 | } 34 | } 35 | pub(crate) fn draw_line(&mut self) -> (bool, Line) { 36 | let mut request_repaint = false; 37 | while let Some(s) = self.buf.try_pop() { 38 | request_repaint = true; 39 | self.local_buf[self.update_index] = s; 40 | self.update_index = (self.update_index + 1) % self.local_buf.len(); 41 | } 42 | 43 | let line = Line::new(PlotPoints::from_parametric_callback( 44 | |t| (t, self.local_buf[t as usize]), 45 | 0.0..4096.0, 46 | 4096, 47 | )) 48 | // .color(...) 49 | .color(self.color) 50 | .style(LineStyle::Solid) 51 | .name(&self.label); 52 | (request_repaint, line) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mimium-guitools/src/plot_window.rs: -------------------------------------------------------------------------------- 1 | use crate::plot_ui::{self, PlotUi}; 2 | use eframe; 3 | 4 | use egui::Color32; 5 | use egui_plot::{CoordinatesFormatter, Corner, Legend, Plot}; 6 | use ringbuf::HeapCons; 7 | 8 | #[derive(Default)] 9 | pub struct PlotApp { 10 | plot: Vec, 11 | hue: f32, 12 | autoscale: bool, 13 | } 14 | 15 | impl PlotApp { 16 | pub fn new_test() -> Self { 17 | let plot = vec![PlotUi::new_test("test")]; 18 | Self { 19 | plot, 20 | hue: 0.0, 21 | autoscale: false, 22 | } 23 | } 24 | const HUE_MARGIN: f32 = 1.0 / 8.0 + 0.3; 25 | pub fn add_plot(&mut self, label: &str, buf: HeapCons) { 26 | let [r, g, b] = egui::ecolor::Hsva::new(self.hue, 0.7, 0.7, 1.0).to_srgb(); 27 | self.hue += Self::HUE_MARGIN; 28 | self.plot.push(PlotUi::new( 29 | label, 30 | buf, 31 | Color32::from_rgba_premultiplied(r, g, b, 200), 32 | )) 33 | } 34 | pub fn is_empty(&self) -> bool { 35 | self.plot.is_empty() 36 | } 37 | } 38 | 39 | impl eframe::App for PlotApp { 40 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 41 | egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { 42 | // The top panel is often a good place for a menu bar: 43 | 44 | egui::menu::bar(ui, |ui| { 45 | egui::widgets::global_theme_preference_buttons(ui); 46 | ui.add_space(16.0); 47 | use egui::special_emojis::GITHUB; 48 | ui.hyperlink_to( 49 | format!("{GITHUB} mimium-rs on GitHub"), 50 | "https://github.com/tomoyanonymous/mimium-rs", 51 | ); 52 | ui.checkbox(&mut self.autoscale, "Auto Scale") 53 | }); 54 | }); 55 | 56 | egui::CentralPanel::default().show(ctx, |ui| { 57 | let plot = Plot::new("lines_demo") 58 | .legend(Legend::default()) 59 | .show_axes(true) 60 | .show_grid(true) 61 | .auto_bounds([true, self.autoscale].into()) 62 | .coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default()); 63 | 64 | plot.show(ui, |plot_ui| { 65 | self.plot.iter_mut().for_each(|line| { 66 | let (_req_repaint, line) = line.draw_line(); 67 | plot_ui.line(line); 68 | }) 69 | }); 70 | 71 | ui.ctx().request_repaint(); 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mimium-lang/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-lang" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "mimium(minimal-musical-medium) an infrastructural programming language for sound and music." 8 | readme.workspace = true 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | chumsky = { version = "0.9", default-features = false, features = ["std"] } 15 | ariadne = "0.4" 16 | log = "0.4.22" 17 | string-interner = "0.17.0" 18 | slotmap = "1.0.7" 19 | colog = "1.3.0" 20 | half = "2.4.1" 21 | intx = "0.1.0" 22 | itertools.workspace = true 23 | wasm-bindgen.workspace = true 24 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 25 | homedir = { version = "0.3.4" } 26 | mimalloc = { version = "0.1.43" } 27 | 28 | [dev-dependencies] 29 | wasm-bindgen-test = { workspace = true } 30 | 31 | [lints.clippy] 32 | useless_format = "allow" 33 | expect_fun_call = "allow" 34 | -------------------------------------------------------------------------------- /mimium-lang/benches/bench.rs: -------------------------------------------------------------------------------- 1 | // If you want to run benchmark, you need to run with nightly channel. Run with `cargo +nightly bench`. 2 | #![feature(test)] 3 | extern crate test; 4 | 5 | fn main() { 6 | // 省略 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | 12 | mod runtime { 13 | use mimium_lang::compiler::{self, Config}; 14 | use mimium_lang::interner::ToSymbol; 15 | use mimium_lang::runtime::vm::Machine; 16 | use test::Bencher; 17 | 18 | fn make_multiosc_src(n: usize) -> String { 19 | format!( 20 | "let pi = 3.14159265359 21 | let sr = 44100.0 22 | fn phasor(freq){{ 23 | (self + freq/sr)%1.0 24 | }} 25 | fn osc(freq){{ 26 | sin(phasor(freq)*pi*2.0) 27 | }} 28 | fn amosc(freq,rate){{ 29 | osc(freq + osc(rate)*100.0) 30 | }} 31 | 32 | fn replicate(n:float,gen:()->(float,float)->float){{ 33 | if (n>0.0){{ 34 | let c = replicate(n - 1.0,gen) 35 | let g = gen() 36 | |x,rate| {{g(x,rate) + c(x+100.0,rate+0.5)}} 37 | }}else{{ 38 | |x,rate| {{ 0.0 }} 39 | }} 40 | }} 41 | let mycounter = replicate({n}.0,| |amosc); 42 | fn dsp(){{ 43 | mycounter(500.0,0.5)*0.1 44 | }}" 45 | ) 46 | } 47 | 48 | fn bench_runtime(b: &mut Bencher, content: &str, times: usize) { 49 | let compiler = compiler::Context::new([], None, Config::default()); 50 | let program = compiler.emit_bytecode(content).expect("ok"); 51 | let idx = program.get_fun_index(&"dsp".to_symbol()).expect("ok"); 52 | let mut machine = Machine::new(program, [].into_iter(), [].into_iter()); 53 | machine.execute_main(); 54 | b.iter(move || { 55 | for _i in 0..times { 56 | let _ = machine.execute_idx(idx); 57 | } 58 | }); 59 | } 60 | #[bench] 61 | fn bench_multiosc5(b: &mut Bencher) { 62 | bench_runtime(b, &make_multiosc_src(5), 1); 63 | } 64 | #[bench] 65 | fn bench_multiosc7(b: &mut Bencher) { 66 | bench_runtime(b, &make_multiosc_src(7), 1); 67 | } 68 | #[bench] 69 | fn bench_multiosc10(b: &mut Bencher) { 70 | bench_runtime(b, &make_multiosc_src(9), 1); 71 | } 72 | #[bench] 73 | fn bench_multiosc15(b: &mut Bencher) { 74 | bench_runtime(b, &make_multiosc_src(15), 1); 75 | } 76 | fn make_partialapp_src_from_template(c: &str) -> String { 77 | format!( 78 | "let pi = 3.14159265359 79 | let sr = 44100.0 80 | fn phasor(freq,phase){{ 81 | (self + freq/sr + phase)%1.0 82 | }} 83 | fn osc(freq,phase){{ 84 | sin(phasor(freq,phase)*pi*2.0) 85 | }} 86 | fn dsp(){{ 87 | {c} 88 | }}" 89 | ) 90 | } 91 | fn make_partialapp_src() -> String { 92 | make_partialapp_src_from_template("440 |> osc(_,0.0)") 93 | } 94 | fn make_no_partialapp_src() -> String { 95 | make_partialapp_src_from_template("osc(440,0.0)") 96 | } 97 | //test the performance degradation when open closure is made on `dsp` function with partial application. 98 | #[bench] 99 | fn bench_partialapp(b: &mut Bencher) { 100 | bench_runtime(b, &make_partialapp_src(), 10); 101 | } 102 | #[bench] 103 | fn bench_partialapp_no(b: &mut Bencher) { 104 | bench_runtime(b, &make_no_partialapp_src(), 10); 105 | } 106 | } 107 | mod parse { 108 | use mimium_lang::compiler::{self, Config}; 109 | use test::Bencher; 110 | 111 | fn gen_fn(fn_name: &str, n: usize) -> String { 112 | let vars = (0..n).map(|i| format!("x{n}_{i}")).collect::>(); 113 | // duplicate variables meaninglessly 114 | let let_statements = vars 115 | .iter() 116 | .map(|v| format!(" let {v} = x{n}")) 117 | .collect::>() 118 | .join("\n"); 119 | let calc = vars 120 | .iter() 121 | .fold(format!("x{n}"), |acc, e| format!("({acc} * 0.1 + {e})")); 122 | format!( 123 | " 124 | fn {fn_name}(x{n}:float) {{ 125 | {let_statements} 126 | {calc} 127 | }} 128 | " 129 | ) 130 | } 131 | 132 | fn make_many_symbols_src(n: usize) -> String { 133 | let fns = (0..n).map(|i| format!("f{i}")).collect::>(); 134 | let fn_declarations = fns 135 | .iter() 136 | .map(|fn_name| gen_fn(fn_name, n)) 137 | .collect::>() 138 | .join("\n\n"); 139 | let fn_sum = fns 140 | .iter() 141 | .map(|fn_name| format!("{fn_name}(1.0)")) 142 | .collect::>() 143 | .join(" + "); 144 | format!( 145 | " 146 | {fn_declarations} 147 | 148 | fn dsp() {{ 149 | {fn_sum} 150 | }} 151 | " 152 | ) 153 | } 154 | 155 | fn bench_many_symbols(b: &mut Bencher, n: usize) { 156 | let content = make_many_symbols_src(n); 157 | let compiler = compiler::Context::new([], None, Config::default()); 158 | b.iter(move || { 159 | let _mir = compiler.emit_mir(&content); 160 | }); 161 | } 162 | 163 | #[bench] 164 | fn bench_many_symbols3(b: &mut Bencher) { 165 | bench_many_symbols(b, 3); 166 | } 167 | 168 | #[bench] 169 | fn bench_many_symbols5(b: &mut Bencher) { 170 | bench_many_symbols(b, 5); 171 | } 172 | 173 | #[bench] 174 | fn bench_many_symbols10(b: &mut Bencher) { 175 | bench_many_symbols(b, 10); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /mimium-lang/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=TEST_ROOT={}", env!("CARGO_MANIFEST_DIR")); 3 | } 4 | -------------------------------------------------------------------------------- /mimium-lang/examples/multistage.mmm: -------------------------------------------------------------------------------- 1 | fn filterbank(filter:(num,num)->num,basefreq:num,num:int) -> <(num,num)->num> { 2 | << |input,basefreq| { 3 | if(num>0) { 4 | ~~(filterbank(filter,basefreq*2,num-1)) + filter(_,freq) 5 | }else{ 6 | + filter(_,freq) 7 | } 8 | } >> 9 | } 10 | //the syntactic sugar will be... 11 | macro filterbank(filter:(num,num)->num,basefreq:num,num:int) -> (num,num)->num{ 12 | |input,basefreq| { 13 | if(num>0) { 14 | filterbank!(filter,basefreq*2,num-1) + filter(_,freq) 15 | }else{ 16 | filter(_,freq) 17 | } 18 | } 19 | } 20 | 21 | fn dsp(input:(num,num))->(num,num){ 22 | mono = ~~filterbank(lowpass,100,16) 23 | mono(input) 24 | } -------------------------------------------------------------------------------- /mimium-lang/src/ast.rs: -------------------------------------------------------------------------------- 1 | pub mod builder; 2 | 3 | use crate::interner::{with_session_globals, ExprNodeId, Symbol, TypeNodeId}; 4 | use crate::pattern::{TypedId, TypedPattern}; 5 | use crate::utils::metadata::Location; 6 | use crate::utils::miniprint::MiniPrint; 7 | use std::fmt::{self}; 8 | pub type Time = i64; 9 | 10 | #[derive(Clone, Debug, PartialEq, Hash)] 11 | pub enum Literal { 12 | String(Symbol), 13 | Int(i64), 14 | Float(Symbol), 15 | SelfLit, 16 | Now, 17 | SampleRate, 18 | PlaceHolder, 19 | } 20 | 21 | impl Expr { 22 | fn into_id_inner(self, loc: Option) -> ExprNodeId { 23 | let loc = loc.unwrap_or_default(); 24 | with_session_globals(|session_globals| session_globals.store_expr_with_location(self, loc)) 25 | } 26 | 27 | pub fn into_id(self, loc: Location) -> ExprNodeId { 28 | self.into_id_inner(Some(loc)) 29 | } 30 | 31 | // For testing purposes 32 | pub fn into_id_without_span(self) -> ExprNodeId { 33 | self.into_id_inner(None) 34 | } 35 | } 36 | 37 | #[derive(Clone, Debug, PartialEq)] 38 | pub enum Expr { 39 | Literal(Literal), // literal, or special symbols (self, now, _) 40 | Var(Symbol), 41 | Block(Option), 42 | Tuple(Vec), 43 | Proj(ExprNodeId, i64), 44 | ArrayAccess(ExprNodeId, ExprNodeId), 45 | Apply(ExprNodeId, Vec), 46 | PipeApply(ExprNodeId, ExprNodeId), // LHS and RHS 47 | Lambda(Vec, Option, ExprNodeId), //lambda, maybe information for internal state is needed 48 | Assign(ExprNodeId, ExprNodeId), 49 | Then(ExprNodeId, Option), 50 | Feed(Symbol, ExprNodeId), //feedback connection primitive operation. This will be shown only after self-removal stage 51 | Let(TypedPattern, ExprNodeId, Option), 52 | LetRec(TypedId, ExprNodeId, Option), 53 | If(ExprNodeId, ExprNodeId, Option), 54 | //exprimental macro system using multi-stage computation 55 | Bracket(ExprNodeId), 56 | Escape(ExprNodeId), 57 | 58 | Error, 59 | } 60 | 61 | impl fmt::Display for Literal { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | match self { 64 | Literal::Float(n) => write!(f, "(float {})", n), 65 | Literal::Int(n) => write!(f, "(int {})", n), 66 | Literal::String(s) => write!(f, "\"{}\"", s), 67 | Literal::Now => write!(f, "now"), 68 | Literal::SampleRate => write!(f, "samplerate"), 69 | Literal::SelfLit => write!(f, "self"), 70 | Literal::PlaceHolder => write!(f, "_"), 71 | } 72 | } 73 | } 74 | 75 | impl MiniPrint for Literal { 76 | fn simple_print(&self) -> String { 77 | self.to_string() 78 | } 79 | } 80 | 81 | fn concat_vec(vec: &[T]) -> String { 82 | vec.iter() 83 | .map(|t| t.simple_print()) 84 | .collect::>() 85 | .join(" ") 86 | } 87 | 88 | impl MiniPrint for ExprNodeId { 89 | fn simple_print(&self) -> String { 90 | let span = self.to_span(); 91 | format!( 92 | "{}:{}..{}", 93 | self.to_expr().simple_print(), 94 | span.start, 95 | span.end 96 | ) 97 | } 98 | } 99 | 100 | impl MiniPrint for Option { 101 | fn simple_print(&self) -> String { 102 | match self { 103 | Some(e) => e.simple_print(), 104 | None => "()".to_string(), 105 | } 106 | } 107 | } 108 | 109 | impl MiniPrint for Expr { 110 | fn simple_print(&self) -> String { 111 | match self { 112 | Expr::Literal(l) => l.simple_print(), 113 | Expr::Var(v) => format!("{v}"), 114 | Expr::Block(e) => e.map_or("".to_string(), |eid| { 115 | format!("(block {})", eid.simple_print()) 116 | }), 117 | Expr::Tuple(e) => { 118 | let e1 = e.iter().map(|e| e.to_expr().clone()).collect::>(); 119 | format!("(tuple ({}))", concat_vec(&e1)) 120 | } 121 | Expr::Proj(e, idx) => format!("(proj {} {})", e.simple_print(), idx), 122 | Expr::Apply(e1, e2) => { 123 | format!("(app {} ({}))", e1.simple_print(), concat_vec(e2)) 124 | } 125 | Expr::ArrayAccess(e, i) => { 126 | format!("(arrayaccess {} ({}))", e.simple_print(), i.simple_print()) 127 | } 128 | Expr::PipeApply(lhs, rhs) => { 129 | format!("(pipe {} {})", lhs.simple_print(), rhs.simple_print()) 130 | } 131 | Expr::Lambda(params, _, body) => { 132 | format!("(lambda ({}) {})", concat_vec(params), body.simple_print()) 133 | } 134 | Expr::Feed(id, body) => format!("(feed {} {})", id, body.simple_print()), 135 | Expr::Let(id, body, then) => format!( 136 | "(let {} {} {})", 137 | id, 138 | body.simple_print(), 139 | then.simple_print() 140 | ), 141 | Expr::LetRec(id, body, then) => format!( 142 | "(letrec {} {} {})", 143 | &id.id, 144 | body.simple_print(), 145 | then.simple_print() 146 | ), 147 | Expr::Assign(lid, rhs) => format!("(assign {lid} {})", rhs.simple_print()), 148 | Expr::Then(first, second) => { 149 | format!("(then {} {})", first.simple_print(), second.simple_print()) 150 | } 151 | Expr::If(cond, then, optelse) => format!( 152 | "(if {} {} {})", 153 | cond.simple_print(), 154 | then.simple_print(), 155 | optelse.simple_print() 156 | ), 157 | Expr::Bracket(_) => todo!(), 158 | Expr::Escape(_) => todo!(), 159 | Expr::Error => "(error)".to_string(), 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /mimium-lang/src/ast/builder.rs: -------------------------------------------------------------------------------- 1 | pub use crate::ast::{Expr, Literal}; 2 | 3 | use super::Symbol; 4 | 5 | pub fn str_to_symbol(x: T) -> Symbol { 6 | use crate::interner::ToSymbol; 7 | x.to_string().to_symbol() 8 | } 9 | 10 | #[macro_export] 11 | macro_rules! dummy_span { 12 | () => { 13 | 0..0 14 | }; 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! number { 19 | ($n:literal) => { 20 | Expr::Literal(Literal::Float(crate::ast::builder::str_to_symbol($n))).into_id_without_span() 21 | }; 22 | } 23 | 24 | #[macro_export] 25 | macro_rules! string { 26 | ($n:expr) => { 27 | Expr::Literal(Literal::String($n)).into_id_without_span() 28 | }; 29 | } 30 | #[macro_export] 31 | macro_rules! var { 32 | ($n:literal) => { 33 | Expr::Var($crate::ast::builder::str_to_symbol($n)).into_id_without_span() 34 | }; 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! app { 39 | ($a:expr,$b:expr) => { 40 | Expr::Apply($a, $b).into_id_without_span() 41 | }; 42 | } 43 | 44 | #[macro_export] 45 | macro_rules! lambda_args { 46 | ($args:expr) => { 47 | //expect vec![id] 48 | $args 49 | .iter() 50 | .map(|a| TypedId { 51 | id: $crate::ast::builder::str_to_symbol(a), 52 | ty: $crate::types::Type::Unknown.into_id_with_span(0..0), 53 | }) 54 | .collect::>() 55 | }; 56 | } 57 | 58 | #[macro_export] 59 | macro_rules! lambda { 60 | ($args:expr,$body:expr) => { 61 | Expr::Lambda( 62 | $args 63 | .iter() 64 | .map(|a: &&'static str| $crate::pattern::TypedId { 65 | id: $crate::ast::builder::str_to_symbol(a), 66 | ty: $crate::types::Type::Unknown.into_id(), 67 | }) 68 | .collect::>(), 69 | None, 70 | $body, 71 | ) 72 | .into_id_without_span() 73 | }; 74 | } 75 | 76 | #[macro_export] 77 | macro_rules! let_ { 78 | ($id:literal,$body:expr,$then:expr) => { 79 | Expr::Let( 80 | $crate::pattern::TypedPattern { 81 | pat: $crate::pattern::Pattern::Single($crate::ast::builder::str_to_symbol($id)), 82 | ty: $crate::types::Type::Unknown.into_id(), 83 | }, 84 | $body, 85 | Some($then), 86 | ) 87 | .into_id_without_span() 88 | }; 89 | ($id:literal,$body:expr) => { 90 | Expr::Let( 91 | $crate::pattern::TypedPattern { 92 | pat: $crate::pattern::Pattern::Single($crate::ast::builder::str_to_symbol($id)), 93 | ty: $crate::types::Type::Unknown.into_id_with_span(0..0), 94 | }, 95 | Box::new($body), 96 | None, 97 | ) 98 | .into_id(0..0) 99 | }; 100 | } 101 | 102 | #[macro_export] 103 | macro_rules! letrec { 104 | ($id:literal,$ty:expr,$body:expr,$then:expr) => { 105 | Expr::LetRec( 106 | TypedId { 107 | id: $crate::ast::builder::str_to_symbol($id), 108 | ty: $ty.unwrap_or($crate::types::Type::Unknown.into_id()), 109 | }, 110 | $body, 111 | $then, 112 | ) 113 | .into_id_without_span() 114 | }; 115 | } 116 | 117 | #[macro_export] 118 | macro_rules! assign { 119 | ($lhs:literal,$rhs:expr) => { 120 | Expr::Assign($crate::ast::builder::str_to_symbol($lhs), Box::new($rhs)).into_id(0..0) 121 | }; 122 | } 123 | #[macro_export] 124 | macro_rules! then { 125 | ($first:expr,$second:expr) => { 126 | Expr::Then(Box::new($first), Box::new($second)).into_id(0..0) 127 | }; 128 | } 129 | 130 | #[macro_export] 131 | macro_rules! ifexpr { 132 | ($cond:expr,$then:expr,$else_:expr) => { 133 | Expr::If($cond, $then, Some($else_)).into_id_without_span() 134 | }; 135 | } 136 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/intrinsics.rs: -------------------------------------------------------------------------------- 1 | use std::cell::LazyCell; 2 | 3 | use crate::interner::{Symbol, ToSymbol}; 4 | 5 | // unary 6 | pub(crate) const NEG: &str = "neg"; 7 | pub(crate) const TOFLOAT: &str = "tofloat"; 8 | 9 | // binary 10 | pub(crate) const ADD: &str = "add"; 11 | pub(crate) const SUB: &str = "sub"; 12 | pub(crate) const MULT: &str = "mult"; 13 | pub(crate) const DIV: &str = "div"; 14 | pub(crate) const EQ: &str = "eq"; 15 | pub(crate) const NE: &str = "ne"; 16 | pub(crate) const LE: &str = "le"; 17 | pub(crate) const LT: &str = "lt"; 18 | pub(crate) const GE: &str = "ge"; 19 | pub(crate) const GT: &str = "gt"; 20 | pub(crate) const MODULO: &str = "modulo"; 21 | pub(crate) const POW: &str = "pow"; 22 | pub(crate) const AND: &str = "and"; 23 | pub(crate) const OR: &str = "or"; 24 | 25 | // arithmetics 26 | pub(crate) const SIN: &str = "sin"; 27 | pub(crate) const COS: &str = "cos"; 28 | pub(crate) const TAN: &str = "tan"; 29 | pub(crate) const ATAN: &str = "atan"; 30 | pub(crate) const ATAN2: &str = "atan2"; 31 | pub(crate) const SQRT: &str = "sqrt"; 32 | pub(crate) const ABS: &str = "abs"; 33 | pub(crate) const LOG: &str = "log"; 34 | pub(crate) const MIN: &str = "min"; 35 | pub(crate) const MAX: &str = "max"; 36 | pub(crate) const CEIL: &str = "ceil"; 37 | pub(crate) const FLOOR: &str = "floor"; 38 | pub(crate) const ROUND: &str = "round"; 39 | 40 | // other operations 41 | pub(crate) const DELAY: &str = "delay"; 42 | pub(crate) const MEM: &str = "mem"; 43 | const BUILTIN_SYMS_UNSORTED: [&str; 31] = [ 44 | NEG, TOFLOAT, ADD, SUB, MULT, DIV, EQ, NE, LE, LT, GE, GT, MODULO, POW, AND, OR, SIN, COS, TAN, 45 | ATAN, ATAN2, SQRT, ABS, LOG, MIN, MAX, CEIL, FLOOR, ROUND, DELAY, MEM, 46 | ]; 47 | thread_local!(pub (crate) static BUILTIN_SYMS: LazyCell> = LazyCell::new(|| { 48 | let mut v = BUILTIN_SYMS_UNSORTED 49 | .iter() 50 | .map(|s| s.to_symbol()) 51 | .collect::>(); 52 | v.sort(); 53 | v 54 | })); 55 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/mirgen/hir_solve_stage.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | use crate::hir::expr::*; 4 | use crate::utils::metadata::WithMeta; 5 | 6 | pub struct EvalContext { 7 | pub stage: u64, 8 | } 9 | 10 | pub fn eval_stage_hir(expr: WithMeta, ctx: &mut EvalContext) -> WithMeta { 11 | let WithMeta(e, span) = expr.clone(); 12 | 13 | match e { 14 | Expr::Var(var, _time) if ctx.stage == 0 => { 15 | // let r = var.0.0.borrow().as_ref().unwrap().clone(); 16 | expr.clone() 17 | } 18 | Expr::Apply(box WithMeta(Expr::Lambda(params, body), _fspan), callee) if ctx.stage == 0 => { 19 | params.iter().zip(callee.iter()).for_each(|(p, e)| { 20 | // let mut myp = p.0.v.borrow_mut(); 21 | // *myp = Some(e.0.clone()); 22 | }); 23 | eval_stage_hir(*body, ctx) 24 | } 25 | Expr::Apply(fun, callee) if fun.0.is_value() => { 26 | let newcallee = callee 27 | .iter() 28 | .map(|e| Box::new(eval_stage_hir(**e, ctx))) 29 | .collect(); 30 | let res = WithMeta(Expr::Apply(fun, newcallee), span.clone()); 31 | eval_stage_hir(res, ctx) 32 | } 33 | Expr::Apply(fun, callee) => { 34 | let res = WithMeta( 35 | Expr::Apply(Box::new(eval_stage_hir(*fun, ctx)), callee.clone()), 36 | span.clone(), 37 | ); 38 | eval_stage_hir(res, ctx) 39 | } 40 | Expr::If(box cond, box then, opt_else) => { 41 | let cond = eval_stage_hir(cond, ctx); 42 | let then = eval_stage_hir(then, ctx); 43 | let opt_else = opt_else.map(|e| Box::new(eval_stage_hir(*e, ctx))); 44 | if ctx.stage == 0 { 45 | if cond.0.eval_condition() { 46 | then 47 | } else { 48 | *opt_else.unwrap() 49 | } 50 | } else { 51 | WithMeta( 52 | Expr::If(Box::new(cond), Box::new(then), opt_else), 53 | span.clone(), 54 | ) 55 | } 56 | } 57 | Expr::Bracket(b) => { 58 | ctx.stage += 1; 59 | eval_stage_hir(*b, ctx) 60 | } 61 | Expr::Escape(b) => { 62 | ctx.stage -= 1; 63 | eval_stage_hir(*b, ctx) 64 | } 65 | Expr::Lambda(params, body) if ctx.stage == 0 => WithMeta( 66 | Expr::Lambda(params, Box::new(eval_stage_hir(*body, ctx))), 67 | span.clone(), 68 | ), 69 | _ if ctx.stage == 0 && e.is_value() => expr.clone(), 70 | _ => * Box::new(expr).walk(|x| Box::new(eval_stage_hir(*x, ctx))), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/mirgen/recursecheck.rs: -------------------------------------------------------------------------------- 1 | ///remove redundunt letrec definition and convert them to plain let 2 | use crate::{ 3 | ast::Expr, 4 | interner::{ExprNodeId, Symbol}, 5 | pattern::TypedPattern, 6 | utils::metadata::Location, 7 | }; 8 | 9 | fn try_find_recurse(e_s: ExprNodeId, name: Symbol) -> bool { 10 | match e_s.to_expr() { 11 | Expr::Var(n) => n == name, 12 | Expr::Let(_id, body, then) => { 13 | try_find_recurse(body, name) || then.map_or(false, |e| try_find_recurse(e, name)) 14 | } 15 | Expr::LetRec(_id, _body, _then) => { 16 | //todo: start new search so we return false here 17 | false 18 | } 19 | Expr::Assign(_v, e) => try_find_recurse(e, name), 20 | Expr::Then(body, then_opt) => { 21 | try_find_recurse(body, name) 22 | || then_opt.map_or(false, |then| try_find_recurse(then, name)) 23 | } 24 | Expr::Lambda(_ids, _opt_type, body) => try_find_recurse(body, name), 25 | Expr::Proj(body, _idx) => try_find_recurse(body, name), 26 | Expr::Block(body) => body.map_or(false, |b| try_find_recurse(b, name)), 27 | Expr::Apply(fun, callee) => { 28 | try_find_recurse(fun, name) || callee.into_iter().any(|v| try_find_recurse(v, name)) 29 | } 30 | Expr::Tuple(vec) => vec.iter().any(|v| try_find_recurse(*v, name)), 31 | Expr::If(cond, then, opt_else) => { 32 | try_find_recurse(cond, name) 33 | || try_find_recurse(then, name) 34 | || opt_else.map_or(false, |e| try_find_recurse(e, name)) 35 | } 36 | Expr::Feed(_x, _body) => panic!("feed should not be shown in recurse removal process"), 37 | _ => false, 38 | } 39 | } 40 | 41 | pub fn convert_recurse(e_s: ExprNodeId, file_path: Symbol) -> ExprNodeId { 42 | let convert = |v: ExprNodeId| convert_recurse(v, file_path); 43 | let convert_vec = |v: Vec<_>| { 44 | v.into_iter() 45 | .map(|e| convert_recurse(e, file_path)) 46 | .collect() 47 | }; 48 | let span = e_s.to_span(); 49 | let res = match e_s.to_expr() { 50 | Expr::LetRec(id, body, then) => { 51 | if !try_find_recurse(body, id.id) { 52 | Expr::Let( 53 | TypedPattern::from(id.clone()), 54 | convert(body), 55 | then.map(convert), 56 | ) 57 | } else { 58 | Expr::LetRec(id.clone(), convert(body), then.map(convert)) 59 | } 60 | } 61 | Expr::Let(id, body, then) => Expr::Let(id.clone(), convert(body), then.map(convert)), 62 | Expr::Assign(v, e) => Expr::Assign(v, convert(e)), 63 | Expr::Then(body, then_opt) => Expr::Then(convert(body), then_opt.map(convert)), 64 | Expr::Tuple(es) => Expr::Tuple(convert_vec(es)), 65 | Expr::Proj(t, idx) => Expr::Proj(convert(t), idx), 66 | Expr::Block(body) => Expr::Block(body.map(convert)), 67 | Expr::Apply(fun, callee) => Expr::Apply(convert(fun), convert_vec(callee)), 68 | Expr::If(cond, then, opt_else) => { 69 | Expr::If(convert(cond), convert(then), opt_else.map(convert)) 70 | } 71 | Expr::Lambda(ids, opt_type, body) => Expr::Lambda(ids.clone(), opt_type, convert(body)), 72 | Expr::Feed(_x, _body) => panic!("feed should not be shown in recurse removal process"), 73 | e => e.clone(), 74 | }; 75 | let loc = Location { 76 | span, 77 | path: file_path, 78 | }; 79 | res.into_id(loc) 80 | } 81 | 82 | #[cfg(test)] 83 | mod test { 84 | 85 | use crate::{ 86 | app, 87 | ast::{Expr, Literal}, 88 | ifexpr, 89 | interner::ToSymbol, 90 | lambda, let_, letrec, number, 91 | pattern::TypedId, 92 | var, 93 | }; 94 | 95 | use super::*; 96 | #[test] 97 | fn recurse_remove() { 98 | let sample = letrec!( 99 | "testfn", 100 | None, 101 | lambda!( 102 | ["count"], 103 | ifexpr!( 104 | var!("test"), 105 | app!(var!("testfn"), vec![number!("10.0")]), 106 | //this letrec should be converted to plain let 107 | letrec!("lettest", None, number!("12.0"), Some(number!("2.0"))) 108 | ) 109 | ), 110 | None 111 | ); 112 | // top letrec should not be converted 113 | let ans = letrec!( 114 | "testfn", 115 | None, 116 | lambda!( 117 | ["count"], 118 | ifexpr!( 119 | var!("test"), 120 | app!(var!("testfn"), vec![number!("10.0")]), 121 | // this 122 | let_!("lettest", number!("12.0"), number!("2.0")) 123 | ) 124 | ), 125 | None 126 | ); 127 | 128 | assert_eq!(convert_recurse(sample, "/".to_symbol()), ans) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/mirgen/test.rs: -------------------------------------------------------------------------------- 1 | use crate::{app, assign, ast::builder::*, lambda, let_, number, then, var}; 2 | 3 | use super::*; 4 | 5 | #[test] 6 | 7 | fn closure_test() { 8 | //fn makeCounter(beg,inc){ 9 | // let n = beg+1; // local- 0:beg, 1:inc 2: 1 3:n 10 | // return | | { //upvalue: n:3 inc:1 11 | // n = n+inc; 12 | // n 13 | // } 14 | //} 15 | //fn main(){ 16 | // let c = makeCounter(13,7); 17 | // print(c()); //print 21 18 | // print(c()); // print 28 19 | //} 20 | let expr = let_!( 21 | "makeCounter", 22 | lambda!( 23 | vec!["beg", "inc"], 24 | let_!( 25 | "n", 26 | app!(var!("add"), vec![var!("beg"), number!(1)]), 27 | lambda!( 28 | vec![], 29 | then!( 30 | assign!("n", app!(var!("add"), vec![var!("n"), var!("inc")])), 31 | var!("n") 32 | ) 33 | ) 34 | ) 35 | ), 36 | let_!( 37 | "main", 38 | lambda!( 39 | vec![], 40 | let_!( 41 | "c", 42 | app!(var!("makeCounter"), vec![number!(13), number!(7)]), 43 | then!( 44 | app!(var!("print"), vec![app!(var!("c"), vec![])]), 45 | app!(var!("print"), vec![app!(var!("c"), vec![])]) 46 | ) 47 | ) 48 | ) 49 | ) 50 | ); 51 | 52 | let prog = compile(expr, &[], None).unwrap(); 53 | let mirstr = format!("{:?}", prog); 54 | println!("{}", mirstr); 55 | } 56 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/parser/chumsky_test.rs: -------------------------------------------------------------------------------- 1 | use chumsky::prelude::*; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub enum Expr { 5 | Int(i64), 6 | String(String), 7 | Let((String, Box, Box)), 8 | } 9 | 10 | pub fn parsetester() -> impl Parser> { 11 | let int = text::int(10) 12 | .labelled("integer") 13 | .map(|v: String| Expr::Int(v.parse().unwrap())); 14 | let ident = text::ident().labelled("ident").map(|s: String| s); 15 | let atom = int.or(ident.map(|s| Expr::String(s))).padded(); 16 | let expr = recursive(|expr| { 17 | let letp = text::keyword("let") 18 | .padded() 19 | .ignore_then(ident.padded()) 20 | .then_ignore(just('=').padded()) 21 | .then(atom.clone()) 22 | .then_ignore(just(';')) 23 | .padded() 24 | .then(expr.clone()) 25 | .map(|((ident, body), then)| Expr::Let((ident, Box::new(body), Box::new(then)))); 26 | letp.or(atom).padded() 27 | }) 28 | .padded(); 29 | expr.then_ignore(end()) 30 | } 31 | 32 | #[test] 33 | pub fn test_hogehoge() { 34 | let src = "let hoge = 100; gufa".to_string(); 35 | match parsetester().parse(src) { 36 | Ok(ast) => { 37 | // assert_eq!(ast, ans); 38 | println!("Parse Success: {:#?}", ast); 39 | } 40 | Err(parse_errs) => { 41 | parse_errs 42 | .into_iter() 43 | .for_each(|e| println!("Parse error: {}", e)); 44 | panic!(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/parser/error.rs: -------------------------------------------------------------------------------- 1 | use crate::interner::Symbol; 2 | use crate::utils::error::ReportableError; 3 | use crate::utils::metadata::Location; 4 | use chumsky; 5 | use std::fmt; 6 | use std::hash::Hash; 7 | // pub struct LexError(chumsky::error::Simple); 8 | #[derive(Debug)] 9 | pub struct ParseError 10 | where 11 | T: Hash + std::cmp::Eq + fmt::Debug + fmt::Display, 12 | { 13 | pub content: chumsky::error::Simple, 14 | pub file: Symbol, 15 | } 16 | 17 | impl From> for chumsky::error::Simple 18 | where 19 | T: Hash + std::cmp::Eq + fmt::Debug + fmt::Display, 20 | { 21 | fn from(value: ParseError) -> Self { 22 | value.content 23 | } 24 | } 25 | 26 | impl fmt::Display for ParseError 27 | where 28 | T: Hash + std::cmp::Eq + fmt::Debug + fmt::Display, 29 | { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | write!(f, "{}", self.content) 32 | } 33 | } 34 | 35 | impl std::error::Error for ParseError where T: Hash + std::cmp::Eq + fmt::Debug + fmt::Display {} 36 | 37 | impl ReportableError for ParseError 38 | where 39 | T: Hash + std::cmp::Eq + fmt::Debug + fmt::Display, 40 | { 41 | fn get_message(&self) -> String { 42 | match self.content.reason() { 43 | chumsky::error::SimpleReason::Unexpected 44 | | chumsky::error::SimpleReason::Unclosed { .. } => { 45 | format!( 46 | "{}{}, expected {}", 47 | if self.content.found().is_some() { 48 | "unexpected token" 49 | } else { 50 | "unexpected end of input" 51 | }, 52 | if let Some(label) = self.content.label() { 53 | format!(" while parsing {label}") 54 | } else { 55 | " something else".to_string() 56 | }, 57 | if self.content.expected().count() == 0 { 58 | "somemething else".to_string() 59 | } else { 60 | self.content 61 | .expected() 62 | .map(|expected| match expected { 63 | Some(expected) => expected.to_string(), 64 | None => "end of input".to_string(), 65 | }) 66 | .collect::>() 67 | .join(", ") 68 | } 69 | ) 70 | } 71 | chumsky::error::SimpleReason::Custom(msg) => msg.clone(), 72 | } 73 | } 74 | 75 | fn get_labels(&self) -> Vec<(Location, String)> { 76 | vec![( 77 | Location { 78 | span: self.content.span(), 79 | path: self.file, 80 | }, 81 | self.get_message(), 82 | )] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/parser/resolve_include.rs: -------------------------------------------------------------------------------- 1 | use super::{parse, Expr, Location, Span}; 2 | use crate::interner::{ExprNodeId, ToSymbol}; 3 | use crate::utils::error::{ReportableError, SimpleError}; 4 | use crate::utils::fileloader; 5 | 6 | fn make_vec_error(e: E, loc: Location) -> Vec> { 7 | vec![Box::new(SimpleError { 8 | message: e.to_string(), 9 | span: loc, 10 | }) as Box] 11 | } 12 | 13 | pub(super) fn resolve_include( 14 | mmm_filepath: &str, 15 | target_path: &str, 16 | span: Span, 17 | ) -> (ExprNodeId, Vec>) { 18 | let loc = Location { 19 | span: span.clone(), 20 | path: mmm_filepath.to_symbol(), 21 | }; 22 | let res = fileloader::load_mmmlibfile(mmm_filepath, target_path) 23 | .map_err(|e| make_vec_error(e, loc.clone())); 24 | match res { 25 | Ok((content, path)) => parse(&content, Some(path)), 26 | Err(err) => (Expr::Error.into_id(loc), err), 27 | } 28 | } 29 | 30 | #[cfg(all(test, target_arch = "wasm32"))] 31 | mod test { 32 | use super::*; 33 | use crate::utils::fileloader; 34 | use wasm_bindgen_test::*; 35 | #[wasm_bindgen_test] 36 | fn test_resolve_include() { 37 | let file = format!( 38 | "{}/../mimium-test/tests/mmm/{}", 39 | fileloader::get_env("TEST_ROOT").expect("TEST_ROOT is not set"), 40 | "error_include_itself.mmm" 41 | ); 42 | let (id, errs) = resolve_include(&file, &file, 0..0); 43 | assert_eq!(errs.len(), 1); 44 | assert!(errs[0] 45 | .get_message() 46 | .contains("File tried to include itself recusively")); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/parser/statement.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::Expr, 3 | interner::ExprNodeId, 4 | pattern::{TypedId, TypedPattern}, 5 | }; 6 | 7 | use super::{Location, Span}; 8 | // an intermediate representation used in parser. 9 | // Note that this struct do not distinct between a global statement(allows `fn(){}`) and a local statement. 10 | // The distinction is done in the actual parser logic. 11 | #[derive(Clone, Debug, PartialEq)] 12 | pub(super) enum Statement { 13 | Let(TypedPattern, ExprNodeId), 14 | MacroExpand(TypedId, ExprNodeId), 15 | LetRec(TypedId, ExprNodeId), 16 | Assign(ExprNodeId, ExprNodeId), 17 | Single(ExprNodeId), 18 | } 19 | 20 | pub fn stmt_from_expr_top(expr: ExprNodeId) -> Vec { 21 | let mut res = vec![]; 22 | stmt_from_expr(expr, &mut res); 23 | res 24 | } 25 | fn stmt_from_expr(expr: ExprNodeId, target: &mut Vec) { 26 | match expr.to_expr() { 27 | Expr::Let(pat, e, then_opt) => { 28 | target.push(Statement::Let(pat, e)); 29 | if let Some(then) = then_opt { 30 | stmt_from_expr(then, target); 31 | } 32 | } 33 | Expr::LetRec(id, e, then_opt) => { 34 | target.push(Statement::LetRec(id, e)); 35 | if let Some(then) = then_opt { 36 | stmt_from_expr(then, target); 37 | } 38 | } 39 | _ => target.push(Statement::Single(expr)), 40 | } 41 | } 42 | 43 | // A helper function to convert vector of statements to nested expression 44 | pub(super) fn into_then_expr(stmts: &[(Statement, Location)]) -> Option { 45 | let get_span = |spana: Span, spanb: Option| match spanb { 46 | Some(b) => { 47 | let start = spana.start; 48 | start..b.to_span().end 49 | } 50 | None => spana, 51 | }; 52 | let e_pre = stmts.iter().rev().fold(None, |then, (stmt, loc)| { 53 | let span = get_span(loc.span.clone(), then); 54 | let new_loc = Location { 55 | span, 56 | path: loc.path, 57 | }; 58 | match (then, stmt) { 59 | (_, Statement::Let(pat, body)) => { 60 | Some(Expr::Let(pat.clone(), *body, then).into_id(new_loc)) 61 | } 62 | 63 | (_, Statement::LetRec(id, body)) => { 64 | Some(Expr::LetRec(id.clone(), *body, then).into_id(new_loc)) 65 | } 66 | (_, Statement::Assign(name, body)) => Some( 67 | Expr::Then(Expr::Assign(*name, *body).into_id(loc.clone()), then).into_id(new_loc), 68 | ), 69 | (_, Statement::MacroExpand(fname, body)) => { 70 | //todo! 71 | Some(Expr::LetRec(fname.clone(), *body, then).into_id(new_loc)) 72 | } 73 | (None, Statement::Single(e)) => Some(*e), 74 | (t, Statement::Single(e)) => Some(Expr::Then(*e, t).into_id(new_loc)), 75 | } 76 | }); 77 | // log::debug!("stmts {:?}, e_pre: {:?}", stmts, e_pre); 78 | e_pre 79 | } 80 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/parser/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::compiler::intrinsics; 4 | use crate::interner::{Symbol, ToSymbol}; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 7 | pub enum Comment { 8 | SingleLine(String), 9 | MultiLine(String), 10 | } 11 | 12 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 13 | pub enum Op { 14 | Sum, // + 15 | Minus, // - 16 | Product, // * 17 | Divide, // / 18 | 19 | Equal, // == 20 | NotEqual, // != 21 | LessThan, // < 22 | LessEqual, // <= 23 | GreaterThan, // > 24 | GreaterEqual, // >= 25 | 26 | Modulo, // % 27 | Exponent, // ^ 28 | 29 | And, // && 30 | Or, // || 31 | 32 | At, // @ 33 | 34 | Pipe, // |> 35 | Unknown(String), 36 | } 37 | 38 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 39 | pub enum Token { 40 | Ident(Symbol), 41 | MacroExpand(Symbol), 42 | 43 | FloatType, 44 | IntegerType, 45 | StringType, 46 | StructType, 47 | 48 | Float(String), 49 | Int(i64), 50 | Str(String), 51 | 52 | Op(Op), 53 | SelfLit, 54 | Now, 55 | SampleRate, 56 | Comma, 57 | Dot, 58 | 59 | Colon, 60 | SemiColon, 61 | 62 | Let, 63 | LetRec, 64 | Assign, 65 | 66 | ParenBegin, 67 | ParenEnd, 68 | ArrayBegin, 69 | ArrayEnd, 70 | BlockBegin, 71 | BlockEnd, 72 | LambdaArgBeginEnd, 73 | 74 | Function, //"fn" 75 | Macro, //"macro" 76 | Arrow, // -> 77 | PlaceHolder, // _ 78 | 79 | If, 80 | Else, 81 | 82 | // Type, 83 | // Alias, 84 | 85 | Include, 86 | 87 | LineBreak, 88 | 89 | Comment(Comment), 90 | } 91 | impl Op { 92 | pub fn get_associated_fn_name(&self) -> Symbol { 93 | match self { 94 | Op::Sum => intrinsics::ADD, 95 | Op::Minus => intrinsics::SUB, 96 | Op::Product => intrinsics::MULT, 97 | Op::Divide => intrinsics::DIV, 98 | Op::Equal => intrinsics::EQ, 99 | Op::NotEqual => intrinsics::NE, 100 | Op::LessThan => intrinsics::LT, 101 | Op::LessEqual => intrinsics::LE, 102 | Op::GreaterThan => intrinsics::GT, 103 | Op::GreaterEqual => intrinsics::GE, 104 | Op::Modulo => intrinsics::MODULO, 105 | Op::Exponent => intrinsics::POW, 106 | Op::And => intrinsics::AND, 107 | Op::Or => intrinsics::OR, 108 | Op::At => "_mimium_schedule_at", 109 | Op::Pipe => unreachable!(), // pipe is a syntax sugar, not a function 110 | Op::Unknown(x) => x.as_str(), 111 | } 112 | .to_symbol() 113 | } 114 | } 115 | 116 | impl fmt::Display for Op { 117 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 118 | match self { 119 | Op::Sum => write!(f, "+"), 120 | Op::Minus => write!(f, "-"), 121 | Op::Product => write!(f, "*"), 122 | Op::Divide => write!(f, "/"), 123 | Op::Equal => write!(f, "=="), 124 | Op::NotEqual => write!(f, "!="), 125 | Op::LessThan => write!(f, "<"), 126 | Op::LessEqual => write!(f, "<="), 127 | Op::GreaterThan => write!(f, ">"), 128 | Op::GreaterEqual => write!(f, ">="), 129 | Op::Modulo => write!(f, "%"), 130 | Op::Exponent => write!(f, "^"), 131 | Op::And => write!(f, "&&"), 132 | Op::Or => write!(f, "||"), 133 | Op::At => write!(f, "@"), 134 | Op::Pipe => write!(f, "|>"), 135 | Op::Unknown(x) => write!(f, "{}", x), 136 | } 137 | } 138 | } 139 | 140 | impl fmt::Display for Token { 141 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 142 | match self { 143 | Token::Ident(x) => write!(f, "{}", x), 144 | Token::MacroExpand(x) => write!(f, "{}!", x), 145 | Token::FloatType => write!(f, "float"), 146 | Token::IntegerType => write!(f, "int"), 147 | Token::StringType => write!(f, "string"), 148 | Token::StructType => write!(f, "struct"), 149 | Token::Int(x) => write!(f, "{}", x), 150 | Token::Float(x) => write!(f, "{}", x), 151 | Token::Str(x) => write!(f, "\"{}\"", x), 152 | Token::Op(x) => write!(f, "{}", x), 153 | Token::SelfLit => write!(f, "self"), 154 | Token::Now => write!(f, "now"), 155 | Token::SampleRate => write!(f, "samplerate"), 156 | Token::Comma => write!(f, ","), 157 | Token::Dot => write!(f, "."), 158 | Token::Colon => write!(f, ":"), 159 | Token::SemiColon => write!(f, ";"), 160 | Token::Let => write!(f, "let"), 161 | Token::LetRec => write!(f, "letrec"), 162 | Token::Assign => write!(f, "="), 163 | Token::ParenBegin => write!(f, "("), 164 | Token::ParenEnd => write!(f, ")"), 165 | Token::ArrayBegin => write!(f, "["), 166 | Token::ArrayEnd => write!(f, "]"), 167 | Token::BlockBegin => write!(f, "{{"), 168 | Token::BlockEnd => write!(f, "}}"), 169 | Token::LambdaArgBeginEnd => write!(f, "|"), 170 | 171 | Token::Function => write!(f, "fn"), 172 | Token::Macro => write!(f, "macro"), 173 | Token::Arrow => write!(f, "->"), 174 | Token::PlaceHolder => write!(f, "_"), 175 | 176 | Token::If => write!(f, "if"), 177 | Token::Else => write!(f, "else"), 178 | 179 | // Token::Type => write!(f, "type"), 180 | // Token::Alias => write!(f, "newtype"), 181 | Token::Include => write!(f, "include"), 182 | Token::LineBreak => write!(f, "linebreak"), 183 | Token::Comment(_) => write!(f, "comment"), 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /mimium-lang/src/compiler/tests/hirgen.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | 3 | mod test { 4 | use mimium_hirgen::typing::{infer_type, InferContext}; 5 | use mmmtype::*; 6 | use ast::*; 7 | use utils::metadata::WithMeta; 8 | #[test] 9 | pub fn unify_prim() { 10 | let ast = WithMeta(Expr::Literal(Literal::Float("10.2".to_string())),0..0); 11 | let mut ctx = InferContext::new(); 12 | let res = infer_type(ast.0,&mut ctx).unwrap(); 13 | assert_eq!(res, Type::Numeric); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mimium-lang/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Main module of compiler and runtime for **mimium**, an infrastructural programming language for sound and music. 2 | 3 | pub mod ast; 4 | pub mod interner; 5 | pub mod mir; 6 | pub(crate) mod pattern; 7 | pub mod types; 8 | pub mod utils; 9 | 10 | pub mod compiler; 11 | pub mod runtime; 12 | 13 | pub mod plugin; 14 | 15 | use compiler::{ExtFunTypeInfo, IoChannelInfo}; 16 | use interner::Symbol; 17 | pub use log; 18 | use plugin::{to_ext_cls_info, DynSystemPlugin, Plugin, SystemPlugin}; 19 | use runtime::vm::{ 20 | self, 21 | builtin::{get_builtin_fn_types, get_builtin_fns}, 22 | ExtClsInfo, Program, ReturnCode, 23 | }; 24 | use utils::error::ReportableError; 25 | 26 | #[cfg(not(target_arch = "wasm32"))] 27 | use mimalloc::MiMalloc; 28 | #[cfg(not(target_arch = "wasm32"))] 29 | #[global_allocator] 30 | static GLOBAL: MiMalloc = MiMalloc; 31 | 32 | /// Configuration for the compiler and runtime. 33 | #[derive(Debug, Clone, Copy, Default)] 34 | pub struct Config { 35 | pub compiler: compiler::Config, 36 | // pub runtime: runtime::Config, 37 | } 38 | 39 | /// A set of compiler and external functions (plugins). 40 | /// From this information, user can generate VM with [`Self::prepare_machine`]. 41 | pub struct ExecContext { 42 | compiler: Option, 43 | vm: Option, 44 | plugins: Vec>, 45 | sys_plugins: Vec, 46 | path: Option, 47 | extclsinfos_reserve: Vec, 48 | extfuntypes: Vec, 49 | config: Config, 50 | } 51 | 52 | impl ExecContext { 53 | //The Argument will be changed to the plugins, when the plugin system is introduced 54 | pub fn new( 55 | plugins: impl Iterator>, 56 | path: Option, 57 | config: Config, 58 | ) -> Self { 59 | let plugins = plugins.collect::>(); 60 | let extfuntypes = plugin::get_extfun_types(&plugins) 61 | .chain(get_builtin_fn_types()) 62 | .collect::>(); 63 | 64 | let sys_plugins = vec![]; 65 | Self { 66 | compiler: None, 67 | vm: None, 68 | plugins, 69 | sys_plugins, 70 | path, 71 | extclsinfos_reserve: vec![], 72 | extfuntypes, 73 | config, 74 | } 75 | } 76 | pub fn add_plugin(&mut self, plug: T) { 77 | self.plugins.push(Box::new(plug)) 78 | } 79 | pub fn get_system_plugins(&self) -> impl Iterator { 80 | self.sys_plugins.iter() 81 | } 82 | //todo: make it to builder pattern 83 | pub fn add_system_plugin(&mut self, plug: T) { 84 | let (plugin_dyn, sysplug_info) = to_ext_cls_info(plug); 85 | let sysplug_typeinfo = sysplug_info 86 | .iter() 87 | .cloned() 88 | .map(|(name, _, ty)| ExtFunTypeInfo { name, ty }); 89 | self.extfuntypes.extend(sysplug_typeinfo); 90 | self.extclsinfos_reserve.extend(sysplug_info); 91 | self.sys_plugins.push(plugin_dyn) 92 | } 93 | pub fn get_compiler(&self) -> Option<&compiler::Context> { 94 | self.compiler.as_ref() 95 | } 96 | pub fn take_vm(&mut self) -> Option { 97 | self.vm.take() 98 | } 99 | pub fn get_vm(&self) -> Option<&runtime::vm::Machine> { 100 | self.vm.as_ref() 101 | } 102 | pub fn get_compiler_mut(&mut self) -> Option<&mut compiler::Context> { 103 | self.compiler.as_mut() 104 | } 105 | pub fn get_vm_mut(&mut self) -> Option<&mut runtime::vm::Machine> { 106 | self.vm.as_mut() 107 | } 108 | pub fn prepare_compiler(&mut self) { 109 | self.compiler = Some(compiler::Context::new( 110 | self.extfuntypes.clone(), 111 | self.path, 112 | self.config.compiler, 113 | )); 114 | } 115 | pub fn prepare_machine(&mut self, src: &str) -> Result<(), Vec>> { 116 | if self.compiler.is_none() { 117 | self.prepare_compiler(); 118 | } 119 | 120 | let prog = self.compiler.as_ref().unwrap().emit_bytecode(src)?; 121 | self.prepare_machine_with_bytecode(prog); 122 | Ok(()) 123 | } 124 | pub fn prepare_machine_with_bytecode(&mut self, prog: Program) { 125 | self.extclsinfos_reserve 126 | .extend(plugin::get_extclsinfos(&self.plugins)); 127 | let extfninfos = plugin::get_extfuninfos(&self.plugins).chain(get_builtin_fns()); 128 | let vm = vm::Machine::new( 129 | prog, 130 | extfninfos, 131 | self.extclsinfos_reserve.clone().into_iter(), 132 | ); 133 | self.vm = Some(vm); 134 | } 135 | pub fn try_get_main_loop(&mut self) -> Option> { 136 | let mut mainloops = self.sys_plugins.iter_mut().filter_map(|p| { 137 | let p = unsafe { p.0.get().as_mut().unwrap_unchecked() }; 138 | p.try_get_main_loop() 139 | }); 140 | let res = mainloops.next(); 141 | if mainloops.next().is_some() { 142 | log::warn!("more than 2 main loops in system plugins found") 143 | } 144 | res 145 | } 146 | pub fn get_iochannel_count(&self) -> Option { 147 | self.vm.as_ref().and_then(|vm| vm.prog.iochannels) 148 | } 149 | pub fn run_main(&mut self) -> ReturnCode { 150 | if let Some(vm) = self.vm.as_mut() { 151 | self.sys_plugins.iter().for_each(|plug: &DynSystemPlugin| { 152 | //todo: encapsulate unsafety within SystemPlugin functionality 153 | let p = unsafe { plug.0.get().as_mut().unwrap_unchecked() }; 154 | let _ = p.on_init(vm); 155 | }); 156 | let res = vm.execute_main(); 157 | self.sys_plugins.iter().for_each(|plug: &DynSystemPlugin| { 158 | //todo: encapsulate unsafety within SystemPlugin functionality 159 | let p = unsafe { plug.0.get().as_mut().unwrap_unchecked() }; 160 | let _ = p.after_main(vm); 161 | }); 162 | res 163 | } else { 164 | 0 165 | } 166 | } 167 | } 168 | //todo: remove 169 | pub mod ast_interpreter; 170 | pub mod repl; 171 | -------------------------------------------------------------------------------- /mimium-lang/src/pattern.rs: -------------------------------------------------------------------------------- 1 | use crate::interner::{Symbol, TypeNodeId}; 2 | //todo! need to replace with interned string. 3 | use crate::types::Type; 4 | use crate::utils::metadata::Span; 5 | use crate::utils::miniprint::MiniPrint; 6 | 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub enum Pattern { 9 | Single(Symbol), 10 | Tuple(Vec), 11 | } 12 | 13 | impl std::fmt::Display for Pattern { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | match self { 16 | Pattern::Single(id) => write!(f, "{id}"), 17 | Pattern::Tuple(vec) => { 18 | let s = vec 19 | .iter() 20 | .map(|p| p.to_string()) 21 | .collect::>() 22 | .concat(); 23 | write!(f, "({s})") 24 | } 25 | } 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug, PartialEq)] 30 | pub struct TypedId { 31 | pub id: Symbol, 32 | // TypeNodeId is always issued even if the expression doesn't have the type 33 | // specification at all. This can be used for querying for the span. 34 | pub ty: TypeNodeId, 35 | } 36 | 37 | impl TypedId { 38 | pub fn to_span(&self) -> Span { 39 | self.ty.to_span() 40 | } 41 | 42 | pub fn is_unknown(&self) -> bool { 43 | self.ty.to_type() == Type::Unknown 44 | } 45 | } 46 | 47 | impl std::fmt::Display for TypedId { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | if !self.is_unknown() { 50 | write!(f, "{} :{}", self.id, self.ty.to_type()) 51 | } else { 52 | write!(f, "{}", self.id) 53 | } 54 | } 55 | } 56 | 57 | impl MiniPrint for TypedId { 58 | fn simple_print(&self) -> String { 59 | if !self.is_unknown() { 60 | format!("(tid {} {})", self.id, self.ty.to_type()) 61 | } else { 62 | self.id.to_string() 63 | } 64 | } 65 | } 66 | 67 | #[derive(Clone, Debug, PartialEq)] 68 | pub struct TypedPattern { 69 | pub pat: Pattern, 70 | pub ty: TypeNodeId, 71 | } 72 | 73 | impl TypedPattern { 74 | pub fn to_span(&self) -> Span { 75 | self.ty.to_span() 76 | } 77 | 78 | pub fn is_unknown(&self) -> bool { 79 | self.ty.to_type() == Type::Unknown 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub struct ConversionError; 85 | impl std::fmt::Display for ConversionError { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | write!( 88 | f, 89 | "Failed to convert pattern. The pattern did not matched to single identifier." 90 | ) 91 | } 92 | } 93 | impl std::error::Error for ConversionError {} 94 | 95 | impl From for TypedPattern { 96 | fn from(value: TypedId) -> Self { 97 | TypedPattern { 98 | pat: Pattern::Single(value.id), 99 | ty: value.ty, 100 | } 101 | } 102 | } 103 | impl TryFrom for TypedId { 104 | type Error = ConversionError; 105 | 106 | fn try_from(value: TypedPattern) -> Result { 107 | match value.pat { 108 | Pattern::Single(id) => Ok(TypedId { id, ty: value.ty }), 109 | Pattern::Tuple(_) => Err(ConversionError), 110 | } 111 | } 112 | } 113 | 114 | impl MiniPrint for TypedPattern { 115 | fn simple_print(&self) -> String { 116 | if !self.is_unknown() { 117 | format!("(tpat {} {})", self.pat, self.ty.to_type()) 118 | } else { 119 | self.pat.to_string() 120 | } 121 | } 122 | } 123 | impl std::fmt::Display for TypedPattern { 124 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 125 | if !self.is_unknown() { 126 | write!(f, "{} :{}", self.pat, self.ty.to_type()) 127 | } else { 128 | write!(f, "{}", self.pat) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /mimium-lang/src/plugin.rs: -------------------------------------------------------------------------------- 1 | //! # Plugin System for mimium 2 | //! In order to extend mimium's capability to communicate between host system, mimium has its own FFI system. 3 | //! The FFI is done through some traits in this plugin module in order to decouple dependencies(modules may depends on external crates). 4 | //! There are 3 types of interfaces you need to define depending on what you need. 5 | //! 6 | //! 1. **IO Plugins** Sets of instance-free external functions such as `print` and `println`. They are mostly for glue functions for host system's IO. `mimium-core` is an example of this type of module. 7 | //! 2. **External Unit Generator(UGen) Plugin.** If you need to define native Unit Generator, use `UGenPlugin` interface. In mimium code, you need to call higher-order function that returns instance of the UGen. You need to write small wrapper for this which simply calls object's constructor (In the future, this wrapper will be automatically implemented through proc-macro). Multiple instances may exist at the same time. `mimium-symphonia` is a example of this type of module. 8 | //! 3. **System Plugin**. If your plugin needs to mutate states of system-wide instance (1 plugin instance per 1 vm), you need to implement `SystemPlugin` traits. System plugin can have callbacks invoked at the important timings of the system like `on_init`, `before_on_sample` & so on. Internal synchronous event scheduler is implemented through this plugins system. `mimium-rand` is also an example of this type of module. 9 | 10 | mod system_plugin; 11 | pub use system_plugin::{ 12 | to_ext_cls_info, DynSystemPlugin, SysPluginSignature, SystemPlugin, SystemPluginFnType, 13 | }; 14 | 15 | use crate::{ 16 | compiler::ExtFunTypeInfo, 17 | runtime::vm::{ExtClsInfo, ExtFnInfo}, 18 | }; 19 | 20 | pub trait Plugin { 21 | fn get_ext_functions(&self) -> Vec; 22 | fn get_ext_closures(&self) -> Vec; 23 | } 24 | 25 | pub struct InstantPlugin { 26 | pub extfns: Vec, 27 | pub extcls: Vec, 28 | } 29 | impl Plugin for InstantPlugin { 30 | fn get_ext_functions(&self) -> Vec { 31 | self.extfns.clone() 32 | } 33 | 34 | fn get_ext_closures(&self) -> Vec { 35 | self.extcls.clone() 36 | } 37 | } 38 | 39 | pub trait IOPlugin { 40 | fn get_ext_functions(&self) -> Vec; 41 | } 42 | 43 | impl Plugin for T 44 | where 45 | T: IOPlugin, 46 | { 47 | fn get_ext_functions(&self) -> Vec { 48 | ::get_ext_functions(self) 49 | } 50 | fn get_ext_closures(&self) -> Vec { 51 | vec![] 52 | } 53 | } 54 | 55 | /// Todo: Make wrapper macro for auto impl `Plugin` 56 | pub trait UGenPlugin { 57 | type InitParam; 58 | type Args; 59 | type Ret; 60 | fn new(param: Self::InitParam) -> Self; 61 | fn on_sample(&mut self, arg: Self::Args) -> Self::Ret; 62 | } 63 | // type DynUgenPlugin{} 64 | // pub type UGenPluginCollection(Vec); 65 | // impl Plugin for UGenPluginCollection{} 66 | 67 | pub fn get_extfun_types(plugins: &[Box]) -> impl Iterator + '_ { 68 | plugins.iter().flat_map(|plugin| { 69 | plugin 70 | .get_ext_functions() 71 | .into_iter() 72 | .map(|(name, _, ty)| ExtFunTypeInfo { name, ty }) 73 | .chain( 74 | plugin 75 | .get_ext_closures() 76 | .into_iter() 77 | .map(|(name, _, ty)| ExtFunTypeInfo { name, ty }), 78 | ) 79 | .collect::>() 80 | }) 81 | } 82 | 83 | pub fn get_extfuninfos(plugins: &[Box]) -> impl Iterator + '_ { 84 | plugins 85 | .iter() 86 | .flat_map(|plugin| plugin.get_ext_functions().into_iter()) 87 | } 88 | pub fn get_extclsinfos(plugins: &[Box]) -> impl Iterator + '_ { 89 | plugins 90 | .iter() 91 | .flat_map(|plugin| plugin.get_ext_closures().into_iter()) 92 | } 93 | -------------------------------------------------------------------------------- /mimium-lang/src/plugin/system_plugin.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | interner::{ToSymbol, TypeNodeId}, 3 | runtime::{ 4 | vm::{ExtClsInfo, Machine, ReturnCode}, 5 | Time, 6 | }, 7 | }; 8 | use std::{ 9 | any::Any, 10 | cell::{RefCell, UnsafeCell}, 11 | rc::Rc, 12 | }; 13 | pub type SystemPluginFnType = fn(&mut T, &mut Machine) -> ReturnCode; 14 | pub struct SysPluginSignature { 15 | name: &'static str, 16 | /// The function internally implements Fn(&mut T:SystemPlugin,&mut Machine)->ReturnCode 17 | /// but the type is erased for dynamic dispatching. later the function is downcasted into their own type. 18 | fun: Rc, 19 | ty: TypeNodeId, 20 | } 21 | impl SysPluginSignature { 22 | pub fn new(name: &'static str, fun: F, ty: TypeNodeId) -> Self 23 | where 24 | F: Fn(&mut T, &mut Machine) -> ReturnCode + 'static, 25 | T: SystemPlugin, 26 | { 27 | Self { 28 | name, 29 | fun: Rc::new(fun), 30 | ty, 31 | } 32 | } 33 | } 34 | 35 | pub trait SystemPlugin { 36 | fn on_init(&mut self, _machine: &mut Machine) -> ReturnCode { 37 | 0 38 | } 39 | fn after_main(&mut self, _machine: &mut Machine) -> ReturnCode { 40 | 0 41 | } 42 | fn on_sample(&mut self, _time: Time, _machine: &mut Machine) -> ReturnCode { 43 | 0 44 | } 45 | fn gen_interfaces(&self) -> Vec; 46 | fn try_get_main_loop(&mut self) -> Option> { 47 | None 48 | } 49 | } 50 | #[derive(Clone)] 51 | pub struct DynSystemPlugin(pub Rc>); 52 | 53 | 54 | pub fn to_ext_cls_info( 55 | sysplugin: T, 56 | ) -> (DynSystemPlugin, Vec) { 57 | let ifs = sysplugin.gen_interfaces(); 58 | let dyn_plugin = DynSystemPlugin(Rc::new(UnsafeCell::new(sysplugin))); 59 | let ifs_res = ifs 60 | .into_iter() 61 | .map(|SysPluginSignature { name, fun, ty }| -> ExtClsInfo { 62 | let plug = dyn_plugin.clone(); 63 | let fun = fun 64 | .clone() 65 | .downcast:: ReturnCode>() 66 | .expect("invalid conversion applied in the system plugin resolution."); 67 | let fun = Rc::new(RefCell::new(move |machine: &mut Machine| -> ReturnCode { 68 | // breaking double borrow rule at here!!! 69 | // Also here I do dirty downcasting because here the type of plugin is ensured as T. 70 | unsafe { 71 | let p = (plug.0.get() as *mut T).as_mut().unwrap(); 72 | fun(p, machine) 73 | } 74 | })); 75 | let res: ExtClsInfo = (name.to_symbol(), fun, ty); 76 | res 77 | }) 78 | .collect::>(); 79 | (dyn_plugin, ifs_res) 80 | } 81 | 82 | // impl SysPluginSignature {} 83 | -------------------------------------------------------------------------------- /mimium-lang/src/repl.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast_interpreter::{self, PValue, Value}, 3 | compiler::{self, interpret_top}, 4 | interner::ToSymbol, 5 | utils::{error, miniprint::MiniPrint}, 6 | }; 7 | use std::io::{stdin, stdout, Write}; 8 | 9 | pub enum ReplMode { 10 | Eval, 11 | EvalMulti(u64), 12 | ShowAST, 13 | } 14 | pub struct ReplAppData { 15 | line_count: u64, 16 | global_ctx: ast_interpreter::Context, 17 | mode: ReplMode, 18 | } 19 | impl Default for ReplAppData { 20 | fn default() -> Self { 21 | Self { 22 | line_count: 0, 23 | global_ctx: ast_interpreter::Context::default(), 24 | mode: ReplMode::Eval, 25 | } 26 | } 27 | } 28 | 29 | fn process_command(mode_str: &str) -> Option { 30 | match mode_str { 31 | ":e" => { 32 | println!("Mode:Eval"); 33 | Some(ReplMode::Eval) 34 | } 35 | ":m" => { 36 | println!("Mode:EvalMulti"); 37 | Some(ReplMode::EvalMulti(3)) 38 | } 39 | ":a" => { 40 | println!("Mode:AST"); 41 | Some(ReplMode::ShowAST) 42 | } 43 | 44 | _ => None, 45 | } 46 | } 47 | 48 | fn repl(data: &mut ReplAppData) -> ! { 49 | let mut src = String::new(); 50 | loop { 51 | print!("> "); 52 | let _ = stdout().flush(); 53 | let mut src_tmp = String::new(); 54 | let _size = stdin().read_line(&mut src_tmp).expect("stdin read error."); 55 | src = format!("{}{}", src, src_tmp); 56 | if let Some(mode) = process_command(&src[0..2]) { 57 | data.mode = mode; 58 | src.clear(); 59 | } else { 60 | let _ = src.pop(); //remove last linebreak 61 | if !src.as_str().ends_with('\\') { 62 | match data.mode { 63 | ReplMode::Eval => match interpret_top(src.clone(), &mut data.global_ctx) { 64 | Ok(v) => { 65 | println!("{:?}", v); 66 | } 67 | Err(e) => error::report(&src, "".to_symbol(), &e), 68 | }, 69 | ReplMode::EvalMulti(n) => { 70 | let mut res = Ok(Value::Primitive(PValue::Numeric(0.0))); 71 | for _i in 0..n { 72 | res = interpret_top(src.clone(), &mut data.global_ctx); 73 | data.global_ctx.history.0 = 0; 74 | } 75 | match res { 76 | Ok(v) => { 77 | println!("{:?}", v); 78 | } 79 | Err(e) => error::report(&src, "".to_symbol(), &e), 80 | } 81 | } 82 | ReplMode::ShowAST => match compiler::emit_ast(&src, None) { 83 | Ok(ast) => { 84 | println!("{}", ast.pretty_print()); 85 | } 86 | Err(e) => error::report(&src, "".to_symbol(), &e), 87 | }, 88 | } 89 | src.clear(); 90 | } else { 91 | src.pop(); //remove last backslash 92 | } 93 | 94 | data.line_count += 1; 95 | } 96 | } 97 | } 98 | 99 | pub fn run_repl() { 100 | repl(&mut ReplAppData::default()) 101 | } 102 | -------------------------------------------------------------------------------- /mimium-lang/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{error::ReportableError, metadata::Location}; 2 | 3 | // pub mod scheduler; 4 | // pub mod hir_interpreter; 5 | pub mod builtin_fn; 6 | 7 | pub mod vm; 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 9 | pub struct Time(pub u64); 10 | 11 | #[derive(Debug)] 12 | pub enum ErrorKind { 13 | Unknown, 14 | } 15 | 16 | impl std::fmt::Display for ErrorKind { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | match self { 19 | ErrorKind::Unknown => write!(f, "Unknown Error"), 20 | } 21 | } 22 | } 23 | #[derive(Debug)] 24 | pub struct Error(pub ErrorKind, pub Location); 25 | impl std::fmt::Display for Error { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | let _ = write!(f, "Runtime Error: "); 28 | self.0.fmt(f) 29 | } 30 | } 31 | 32 | impl std::error::Error for Error {} 33 | 34 | impl ReportableError for Error { 35 | fn get_labels(&self) -> Vec<(crate::utils::metadata::Location, String)> { 36 | vec![(self.1.clone(), self.0.to_string())] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mimium-lang/src/runtime/vm/builtin.rs: -------------------------------------------------------------------------------- 1 | use crate::compiler::ExtFunTypeInfo; 2 | use crate::interner::ToSymbol; 3 | use crate::types::{PType, Type}; 4 | use crate::{function, numeric}; 5 | 6 | use super::{ExtFnInfo, Machine, ReturnCode}; 7 | 8 | fn probef(machine: &mut Machine) -> ReturnCode { 9 | let rv = machine.get_stack(0); 10 | let i = super::Machine::get_as::(rv); 11 | print!("{i}"); 12 | machine.set_stack(0, rv); 13 | 1 14 | } 15 | 16 | fn probelnf(machine: &mut Machine) -> ReturnCode { 17 | let rv = machine.get_stack(0); 18 | let i = super::Machine::get_as::(rv); 19 | println!("{} ", i); 20 | machine.set_stack(0, rv); 21 | 1 22 | } 23 | fn min(machine: &mut Machine) -> ReturnCode { 24 | let lhs = super::Machine::get_as::(machine.get_stack(0)); 25 | let rhs = super::Machine::get_as::(machine.get_stack(1)); 26 | let res = lhs.min(rhs); 27 | machine.set_stack(0, super::Machine::to_value(res)); 28 | 1 29 | } 30 | fn max(machine: &mut Machine) -> ReturnCode { 31 | let lhs = super::Machine::get_as::(machine.get_stack(0)); 32 | let rhs = super::Machine::get_as::(machine.get_stack(1)); 33 | let res = lhs.max(rhs); 34 | machine.set_stack(0, super::Machine::to_value(res)); 35 | 1 36 | } 37 | 38 | pub fn get_builtin_fns() -> [ExtFnInfo; 4] { 39 | [ 40 | ( 41 | "probe".to_symbol(), 42 | probef, 43 | function!(vec![numeric!()], numeric!()), 44 | ), 45 | ( 46 | "probeln".to_symbol(), 47 | probelnf, 48 | function!(vec![numeric!()], numeric!()), 49 | ), 50 | ( 51 | "min".to_symbol(), 52 | min, 53 | function!(vec![numeric!(), numeric!()], numeric!()), 54 | ), 55 | ( 56 | "max".to_symbol(), 57 | max, 58 | function!(vec![numeric!(), numeric!()], numeric!()), 59 | ), 60 | ] 61 | } 62 | 63 | pub fn get_builtin_fn_types() -> Vec { 64 | get_builtin_fns() 65 | .iter() 66 | .map(|(name, _f, t)| ExtFunTypeInfo { 67 | name: *name, 68 | ty: *t, 69 | }) 70 | .collect() 71 | } 72 | -------------------------------------------------------------------------------- /mimium-lang/src/runtime/vm/program.rs: -------------------------------------------------------------------------------- 1 | use super::{ConstPos, Instruction, RawVal}; 2 | use crate::compiler::IoChannelInfo; 3 | use crate::interner::{Symbol, ToSymbol, TypeNodeId}; 4 | use crate::mir; 5 | pub use mir::OpenUpValue; 6 | 7 | /// Function prototype definition in the bytecode program. 8 | 9 | #[derive(Debug, Default, Clone, PartialEq)] 10 | pub struct FuncProto { 11 | pub nparam: usize, 12 | pub nret: usize, 13 | pub upindexes: Vec, 14 | pub bytecodes: Vec, 15 | pub constants: Vec, 16 | pub state_size: u64, 17 | pub delay_sizes: Vec, 18 | } 19 | impl FuncProto { 20 | pub fn new(nparam: usize, nret: usize) -> Self { 21 | Self { 22 | nparam, 23 | nret, 24 | ..Default::default() 25 | } 26 | } 27 | /// Adds new constant to the program. Returns index in the array. 28 | pub fn add_new_constant(&mut self, cval: RawVal) -> ConstPos { 29 | self.constants.binary_search(&cval).unwrap_or_else(|_err| { 30 | self.constants.push(cval); 31 | self.constants.len() - 1 32 | }) as _ 33 | } 34 | } 35 | 36 | /// Complete bytecode programs. 37 | #[derive(Debug, Default, Clone, PartialEq)] 38 | pub struct Program { 39 | pub global_fn_table: Vec<(Symbol, FuncProto)>, 40 | pub ext_fun_table: Vec<(Symbol, TypeNodeId)>, 41 | pub global_vals: Vec, 42 | pub strings: Vec, 43 | pub file_path: Option, 44 | pub iochannels: Option, 45 | } 46 | impl Program { 47 | pub fn get_fun_index(&self, name: &Symbol) -> Option { 48 | self.global_fn_table 49 | .iter() 50 | .position(|(label, _f)| label == name) 51 | } 52 | pub fn get_dsp_fn(&self) -> Option<&FuncProto> { 53 | self.get_fun_index(&"dsp".to_symbol()) 54 | .and_then(|idx| self.global_fn_table.get(idx).map(|(_, f)| f)) 55 | } 56 | pub fn add_new_str(&mut self, s: Symbol) -> usize { 57 | self.strings 58 | .iter() 59 | .position(|&c| s == c) 60 | .unwrap_or_else(|| { 61 | self.strings.push(s); 62 | self.strings.len() - 1 63 | }) 64 | } 65 | } 66 | 67 | impl std::fmt::Display for Program { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | for fns in self.global_fn_table.iter() { 70 | let _ = writeln!(f, "{}", fns.0); 71 | let _ = writeln!(f, "nparams:{} nret: {}", fns.1.nparam, fns.1.nret); 72 | let _ = write!(f, "upindexes: {:?} ", fns.1.upindexes); 73 | let _ = writeln!(f, "state_size: {} ", fns.1.state_size); 74 | let _ = writeln!(f, "constants: {:?}", fns.1.constants); 75 | let _ = writeln!(f, "instructions:"); 76 | for inst in fns.1.bytecodes.iter() { 77 | let _ = writeln!(f, " {}", inst); 78 | } 79 | } 80 | let _ = write!( 81 | f, 82 | "ext_fun:\n{:?}\n", 83 | self.ext_fun_table 84 | .iter() 85 | .fold("".to_string(), |s, (f, _)| if s.is_empty() { 86 | format!("{f}") 87 | } else { 88 | format!("{s}, {f}") 89 | }) 90 | ); 91 | let _ = write!(f, "globals:\n{:?}", self.global_vals); 92 | writeln!( 93 | f, 94 | "strings: {:?}", 95 | self.strings 96 | .iter() 97 | .map(|s| s.to_string()) 98 | .collect::>() 99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /mimium-lang/src/runtime/vm/ringbuffer.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::slice_from_raw_parts_mut; 2 | 3 | use super::RawVal; 4 | 5 | /// A minimal implementation of synchronous ringbuffer for a single thread code. 6 | /// Because this Ringbuffer is constructed by re-interpreting the portion of linear memory array temporary, it has limited lifetime. 7 | #[repr(C)] 8 | pub(super) struct Ringbuffer<'a> { 9 | read_idx: &'a mut u64, 10 | write_idx: &'a mut u64, 11 | data: &'a mut [u64], 12 | } 13 | 14 | impl<'a> Ringbuffer<'a> { 15 | pub fn new(head: *mut u64, size_in_samples: u64) -> Self { 16 | let (read_idx, write_idx, data) = unsafe { 17 | let read_idx = head.as_mut().unwrap_unchecked(); 18 | let write_ptr = head.offset(1); 19 | let write_idx = write_ptr.as_mut().unwrap_unchecked(); 20 | let data_head = head.offset(2); 21 | let data = slice_from_raw_parts_mut(data_head, size_in_samples as usize); 22 | ( 23 | read_idx, 24 | write_idx, 25 | data.as_mut().unwrap(), 26 | ) 27 | }; 28 | 29 | Self { 30 | read_idx, 31 | write_idx, 32 | data, 33 | } 34 | } 35 | pub fn process(&mut self, input: RawVal, time_raw: u64) -> RawVal { 36 | let len = self.data.len() as u64; 37 | let res = unsafe { 38 | let time = f64::from_bits(time_raw) as u64; 39 | let read_idx = *self.read_idx; 40 | *self.write_idx = (read_idx + time) % len; 41 | let write_idx = *self.write_idx; 42 | let res = *self.data.get_unchecked(read_idx as usize); 43 | *self.data.get_unchecked_mut(write_idx as usize) = input; 44 | *self.read_idx = (read_idx + 1) % len; 45 | res 46 | }; 47 | res 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mimium-lang/src/types/builder.rs: -------------------------------------------------------------------------------- 1 | use super::PType; 2 | use super::Type; 3 | 4 | #[macro_export] 5 | macro_rules! unit { 6 | () => { 7 | Type::Primitive(PType::Unit).into_id() 8 | }; 9 | } 10 | 11 | #[macro_export] 12 | macro_rules! integer { 13 | () => { 14 | Type::Primitive(PType::Int).into_id() 15 | }; 16 | } 17 | #[macro_export] 18 | macro_rules! numeric { 19 | () => { 20 | Type::Primitive(PType::Numeric).into_id() 21 | }; 22 | } 23 | #[macro_export] 24 | macro_rules! string_t { 25 | () => { 26 | Type::Primitive(PType::String).into_id() 27 | }; 28 | } 29 | #[macro_export] 30 | macro_rules! function { 31 | ($params:expr, $return:expr) => { 32 | Type::Function($params, $return, None).into_id() 33 | }; 34 | } 35 | 36 | #[macro_export] 37 | macro_rules! refer { 38 | ($t:expr) => { 39 | Type::Ref($t).into_id() 40 | }; 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! tuple { 45 | 46 | ($($t:expr),*) => { 47 | Type::Tuple(vec![$($t,)*]).into_id() 48 | }; 49 | } 50 | 51 | #[cfg(test)] 52 | mod typemacro_test { 53 | use super::*; 54 | #[test] 55 | fn buildertest() { 56 | let t = tuple!( 57 | refer!(function!(vec![integer!(), integer!()], numeric!())), 58 | string_t!() 59 | ); 60 | let answer = Type::Tuple(vec![ 61 | Type::Ref( 62 | Type::Function( 63 | vec![ 64 | Type::Primitive(PType::Int).into_id(), 65 | Type::Primitive(PType::Int).into_id(), 66 | ], 67 | Type::Primitive(PType::Numeric).into_id(), 68 | None, 69 | ) 70 | .into_id(), 71 | ) 72 | .into_id(), 73 | Type::Primitive(PType::String).into_id(), 74 | ]) 75 | .into_id(); 76 | assert_eq!(t, answer); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mimium-lang/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod environment; 2 | pub mod error; 3 | pub mod fileloader; 4 | pub mod metadata; 5 | pub mod miniprint; 6 | pub mod half_float; 7 | 8 | #[macro_export] 9 | macro_rules! format_vec { 10 | ($vec:expr,$sep:expr) => { 11 | $vec.iter() 12 | .map(|v| v.to_string()) 13 | .collect::>() 14 | .join($sep) 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /mimium-lang/src/utils/environment.rs: -------------------------------------------------------------------------------- 1 | use std::collections::LinkedList; 2 | 3 | use crate::interner::Symbol; 4 | 5 | type EnvInner = LinkedList>; 6 | 7 | #[derive(Clone, Debug, PartialEq)] 8 | pub struct Environment(pub EnvInner); 9 | 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum LookupRes { 12 | Local(T), 13 | UpValue(usize, T), 14 | Global(T), 15 | None, 16 | } 17 | impl Default for Environment { 18 | fn default() -> Self { 19 | Self(EnvInner::new()) 20 | } 21 | } 22 | impl Environment { 23 | pub fn new() -> Self { 24 | Self(EnvInner::new()) 25 | } 26 | pub fn is_global(&self) -> bool { 27 | self.0.len() <= 1 28 | } 29 | pub fn extend(&mut self) { 30 | self.0.push_front(Vec::new()); 31 | } 32 | pub fn to_outer(&mut self) { 33 | let _ = self.0.pop_front(); 34 | } 35 | pub fn add_bind(&mut self, binds: &[(Symbol, T)]) { 36 | assert!(!self.0.is_empty()); 37 | self.0.front_mut().unwrap().extend_from_slice(binds); 38 | } 39 | 40 | pub fn lookup_cls(&self, name: &Symbol) -> LookupRes<&T> { 41 | match self 42 | .0 43 | .iter() 44 | .enumerate() 45 | .find(|(_level, vec)| vec.iter().any(|(n, _)| n == name)) 46 | .and_then(|(level, vec)| vec.iter().rfind(|(n, _)| n == name).map(|(_, v)| (level, v))) 47 | { 48 | None => LookupRes::None, 49 | Some((level, e)) if level >= self.0.len() - 1 => LookupRes::Global(e), 50 | Some((0, e)) if self.0.len() <= 1 => LookupRes::Global(e), 51 | Some((0, e)) => LookupRes::Local(e), 52 | Some((level, e)) => LookupRes::UpValue(level, e), 53 | } 54 | } 55 | pub fn lookup(&self, name: &Symbol) -> Option<&T> { 56 | match self.lookup_cls(name) { 57 | LookupRes::None => None, 58 | LookupRes::Global(e) | LookupRes::Local(e) | LookupRes::UpValue(_, e) => Some(e), 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mimium-lang/src/utils/error.rs: -------------------------------------------------------------------------------- 1 | use ariadne::{ColorGenerator, Label, Report, ReportKind, Source}; 2 | 3 | use crate::interner::Symbol; 4 | 5 | use super::metadata::Location; 6 | 7 | /// A dynamic error type that can hold specific error messages and the location where the error happened. 8 | pub trait ReportableError: std::error::Error { 9 | /// message is used for reporting verbose message for `ariadne``. 10 | fn get_message(&self) -> String { 11 | self.to_string() 12 | } 13 | /// Label is used for indicating error with the specific position for `ariadne``. 14 | /// One error may have multiple labels, because the reason of the error may be caused by the mismatch of the properties in 2 or more different locations in the source (such as the type mismatch). 15 | fn get_labels(&self) -> Vec<(Location, String)>; 16 | } 17 | 18 | /// ReportableError implements `PartialEq`` mostly for testing purpose. 19 | impl PartialEq for dyn ReportableError + '_ { 20 | fn eq(&self, other: &Self) -> bool { 21 | self.get_labels() == other.get_labels() 22 | } 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct SimpleError { 27 | pub message: String, 28 | pub span: Location, 29 | } 30 | impl std::fmt::Display for SimpleError { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{}", self.message) 33 | } 34 | } 35 | impl std::error::Error for SimpleError {} 36 | impl ReportableError for SimpleError { 37 | fn get_labels(&self) -> Vec<(Location, String)> { 38 | vec![(self.span.clone(), self.message.clone())] 39 | } 40 | } 41 | 42 | struct FileCache { 43 | path: Symbol, 44 | src: ariadne::Source, 45 | } 46 | 47 | impl ariadne::Cache for FileCache { 48 | type Storage = String; 49 | 50 | fn fetch( 51 | &mut self, 52 | _id: &Symbol, 53 | ) -> Result<&Source, Box> { 54 | Ok(&self.src) 55 | } 56 | 57 | fn display<'a>(&self, id: &'a Symbol) -> Option> { 58 | Some(Box::new(id.to_string())) 59 | } 60 | } 61 | 62 | pub fn report(src: &str, path: Symbol, errs: &[Box]) { 63 | let mut colors = ColorGenerator::new(); 64 | for e in errs { 65 | // let a_span = (src.source(), span);color 66 | let rawlabels = e.get_labels(); 67 | let labels = rawlabels.iter().map(|(span, message)| { 68 | Label::new(span.clone()) 69 | .with_message(message) 70 | .with_color(colors.next()) 71 | }); 72 | let builder = Report::build(ReportKind::Error, path, 4) 73 | .with_message(e.get_message()) 74 | .with_labels(labels) 75 | .finish(); 76 | builder 77 | .eprint(FileCache { 78 | path, 79 | src: ariadne::Source::from(src.to_owned()), 80 | }) 81 | .unwrap(); 82 | } 83 | } 84 | 85 | pub fn dump_to_string(errs: &[Box]) -> String { 86 | let mut res = String::new(); 87 | for e in errs { 88 | res += e.get_message().as_str(); 89 | } 90 | res 91 | } 92 | -------------------------------------------------------------------------------- /mimium-lang/src/utils/fileloader.cjs: -------------------------------------------------------------------------------- 1 | // foo.js 2 | // import fs from "fs"; 3 | const fs = require("fs"); 4 | 5 | function read_file(path) { 6 | return fs.readFileSync(path, { encoding: "utf8" }); 7 | } 8 | exports.read_file = read_file; 9 | function get_env(key){ 10 | return process.env[key] 11 | } 12 | exports.get_env = get_env; -------------------------------------------------------------------------------- /mimium-lang/src/utils/half_float.rs: -------------------------------------------------------------------------------- 1 | use half::f16; 2 | use std::fmt::Display; 3 | /// Half-Precision floating point type that can be converted from 64bit float with truncation checking. 4 | pub const ALLOWED_ERROR: f64 = 0.00001; 5 | 6 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 7 | pub struct HFloat(f16); 8 | 9 | impl Display for HFloat { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | self.0.fmt(f) 12 | } 13 | } 14 | 15 | impl From for HFloat { 16 | fn from(value: f16) -> Self { 17 | Self(value) 18 | } 19 | } 20 | impl From for f64 { 21 | fn from(value: HFloat) -> Self { 22 | value.0.to_f64() 23 | } 24 | } 25 | 26 | impl TryFrom for HFloat { 27 | type Error = (); 28 | 29 | fn try_from(value: f64) -> Result { 30 | let hv = f16::from_f64(value); 31 | let error = (hv.to_f64() - value).abs(); 32 | if error < ALLOWED_ERROR { 33 | Ok(Self(f16::from_f64(value))) 34 | } else { 35 | Err(()) 36 | } 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod test { 42 | use super::*; 43 | 44 | #[test] 45 | fn safecast() { 46 | let f64v: f64 = 2.0; 47 | let hv = HFloat::try_from(f64v); 48 | assert!(hv.is_ok()); 49 | assert!((hv.unwrap().0.to_f64() - f64v).abs() < ALLOWED_ERROR) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mimium-lang/src/utils/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::interner::{Symbol, ToSymbol}; 2 | 3 | pub type Span = std::ops::Range; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct Location { 7 | pub span: Span, 8 | pub path: Symbol, 9 | } 10 | impl Location { 11 | pub fn new(span: Span, path: Symbol) -> Self { 12 | Self { span, path } 13 | } 14 | } 15 | impl Default for Location { 16 | fn default() -> Self { 17 | Self { 18 | span: 0..0, 19 | path: "".to_symbol(), 20 | } 21 | } 22 | } 23 | 24 | impl ariadne::Span for Location { 25 | type SourceId = Symbol; 26 | 27 | fn source(&self) -> &Self::SourceId { 28 | &self.path 29 | } 30 | 31 | fn start(&self) -> usize { 32 | self.span.start 33 | } 34 | 35 | fn end(&self) -> usize { 36 | self.span.end 37 | } 38 | } 39 | 40 | // #[derive(Clone, Debug, PartialEq)] 41 | // pub struct WithMeta{ 42 | // pub location: Span, 43 | // pub value : T 44 | // } 45 | pub(crate) const GLOBAL_LABEL: &str = "_mimium_global"; 46 | 47 | // #[derive(Clone, Debug, PartialEq)] 48 | // pub struct WithMeta(pub T, pub Span); 49 | 50 | // impl WithMeta { 51 | // fn map(self, f: F) -> WithMeta 52 | // where 53 | // F: FnOnce(T) -> O, 54 | // O: Clone + PartialEq, 55 | // { 56 | // WithMeta(f(self.0), self.1) 57 | // } 58 | // } 59 | -------------------------------------------------------------------------------- /mimium-lang/src/utils/miniprint.rs: -------------------------------------------------------------------------------- 1 | pub trait MiniPrint { 2 | fn simple_print(&self) -> String; 3 | 4 | fn pretty_print(&self) -> String { 5 | let src = self.simple_print(); 6 | let mut level = 0; 7 | let mut res = String::new(); 8 | let indent = " "; 9 | for c in src.chars() { 10 | match c { 11 | '(' => { 12 | level += 1; 13 | } 14 | ')' => { 15 | level -= 1; 16 | } 17 | ' ' => { 18 | res.push('\n'); 19 | for _i in 0..level { 20 | res.push_str(indent); 21 | } 22 | } 23 | _ => {} 24 | } 25 | 26 | res.push(c); 27 | } 28 | res 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mimium-midi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-midi" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "midi input plugin for mimium using midir." 8 | build = "build.rs" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | # [lib] 12 | 13 | 14 | [dependencies] 15 | atomic_float = "1.1.0" 16 | midir = "0.10.0" 17 | 18 | mimium-lang = { workspace = true } 19 | wmidi = "4.0.10" 20 | 21 | [dev-dependencies] 22 | mimium-test = { workspace = true } 23 | -------------------------------------------------------------------------------- /mimium-midi/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=TEST_ROOT={}", env!("CARGO_MANIFEST_DIR")); 3 | } -------------------------------------------------------------------------------- /mimium-midi/examples/midiin.mmm: -------------------------------------------------------------------------------- 1 | let pi = 3.14159265359 2 | let sr = 48000.0 3 | let _ = set_midi_port("from Max 1") 4 | fn sec2samp(sec){ 5 | sec*48000.0 6 | } 7 | fn countup(active){ 8 | let r =(self+1.0); 9 | let rr = if (active) r else 0.0 10 | rr 11 | } 12 | fn countupn(time,active){ 13 | let res = countup(active) 14 | let r = if(res0.0 19 | } 20 | fn get_gain(gate){ 21 | if (gate>0.1) gate else self 22 | } 23 | fn adsr(attack,decay,sustain,release,input){ 24 | let s = self 25 | let atsig = min(1.0,(s + 1.0/sec2samp(attack))) 26 | let decsig = max(sustain,(s-1.0/sec2samp(decay))) 27 | let releasesig =max(0.0,(s-1.0/sec2samp(release))) 28 | let at_or_dec = hold(sec2samp(attack),input>0.1) 29 | let at_dec_sus_sig = if (at_or_dec>0.1) atsig else decsig 30 | let res = if (input>0.1) at_dec_sus_sig else releasesig 31 | res 32 | } 33 | fn phasor(freq){ 34 | (self + freq/sr)%1.0 35 | } 36 | fn osc(freq){ 37 | sin(phasor(freq)*pi*2.0) 38 | } 39 | fn midi_to_hz(note){ 40 | 440.0* (2.0 ^((note-69.0)/12.0)) 41 | } 42 | let ch0 = bind_midi_note_mono( 0.0,69.0,0.0); 43 | let ch1 = bind_midi_note_mono( 1.0,69.0,0.0); 44 | let ch2 = bind_midi_note_mono( 2.0,69.0,0.0); 45 | let ch3 = bind_midi_note_mono( 3.0,69.0,0.0); 46 | let ch4 = bind_midi_note_mono( 4.0,69.0,0.0); 47 | let ch5 = bind_midi_note_mono( 5.0,69.0,0.0); 48 | let ch6 = bind_midi_note_mono( 6.0,69.0,0.0); 49 | let ch7 = bind_midi_note_mono( 7.0,69.0,0.0); 50 | let ch8 = bind_midi_note_mono( 8.0,69.0,0.0); 51 | let ch9 = bind_midi_note_mono( 9.0,69.0,0.0); 52 | let ch10 = bind_midi_note_mono(10.0,69.0,0.0); 53 | let ch11 = bind_midi_note_mono(11.0,69.0,0.0); 54 | let ch12 = bind_midi_note_mono(12.0,69.0,0.0); 55 | let ch13 = bind_midi_note_mono(13.0,69.0,0.0); 56 | let ch14 = bind_midi_note_mono(14.0,69.0,0.0); 57 | let ch15 = bind_midi_note_mono(15.0,69.0,0.0); 58 | 59 | fn myadsr(gate){ 60 | adsr(0.01,0.1,1.0,2.0,gate) *get_gain(gate) 61 | } 62 | fn synth(midiv){ 63 | let (note,vel) = midiv 64 | let sig = note |> midi_to_hz |> osc 65 | let gain = vel /127.0; 66 | sig * ( gain |> myadsr ) 67 | } 68 | 69 | 70 | fn dsp(){ 71 | let r_r = (ch0() |> synth) + 72 | (ch1() |> synth) + 73 | (ch2() |> synth) + 74 | (ch3() |> synth) + 75 | (ch4() |> synth) + 76 | (ch5() |> synth) + 77 | (ch6() |> synth) + 78 | (ch7() |> synth)+ 79 | (ch8() |> synth) + 80 | (ch9() |> synth) + 81 | (ch10() |> synth) + 82 | (ch11() |> synth) + 83 | (ch12() |> synth) + 84 | (ch13() |> synth) + 85 | (ch14() |> synth)+ 86 | (ch15() |> synth) 87 | 88 | let r = r_r/16.0 89 | (r,r) 90 | } -------------------------------------------------------------------------------- /mimium-midi/readme.md: -------------------------------------------------------------------------------- 1 | # mimium MIDI Plugin 2 | 3 | MIDIPlugin provides 2APIs: `set_midi_port("port_name")` and `bind_midi_note_mono(channel,default_note,default_velocity)`. 4 | 5 | `bind_midi_note_mono` returns getter function of (float,float) value which is updated asynchronously by midi note event. 6 | 7 | (NoteOff is treated as NoteOn with 0 velocity). 8 | 9 | Processing for raw MIDI events like midi plugin in VST cannot be realized for now. 10 | 11 | (Note that MIDI devices are not available for WSL. I tested only on macOS.) 12 | 13 | 14 | ```rust 15 | let _ = set_midi_port("from Max 1") 16 | fn osc(freq){ 17 | ... 18 | } 19 | fn midi_to_hz(note){ 20 | 440.0* (2.0 ^((note-69.0)/12.0)) 21 | } 22 | let boundval = bind_midi_note_mono(0.0,69.0,127.0); 23 | fn dsp(){ 24 | let (note,vel) = boundval(); 25 | 26 | let sig = note |> midi_to_hz |> osc 27 | let r = sig * (vel /127.0); 28 | (r,r) 29 | } 30 | ``` -------------------------------------------------------------------------------- /mimium-midi/tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | // use midir::os::unix::{VirtualInput,VirtualOutput}; 2 | // use mimium_midi::MidiPlugin; 3 | // use mimium_lang::plugin::Plugin; 4 | // use mimium_test::*; 5 | -------------------------------------------------------------------------------- /mimium-midi/tests/mmm/midi_test.mmm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mimium-org/mimium-rs/f10d5c414ef760b79dc4c38f289d96b78e7917ab/mimium-midi/tests/mmm/midi_test.mmm -------------------------------------------------------------------------------- /mimium-rs.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "rust-analyzer.check.command": "clippy", 9 | // Uncomment when debugging wasm target! 10 | // "rust-analyzer.cargo.target": "wasm32-unknown-unknown", 11 | }, 12 | "launch": { 13 | "version": "0.2.0", 14 | "configurations": [ 15 | { 16 | "type": "lldb", 17 | "request": "launch", 18 | "name": "Debug unit tests in library 'mimium-parser'", 19 | "cargo": { 20 | "args": ["test", "--no-run", "--lib", "--package=mimium-parser"], 21 | "filter": { 22 | "name": "mimium-parser", 23 | "kind": "lib" 24 | } 25 | }, 26 | "args": [], 27 | "cwd": "${workspaceFolder}" 28 | // "preLaunchTask": "symlink dSYM" 29 | }, 30 | { 31 | "type": "lldb", 32 | "request": "launch", 33 | "name": "Debug executable 'mimium-CLI'", 34 | "cargo": { 35 | "cwd": "${workspaceFolder}", 36 | "args": ["build", "--bin=mimium-cli", "--package=mimium-cli"], 37 | "filter": { 38 | "name": "mimium-cli", 39 | "kind": "bin" 40 | } 41 | }, 42 | "args": ["mimium-cli/examples/sinewave.mmm"], 43 | "cwd": "${workspaceFolder}" 44 | }, 45 | { 46 | "type": "lldb", 47 | "request": "launch", 48 | "name": "Debug bc emitter 'mimium-cli'", 49 | "cargo": { 50 | "cwd": "${workspaceFolder}", 51 | "args": ["build", "--bin=mimium-cli", "--package=mimium-cli"], 52 | "filter": { 53 | "name": "mimium-cli", 54 | "kind": "bin" 55 | } 56 | }, 57 | "args": ["tests/mmm/mir_counter.mmm"], 58 | "cwd": "${workspaceFolder}" 59 | }, 60 | { 61 | "type": "lldb", 62 | "request": "launch", 63 | "name": "Debug unit tests in executable 'mimium-rs'", 64 | "cargo": { 65 | "cwd": "${workspaceFolder}", 66 | "args": ["test", "--no-run", "--bin=mimium-lang"], 67 | "filter": { 68 | // "name": "mimium-rs", 69 | // "kind": "bin" 70 | } 71 | }, 72 | "args": [], 73 | "cwd": "${workspaceFolder}" 74 | } 75 | ] 76 | }, 77 | "extensions": { 78 | "recommendations": [ 79 | "vadimcn.vscode-lldb", 80 | "tamasfe.even-better-toml", 81 | "mimium-org.mimium-language", 82 | "rust-lang.rust-analyzer" 83 | ] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /mimium-scheduler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-scheduler" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "basic scheduler implementation as a plugin for mimium" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | # [lib] 11 | 12 | [dependencies] 13 | 14 | mimium-lang = { workspace = true } 15 | 16 | [dev-dependencies] 17 | mimium-test = { workspace = true } 18 | -------------------------------------------------------------------------------- /mimium-scheduler/benches/bench.rs: -------------------------------------------------------------------------------- 1 | // If you want to run benchmark, you need to run with nightly channel. Run with `cargo +nightly bench`. 2 | #![feature(test)] 3 | extern crate test; 4 | 5 | fn main(){ 6 | 7 | } 8 | #[cfg(test)] 9 | mod tests{ 10 | use test::Bencher; 11 | use mimium_test::run_source_with_scheduler; 12 | #[bench] 13 | fn scheduler_counter(b:&mut Bencher){ 14 | let src = r"fn makecounter(){ 15 | let x = 0.0 16 | letrec gen = | |{ 17 | x = x+1.0 18 | gen@(now+1.0) 19 | } 20 | gen@1.0 21 | let getter = | | {x} 22 | getter 23 | } 24 | let x_getter = makecounter(); 25 | fn dsp(){ 26 | x_getter() 27 | }"; 28 | b.iter(move ||{ 29 | let _res = run_source_with_scheduler(src,10); 30 | }) 31 | } 32 | } -------------------------------------------------------------------------------- /mimium-scheduler/src/lib.rs: -------------------------------------------------------------------------------- 1 | use mimium_lang::plugin::{SysPluginSignature, SystemPlugin}; 2 | use mimium_lang::runtime::vm::{self, ClosureIdx, Machine, ReturnCode}; 3 | use mimium_lang::runtime::Time; 4 | use mimium_lang::{ 5 | function, numeric, 6 | types::{PType, Type}, 7 | unit, 8 | }; 9 | mod scheduler; 10 | pub use scheduler::{DummyScheduler, SchedulerInterface, SyncScheduler}; 11 | 12 | pub struct Scheduler(T); 13 | 14 | impl Scheduler { 15 | fn schedule_at(&mut self, machine: &mut Machine) -> ReturnCode { 16 | let time = Time(vm::Machine::get_as::(machine.get_stack(0)) as u64); 17 | let clsid = vm::Machine::get_as::(machine.get_stack(1)); 18 | self.0.schedule_at(time, clsid); 19 | 0 20 | } 21 | } 22 | 23 | impl SystemPlugin for Scheduler { 24 | fn on_init(&mut self, _machine: &mut Machine) -> ReturnCode { 25 | 0 26 | } 27 | 28 | fn on_sample(&mut self, time: Time, machine: &mut Machine) -> ReturnCode { 29 | self.0.set_cur_time(time); 30 | while let Some(task_cls) = self.0.pop_task(time) { 31 | let closure = machine.get_closure(task_cls); 32 | machine.execute(closure.fn_proto_pos, Some(task_cls)); 33 | vm::drop_closure(&mut machine.closures, task_cls); 34 | } 35 | 36 | 0 37 | } 38 | 39 | fn gen_interfaces(&self) -> Vec { 40 | let fun: fn(&mut Self, &mut Machine) -> ReturnCode = Self::schedule_at; 41 | let schedule_fn = SysPluginSignature::new( 42 | "_mimium_schedule_at", 43 | fun, 44 | function!(vec![numeric!(), function!(vec![], unit!())], unit!()), 45 | ); 46 | vec![schedule_fn] 47 | } 48 | } 49 | 50 | pub fn get_default_scheduler_plugin() -> impl SystemPlugin { 51 | Scheduler::<_>(SyncScheduler::new()) 52 | } 53 | -------------------------------------------------------------------------------- /mimium-scheduler/src/scheduler.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Reverse, collections::BinaryHeap}; 2 | 3 | use mimium_lang::runtime::{vm::ClosureIdx, Time}; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 | pub struct Task { 7 | when: Time, 8 | cls: ClosureIdx, 9 | } 10 | impl PartialOrd for Task { 11 | fn partial_cmp(&self, other: &Self) -> Option { 12 | Some(self.cmp(other)) 13 | } 14 | } 15 | impl Ord for Task { 16 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 17 | self.when.cmp(&other.when) 18 | } 19 | } 20 | pub trait SchedulerInterface { 21 | fn new() -> Self 22 | where 23 | Self: Sized; 24 | fn schedule_at(&mut self, time: Time, task: ClosureIdx); 25 | fn pop_task(&mut self, now: Time) -> Option; 26 | fn set_cur_time(&mut self, time: Time); 27 | } 28 | 29 | #[derive(Clone)] 30 | pub struct SyncScheduler { 31 | tasks: BinaryHeap>, 32 | cur_time: Time, 33 | } 34 | 35 | impl SchedulerInterface for SyncScheduler { 36 | fn new() -> Self { 37 | Self { 38 | tasks: Default::default(), 39 | cur_time: Time(0), 40 | } 41 | } 42 | fn schedule_at(&mut self, when: Time, cls: ClosureIdx) { 43 | if when <= self.cur_time { 44 | // TODO: ideally, this should not stop runtime. 45 | panic!( 46 | "A task must be scheduled in future (current time: {}, schedule: {})", 47 | self.cur_time.0, when.0 48 | ) 49 | } 50 | self.tasks.push(Reverse(Task { when, cls })); 51 | } 52 | 53 | fn pop_task(&mut self, now: Time) -> Option { 54 | match self.tasks.peek() { 55 | Some(Reverse(Task { when, cls })) if *when <= now => { 56 | let res = Some(*cls); 57 | let _ = self.tasks.pop(); 58 | res 59 | } 60 | _ => None, 61 | } 62 | } 63 | 64 | fn set_cur_time(&mut self, time: Time) { 65 | self.cur_time = time 66 | } 67 | } 68 | 69 | #[derive(Clone)] 70 | pub struct DummyScheduler; 71 | impl SchedulerInterface for DummyScheduler { 72 | fn new() -> Self 73 | where 74 | Self: Sized, 75 | { 76 | Self 77 | } 78 | 79 | fn schedule_at(&mut self, _time: Time, _task: ClosureIdx) { 80 | // do nothing 81 | } 82 | 83 | fn pop_task(&mut self, _now: Time) -> Option { 84 | // do nothing 85 | None 86 | } 87 | 88 | fn set_cur_time(&mut self, _time: Time) { 89 | // do nothing 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /mimium-symphonia/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-symphonia" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "sound file sampler implementation using symphonia for mimium." 8 | build = "build.rs" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | # [lib] 12 | 13 | 14 | [dependencies] 15 | 16 | mimium-lang = { workspace = true } 17 | symphonia = "0.5.4" 18 | 19 | [dev-dependencies] 20 | mimium-test = { workspace = true } 21 | -------------------------------------------------------------------------------- /mimium-symphonia/Readme.md: -------------------------------------------------------------------------------- 1 | # mimium-symphonia 2 | 3 | An external function implementation to read audio files in mimium using [Symphonia](https://github.com/pdeljanov/Symphonia/) crate. 4 | 5 | ## Example 6 | 7 | ```rust 8 | //gen_sampler_mono returns higher-order function that takes playback position in samples as an argument 9 | let sampler = gen_sampler_mono("test.wav") 10 | fn counter(){ 11 | self+1 12 | } 13 | fn dsp(){ 14 | counter() |> sampler 15 | } 16 | ``` 17 | 18 | *`gen_sampler_mono` should be called only on global evaluation context.* 19 | 20 | ## Current status 21 | 22 | - An argument for `gen_sampler_mono` is a relative path from the source `.mmm`, or an absolute path. 23 | - Currently there's no operation is supported on string type (only the literal is supported) in mimium, parametric file loading can not be realized for now. 24 | - Supported file format are the same as [what `symphonia` can decode](https://github.com/pdeljanov/Symphonia?tab=readme-ov-file#codecs-decoders) 25 | - Currently, only mono files are supported (other formats will cause panic on runtime). 26 | - An index for sampler is a raw position of samples (now, samplerate mismatch between audio driver and audio file are not handled. Resampler is not implemented as a part of symphonia, but there's an example code of resampler in symphonia-play application, it can be derived in the future). 27 | - An index are not needed to be integer, a boundary check and linear interpolation are performed on read.(Aceess out of range returns 0.0 .) 28 | - `gen_sampler_mono` function internally creates external Rust closure that holds Vec as internal data(The allocated external closure will never be released in the current implementation. ) -------------------------------------------------------------------------------- /mimium-symphonia/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=TEST_ROOT={}", env!("CARGO_MANIFEST_DIR")); 3 | } -------------------------------------------------------------------------------- /mimium-symphonia/src/filemanager.rs: -------------------------------------------------------------------------------- 1 | /// copied from otopoiesis code. 2 | /// TODO: move this to mimium-lang/utils/fileloader. 3 | use symphonia::core::io::MediaSource; 4 | 5 | pub trait FileManager { 6 | //required to convert into symphonia::MediaSource 7 | // type File: std::fs::File; 8 | type Stream: MediaSource; 9 | type Error: std::error::Error; 10 | // fn open_file(&self, path: impl ToString)-> Result; 11 | fn open_file_stream(&self, path: impl ToString) -> Result; 12 | fn read_to_string(&self, path: impl ToString, str: &mut String) -> Result<(), Self::Error>; 13 | fn save_file>(&self, path: impl ToString, content: C) 14 | -> Result<(), Self::Error>; 15 | } 16 | 17 | #[cfg(not(target_arch = "wasm32"))] 18 | mod native { 19 | use std::io::Read; 20 | 21 | use super::*; 22 | pub struct NativeFileManager { 23 | // currently has no member 24 | } 25 | impl FileManager for NativeFileManager { 26 | type Stream = std::fs::File; 27 | type Error = std::io::Error; 28 | fn open_file_stream(&self, path: impl ToString) -> Result { 29 | let s = path.to_string(); 30 | let p = std::path::Path::new(&s); 31 | std::fs::File::open(p) 32 | } 33 | fn read_to_string(&self, path: impl ToString, str: &mut String) -> Result<(), Self::Error> { 34 | let mut file = self.open_file_stream(path)?; 35 | file.read_to_string(str).map(|_| ()) 36 | } 37 | fn save_file>( 38 | &self, 39 | path: impl ToString, 40 | content: C, 41 | ) -> Result<(), Self::Error> { 42 | let s = path.to_string(); 43 | let p = std::path::Path::new(&s); 44 | std::fs::write(p, content) 45 | } 46 | } 47 | } 48 | 49 | #[cfg(target_arch = "wasm32")] 50 | pub mod web { 51 | use super::*; 52 | pub struct WebMediaSource {} 53 | impl std::io::Read for WebMediaSource { 54 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 55 | todo!() 56 | } 57 | } 58 | impl std::io::Seek for WebMediaSource { 59 | fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { 60 | todo!() 61 | } 62 | } 63 | unsafe impl Send for WebMediaSource {} 64 | unsafe impl Sync for WebMediaSource {} 65 | impl MediaSource for WebMediaSource { 66 | fn is_seekable(&self) -> bool { 67 | todo!() 68 | } 69 | 70 | fn byte_len(&self) -> Option { 71 | todo!() 72 | } 73 | } 74 | pub struct WebFileManager {} 75 | 76 | pub struct WebFileError {} 77 | impl std::error::Error for WebFileError {} 78 | impl std::fmt::Display for WebFileError { 79 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 80 | write!(f, "file operation is currenrly not compatible with wasm") 81 | } 82 | } 83 | 84 | impl std::fmt::Debug for WebFileError { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | f.debug_struct("WebFileError").finish() 87 | } 88 | } 89 | 90 | impl FileManager for WebFileManager { 91 | type Stream = WebMediaSource; 92 | type Error = WebFileError; 93 | fn open_file_stream(&self, path: impl ToString) -> Result { 94 | Err(WebFileError {}) 95 | } 96 | fn read_to_string(&self, path: impl ToString, str: &mut String) -> Result<(), Self::Error> { 97 | Err(WebFileError {}) 98 | } 99 | fn save_file>( 100 | &self, 101 | path: impl ToString, 102 | content: C, 103 | ) -> Result<(), Self::Error> { 104 | Err(WebFileError {}) 105 | } 106 | } 107 | } 108 | 109 | #[cfg(not(target_arch = "wasm32"))] 110 | 111 | static GLOBAL_FILE_MANAGER: native::NativeFileManager = native::NativeFileManager {}; 112 | #[cfg(target_arch = "wasm32")] 113 | 114 | pub static GLOBAL_FILE_MANAGER: web::WebFileManager = web::WebFileManager {}; 115 | 116 | pub fn get_global_file_manager() -> &'static impl FileManager { 117 | &GLOBAL_FILE_MANAGER 118 | } 119 | -------------------------------------------------------------------------------- /mimium-symphonia/tests/assets/count_100_by_0_01_f32_48000Hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mimium-org/mimium-rs/f10d5c414ef760b79dc4c38f289d96b78e7917ab/mimium-symphonia/tests/assets/count_100_by_0_01_f32_48000Hz.wav -------------------------------------------------------------------------------- /mimium-symphonia/tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | use mimium_lang::plugin::Plugin; 2 | use mimium_symphonia::SamplerPlugin; 3 | use mimium_test::*; 4 | 5 | fn run_file_with_symphonia(path: &'static str, times: u64) -> Option> { 6 | let plugins: [Box; 1] = [Box::new(SamplerPlugin)]; 7 | run_file_with_plugins(path, times, plugins.into_iter(), false) 8 | } 9 | 10 | //loadwav reads wave file `count_100_by_0_01_f32_48000Hz.wav` that is sequence of 0, 0.01, 0.02... 11 | 12 | #[test] 13 | fn test_readwav() { 14 | let res = run_file_with_symphonia("loadwav.mmm", 101).expect("failed to evaluate"); 15 | let res_int = res 16 | .iter() 17 | .map(|f| (*f * 100.0).round() as u32) 18 | .collect::>(); 19 | let mut ans = (0u32..100).collect::>(); 20 | ans.push(0); //0 should be returned when the index exceeds the boundary 21 | assert_eq!(res_int, ans); 22 | } 23 | 24 | #[test] 25 | fn test_readwav_interp() { 26 | //res should be 0.005, 0.0015, 27 | let res = run_file_with_symphonia("loadwav_interp.mmm", 101).expect("failed to evaluate"); 28 | let res_int = res 29 | .iter() 30 | .map(|f| (*f * 1000.0).round() as u32) 31 | .collect::>(); 32 | let mut ans = (0u32..100) 33 | .map(|x| (x * 10 + 5).min(990)) 34 | .collect::>(); 35 | ans.push(0); //0 should be returned when the index exceeds the boundary 36 | assert_eq!(res_int, ans); 37 | } 38 | -------------------------------------------------------------------------------- /mimium-symphonia/tests/mmm/loadwav.mmm: -------------------------------------------------------------------------------- 1 | //gen_sampler_mono returns higher-order function that takes playback position in samples as an argument 2 | let sampler = gen_sampler_mono("../assets/count_100_by_0_01_f32_48000Hz.wav") 3 | fn counter(){ 4 | self+1.0 5 | } 6 | //add -1.0 offset so that the counter start from 0 at t=0 7 | fn dsp(){ 8 | counter()-1.0 |> sampler 9 | } -------------------------------------------------------------------------------- /mimium-symphonia/tests/mmm/loadwav_interp.mmm: -------------------------------------------------------------------------------- 1 | let sampler = gen_sampler_mono("../assets/count_100_by_0_01_f32_48000Hz.wav") 2 | fn counter(){ 3 | self+1.0 4 | } 5 | fn dsp(){ 6 | counter()-1.0+0.5 |> sampler 7 | } -------------------------------------------------------------------------------- /mimium-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-test" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | description = "Common test modules & basic regression tests for mimium" 8 | build = "build.rs" 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | # [lib] 11 | 12 | # do not need to publish this crate on crate.io (config for `cargo release`) 13 | [package.metadata.release] 14 | release = false 15 | 16 | [dependencies] 17 | 18 | mimium-lang = { workspace = true } 19 | mimium-scheduler = { workspace = true } 20 | mimium-audiodriver = { workspace = true } 21 | [dev-dependencies] 22 | wasm-bindgen-test = {workspace = true } 23 | -------------------------------------------------------------------------------- /mimium-test/Readme.md: -------------------------------------------------------------------------------- 1 | # mimium-test 2 | 3 | Common integrated (or regression) test modules and basic integration tests of mimium. 4 | 5 | ## When use test modules from external crate 6 | 7 | When you write test for mimium-plugin, you can import this test crate as `dev-dependencies` to utilize integrated test modules. 8 | 9 | When you use this crate from external crate, you must set OS environment variable `TEST_ROOT` to the same location of `Cargo.toml` in your crate. Typically, this can be set by making `build.rs` at your crate root with the content like this. 10 | 11 | ```rust 12 | fn main() { 13 | println!("cargo:rustc-env=TEST_ROOT={}", env!("CARGO_MANIFEST_DIR")); 14 | } 15 | ``` 16 | 17 | And you need to set the line to `Cargo.toml` in your crate. 18 | 19 | ```toml 20 | ... 21 | build = "build.rs" 22 | ... 23 | ``` 24 | 25 | See `mimium-symphonia` crate for instance if you want to know more. -------------------------------------------------------------------------------- /mimium-test/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=TEST_ROOT={}", env!("CARGO_MANIFEST_DIR")); 3 | } 4 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/add.mmm: -------------------------------------------------------------------------------- 1 | (1.0+2.0)*100.0+20.0 -------------------------------------------------------------------------------- /mimium-test/tests/mmm/block_local_scope.mmm: -------------------------------------------------------------------------------- 1 | //Curly brace creates local scope. should be 3.0 2 | fn dsp(){ 3 | let test = 0 4 | { 5 | let local = 1.0 6 | } 7 | let local = 3.0 8 | test + local 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/block_local_scope_fail.mmm: -------------------------------------------------------------------------------- 1 | //Curly brace creates local scope. 2 | fn dsp(){ 3 | let test = 0 4 | { 5 | let local1 = 1.0 6 | } 7 | test + local1 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/cascade.mmm: -------------------------------------------------------------------------------- 1 | fn cascade(N:float,fb:float)->((float)->float){ 2 | if(N>0.0) { 3 | |x:float| ->float { 4 | (cascade(N-1.0,fb)(x)) *(1.0-fb) + self*fb 5 | } 6 | }else{ 7 | |x:float| ->float {x} 8 | } 9 | } 10 | cascade(1.0,0.9)(1.2) -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_argument.mmm: -------------------------------------------------------------------------------- 1 | //answer:24 2 | fn hof(f:(float)->float,y){ 3 | f(y*2.0) 4 | } 5 | fn dsp(){ 6 | let x = |y| {y*3.0} 7 | hof(x,4.0) 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_closed.mmm: -------------------------------------------------------------------------------- 1 | //expect -6.0 2 | fn test(x:float){ 3 | let y = 5.0 4 | let f = | | { 5 | let z = 3.0 6 | let ff = | |{ 7 | let a = x 8 | let b = y 9 | let c = z 10 | a-b*c 11 | } 12 | ff() 13 | } 14 | f 15 | } 16 | 17 | fn dsp(){ 18 | let f2 = test(9.0) 19 | f2() 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_counter.mmm: -------------------------------------------------------------------------------- 1 | // counter implemented using closure. for the test of assignment expression 2 | fn makecounter(){ 3 | let x = 0.0 4 | let countup = | |{ 5 | let res = x 6 | x = x+1.0 7 | res 8 | } 9 | countup 10 | } 11 | let myc = makecounter(); 12 | fn dsp(){ 13 | myc() 14 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_counter2.mmm: -------------------------------------------------------------------------------- 1 | //deeply nested closure with destructive assignment 2 | fn makemakecounter(){ 3 | let makecounter = | |{ 4 | let x = 0.0 5 | let countup = | |{ 6 | let res = x 7 | x = x+1.0 8 | res 9 | } 10 | countup 11 | } 12 | makecounter 13 | } 14 | let mymc = makemakecounter(); 15 | let myc = mymc(); 16 | fn dsp(){ 17 | myc() 18 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_counter_tuple.mmm: -------------------------------------------------------------------------------- 1 | // this creates two counters on global scope with tuple assignment 2 | fn makecounter(){ 3 | let x = 0.0 4 | let countup = | |{ 5 | let res = x 6 | x = x+1.0 7 | res 8 | } 9 | 10 | let y = 0.0 11 | let countdown = | |{ 12 | let res = y 13 | y = y-1.0 14 | res 15 | } 16 | (countup,0.0,countdown) 17 | } 18 | let (c1,_x,c2) = makecounter() 19 | fn dsp(){ 20 | (c1(),c2()) 21 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_escape_3nested.mmm: -------------------------------------------------------------------------------- 1 | // open closure "inner" should be closed and escaped at the end of 'outer' context. 2 | fn outer(){ 3 | let x = 0.0 4 | let inner = | | { 5 | x = x+1.0 6 | x 7 | } 8 | let mid = | | {inner} 9 | mid 10 | } 11 | let mid = outer(); 12 | let inner = mid(); 13 | fn dsp(){ 14 | let r = inner() 15 | r 16 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_open.mmm: -------------------------------------------------------------------------------- 1 | fn dsp(){ 2 | let x = 9.0 3 | let f = | | { x - 5.0 } 4 | f() 5 | } 6 | 7 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_open_3nested.mmm: -------------------------------------------------------------------------------- 1 | //answer 2 2 | fn dsp(){ 3 | let x = 5.0 4 | let f = | | { 5 | let y = 3.0 6 | let ff = | |{ x-y} 7 | ff() 8 | } 9 | f() 10 | } 11 | 12 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_open_inline.mmm: -------------------------------------------------------------------------------- 1 | //answer 2 2 | fn dsp(){ 3 | let x = 5.0 4 | | | { 5 | let y = 3.0 6 | let ff = | |{ x-y} 7 | ff() 8 | }() 9 | } 10 | 11 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/closure_tuple_escape.mmm: -------------------------------------------------------------------------------- 1 | //modified from closure_closed.mmm expect:45-4+3 = 44 2 | fn test(x:float){ 3 | let y = (5.0,4.0) 4 | let f = | | { 5 | let z = 3.0 6 | let ff = | |{ 7 | let a = x 8 | let (b1,b2) = y 9 | let c = z 10 | a*b1 - b2 + c 11 | } 12 | ff() 13 | } 14 | f 15 | } 16 | fn dsp(){ 17 | let f2 = test(9.0) 18 | f2() 19 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/counter.mmm: -------------------------------------------------------------------------------- 1 | fn dsp(){ 2 | self+1 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/delay.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | self+1.0 3 | } 4 | 5 | fn dsp(){ 6 | let c = counter() 7 | delay(10.0,c,5.0) 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/delay2.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | self+1 3 | } 4 | fn dsp(){ 5 | delay(10,counter(),5) 6 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/error_include_itself.mmm: -------------------------------------------------------------------------------- 1 | include("error_include_itself.mmm") 2 | fn dsp(){ 3 | 0.0 4 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/fb_and_stateful_call.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | self+1.0 3 | } 4 | fn testcounter(){ 5 | counter()+self 6 | } 7 | 8 | fn dsp(){ 9 | testcounter() 10 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/fb_mem.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | 1.0+self 3 | } 4 | 5 | fn mem_by_hand(x){ 6 | let (y,ys) = self 7 | (x,y) 8 | } 9 | 10 | fn dsp(){ 11 | mem_by_hand(counter()) 12 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/fb_mem2.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | 1.0+self 3 | } 4 | 5 | fn mem_by_hand(x){ 6 | let (y,ys,_yss) = self 7 | (x,y,ys) 8 | } 9 | 10 | fn dsp(){ 11 | let (x,y, ys) =mem_by_hand(counter()) 12 | (x,ys) 13 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/fb_mem3.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){ 2 | 1.0+self 3 | } 4 | 5 | fn mem_by_hand(x){ 6 | let (y,ys,yss,_ysss) = self 7 | (x,y,ys,yss) 8 | } 9 | 10 | fn dsp(){ 11 | let (x,y,ys,yss) =mem_by_hand(counter()) 12 | (x,yss) 13 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/generic_id.mmm: -------------------------------------------------------------------------------- 1 | //test for generic type inference. 2 | fn id(x){ 3 | x 4 | } 5 | fn dsp(){ 6 | tofloat(id(0)) + id(0.0) 7 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/hello.mmm: -------------------------------------------------------------------------------- 1 | sin(sqrt(2.0)) -------------------------------------------------------------------------------- /mimium-test/tests/mmm/hof.mmm: -------------------------------------------------------------------------------- 1 | fn makec(n) { |x| n+x } 2 | makec(3.0)(1.0) -------------------------------------------------------------------------------- /mimium-test/tests/mmm/hof_infer.mmm: -------------------------------------------------------------------------------- 1 | //this is exactly same as hof_state.mmm but now works without explicit type annotation. 2 | fn counter(inc){ 3 | self+inc 4 | } 5 | 6 | fn replicate(n,gen){ 7 | if (n>0.0){ 8 | let c = replicate(n - 1.0,gen) 9 | let g = gen() 10 | |x| {g(x) * n + c(x)} 11 | }else{ 12 | |x| { 0.0 } 13 | } 14 | } 15 | let mycounter = replicate(3.0,| |counter); 16 | fn dsp(){ 17 | mycounter(0.1) 18 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/hof_state.mmm: -------------------------------------------------------------------------------- 1 | fn counter(inc:float){ 2 | self+inc 3 | } 4 | 5 | fn replicate(n:float,gen:()->(float)->float){ 6 | if (n>0.0){ 7 | let c = replicate(n - 1.0,gen) 8 | let g = gen() 9 | |x| {g(x) * n + c(x)} 10 | }else{ 11 | |x| { 0.0 } 12 | } 13 | } 14 | let mycounter = replicate(3.0,| |counter); 15 | fn dsp(){ 16 | mycounter(0.1) 17 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/hof_typefail.mmm: -------------------------------------------------------------------------------- 1 | let doublethunk = | | {| | 1.0} 2 | 3 | fn hof_ident(f:()->float)-> ()->float { 4 | f 5 | } 6 | 7 | let f = hof_ident(doublethunk) //this should be an error 8 | 9 | fn dsp()->float{ 10 | f() 11 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/if.mmm: -------------------------------------------------------------------------------- 1 | fn test(c){ 2 | if (c) { 3 | 100.0 + 20.0 4 | }else{ 5 | 200.0 * 20.0 6 | } 7 | } 8 | 9 | fn dsp(){ 10 | test(1.0) + test(-2.0) 11 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/if_parse.mmm: -------------------------------------------------------------------------------- 1 | fn foo(x) { 2 | if (x == 0.0) 0.0 else if (x > 0.0) 1.0 else -1.0 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/if_state.mmm: -------------------------------------------------------------------------------- 1 | fn countup(active){ 2 | let r =(self+1.0); 3 | let rr = if (active) r else 0.0 4 | rr 5 | } 6 | fn dsp(){ 7 | (countup(1.0),countup(0.0)) 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/let_multi.mmm: -------------------------------------------------------------------------------- 1 | // referring multiple file 2 | fn test(){ 3 | let n = 1.0 4 | n + n + 1.0 5 | } 6 | 7 | fn dsp(){ 8 | test() 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/let_tuple.mmm: -------------------------------------------------------------------------------- 1 | fn gen_tup1(){ 2 | (5.0,2.0,3.0) 3 | } 4 | fn gen_tup2(){ 5 | (7.0,1.0) 6 | } 7 | let (a,b,c) = gen_tup1() 8 | fn dsp(){ 9 | let (d,e) = gen_tup2() 10 | a+b*c+d*e 11 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/let_tuple_nested.mmm: -------------------------------------------------------------------------------- 1 | //expect:34 2 | fn gen_tup(){ 3 | (5.0,(2.0, 8.0), 3.0) 4 | } 5 | 6 | fn dsp(){ 7 | let (a,(b1,b2),c) = gen_tup() 8 | a*b1 + b2*c 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/loopcounter.mmm: -------------------------------------------------------------------------------- 1 | fn loopcounter(){ 2 | (self+1.0) % 5.0 3 | } 4 | 5 | fn dsp(){ 6 | loopcounter() 7 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/many_comments.mmm: -------------------------------------------------------------------------------- 1 | //start comment 2 | fn dsp(){//test 3 | let /*test*/ test /*test*/= /*test*/ 0 4 | { 5 | /*test*/ let local = 1.0 //test 6 | /*test*/ }/* test test test 7 | test test test ~~~~ 8 | */ 9 | let local = 3.0 /*test*/ 10 | let r = test /*test*/+ local 11 | 0 12 | } 13 | //last line comment -------------------------------------------------------------------------------- /mimium-test/tests/mmm/many_errors.mmm: -------------------------------------------------------------------------------- 1 | fn parse_error_continue(){ 2 | 1+ +- ab |> 3 | } 4 | 5 | fn type_error_continue(){ 6 | let a:string = 1.0 7 | let str = "test" 8 | let mismatch = str + 2.0 9 | let nonapplicable = str(100.0) 10 | let (a,b) = (1.0,2.0,3.0) 11 | let unknownvar = hogehoge 12 | 0.0 13 | } 14 | 15 | fn functional_feed (x){ 16 | let s = self 17 | |x| { s(x) } 18 | } 19 | 20 | fn dsp(){ 21 | 0.0 22 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/mem.mmm: -------------------------------------------------------------------------------- 1 | fn memtest(input){ 2 | mem(input) 3 | } 4 | fn dsp(){ 5 | memtest(1.0) + memtest(2.0) 6 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/mir_counter.mmm: -------------------------------------------------------------------------------- 1 | fn counter(inc:float){ 2 | inc+self 3 | } 4 | 5 | fn dsp(){ 6 | counter(1.0) 7 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/mirtest.mmm: -------------------------------------------------------------------------------- 1 | fn test(hoge){ 2 | hoge+1.0 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/nested_if.mmm: -------------------------------------------------------------------------------- 1 | fn foo(x) { 2 | if (x == 0.0) { 3 | 100.0 4 | } else { 5 | if (x > 0.0) { 6 | 20.0 7 | } else { 8 | -1.0 9 | } 10 | } 11 | } 12 | 13 | fn dsp(){ 14 | foo(1.0) + foo(0.0) + foo(-1.0) 15 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/osc.mmm: -------------------------------------------------------------------------------- 1 | fn counter(){self+1.0} 2 | counter() -------------------------------------------------------------------------------- /mimium-test/tests/mmm/parse_fntest.mmm: -------------------------------------------------------------------------------- 1 | fn hoge(input,gue){ 2 | input 3 | } 4 | fn hoge(input,gue){ 5 | input 6 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/parser_firstbreak.mmm: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;;; 4 | ; 5 | 6 | //this file ensures the line break and semicolons before initial meaningful statements are ignored by the parser. 7 | fn dsp(){ 8 | 0.0 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/pipe.mmm: -------------------------------------------------------------------------------- 1 | // pipe operator can be used against either a function or an inline lambda 2 | // pipe operator can be placed at either the beginning or the end of the line 3 | fn foo(x, y, z) { 4 | 100.0 * x + 10.0 * y + z 5 | } 6 | let d2 = _ / _ 7 | let f = foo(1.0, _, 3.0) 8 | fn dsp(){ 9 | let x = 3.0 |> 10 | 1.0 + _ |> 11 | d2(_, 2.0) |> 12 | f 13 | 14 | let y = 3.0 15 | |> 1.0 + _ 16 | |> |arg| d2(arg, 2.0) 17 | |> f 18 | 19 | (x, y) 20 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/placeholder.mmm: -------------------------------------------------------------------------------- 1 | fn foo(x, y, z) { 2 | 100.0 * x + 10.0 * y + z 3 | } 4 | let p1 = 1.0 + _ 5 | let d2 = _ / _ 6 | let f = foo(1.0, _, 3.0) 7 | fn dsp(){ 8 | f(d2(p1(3.0), 2.0)) 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/primitive_log.mmm: -------------------------------------------------------------------------------- 1 | //log primitive is x.ln() in rust 2 | fn log10(x){ 3 | log(x) / log(10.0) 4 | } 5 | 6 | fn dsp(){ 7 | log10(2.0) 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/primitive_sin.mmm: -------------------------------------------------------------------------------- 1 | fn dsp(){ 2 | sin(3.1415926535897932384626433832795) 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/primitive_sqrt.mmm: -------------------------------------------------------------------------------- 1 | fn dsp(){ 2 | sqrt(2.0) 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/recursion.mmm: -------------------------------------------------------------------------------- 1 | fn sum(n){ 2 | if (n>0.0){ 3 | sum(n - 1.0)+1.0 4 | }else{ 5 | 0.0 6 | } 7 | } 8 | fn dsp(){ 9 | sum(5.0) 10 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_counter.mmm: -------------------------------------------------------------------------------- 1 | //more abstract pattern of counter using scheduler. 2 | //this requires that compiler can define a recursive closure inside function using `letrec`. 3 | fn makecounter(){ 4 | let x = 0.0 5 | letrec gen = | |{ 6 | x = x+1.0 7 | gen@(now+1.0) 8 | } 9 | gen@1.0 10 | let getter = | | {x} 11 | getter 12 | } 13 | let x_getter = makecounter(); 14 | fn dsp(){ 15 | x_getter() 16 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_counter_indirect.mmm: -------------------------------------------------------------------------------- 1 | //more abstract pattern of counter using scheduler. 2 | //this requires that compiler can define a recursive closure inside function using `letrec`. 3 | fn makecounter(){ 4 | let x = 0.0 5 | letrec gen = | |{ 6 | x = x+1.0 7 | | |{gen()}@(now+1.0) 8 | } 9 | | |{gen() }@1.0 10 | let getter = | | {x} 11 | getter 12 | } 13 | let x_getter = makecounter(); 14 | fn dsp(){ 15 | x_getter() 16 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_global_recursion.mmm: -------------------------------------------------------------------------------- 1 | let x = 0.0 2 | 3 | fn updater(){ 4 | x = x + 1.0 5 | updater@(now+1.0) 6 | } 7 | updater@1.0 8 | fn dsp(){ 9 | x 10 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_invalid.mmm: -------------------------------------------------------------------------------- 1 | // this code falls into an infinite task execution loop. 2 | let x = 0.0 3 | 4 | fn updater(){ 5 | x = x + 1.0 6 | _mimium_schedule_at(now,updater) 7 | } 8 | let _ = _mimium_schedule_at(1.0,updater) 9 | fn dsp(){ 10 | x 11 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_multiple_at_sametime.mmm: -------------------------------------------------------------------------------- 1 | // this code execute updater twice at a time, from time 1.0 2 | // answer should be 0.0, 2.0, 4.0 3 | let x = 0.0 4 | 5 | fn updater(){ 6 | x = x + 1.0 7 | updater@(now+1.0) 8 | } 9 | updater@1.0 10 | updater@1.0 11 | fn dsp(){ 12 | x 13 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/scheduler_reactive.mmm: -------------------------------------------------------------------------------- 1 | //combination of `self` and temporal recursion. 2 | 3 | fn counter(){ 4 | self+1.0 5 | } 6 | 7 | fn metro(interval,sig:()->float)->()->float{ 8 | let v = 0.0 9 | letrec updater = | |{ 10 | v = sig(); 11 | let _ = updater@(now+interval); 12 | } 13 | let _ = updater@(now+1) 14 | | | {v} 15 | } 16 | 17 | let myv = metro(3.0 ,counter); 18 | 19 | fn dsp(){ 20 | myv() 21 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/shadowing.mmm: -------------------------------------------------------------------------------- 1 | //answer should be 2 2 | fn dsp(){ 3 | let phase = 1.0 4 | let phase = 2.0 5 | phase 6 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/shadowing_assign.mmm: -------------------------------------------------------------------------------- 1 | //answer should be 7 2 | 3 | 4 | fn dsp(){ 5 | let phase = 1.0 6 | let phase = 2.0 7 | phase = phase+5.0 8 | phase 9 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/simple_stereo.mmm: -------------------------------------------------------------------------------- 1 | fn to_stereo(x:float) -> (float,float) { 2 | (x, x * 2.0) 3 | } 4 | 5 | fn dsp() { 6 | to_stereo(1.0) 7 | } 8 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/state_tuple.mmm: -------------------------------------------------------------------------------- 1 | fn bifb()->(float,float){ 2 | let (a,b) =self 3 | (a+1.0,b+2.0) 4 | } 5 | 6 | fn dsp(){ 7 | bifb() 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/statefn.mmm: -------------------------------------------------------------------------------- 1 | fn counter(inc:float){ 2 | inc+self 3 | } 4 | 5 | fn dsp(){ 6 | counter(1.0) 7 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/statefn2.mmm: -------------------------------------------------------------------------------- 1 | //2 different state function 2 | fn counter(inc:float){ 3 | inc+self 4 | } 5 | 6 | fn counter2(inc2:float){ 7 | inc2 + 2.0 + self 8 | } 9 | 10 | fn dsp(){ 11 | counter(1.0)+counter2(5.0) 12 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/statefn2_same.mmm: -------------------------------------------------------------------------------- 1 | fn counter(inc:float){ 2 | inc+self 3 | } 4 | 5 | fn dsp(){ 6 | counter(1.0)+counter(2.0)+counter(3.0) 7 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/stateful_closure.mmm: -------------------------------------------------------------------------------- 1 | fn mycount(rate){ 2 | self+ rate*0.1 3 | } 4 | 5 | fn hof(counter:()->(float)->float){ 6 | let c1 = counter(); 7 | let c2 = counter(); 8 | |x| {x + c1(1.0) + c2(2.0)} 9 | } 10 | let myfilter = hof(| |mycount) 11 | fn dsp(){ 12 | myfilter(20.0) 13 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/test_include.mmm: -------------------------------------------------------------------------------- 1 | include("test_include_target.mmm") 2 | fn dsp(){ 3 | hoge() 4 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/test_include_target.mmm: -------------------------------------------------------------------------------- 1 | fn hoge()->float{ 2 | self+1.0 3 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/test_phase_reset.mmm: -------------------------------------------------------------------------------- 1 | // run this test with self_eval_mode=simplestate. 2 | // ch0: 0,0,0,0,0,1,1,1,1,1 3 | // ch1: 1,2,3,4,5,0,0,0,0,0 4 | // if run with --self-init-0 from a command line, the result will be: 5 | // ch0: 0,0,0,0,0,0,1,1,1,1 6 | // ch1: 0,1,2,3,4,5,6,0,0,0 7 | fn counter(){ 8 | self+1 9 | } 10 | fn integ(x, reset) { 11 | if (reset) { 12 | 0 13 | } else { 14 | self + x 15 | } 16 | } 17 | fn dsp(){ 18 | let count = counter() 19 | let reset = count > 5 20 | let r = integ(1,reset) 21 | (reset,r) 22 | } 23 | -------------------------------------------------------------------------------- /mimium-test/tests/mmm/tuple_args.mmm: -------------------------------------------------------------------------------- 1 | //expect (30.0,50.0) 2 | fn get(tup){ 3 | let (a,b,c) = tup 4 | (a+b,c) 5 | } 6 | 7 | fn dsp(){ 8 | let v = (10.0,20.0,50.0) 9 | get(v) 10 | } -------------------------------------------------------------------------------- /mimium-test/tests/mmm/typing_tuple_fail.mmm: -------------------------------------------------------------------------------- 1 | //this should be fail when non-float type is given 2 | fn ident(input){ 3 | input+1.0 4 | } 5 | fn dsp(){ 6 | let test = (0.5,0.6) 7 | ident(test) 8 | } -------------------------------------------------------------------------------- /mimium-test/tests/scheduler_test.rs: -------------------------------------------------------------------------------- 1 | use mimium_audiodriver::{backends::local_buffer::LocalBufferDriver, driver::Driver}; 2 | use mimium_lang::{plugin::Plugin, ExecContext}; 3 | use mimium_test::*; 4 | use wasm_bindgen_test::*; 5 | 6 | #[wasm_bindgen_test(unsupported = test)] 7 | fn scheduler_global_recursion() { 8 | let res = run_file_with_scheduler("scheduler_global_recursion.mmm", 10).unwrap(); 9 | let ans = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 10 | assert_eq!(res, ans); 11 | } 12 | 13 | #[wasm_bindgen_test(unsupported = test)] 14 | fn scheduler_multiple_at_sametime() { 15 | let res = run_file_with_scheduler("scheduler_multiple_at_sametime.mmm", 5).unwrap(); 16 | let ans = vec![0.0, 2.0, 4.0, 6.0, 8.0]; 17 | assert_eq!(res, ans); 18 | } 19 | 20 | #[wasm_bindgen_test(unsupported = test)] 21 | #[should_panic] 22 | fn scheduler_invalid() { 23 | // recursion with @now would cause infinite recursion, so this should be errored. 24 | let _ = run_file_with_scheduler("scheduler_invalid.mmm", 10); 25 | } 26 | 27 | #[wasm_bindgen_test(unsupported = test)] 28 | fn scheduler_counter() { 29 | let res = run_file_with_scheduler("scheduler_counter.mmm", 10).unwrap(); 30 | let ans = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 31 | assert_eq!(res, ans); 32 | } 33 | #[wasm_bindgen_test(unsupported = test)] 34 | fn scheduler_counter_indirect() { 35 | let res = run_file_with_scheduler("scheduler_counter_indirect.mmm", 10).unwrap(); 36 | let ans = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 37 | assert_eq!(res, ans); 38 | } 39 | #[wasm_bindgen_test(unsupported = test)] 40 | fn scheduler_reactive() { 41 | let res = run_file_with_scheduler("scheduler_reactive.mmm", 10).unwrap(); 42 | let ans = vec![0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0]; 43 | assert_eq!(res, ans); 44 | } 45 | 46 | 47 | fn prep_gc_test_machine(times: usize, src: &str) -> LocalBufferDriver { 48 | let mut driver = LocalBufferDriver::new(times); 49 | let driverplug: Box = Box::new(driver.get_as_plugin()); 50 | let mut ctx = ExecContext::new([driverplug].into_iter(), None, Default::default()); 51 | ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin()); 52 | let _ = ctx.prepare_machine(src); 53 | let _ = ctx.run_main(); 54 | driver.init(ctx, None); 55 | driver 56 | } 57 | //check if the number of closure does not change over times. 58 | #[wasm_bindgen_test(unsupported = test)] 59 | fn scheduler_gc_test() { 60 | let (_, src) = load_src("scheduler_counter_indirect.mmm"); 61 | let mut driver1 = prep_gc_test_machine(2, &src); 62 | driver1.play(); 63 | let first = driver1.vmdata.unwrap().vm.closures.len(); 64 | 65 | let mut driver2 = prep_gc_test_machine(3, &src); 66 | driver2.play(); 67 | let second = driver2.vmdata.unwrap().vm.closures.len(); 68 | 69 | let mut driver3 = prep_gc_test_machine(4, &src); 70 | driver3.play(); 71 | let third = driver3.vmdata.unwrap().vm.closures.len(); 72 | assert!(first == second && second == third) 73 | } 74 | -------------------------------------------------------------------------------- /mimium-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mimium-web" 3 | edition.workspace = true 4 | license.workspace = true 5 | version.workspace = true 6 | repository.workspace = true 7 | readme.workspace = true 8 | authors.workspace = true 9 | description = "mimium wasm binding package." 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [dependencies] 15 | mimium-lang = { workspace = true } 16 | mimium-audiodriver = { workspace = true } 17 | mimium-midi = { workspace = true } 18 | mimium-scheduler = { workspace = true } 19 | 20 | wasm-bindgen = "0.2.93" 21 | wasm-bindgen-futures = "0.4.38" 22 | wee_alloc = "0.4.5" 23 | console_error_panic_hook = "0.1.6" 24 | itertools.workspace = true 25 | [dev-dependencies] 26 | wasm-bindgen-test = "0.3" 27 | -------------------------------------------------------------------------------- /mimium-web/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /mimium-web/src/lib.rs: -------------------------------------------------------------------------------- 1 | use mimium_audiodriver::backends::local_buffer::LocalBufferDriver; 2 | use mimium_audiodriver::driver::Driver; 3 | use mimium_lang::interner::ToSymbol; 4 | use mimium_lang::log; 5 | use mimium_lang::utils::error::report; 6 | use mimium_lang::ExecContext; 7 | use wasm_bindgen::prelude::*; 8 | #[wasm_bindgen] 9 | #[derive(Default)] 10 | pub struct Config { 11 | pub sample_rate: f64, 12 | pub input_channels: u32, 13 | pub output_channels: u32, 14 | pub buffer_size: u32, 15 | } 16 | #[wasm_bindgen] 17 | impl Config { 18 | #[wasm_bindgen] 19 | pub fn new() -> Self { 20 | Self::default() 21 | } 22 | } 23 | 24 | type Output<'a> = &'a mut [f32]; 25 | type Input<'a> = &'a [f32]; 26 | 27 | type Processer = Box u64>; 28 | #[wasm_bindgen] 29 | #[derive(Default)] 30 | pub struct Context { 31 | processor: Option, 32 | config: Config, 33 | } 34 | 35 | fn get_default_context() -> ExecContext { 36 | let mut ctx = ExecContext::new([].into_iter(), None, Default::default()); 37 | ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin()); 38 | if let Some(midi_plug) = mimium_midi::MidiPlugin::try_new() { 39 | ctx.add_system_plugin(midi_plug); 40 | } else { 41 | log::warn!("Midi is not supported on this platform.") 42 | } 43 | ctx 44 | } 45 | 46 | #[wasm_bindgen] 47 | impl Context { 48 | #[wasm_bindgen(constructor)] 49 | pub fn new(config: Config) -> Self { 50 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 51 | Context { 52 | config, 53 | ..Default::default() 54 | } 55 | } 56 | #[wasm_bindgen] 57 | pub fn compile(&mut self, src: String) { 58 | let mut ctx = get_default_context(); 59 | let mut driver = LocalBufferDriver::new(self.config.buffer_size as usize); 60 | ctx.add_plugin(driver.get_as_plugin()); 61 | 62 | if let Err(e) = ctx.prepare_machine(src.as_str()) { 63 | report(&src, "".to_symbol(), &e); 64 | } 65 | ctx.run_main(); 66 | let iochannels = driver.init( 67 | ctx, 68 | Some(mimium_audiodriver::driver::SampleRate::from( 69 | self.config.sample_rate as u32, 70 | )), 71 | ); 72 | let (ichannels, ochannels) = iochannels.map_or((0, 0), |io| (io.input, io.output)); 73 | self.config.input_channels = ichannels; 74 | self.config.output_channels = ochannels; 75 | let out_ch = self.config.output_channels; 76 | let mut out_buf = vec![0.0; (out_ch * self.config.buffer_size) as usize]; 77 | self.processor = Some(Box::new(move |_input, output: Output| -> u64 { 78 | driver.play(); 79 | driver 80 | .get_generated_samples() 81 | .iter() 82 | .map(|f| *f as f32) 83 | .enumerate() 84 | .for_each(|(i, f)| { 85 | out_buf[i] = f; 86 | }); 87 | output.copy_from_slice(&out_buf); 88 | 0 89 | })); 90 | } 91 | #[wasm_bindgen] 92 | pub fn get_input_channels(&self) -> u32 { 93 | self.config.input_channels 94 | } 95 | #[wasm_bindgen] 96 | pub fn get_output_channels(&self) -> u32 { 97 | self.config.output_channels 98 | } 99 | /// . 100 | /// 101 | /// # Safety 102 | /// Array size of input and output must be equal to `input_channels * buffer_size` and `output_channels * buffer_size` respectively. 103 | /// . 104 | #[wasm_bindgen] 105 | pub fn process(&mut self, input: &[f32], output: &mut [f32]) -> u64 { 106 | unsafe { self.processor.as_mut().unwrap_unchecked()(input, output) } 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use super::*; 113 | #[test] 114 | fn test_iochannels() { 115 | let mut ctx = Context::new(Config::default()); 116 | ctx.compile( 117 | r#"fn dsp(input:float){ 118 | (0,input) 119 | }"# 120 | .to_string(), 121 | ); 122 | assert_eq!(1, ctx.get_input_channels()); 123 | assert_eq!(2, ctx.get_output_channels()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /mimium_logo_slant.svg: -------------------------------------------------------------------------------- 1 | アセット 2 --------------------------------------------------------------------------------