mirror of
https://github.com/ivabus/rinth
synced 2024-11-24 09:25:08 +03:00
0.0.1: First alpha release
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
commit
1aff34a06a
25 changed files with 1383 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
target/
|
||||||
|
*.DS_Store
|
||||||
|
.idea
|
||||||
|
*.wav
|
||||||
|
*.m4a
|
9
.rustfmt.toml
Normal file
9
.rustfmt.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
edition = "2021"
|
||||||
|
hard_tabs = true
|
||||||
|
merge_derives = true
|
||||||
|
reorder_imports = true
|
||||||
|
reorder_modules = true
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_small_heuristics = "Off"
|
||||||
|
wrap_comments = true
|
||||||
|
comment_width = 80
|
464
Cargo.lock
generated
Normal file
464
Cargo.lock
generated
Normal file
|
@ -0,0 +1,464 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "628a8f9bd1e24b4e0db2b4bc2d000b001e7dd032d54afa60a68836aeec5aa54a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hound"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "meval"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "midly"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a"
|
||||||
|
dependencies = [
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "1.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.76"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinth-midi"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"midly",
|
||||||
|
"rinth-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinth-synth"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"hound",
|
||||||
|
"meval",
|
||||||
|
"rinth-types",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinth-types"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.195"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.195"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.5.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[workspace]
|
||||||
|
members = [ "rinth-midi", "rinth-synth", "rinth-types" ]
|
||||||
|
resolver = "2"
|
3
LICENSE
Normal file
3
LICENSE
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Rinth and all of it's subcrates are available for non-commercial (non-profit) personal use under the terms of BSD 3-Clause New (Revised) License license, see LICENSE-BSD
|
||||||
|
|
||||||
|
At the moment, the commercial use of Rinth is completely prohibited at the moment.
|
28
LICENSE-BSD
Normal file
28
LICENSE-BSD
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Ivan Bushchik
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
92
README.md
Normal file
92
README.md
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# Rinth synthesiser
|
||||||
|
|
||||||
|
FM and SSG synthesiser
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
rinth-synth b[uild] <path/to/project.yml> # Will build separate tracks
|
||||||
|
rinth-synth m[aster] <path/to/project.yml> # Will "master" tracks into one track
|
||||||
|
```
|
||||||
|
|
||||||
|
See `--help` for more
|
||||||
|
|
||||||
|
## File formats
|
||||||
|
|
||||||
|
### \<project\>.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: <NAME>
|
||||||
|
bpm: <BPM>
|
||||||
|
channels:
|
||||||
|
- type: <SSG|FM>
|
||||||
|
path: <RELATIVE_TO_YML_PATH>
|
||||||
|
volume: [Optional in [0;1]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Megalovania
|
||||||
|
bpm: 210
|
||||||
|
channels:
|
||||||
|
- type: SSG
|
||||||
|
path: _channel
|
||||||
|
- type: FM
|
||||||
|
path: _channel
|
||||||
|
volume: 0.7
|
||||||
|
```
|
||||||
|
|
||||||
|
### _channel (FM)
|
||||||
|
|
||||||
|
```
|
||||||
|
/ Comment
|
||||||
|
#<Frequency deviation>
|
||||||
|
@<Modulating frequency>
|
||||||
|
<Note in SPN> <Note value> [Time from previous note]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
#1
|
||||||
|
@2000
|
||||||
|
D4 0.0625
|
||||||
|
Dd5 0.0625 0.0125
|
||||||
|
Db5 1/32+1/32 1/80
|
||||||
|
```
|
||||||
|
|
||||||
|
### _channel (SSG)
|
||||||
|
|
||||||
|
```
|
||||||
|
/ Comment
|
||||||
|
# Ignored
|
||||||
|
@ Ignored
|
||||||
|
<Note in SPN> <Note value> [Time from previous note]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
/ Comment
|
||||||
|
# Comment 2
|
||||||
|
@ Comment 3
|
||||||
|
D4 0.0625
|
||||||
|
Dd5 0.0625 0.0125
|
||||||
|
Db5 1/32+1/32 1/80
|
||||||
|
```
|
||||||
|
More examples in [examples/](./examples)
|
||||||
|
|
||||||
|
## MIDI Converter
|
||||||
|
|
||||||
|
WIP MIDI converter is available in the `rinth-midi` crate.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
rinth-midi <path/to/midi.mid>
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The project is available for non-commercial personal use under the terms of the [BSD 3-Clause New (Revised) License](./LICENSE) and fully unavailable for any other kind of use.
|
165
examples/determination/main
Normal file
165
examples/determination/main
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#1
|
||||||
|
@4
|
||||||
|
|
||||||
|
G5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
E5 1/8
|
||||||
|
B4 1/8
|
||||||
|
|
||||||
|
Cd5 1/4
|
||||||
|
A4 1/4
|
||||||
|
|
||||||
|
E5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
|
||||||
|
G5 1/4
|
||||||
|
A5 1/4
|
||||||
|
D6 1/4
|
||||||
|
|
||||||
|
B5 1/2+1/4
|
||||||
|
|
||||||
|
|
||||||
|
G5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
E5 1/8
|
||||||
|
B4 1/8
|
||||||
|
|
||||||
|
Cd5 1/4
|
||||||
|
A4 1/4
|
||||||
|
|
||||||
|
E4 1/8
|
||||||
|
Fd4 1/8
|
||||||
|
|
||||||
|
G4 1/4
|
||||||
|
Fd4 1/4
|
||||||
|
D4 1/4
|
||||||
|
E4 1/2+1/4
|
||||||
|
|
||||||
|
G5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
E5 1/8
|
||||||
|
B4 1/8
|
||||||
|
|
||||||
|
Cd5 1/4
|
||||||
|
A4 1/4
|
||||||
|
|
||||||
|
E5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
|
||||||
|
G5 1/4
|
||||||
|
A5 1/4
|
||||||
|
D6 1/4
|
||||||
|
|
||||||
|
B5 1/2+1/4
|
||||||
|
|
||||||
|
|
||||||
|
G5 1/8
|
||||||
|
Fd5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
E5 1/8
|
||||||
|
B4 1/8
|
||||||
|
|
||||||
|
Cd5 1/4
|
||||||
|
A4 1/4
|
||||||
|
|
||||||
|
E4 1/8
|
||||||
|
Fd4 1/8
|
||||||
|
|
||||||
|
G4 1/4
|
||||||
|
Fd4 1/4
|
||||||
|
D4 1/4
|
||||||
|
E4 1/2+1/4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A5 1/8
|
||||||
|
G5 1/8
|
||||||
|
F5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
F5 1/8
|
||||||
|
|
||||||
|
E5 1/4
|
||||||
|
B4 1/4
|
||||||
|
|
||||||
|
B4 1/8
|
||||||
|
E5 1/8
|
||||||
|
|
||||||
|
A5 1/8
|
||||||
|
G5 1/8
|
||||||
|
F5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
F5 1/8
|
||||||
|
|
||||||
|
E5 1/2
|
||||||
|
E4 1/8
|
||||||
|
A4 1/8
|
||||||
|
|
||||||
|
D5 1/8
|
||||||
|
Cd5 1/8
|
||||||
|
B4 1/8
|
||||||
|
A4 1/8
|
||||||
|
B4 1/8
|
||||||
|
Cd5 1/8
|
||||||
|
|
||||||
|
B4 1/4
|
||||||
|
E4 1/4
|
||||||
|
|
||||||
|
E4 1/8
|
||||||
|
Fd4 1/8
|
||||||
|
|
||||||
|
G4 1/4
|
||||||
|
A4 1/4
|
||||||
|
C5 1/4
|
||||||
|
|
||||||
|
B4 1/2+1/4
|
||||||
|
|
||||||
|
A5 1/8
|
||||||
|
G5 1/8
|
||||||
|
F5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
F5 1/8
|
||||||
|
|
||||||
|
E5 1/4
|
||||||
|
B4 1/4
|
||||||
|
|
||||||
|
B4 1/8
|
||||||
|
E5 1/8
|
||||||
|
|
||||||
|
A5 1/8
|
||||||
|
G5 1/8
|
||||||
|
F5 1/8
|
||||||
|
E5 1/8
|
||||||
|
D5 1/8
|
||||||
|
F5 1/8
|
||||||
|
|
||||||
|
E5 1/2
|
||||||
|
E4 1/8
|
||||||
|
A4 1/8
|
||||||
|
|
||||||
|
D5 1/8
|
||||||
|
Cd5 1/8
|
||||||
|
B4 1/8
|
||||||
|
A4 1/8
|
||||||
|
B4 1/8
|
||||||
|
Cd5 1/8
|
||||||
|
|
||||||
|
B4 1/4
|
||||||
|
E4 1/4
|
||||||
|
E4 1/8
|
||||||
|
Fd4 1/8
|
||||||
|
|
||||||
|
G4 1/4
|
||||||
|
Fd4 1/4
|
||||||
|
D4 1/4
|
||||||
|
E4 1/2+1/4
|
6
examples/determination/project.yml
Normal file
6
examples/determination/project.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
name: Determination
|
||||||
|
bpm: 120
|
||||||
|
channels:
|
||||||
|
- type: FM
|
||||||
|
path: main
|
||||||
|
volume: 0.7
|
22
examples/megalovania-no-delay/channel
Normal file
22
examples/megalovania-no-delay/channel
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
@2000
|
||||||
|
D4 0.0625 0
|
||||||
|
D4 0.0625 0
|
||||||
|
D5 0.1875 0
|
||||||
|
A4 0.1875 0
|
||||||
|
Gd4 0.125 0
|
||||||
|
G4 0.125 0
|
||||||
|
F4 0.125 0
|
||||||
|
D4 0.125 0.0
|
||||||
|
F4 0.0625 0
|
||||||
|
G4 0.0625 0
|
||||||
|
@1000
|
||||||
|
C4 0.0625 0.0
|
||||||
|
C4 0.0625 0
|
||||||
|
D5 0.1875 0
|
||||||
|
A4 0.1875 0
|
||||||
|
Gd4 0.125 0
|
||||||
|
G4 0.125 0
|
||||||
|
F4 0.125 0
|
||||||
|
D4 0.0625 0.0
|
||||||
|
F4 0.0625 0
|
||||||
|
G4 0.0625 0.0
|
7
examples/megalovania-no-delay/project.yml
Normal file
7
examples/megalovania-no-delay/project.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
name: Megalovania
|
||||||
|
bpm: 210
|
||||||
|
channels:
|
||||||
|
- type: SSG
|
||||||
|
path: channel
|
||||||
|
- type: FM
|
||||||
|
path: channel
|
24
examples/megalovania/channel
Normal file
24
examples/megalovania/channel
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#1
|
||||||
|
@1
|
||||||
|
D4 0.0625 0.05
|
||||||
|
D4 0.0625 1/80
|
||||||
|
D5 0.1875 0.0125
|
||||||
|
A4 0.1875 0.0125
|
||||||
|
Gd4 0.125 0.0625
|
||||||
|
G4 0.125 0.0625
|
||||||
|
F4 0.125 0.0625
|
||||||
|
D4 0.125 0.025
|
||||||
|
F4 0.0625 0.0125
|
||||||
|
G4 0.0625 0.0125
|
||||||
|
#-1
|
||||||
|
@-1
|
||||||
|
C4 0.0625 0.05
|
||||||
|
C4 0.0625 0.0125
|
||||||
|
D5 0.1875 0.0125
|
||||||
|
A4 0.1875 0.0125
|
||||||
|
Gd4 0.125 0.0625
|
||||||
|
G4 0.125 0.0625
|
||||||
|
F4 0.125 0.0625
|
||||||
|
D4 0.0625 0.025
|
||||||
|
F4 0.0625 0.0125
|
||||||
|
G4 0.0625 0.0125
|
9
examples/megalovania/project.yml
Normal file
9
examples/megalovania/project.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name: Megalovania
|
||||||
|
bpm: 210
|
||||||
|
channels:
|
||||||
|
- type: SSG
|
||||||
|
path: channel
|
||||||
|
volume: 0.3
|
||||||
|
- type: FM
|
||||||
|
path: channel
|
||||||
|
volume: 1
|
12
rinth-midi/Cargo.toml
Normal file
12
rinth-midi/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "rinth-midi"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license-file = "../LICENSE"
|
||||||
|
authors = [ "Ivan Bushchik <ivabus@ivabus.dev>" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rinth-types = { path="../rinth-types" }
|
||||||
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
|
midly = "0.5.3"
|
73
rinth-midi/src/main.rs
Normal file
73
rinth-midi/src/main.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use midly::num::{u28, u7};
|
||||||
|
use midly::{MidiMessage, Smf, TrackEventKind};
|
||||||
|
use rinth_types::note::{Note, Tone};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(version)]
|
||||||
|
struct Args {
|
||||||
|
midi: PathBuf,
|
||||||
|
|
||||||
|
#[arg(short, default_value = "0")]
|
||||||
|
channel: u8,
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let binding = std::fs::read(args.midi).unwrap();
|
||||||
|
let smf = Smf::parse(binding.as_slice()).unwrap();
|
||||||
|
let mut is_pressed = false;
|
||||||
|
let mut delay = 0;
|
||||||
|
let mut note_pressed = u7::new(0);
|
||||||
|
let mut timer = u28::new(0);
|
||||||
|
let mut notes: Vec<Note> = vec![];
|
||||||
|
let note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
||||||
|
for track in smf.tracks.iter() {
|
||||||
|
for event in track {
|
||||||
|
if let TrackEventKind::Midi {
|
||||||
|
channel: _channel,
|
||||||
|
message,
|
||||||
|
} = event.kind
|
||||||
|
{
|
||||||
|
match message {
|
||||||
|
MidiMessage::NoteOff {
|
||||||
|
key,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if is_pressed && note_pressed == key {
|
||||||
|
let note = Note {
|
||||||
|
tone: Tone::from(&format!(
|
||||||
|
"{}{}",
|
||||||
|
note_names[(key.as_int() % 12) as usize],
|
||||||
|
(key.as_int() / 12) - 1
|
||||||
|
)),
|
||||||
|
length: event.delta.as_int() as f32 / 1000.0,
|
||||||
|
delay: delay as f32 / 1000.0,
|
||||||
|
};
|
||||||
|
notes.push(note);
|
||||||
|
note_pressed = u7::new(0);
|
||||||
|
is_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MidiMessage::NoteOn {
|
||||||
|
key,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if !is_pressed {
|
||||||
|
eprintln!("pressing on {}", key);
|
||||||
|
is_pressed = true;
|
||||||
|
note_pressed = key;
|
||||||
|
timer = u28::new(0);
|
||||||
|
delay = event.delta.as_int();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer += event.delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for note in notes {
|
||||||
|
println!("{} {} {}", note.tone, note.length, note.delay);
|
||||||
|
}
|
||||||
|
}
|
15
rinth-synth/Cargo.toml
Normal file
15
rinth-synth/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "rinth-synth"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license-file = "../LICENSE"
|
||||||
|
authors = [ "Ivan Bushchik <ivabus@ivabus.dev>" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rinth-types = {path = "../rinth-types" }
|
||||||
|
hound = "3.5.1"
|
||||||
|
serde_yaml = "0.9.30"
|
||||||
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
|
meval = "0.2.0"
|
101
rinth-synth/src/fm.rs
Normal file
101
rinth-synth/src/fm.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use crate::shared::{add_delay, get_note};
|
||||||
|
use rinth_types::file::Channel;
|
||||||
|
use rinth_types::note::get_note_len;
|
||||||
|
use rinth_types::{file::ChannelType, note::Note, traits::Synth};
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct FM {
|
||||||
|
pub bpm: u16,
|
||||||
|
pub notes: Vec<(Note, ModulationFreq, FrequencyDeviation)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ModulationFreq(f32);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct FrequencyDeviation(f32);
|
||||||
|
|
||||||
|
const FIGHT_CLICKS: usize = 128;
|
||||||
|
|
||||||
|
impl Synth for FM {
|
||||||
|
fn from_channel(channel: Channel, bpm: u16) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let channel = match channel {
|
||||||
|
Channel {
|
||||||
|
path,
|
||||||
|
channel_type: ChannelType::FM,
|
||||||
|
..
|
||||||
|
} => path,
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = fs::read(channel).unwrap();
|
||||||
|
|
||||||
|
let mut notes: Vec<(Note, ModulationFreq, FrequencyDeviation)> = vec![];
|
||||||
|
let mut current_tone = ModulationFreq(440.0);
|
||||||
|
let mut current_deviation = FrequencyDeviation(8.0);
|
||||||
|
|
||||||
|
for line in contents.split(|&x| x == b'\n') {
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let s = String::from_utf8_lossy(line);
|
||||||
|
let l = s.split_ascii_whitespace().collect::<Vec<&str>>();
|
||||||
|
// Ignore comment
|
||||||
|
if l[0].as_bytes()[0] == b'/' {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// String in FM channel may be or carrier tone or note
|
||||||
|
if l[0].as_bytes()[0] == b'@' {
|
||||||
|
current_tone = ModulationFreq(l[0][1..].parse::<f32>().unwrap());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if l[0].as_bytes()[0] == b'#' {
|
||||||
|
current_deviation = FrequencyDeviation(l[0][1..].parse::<f32>().unwrap());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
notes.push((get_note(l), current_tone, current_deviation))
|
||||||
|
}
|
||||||
|
FM {
|
||||||
|
bpm,
|
||||||
|
notes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthesise(&self, sample_rate: u32) -> Vec<f32> {
|
||||||
|
// t - time, m - modulating freq (carrier)
|
||||||
|
let y: fn(f32, f32, f32, f32) -> f32 =
|
||||||
|
|t: f32, f: f32, m: f32, d| (2_f32 * PI * m * t + d * (2_f32 * PI * f * t).sin()).cos();
|
||||||
|
let mut stream: Vec<f32> = vec![];
|
||||||
|
for (note, tone, deviation) in &self.notes {
|
||||||
|
if note.delay != 0_f32 {
|
||||||
|
add_delay(&mut stream, note.delay, self.bpm, sample_rate);
|
||||||
|
}
|
||||||
|
// Samples in _THIS_ note
|
||||||
|
let samples = (get_note_len(note.length, self.bpm) * sample_rate as f32) as usize;
|
||||||
|
for k in 0..samples {
|
||||||
|
stream.push(
|
||||||
|
y(k as f32 / sample_rate as f32, tone.0, note.tone.get_freq(), deviation.0)
|
||||||
|
// This makes a little linear fade-in-out so we don't get "clicks"
|
||||||
|
* if k <= FIGHT_CLICKS {
|
||||||
|
k as f32 / FIGHT_CLICKS as f32
|
||||||
|
} else if k
|
||||||
|
>= samples
|
||||||
|
- FIGHT_CLICKS
|
||||||
|
{
|
||||||
|
(samples
|
||||||
|
- k) as f32 / FIGHT_CLICKS as f32
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
}
|
127
rinth-synth/src/main.rs
Normal file
127
rinth-synth/src/main.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use rinth_types::file::ChannelType;
|
||||||
|
use rinth_types::traits::Synth;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
mod fm;
|
||||||
|
mod shared;
|
||||||
|
mod ssg;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
enum Commands {
|
||||||
|
#[command(visible_alias = "b")]
|
||||||
|
Build(Build),
|
||||||
|
#[command(visible_alias = "m")]
|
||||||
|
Master(Build),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, about, version)]
|
||||||
|
struct Build {
|
||||||
|
path: PathBuf,
|
||||||
|
#[arg(short, long, default_value = "44100")]
|
||||||
|
sample_rate: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(sample: f32) -> f32 {
|
||||||
|
if sample.abs() > 1_f32 {
|
||||||
|
sample.signum()
|
||||||
|
} else {
|
||||||
|
sample
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge(master: &mut Vec<f32>, slave: Vec<f32>) {
|
||||||
|
master.resize(max(master.len(), slave.len()), 0_f32);
|
||||||
|
let slave_len = slave.len();
|
||||||
|
for (n, sample) in &mut master.iter_mut().enumerate() {
|
||||||
|
*sample = if n < slave_len {
|
||||||
|
truncate(*sample + slave[n])
|
||||||
|
} else {
|
||||||
|
truncate(*sample)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Commands::parse();
|
||||||
|
|
||||||
|
let (args, mastering) = match args {
|
||||||
|
Commands::Build(args) => (args, false),
|
||||||
|
Commands::Master(args) => (args, true),
|
||||||
|
};
|
||||||
|
let mut project_file = std::fs::File::open(&args.path).unwrap();
|
||||||
|
let mut project = String::new();
|
||||||
|
project_file.read_to_string(&mut project).unwrap();
|
||||||
|
|
||||||
|
let project: rinth_types::file::Header = serde_yaml::from_str(&project).unwrap();
|
||||||
|
let spec = hound::WavSpec {
|
||||||
|
channels: 1,
|
||||||
|
sample_rate: args.sample_rate,
|
||||||
|
bits_per_sample: 32,
|
||||||
|
sample_format: hound::SampleFormat::Float,
|
||||||
|
};
|
||||||
|
let mut master = vec![];
|
||||||
|
for channel in project.channels {
|
||||||
|
let mut channel = channel;
|
||||||
|
let mut channel_path = args.path.parent().unwrap().to_path_buf();
|
||||||
|
channel_path.push(channel.path);
|
||||||
|
channel.path = channel_path;
|
||||||
|
|
||||||
|
let synth: Box<dyn Synth> = match channel.channel_type {
|
||||||
|
ChannelType::FM => Box::new(fm::FM::from_channel(channel.clone(), project.bpm)),
|
||||||
|
ChannelType::SSG => Box::new(ssg::SSG::from_channel(channel.clone(), project.bpm)),
|
||||||
|
};
|
||||||
|
if !mastering {
|
||||||
|
let mut writer = hound::WavWriter::create(
|
||||||
|
format!(
|
||||||
|
"{}-{:?}.wav",
|
||||||
|
channel.path.as_os_str().to_str().unwrap(),
|
||||||
|
channel.channel_type
|
||||||
|
),
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
synth.synthesise(args.sample_rate).iter().for_each(|sample| {
|
||||||
|
writer
|
||||||
|
.write_sample(if let Some(volume) = channel.volume {
|
||||||
|
*sample * volume
|
||||||
|
} else {
|
||||||
|
*sample
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
writer.finalize().unwrap();
|
||||||
|
} else {
|
||||||
|
merge(
|
||||||
|
&mut master,
|
||||||
|
synth
|
||||||
|
.synthesise(args.sample_rate)
|
||||||
|
.iter()
|
||||||
|
.map(|sample| {
|
||||||
|
if let Some(volume) = channel.volume {
|
||||||
|
*sample * volume
|
||||||
|
} else {
|
||||||
|
*sample
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mastering {
|
||||||
|
let mut writer = hound::WavWriter::create(
|
||||||
|
format!(
|
||||||
|
"{}/{}-master.wav",
|
||||||
|
args.path.parent().unwrap().to_path_buf().to_str().unwrap(),
|
||||||
|
project.name
|
||||||
|
),
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
master.iter().for_each(|sample| writer.write_sample(*sample).unwrap());
|
||||||
|
writer.finalize().unwrap();
|
||||||
|
}
|
||||||
|
}
|
27
rinth-synth/src/shared.rs
Normal file
27
rinth-synth/src/shared.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use rinth_types::note::{get_note_len, Note, Tone};
|
||||||
|
|
||||||
|
pub const DEFAULT_DELAY: f32 = 0.0;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn add_delay(stream: &mut Vec<f32>, delay: f32, bpm: u16, sample_rate: u32) {
|
||||||
|
// println!("Pushing delay of len {}s", get_note_len(delay, bpm));
|
||||||
|
stream.append(&mut vec![0_f32; (get_note_len(delay, bpm) * sample_rate as f32) as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_note(l: Vec<&str>) -> Note {
|
||||||
|
Note {
|
||||||
|
tone: Tone::from(l[0]),
|
||||||
|
length: match l[1].parse::<f32>() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => meval::eval_str(l[1]).unwrap() as f32,
|
||||||
|
},
|
||||||
|
delay: if l.len() > 2 {
|
||||||
|
match l[2].parse::<f32>() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => meval::eval_str(l[2]).unwrap() as f32,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEFAULT_DELAY
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
68
rinth-synth/src/ssg.rs
Normal file
68
rinth-synth/src/ssg.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::shared::{add_delay, get_note};
|
||||||
|
use rinth_types::file::Channel;
|
||||||
|
use rinth_types::note::get_note_len;
|
||||||
|
use rinth_types::{file::ChannelType, note::Note, traits::Synth};
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub struct SSG {
|
||||||
|
pub bpm: u16,
|
||||||
|
pub notes: Vec<Note>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Synth for SSG {
|
||||||
|
fn from_channel(channel: Channel, bpm: u16) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let channel = match channel {
|
||||||
|
Channel {
|
||||||
|
path,
|
||||||
|
channel_type: ChannelType::SSG,
|
||||||
|
..
|
||||||
|
} => path,
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = fs::read(channel).unwrap();
|
||||||
|
|
||||||
|
let mut notes: Vec<Note> = vec![];
|
||||||
|
for line in contents.split(|&x| x == b'\n') {
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let s = String::from_utf8_lossy(line);
|
||||||
|
let l = s.split_ascii_whitespace().collect::<Vec<&str>>();
|
||||||
|
// Ignoring FM-only lines and comments
|
||||||
|
if l[0].as_bytes()[0] == b'@'
|
||||||
|
|| l[0].as_bytes()[0] == b'#'
|
||||||
|
|| l[0].as_bytes()[0] == b'/'
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
notes.push(get_note(l))
|
||||||
|
}
|
||||||
|
SSG {
|
||||||
|
bpm,
|
||||||
|
notes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthesise(&self, sample_rate: u32) -> Vec<f32> {
|
||||||
|
// t - time, m - modulating freq (carrier)
|
||||||
|
let y: fn(f32, f32) -> f32 = |t: f32, f: f32| (2_f32 * PI * f * t).sin().signum();
|
||||||
|
let mut stream: Vec<f32> = vec![];
|
||||||
|
for note in &self.notes {
|
||||||
|
if note.delay != 0_f32 {
|
||||||
|
add_delay(&mut stream, note.delay, self.bpm, sample_rate);
|
||||||
|
}
|
||||||
|
for k in 0..(get_note_len(note.length, self.bpm) * sample_rate as f32) as usize {
|
||||||
|
stream.push(y(k as f32 / sample_rate as f32, note.tone.get_freq()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
}
|
11
rinth-types/Cargo.toml
Normal file
11
rinth-types/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "rinth-types"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license-file = "../LICENSE"
|
||||||
|
authors = [ "Ivan Bushchik <ivabus@ivabus.dev>" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
toml = "0.8.8"
|
||||||
|
serde = { version = "1.0.195", features = ["derive"] }
|
28
rinth-types/src/file.rs
Normal file
28
rinth-types/src/file.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub enum FileType {
|
||||||
|
Header(Header),
|
||||||
|
Channel(Channel),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
|
pub enum ChannelType {
|
||||||
|
FM,
|
||||||
|
SSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct Header {
|
||||||
|
pub name: String,
|
||||||
|
pub bpm: u16,
|
||||||
|
pub channels: Vec<Channel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Channel {
|
||||||
|
pub path: PathBuf,
|
||||||
|
#[serde(alias = "type")]
|
||||||
|
pub channel_type: ChannelType,
|
||||||
|
pub volume: Option<f32>,
|
||||||
|
}
|
3
rinth-types/src/lib.rs
Normal file
3
rinth-types/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod file;
|
||||||
|
pub mod note;
|
||||||
|
pub mod traits;
|
72
rinth-types/src/note.rs
Normal file
72
rinth-types/src/note.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
//use rinth_macros::*;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Get real length of note from musical length
|
||||||
|
pub fn get_note_len(len: f32, bpm: u16) -> f32 {
|
||||||
|
len * 240_f32 / bpm as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Note {
|
||||||
|
pub tone: Tone,
|
||||||
|
pub length: f32,
|
||||||
|
pub delay: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ToneError {
|
||||||
|
InvalidTone(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ToneError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
ToneError::InvalidTone(s) => format!("Invalid tone ({})", s),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Tone(String);
|
||||||
|
|
||||||
|
impl Tone {
|
||||||
|
pub fn from(t: &str) -> Self {
|
||||||
|
Tone(t.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_freq(&self) -> f32 {
|
||||||
|
const BASE_FREQ: f32 = 440.0;
|
||||||
|
let sr = self.to_string();
|
||||||
|
let modifier: i8 = if sr.as_bytes()[1] == b'b' {
|
||||||
|
-1
|
||||||
|
} else if sr.as_bytes()[1] == b'd' {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let magic_num = (12 * sr.get(sr.len() - 1..).unwrap().parse::<i8>().unwrap() + modifier)
|
||||||
|
as u8 + match sr.as_bytes()[0] {
|
||||||
|
b'C' => 2,
|
||||||
|
b'D' => 4,
|
||||||
|
b'E' => 6,
|
||||||
|
b'F' => 7,
|
||||||
|
b'G' => 9,
|
||||||
|
b'A' => 11,
|
||||||
|
b'B' => 13,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
BASE_FREQ * 2_f32.powf((magic_num as f32 - 59_f32) / 12_f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Tone {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.0.to_string())
|
||||||
|
}
|
||||||
|
}
|
9
rinth-types/src/traits.rs
Normal file
9
rinth-types/src/traits.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use crate::file::Channel;
|
||||||
|
|
||||||
|
pub trait Synth {
|
||||||
|
fn from_channel(channel: Channel, bpm: u16) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
fn synthesise(&self, sample_rate: u32) -> Vec<f32>;
|
||||||
|
}
|
Loading…
Reference in a new issue