forked from fluora/interval.rs
initial commit of version 1.2
This commit is contained in:
commit
114a46ac82
|
@ -0,0 +1 @@
|
|||
/interval
|
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
|
@ -0,0 +1,146 @@
|
|||
#Interval Music Maker v1.2
|
||||
###Ellie Diode, May 2019
|
||||
###Public Domain
|
||||
|
||||
##Introduction
|
||||
Interval.rs is a terse, nonlinear, compiled language for composing music. Code is read from a .ITV plaintext source file
|
||||
and converted to PCM audio data, which is emitted to stdout by the compiler. Interval is designed with unconventional
|
||||
music in mind, particularly that involving polyrhythms and microtonal tunings. Notes are defined by position in time,
|
||||
pitch, and ringing duration, and played using simple sine waves modulated by a gaussian envelope generator. Any number
|
||||
of notes can be played simultaneously, and note definitions can be supplied out of the order in which they play.
|
||||
|
||||
|
||||
|
||||
###How to Compile
|
||||
Interval.rs is written in Rust version 1.31.1, and depends only on Rust's stdlib.
|
||||
With the Rust compiler installed under a Unix-like OS, you can compile this source file with this shell command:
|
||||
`rustc -O interval.rs` to produce the `interval` binary.
|
||||
|
||||
###How to Use
|
||||
Interval can be run at the command line with the following syntax:
|
||||
`./interval [input file]`
|
||||
This will produce a WAV RIFF header followed by a PCM audio stream to stdout, which can then be piped into a file, audio
|
||||
device, or other audio stream acceptor. If you don't pipe it into anything and it dumps the stream out onto the console,
|
||||
then it will probably flood your screen with garbage text, so be warned.
|
||||
|
||||
###Example Usage
|
||||
Under Linux with ALSA, you can compile and play a .ITV source file with a command like this:
|
||||
`./interval test.itv | aplay`
|
||||
|
||||
###Writing to Files
|
||||
You can pipe the output of the program into a file:
|
||||
`./interval test.itv > test.wav`
|
||||
This file will be in standard WAV format, and can be converted to other formats with a conversion tool such as
|
||||
avconv or ffmpeg.
|
||||
|
||||
|
||||
|
||||
##ITV Source Code Syntax
|
||||
|
||||
Each line of the file defines one note.
|
||||
Lines are in the following form (in any order):
|
||||
|
||||
`p[ratio] v[volume] t[time] r[rise] f[fall]`
|
||||
|
||||
For example, `p1:1 v200 t0 r0 f2000;` produces a bell-like tone, starting immediately when the program starts and ringing
|
||||
with a time constant of 2 seconds.
|
||||
|
||||
Lines do not need to be complete; lines with missing parameters will recycle the values for those parameters from the
|
||||
previous line. If the first line is missing one or more parameters, then the defaults will be used, which are as follows:
|
||||
`r1:1 v200 t0 r0 f1000`
|
||||
Notes are played with whatever parameters are set whenever a semicolon is placed. Use semicolons to terminate lines that
|
||||
describe audible notes. Lines that do not end in semicolons will simply set the parameters given without playing a note.
|
||||
|
||||
You can define a relative tempo percentage with a line with a single argument in the form `T[tempo]` (e.g. `T100`), and
|
||||
a base pitch with a line in the form `P[frequency]` (e.g. `P440`). Tempo and base frequency can be set anywhere, including
|
||||
on the same line as a note (in which case they will apply to that note). They can be set an arbitrary number of times.
|
||||
|
||||
Comments can be added with the # character. Multiline comments using /* */ are also supported.
|
||||
|
||||
|
||||
###Detailed Explanation of Parameters
|
||||
|
||||
####p pitch
|
||||
Frequency ratio relative to 440 Hz, in the form 'numerator:denominator'.
|
||||
|
||||
####v volume
|
||||
Note amplitude between 0 and 1000. Be warned: if you set all your notes to 1000 and they overlap, then they will clip and
|
||||
sound dreadful. Try starting with something like 200, and scaling it as needed.
|
||||
|
||||
####t time
|
||||
Time between the start of playback and the start of the note, in milliseconds - think of it as a timestamp for when the
|
||||
note plays.
|
||||
|
||||
####r rise
|
||||
The time constant in milliseconds for the rise rate of the note's loudness. This program uses a simple unsymmetric gaussian
|
||||
envelope, and this parameter is the σ parameter for the leading half of that gaussian. Set this to a smaller value for a
|
||||
sharper sound.
|
||||
|
||||
####f fall
|
||||
Similar to rise, this is the width of the trailing half of the gaussian envelope, the σ parameter for the decay of the note.
|
||||
Set this to a larger value for a longer ring time.
|
||||
|
||||
Every time a note is played, the given parameters will be set again, including operators. This means that if you give
|
||||
`p3:2*` for a pitch parameter on a note, then play three more notes with no pitch parameter supplied, then each subsequent
|
||||
note will have a pitch one fifth higher than the previous one. If you want to simply play the same pitch again and not
|
||||
continue to change it, issue `p1*` or `p0+` to indicate that the pitch should use the value of the previous note and
|
||||
stop changing.
|
||||
|
||||
|
||||
###Relative Mode Operators
|
||||
|
||||
Pitch ratios, volumes, and times can have operators appended to them to make these values depend on the corresponding value
|
||||
of the previous note. For example, if you wanted one note come 400 milliseconds after one that plays at the 3-second mark,
|
||||
you could do this:
|
||||
|
||||
`p1:1 v200 t3000;
|
||||
p2:1 v200 t400+;`
|
||||
|
||||
The list of allowed operators is as follows:
|
||||
|
||||
####+ Plus
|
||||
Adds the value to the previous one. For pitch, this calculates the frequency of the given interval against the base pitch,
|
||||
then adds the frequency directly to the previous frequency. If you want to change the pitch by an interval ratio, you almost
|
||||
certainly want `*` instead.
|
||||
|
||||
####- Minus
|
||||
Subtracts the value from the previous one.
|
||||
|
||||
####* Asterisk
|
||||
Multiplies the value by the previous one.
|
||||
|
||||
####/ Forward Slash
|
||||
Divides the value by the previous one.
|
||||
|
||||
####^ Caret
|
||||
Multiplies the previous value by 2 raised to the power of this value. Hint: use this to raise the pitch of a note by a given
|
||||
number of cents: `p211:1200^` will raise the pitch by 211 cents.
|
||||
|
||||
####_ Underscore
|
||||
Divides the previous value by 2 raised to the power of this value. Can be used to lower a pitch by a number of cents.
|
||||
|
||||
Any value without an operator is taken as an absolute value instead.
|
||||
|
||||
|
||||
###Loops
|
||||
Loops can be added using the syntax `R[iteration count] { [notes to be repeated] }`. For example, to play A440 five times
|
||||
in succession, you could use this:
|
||||
|
||||
`R5 {
|
||||
p1:1 t300+;
|
||||
}`
|
||||
|
||||
Loops are equivalent to simply writing out the same sequence of notes multiple times. The same changes to parameters that are
|
||||
specified absolutely or using relative mode operators will be applied repeatedly.
|
||||
|
||||
Note that loops need not necessarily iterate in time - you can also iterate pitch, volume, rise time, fall time, base
|
||||
frequency, and base tempo.
|
||||
|
||||
|
||||
###Tricks
|
||||
- Notes do not have to be defined in the same order as they are to be played. Use this to your advantage for easier
|
||||
polyrhythms! You can even set relative tempos and base pitches out-of-order.
|
||||
- Relative time offsets can be negative, resulting in notes that play before the note they follow in the file.
|
||||
- Loudness values can be set above 1000 (this will cause the notes to clip, but maybe you want that!)
|
||||
- Ratios can be arbitrarily large, or non-integers. Go nuts with microtones!
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# A little polyrhythm jingle for testing interval.rs!
|
||||
# Ellie Diode, May 2019
|
||||
# Public Domain
|
||||
|
||||
T100
|
||||
|
||||
P440
|
||||
p1:1 v200 t0 r0 f4000;
|
||||
p4:3 t800+;
|
||||
p3:2; #comment comment comment
|
||||
p2:1;
|
||||
|
||||
T4:3*
|
||||
p2:1 v200 t0 r0 f2000;
|
||||
p8:3 t800+;
|
||||
p3:1;
|
||||
p4:1;
|
||||
p5:1;
|
|
@ -0,0 +1,495 @@
|
|||
// Interval Music Maker v1.2
|
||||
// Ellie Diode, May 2019
|
||||
// Public Domain (via the Unlicense)
|
||||
|
||||
/*
|
||||
Disclaimer:
|
||||
Interval.rs is provided with the hope that it will be useful, but with absolutely no warranty at all, not even regarding
|
||||
merchantability, fitness for a particular purpose, or safety. To the extent permitted by applicable law, the creator(s)
|
||||
of Interval shall not be held liable for any damages, losses, or other undesirable outcomes resulting from the use of this
|
||||
program, its source code, its compiled binary forms, or any data processed or produced by this program in any form.
|
||||
|
||||
*/
|
||||
/*
|
||||
Introduction:
|
||||
Interval.rs is a terse, nonlinear, compiled language for composing music. Code is read from a .ITV plaintext source file
|
||||
and converted to PCM audio data, which is emitted to stdout by the compiler. Interval is designed with unconventional
|
||||
music in mind, particularly that involving polyrhythms and microtonal tunings. Notes are defined by position in time,
|
||||
pitch, and ringing duration, and played using simple sine waves modulated by a gaussian envelope generator. Any number
|
||||
of notes can be played simultaneously, and note definitions can be supplied out of the order in which they play.
|
||||
|
||||
*/
|
||||
/*
|
||||
How to Compile:
|
||||
Interval.rs is written in Rust version 1.31.1, and depends only on Rust's stdlib.
|
||||
With the Rust compiler installed under a Unix-like OS, you can compile this source file with this shell command:
|
||||
`rustc -O interval.rs` to produce the `interval` binary.
|
||||
|
||||
How to Use:
|
||||
Interval can be run at the command line with the following syntax:
|
||||
`./interval [input file]`
|
||||
This will produce a WAV RIFF header followed by a PCM audio stream to stdout, which can then be piped into a file, audio
|
||||
device, or other audio stream acceptor. If you don't pipe it into anything and it dumps the stream out onto the console,
|
||||
then it will probably flood your screen with garbage text, so be warned.
|
||||
|
||||
Example Usage:
|
||||
Under Linux with ALSA, you can compile and play a .ITV source file with a command like this:
|
||||
`./interval test.itv | aplay`
|
||||
|
||||
Writing to Files:
|
||||
You can pipe the output of the program into a file:
|
||||
`./interval test.itv > test.wav`
|
||||
This file will be in standard WAV format, and can be converted to other formats with a conversion tool such as
|
||||
avconv or ffmpeg.
|
||||
|
||||
*/
|
||||
/*
|
||||
ITV Source Code Syntax
|
||||
|
||||
Each line of the file defines one note.
|
||||
Lines are in the following form (in any order):
|
||||
`p[ratio] v[volume] t[time] r[rise] f[fall]`
|
||||
For example:
|
||||
`p1:1 v200 t0 r0 f2000;`
|
||||
produces a bell-like tone, starting immediately when the program starts and ringing with a time constant of 2 seconds.
|
||||
|
||||
Lines do not need to be complete; lines with missing parameters will recycle the values for those parameters from the
|
||||
previous line. If the first line is missing one or more parameters, then the defaults will be used, which are as follows:
|
||||
`r1:1 v200 t0 r0 f1000`
|
||||
Notes are played with whatever parameters are set whenever a semicolon is placed. Use semicolons to terminate lines that
|
||||
describe audible notes. Lines that do not end in semicolons will simply set the parameters given without playing a note.
|
||||
|
||||
You can define a relative tempo percentage with a line with a single argument in the form `T[tempo]` (e.g. `T100`), and
|
||||
a base pitch with a line in the form `P[frequency]` (e.g. `P440`). Tempo and base frequency can be set anywhere, including
|
||||
on the same line as a note (in which case they will apply to that note). They can be set an arbitrary number of times.
|
||||
|
||||
Comments can be added with the # character. Multiline comments using /* */ are also supported.
|
||||
|
||||
|
||||
- Detailed Explanation of Parameters -
|
||||
|
||||
p pitch : Frequency ratio relative to 440 Hz, in the form 'numerator:denominator'.
|
||||
v volume : Note amplitude between 0 and 1000. Be warned: if you set all your notes to 1000 and they overlap,
|
||||
then they will clip and sound dreadful. Try starting with something like 200, and scaling it as needed.
|
||||
t time : Time between the start of playback and the start of the note, in milliseconds - think of it as
|
||||
a timestamp for when the note plays.
|
||||
r rise : The time constant in milliseconds for the rise rate of the note's loudness. This program uses a simple
|
||||
unsymmetric gaussian envelope, and this parameter is the σ parameter for the leading half of that gaussian.
|
||||
Set this to a smaller value for a sharper sound.
|
||||
f fall : Similar to rise, this is the width of the trailing half of the gaussian envelope, the σ parameter for the
|
||||
decay of the note. Set this to a larger value for a longer ring time.
|
||||
|
||||
Every time a note is played, the given parameters will be set again, including operators. This means that if you give
|
||||
`p3:2*` for a pitch parameter on a note, then play three more notes with no pitch parameter supplied, then each subsequent
|
||||
note will have a pitch one fifth higher than the previous one. If you want to simply play the same pitch again and not
|
||||
continue to change it, issue `p1*` or `p0+` to indicate that the pitch should use the value of the previous note and
|
||||
stop changing.
|
||||
|
||||
|
||||
- Relative Mode Operators -
|
||||
|
||||
Pitch ratios, volumes, and times can have operators appended to them to make these values depend on the corresponding value
|
||||
of the previous note. For example, if you wanted one note come 400 milliseconds after one that plays at the 3-second mark,
|
||||
you could do this:
|
||||
`p1:1 v200 t3000;
|
||||
p2:1 v200 t400+;`
|
||||
The list of allowed operators is as follows:
|
||||
+ : Adds the value to the previous one. For pitch, this calculates the frequency of the given interval against the base
|
||||
pitch, then adds the frequency directly to the previous frequency. If you want to change the pitch by an interval
|
||||
ratio, you almost certainly want * instead.
|
||||
- : Subtracts the value from the previous one.
|
||||
* : Multiplies the value by the previous one.
|
||||
/ : Divides the value by the previous one.
|
||||
^ : Multiplies the previous value by 2 raised to the power of this value. Hint: use this to raise the pitch of a note by a
|
||||
given number of cents: `p211:1200^` will raise the pitch by 211 cents.
|
||||
_ : Divides the previous value by 2 raised to the power of this value. Can be used to lower a pitch by a number of cents.
|
||||
|
||||
Any value without an operator is taken as an absolute value instead.
|
||||
|
||||
|
||||
- Loops -
|
||||
Loops can be added using the syntax `R[iteration count] { [notes to be repeated] }`. For example, to play A440 five times
|
||||
in succession, you could use this:
|
||||
`R5 {
|
||||
p1:1 t300+;
|
||||
}`
|
||||
Loops are equivalent to simply writing out the same sequence of notes multiple times. The same changes to parameters that are
|
||||
specified absolutely or using relative mode operators will be applied repeatedly.
|
||||
Note that loops need not necessarily iterate in time - you can also iterate pitch, volume, rise time, fall time, base
|
||||
frequency, and base tempo.
|
||||
|
||||
|
||||
Tricks:
|
||||
- Notes do not have to be defined in the same order as they are to be played. Use this to your advantage for easier
|
||||
polyrhythms! You can even set relative tempos and base pitches out-of-order.
|
||||
- Relative time offsets can be negative, resulting in notes that play before the note they follow in the file.
|
||||
- Loudness values can be set above 1000 (this will cause the notes to clip, but maybe you want that!)
|
||||
- Ratios can be arbitrarily large, or non-integers. Go nuts with microtones!
|
||||
|
||||
*/
|
||||
|
||||
// you can modify these all you like!
|
||||
static RATE:u64 = 44100; // audio sample rate in samples/sec
|
||||
static TAUS:f64 = 5.0; // number of time contants to allow for notes to finish ringing before ending the output stream
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::stdout;
|
||||
use std::env::args;
|
||||
use std::f64::consts::PI;
|
||||
use std::fs::File;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
fn i16_to_bytes(number:&i16) -> [u8;2] {
|
||||
let mut bytes:[u8;2] = [0;2];
|
||||
for x in 0..2 {
|
||||
bytes[x] = ((*number >> (8*x)) & 0xFF) as u8;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
fn u16_to_bytes(number:&u16) -> [u8;2] {
|
||||
let mut bytes:[u8;2] = [0;2];
|
||||
for x in 0..2 {
|
||||
bytes[x] = ((*number >> (8*x)) & 0xFF) as u8;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
fn u32_to_bytes(number:&u32) -> [u8;4] {
|
||||
let mut bytes:[u8;4] = [0;4];
|
||||
for x in 0..4 {
|
||||
bytes[x] = ((*number >> (8*x)) & 0xFF) as u8;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
fn operate(oper:char,arg1:f64,arg2:f64,scale:f64) -> f64 {
|
||||
match oper {
|
||||
'*' => {
|
||||
return arg1*arg2;
|
||||
},
|
||||
'/' => {
|
||||
return arg1/arg2;
|
||||
},
|
||||
'+' => {
|
||||
return arg1+arg2*scale;
|
||||
},
|
||||
'-' => {
|
||||
return arg1-arg2*scale;
|
||||
},
|
||||
'^' => {
|
||||
return arg1*arg2.exp2();
|
||||
},
|
||||
'_' => {
|
||||
return arg1/arg2.exp2();
|
||||
},
|
||||
_ => {
|
||||
return arg2*scale;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Note {
|
||||
pitch:f64,
|
||||
louds:f64,
|
||||
atime:f64,
|
||||
riset:f64,
|
||||
fallt:f64,
|
||||
fbase:f64,
|
||||
tempo:f64,
|
||||
}
|
||||
|
||||
struct Loop {
|
||||
iters:usize,
|
||||
conts:Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// parse CLI arguments and read the file we're after; gripe if something breaks
|
||||
let argv = args().collect::<Vec<String>>();
|
||||
if argv.len() != 2 {
|
||||
eprintln!("Usage: interval [input file] | aplay -rcd");
|
||||
return;
|
||||
}
|
||||
let mut notefile = match File::open(&argv[1]) {
|
||||
Err(why) => {
|
||||
eprintln!("Failed to open file {}: {}",argv[1],why);
|
||||
return;
|
||||
},
|
||||
Ok(f) => f,
|
||||
};
|
||||
let mut notestring = String::new();
|
||||
match notefile.read_to_string(&mut notestring) {
|
||||
Err(why) => {
|
||||
eprintln!("Failed to read file {}: {}",argv[1],why);
|
||||
return;
|
||||
},
|
||||
Ok(_) => (),
|
||||
};
|
||||
|
||||
let opers:&[_] = &['+','-','*','/','^','_'];
|
||||
let params:&[_] = &['p','v','t','r','f','T','P'];
|
||||
let loopcs:&[_] = &['R','{','}'];
|
||||
|
||||
let mut notelines:Vec<Vec<String>> = vec![];
|
||||
let mut loops:Vec<Loop> = vec![];
|
||||
let mut reps:usize = 1;
|
||||
let mut commented:u8 = 0; // 0-not commented; 1-single line comment; 2-multiline comment
|
||||
for statement in notestring.trim().split_terminator(";") {
|
||||
let mut newnote:Vec<String> = vec![];
|
||||
for line in statement.lines() {
|
||||
if commented == 1 {
|
||||
commented = 0;
|
||||
}
|
||||
for part in line.split_whitespace() {
|
||||
if commented == 0 {
|
||||
if part.starts_with("#") {
|
||||
commented = 1;
|
||||
} else if part.starts_with("/*") {
|
||||
commented = 2;
|
||||
}
|
||||
} else if commented == 2 {
|
||||
if part.ends_with("*/") {
|
||||
commented = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if commented != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if part.starts_with("R") {
|
||||
reps = match part.trim_matches(loopcs).parse::<usize>() {
|
||||
Err(_) => {
|
||||
eprintln!(" {:3}n!# {}",part.trim_matches(loopcs),line);
|
||||
continue;
|
||||
},
|
||||
Ok(n) => n,
|
||||
};
|
||||
}
|
||||
if part.ends_with("{") || part.starts_with("{") {
|
||||
loops.push(Loop {
|
||||
iters:reps,
|
||||
conts:vec![],
|
||||
});
|
||||
} else if part.starts_with("}") || part.ends_with("}") {
|
||||
if let Some(loope) = loops.pop() {
|
||||
match loops.last_mut() {
|
||||
Some(outer) => {
|
||||
for _ in 0..loope.iters {
|
||||
outer.conts.append(&mut loope.conts.clone());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
for _ in 0..loope.iters {
|
||||
notelines.append(&mut loope.conts.clone());
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if part.starts_with(params) {
|
||||
newnote.push(part.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(loope) = loops.last_mut() {
|
||||
loope.conts.push(newnote);
|
||||
} else {
|
||||
notelines.push(newnote);
|
||||
}
|
||||
}
|
||||
|
||||
// parse note definitions from file
|
||||
let mut notes:Vec<Note> = vec![]; // notes are of the form "numerator:denominator amplitude atime rise fall"
|
||||
|
||||
let mut note = Note {
|
||||
pitch:440.0,
|
||||
louds:200.0,
|
||||
atime:0.0,
|
||||
riset:0.0,
|
||||
fallt:0000.0,
|
||||
tempo:100.0,
|
||||
fbase:440.0,
|
||||
};
|
||||
|
||||
let mut param_opers:[char;5] = [' ';5];
|
||||
let mut param_valus:[f64;5] = [440.0,200.0,0.0,0.0,2000.0];
|
||||
|
||||
let mut oper:char;
|
||||
let mut rparts:Vec<&str>;
|
||||
let mut numer:f64;
|
||||
let mut denom:f64;
|
||||
|
||||
'lines:for line in notelines.iter() {
|
||||
'args:for part in line.iter() {
|
||||
let firstchar = part.chars().collect::<Vec<char>>()[0];
|
||||
if firstchar == '#' {
|
||||
break 'args;
|
||||
}
|
||||
if params.contains(&firstchar) {
|
||||
rparts = part.trim_matches(opers).trim_matches(params).split(":").collect::<Vec<&str>>();
|
||||
numer = match rparts[0].parse::<f64>() {
|
||||
Err(_) => {
|
||||
eprintln!(" {:3}n!# {}",rparts[0],line.join(" "));
|
||||
continue 'lines;
|
||||
},
|
||||
Ok(n) => n,
|
||||
};
|
||||
if rparts.len() >= 2 {
|
||||
denom = match rparts[1].parse::<f64>() {
|
||||
Err(_) => {
|
||||
eprintln!(" {:3}n!# {}",rparts[1],line.join(" "));
|
||||
continue 'lines;
|
||||
},
|
||||
Ok(n) => n,
|
||||
};
|
||||
} else {
|
||||
denom = 1.0;
|
||||
}
|
||||
if part.ends_with(opers) {
|
||||
oper = part.chars().last().unwrap_or(' ');
|
||||
} else {
|
||||
oper = ' ';
|
||||
}
|
||||
match firstchar {
|
||||
'T' => {
|
||||
note.tempo = operate(oper,note.tempo,numer/denom,1.0);
|
||||
},
|
||||
'P' => {
|
||||
note.fbase = operate(oper,note.fbase,numer/denom,1.0);
|
||||
},
|
||||
'p' => {
|
||||
param_opers[0] = oper;
|
||||
param_valus[0] = numer/denom;
|
||||
},
|
||||
'v' => {
|
||||
param_opers[1] = oper;
|
||||
param_valus[1] = numer/denom;
|
||||
},
|
||||
't' => {
|
||||
param_opers[2] = oper;
|
||||
param_valus[2] = numer/denom;
|
||||
},
|
||||
'r' => {
|
||||
param_opers[3] = oper;
|
||||
param_valus[3] = numer/denom;
|
||||
},
|
||||
'f' => {
|
||||
param_opers[4] = oper;
|
||||
param_valus[4] = numer/denom;
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
} else {
|
||||
eprintln!(" {:3}?!# {}",firstchar,line.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
// finished accumulating note parameters; now we process the note value changes.
|
||||
note.pitch = operate(param_opers[0],note.pitch,param_valus[0],note.fbase);
|
||||
note.louds = operate(param_opers[1],note.louds,param_valus[1],1.0);
|
||||
note.atime = operate(param_opers[2],note.atime,param_valus[2],1.0);
|
||||
note.riset = operate(param_opers[3],note.riset,param_valus[3],1.0);
|
||||
note.fallt = operate(param_opers[4],note.fallt,param_valus[4],1.0);
|
||||
|
||||
notes.push(note.clone());
|
||||
}
|
||||
|
||||
let mut sound:VecDeque<i16> = VecDeque::new();
|
||||
|
||||
let mut skew:f64 = 0.0;
|
||||
|
||||
// generate actual sounds using note parameters
|
||||
for note in notes.iter() {
|
||||
|
||||
if note.pitch == 0.0 || note.tempo == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!("pitch: {:6}Hz | vol: {:5}% | time: {:6}ms | rise: {:6}ms | fall: {:6}ms",
|
||||
(note.pitch*10.0).round()/10.0,note.louds.round()/10.0,(note.atime*note.tempo/100.00).round(),note.riset.round(),note.fallt.round());
|
||||
|
||||
let ar = note.riset*(RATE as f64)/10000.0;
|
||||
let rr = note.fallt*(RATE as f64)/10000.0;
|
||||
let start = (-1.0*ar*TAUS) as i64;
|
||||
let end = (rr*TAUS) as i64;
|
||||
|
||||
let mut neededmax = (((end-start) as f64)+(RATE as f64)*((note.atime+skew)/(note.tempo*10.0))) as isize;
|
||||
let mut neededmin = ((RATE as f64)*((note.atime+skew)/(note.tempo*10.0))) as isize;
|
||||
if 0 > neededmin {
|
||||
let mut new:VecDeque<i16> = VecDeque::new();
|
||||
new.resize((0-neededmin) as usize,0);
|
||||
new.append(&mut sound);
|
||||
sound = new;
|
||||
skew -= note.atime;
|
||||
}
|
||||
if neededmax > 0 && sound.len() < (neededmax as usize) {
|
||||
sound.resize(neededmax as usize,0);
|
||||
}
|
||||
|
||||
for t in start..end {
|
||||
|
||||
// `wave` is the value of the waveform at this position in time. it uses a simple sine function.
|
||||
let wave = (((t as f64)*note.pitch*2.0*PI)/((RATE as f64))).sin();
|
||||
|
||||
// `ramp` is the relative envelope scaling factor at this position in time. it uses an asymmetric
|
||||
// gaussian function.
|
||||
let mut ramp;
|
||||
if t < 0 {
|
||||
ramp = (-0.5*((t as f64)/ar).powi(2)).exp();
|
||||
} else if t > 0 {
|
||||
ramp = (-0.5*((t as f64)/rr).powi(2)).exp();
|
||||
} else {
|
||||
ramp = 1.0;
|
||||
}
|
||||
|
||||
let soundpos = (((t-start) as f64)+(RATE as f64)*((note.atime+skew)/(note.tempo*10.0))) as usize;
|
||||
let mut newv = sound[soundpos] as f64 + 32.767*note.louds*wave*ramp;
|
||||
if newv > 32767.0 {
|
||||
newv = 32767.0;
|
||||
} else if newv < -32767.0 {
|
||||
newv = -32767.0;
|
||||
}
|
||||
sound[soundpos] = newv.round() as i16;
|
||||
}
|
||||
}
|
||||
|
||||
// generate and emit a WAV RIFF header, to make this a true WAV file and tell aplay how to deal with it
|
||||
let mut header:Vec<u8> = Vec::with_capacity(44);
|
||||
header.append(&mut b"RIFF".to_vec()); // format info
|
||||
header.append(&mut u32_to_bytes(&(sound.len() as u32 + 44)).to_vec()); // total file size
|
||||
header.append(&mut b"WAVE".to_vec()); // format info
|
||||
header.append(&mut b"fmt ".to_vec()); // magic
|
||||
header.append(&mut u32_to_bytes(&16).to_vec()); // length of format data
|
||||
header.append(&mut u16_to_bytes(&1).to_vec()); // 1 means PCM
|
||||
header.append(&mut u16_to_bytes(&2).to_vec()); // channel count
|
||||
header.append(&mut u32_to_bytes(&(RATE as u32)).to_vec()); // sample rate
|
||||
header.append(&mut u32_to_bytes(&((RATE as u32)*4)).to_vec()); // equivalent to RATE*BITS*CHANNELS/8
|
||||
header.append(&mut u16_to_bytes(&4).to_vec()); // 4 means 16-bit stereo
|
||||
header.append(&mut u16_to_bytes(&16).to_vec()); // 16 bits per sample
|
||||
header.append(&mut b"data".to_vec()); // magic
|
||||
header.append(&mut u32_to_bytes(&((sound.len() as u32)*4)).to_vec()); // (n samples)*(2 bytes per sample)*(2 channels)
|
||||
|
||||
assert_eq!(header.len(),44);
|
||||
|
||||
// write the audio stream to stdout
|
||||
let sout = stdout();
|
||||
let mut handle = sout.lock();
|
||||
match handle.write(&header) {
|
||||
Err(_) => return,
|
||||
Ok(_) => (),
|
||||
};
|
||||
for b in sound.iter() {
|
||||
// left channel
|
||||
match handle.write(&i16_to_bytes(&b)) {
|
||||
Err(_) => break,
|
||||
Ok(_) => (),
|
||||
};
|
||||
// right channel
|
||||
match handle.write(&i16_to_bytes(&b)) {
|
||||
Err(_) => break,
|
||||
Ok(_) => (),
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue