diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4e3ee4c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..f76b64d2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,202 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "structopt" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tune" +version = "0.1.0" +dependencies = [ + "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)" = "023a4cd09b2ff695f9734c1934145a315594b7986398496841c7031a5a1bbdbd" +"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" +"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)" = "52ee9a534dc1301776eff45b4fa92d2c39b1d8c3d3357e6eb593e0d795506fc2" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3" +"checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04" +"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..a35b1fae --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tune" +version = "0.1.0" +authors = ["Woyten "] +description = "Create synthesizer tuning files for microtonal scales." +license = "MIT" +edition = "2018" + +[dependencies] +structopt = "0.2.14" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..be454492 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Woyten Tielesch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..98dbca49 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Create synthesizer tuning files for microtonal scales. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..f89d8c58 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,158 @@ +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +enum Mode { + /// Create a scale file + #[structopt(name = "scl")] + Scale(Scale), +} + +#[derive(Debug, StructOpt)] +enum Scale { + /// Equal divisions of an interval + #[structopt(name = "equal")] + EqualTemperament { + /// Number of divisions per interval, e.g. 12 + number_of_divisions: f64, + + /// Interval to divide + #[structopt(short, default_value = "2.0")] + interval_to_divide: f64, + }, + + /// Rank-2 temperament + #[structopt(name = "rank2")] + Rank2Temperament { + /// First generator (finite), e.g. 1.5 + generator: f64, + + /// Number of notes to create by first generator, e.g. 7 + number_of_notes: u16, + + /// Offset + #[structopt(short, default_value = "0")] + offset: i16, + + /// Second generator (infinite) + #[structopt(short, default_value = "2.0")] + period: f64, + }, + + /// Harmonic series + #[structopt(name = "harm")] + HarmonicSeries { + /// The lowest harmonic, e.g. 8 + lowest_harmonic: u16, + + /// Number of of notes, e.g. 8 + #[structopt(short)] + number_of_notes: Option, + + /// Build subharmonic series + #[structopt(short)] + subharmonics: bool, + }, +} + +fn main() { + let mode = Mode::from_args(); + match mode { + Mode::Scale(Scale::EqualTemperament { + number_of_divisions, + interval_to_divide, + }) => print_equal_temperament_file(number_of_divisions, interval_to_divide), + Mode::Scale(Scale::HarmonicSeries { + lowest_harmonic, + number_of_notes, + subharmonics, + }) => print_harmonics_file( + lowest_harmonic, + number_of_notes.unwrap_or(lowest_harmonic), + subharmonics, + ), + Mode::Scale(Scale::Rank2Temperament { + generator, + number_of_notes, + offset, + period, + }) => print_rank2_temperament_file(generator, number_of_notes, offset, period), + } +} + +fn print_equal_temperament_file(number_of_divisions: f64, interval_to_divide: f64) { + assert!(number_of_divisions > 0.0); + assert!(interval_to_divide >= 1.0); + + let step_size_in_cents = interval_to_divide.log2() / number_of_divisions * 1200.0; + + println!( + "{} equal divisions of ratio {}", + number_of_divisions, interval_to_divide + ); + println!("1"); + println!("{:.3}", step_size_in_cents); +} + +fn print_rank2_temperament_file(generator: f64, number_of_notes: u16, offset: i16, period: f64) { + assert!(generator > 0.0); + assert!(period > 1.0); + + let generator_log = generator.log2(); + let period_log = period.log2(); + + let mut notes = (0..number_of_notes) + .map(|generation| { + let exponent = i32::from(generation) + i32::from(offset); + if exponent == 0 { + return period_log; + } + + let generated_note = f64::from(exponent) * generator_log; + let note_in_period_interval = generated_note % period_log; + + if note_in_period_interval <= 0.0 { + note_in_period_interval + period_log + } else { + note_in_period_interval + } + }) + .collect::>(); + notes.sort_by(|a, b| { + a.partial_cmp(b) + .expect("Comparison yielded an invalid result") + }); + + println!( + "{} generations of generator {} with period {}", + number_of_notes, generator, period + ); + println!("{}", number_of_notes); + for note in notes { + println!("{:.3}", note * 1200.0); + } +} + +fn print_harmonics_file(lowest_harmonic: u16, number_of_notes: u16, subharmonics: bool) { + assert!(lowest_harmonic > 0); + + let debug_text = if subharmonics { + "subharmonics" + } else { + "harmonics" + }; + println!( + "{} {} starting with {}", + number_of_notes, debug_text, lowest_harmonic + ); + println!("{}", number_of_notes); + let highest_harmonic = lowest_harmonic + number_of_notes; + if subharmonics { + for harmonic in (lowest_harmonic..highest_harmonic).rev() { + println!("{}/{}", highest_harmonic, harmonic); + } + } else { + for harmonic in (lowest_harmonic + 1)..=highest_harmonic { + println!("{}/{}", harmonic, lowest_harmonic); + } + } +}