Initial working commit
This commit is contained in:
commit
7d74d542db
|
@ -0,0 +1,3 @@
|
|||
libsig/target/
|
||||
sigweb/target/
|
||||
target/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"libsig",
|
||||
"sigweb"
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "libsig"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
rand_distr = "0.4"
|
||||
derive_more = "0.99"
|
||||
structopt = "0.3"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
features = ["derive"]
|
|
@ -0,0 +1,26 @@
|
|||
use libsig;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(name="sigcli", about="generate ideas for songs, tracks, and sounds")]
|
||||
struct CliOptions {
|
||||
/// Don't suggest percussion elements
|
||||
#[structopt(short, long)]
|
||||
ambient: bool,
|
||||
/// The number of suggestions to generate
|
||||
#[structopt(default_value="1")]
|
||||
suggestions: usize
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = CliOptions::from_args();
|
||||
|
||||
for _ in 0..opt.suggestions {
|
||||
let idea = if opt.ambient {
|
||||
libsig::SongIdea::generate_ambient()
|
||||
} else {
|
||||
libsig::SongIdea::generate()
|
||||
};
|
||||
println!("{}", idea)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
pub fn display_list(list: &[impl Display]) -> String {
|
||||
match list.len() {
|
||||
0 => "".into(),
|
||||
1 => format!("{}", list[0]),
|
||||
2 => format!("{} and {}", list[0], list[1]),
|
||||
_ => format!("{}and {}", display_list_commas(&list[0..list.len()-1]), list[list.len()-1])
|
||||
}
|
||||
}
|
||||
|
||||
fn display_list_commas(list: &[impl Display]) -> String {
|
||||
let mut string = String::new();
|
||||
for item in list {
|
||||
string.push_str(&format!("{}, ", item));
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_item() {
|
||||
let list = ["red"];
|
||||
assert_eq!(&display_list(&list), "red");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_items() {
|
||||
let list = ["red", "green"];
|
||||
assert_eq!(&display_list(&list), "red and green");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_items() {
|
||||
let list = ["red", "green", "blue"];
|
||||
assert_eq!(&display_list(&list), "red, green, and blue");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_items() {
|
||||
let list = ["red", "yellow", "green", "blue"];
|
||||
assert_eq!(&display_list(&list), "red, yellow, green, and blue");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_items() {
|
||||
let list = ["red", "orange" , "yellow", "green", "blue", "purple"];
|
||||
assert_eq!(&display_list(&list), "red, orange, yellow, green, blue, and purple");
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use derive_more::Display;
|
||||
use rand::distributions::{Distribution, Standard};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::displayutil::display_list;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Display, Serialize, Deserialize)]
|
||||
pub enum Effect {
|
||||
#[display(fmt = "delay")]
|
||||
Delay,
|
||||
#[display(fmt = "reverb")]
|
||||
Reverb,
|
||||
#[display(fmt = "lofi")]
|
||||
Lofi,
|
||||
#[display(fmt = "drive")]
|
||||
Drive,
|
||||
#[display(fmt = "chorus")]
|
||||
Chorus,
|
||||
#[display(fmt = "tremolo")]
|
||||
Tremolo,
|
||||
#[display(fmt = "distortion")]
|
||||
Distortion,
|
||||
#[display(fmt = "flanger")]
|
||||
Flanger,
|
||||
#[display(fmt = "phaser")]
|
||||
Phaser,
|
||||
#[display(fmt = "compressor")]
|
||||
Compressor,
|
||||
#[display(fmt = "sustain")]
|
||||
Sustain,
|
||||
#[display(fmt = "bitcrusher")]
|
||||
Bitcrusher,
|
||||
#[display(fmt = "wavefolder")]
|
||||
Wavefolder,
|
||||
#[display(fmt = "ring modulator")]
|
||||
RingMod,
|
||||
#[display(fmt = "wah")]
|
||||
Wah,
|
||||
}
|
||||
|
||||
impl Distribution<Effect> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Effect {
|
||||
let index: u8 = rng.gen_range(0..15);
|
||||
use Effect::*;
|
||||
match index {
|
||||
0 => Delay,
|
||||
1 => Reverb,
|
||||
2 => Lofi,
|
||||
3 => Drive,
|
||||
4 => Chorus,
|
||||
5 => Tremolo,
|
||||
6 => Distortion,
|
||||
7 => Flanger,
|
||||
8 => Phaser,
|
||||
9 => Compressor,
|
||||
10 => Sustain,
|
||||
11 => Bitcrusher,
|
||||
12 => Wavefolder,
|
||||
13 => RingMod,
|
||||
14 => Wah,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_effects() -> Vec<Effect> {
|
||||
let mut vec = Vec::<Effect>::new();
|
||||
while rand::random() {
|
||||
vec.push(rand::random())
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
pub fn display_effects(effects: &[Effect]) -> String {
|
||||
if effects.is_empty() {
|
||||
"clean".into()
|
||||
} else {
|
||||
format!("through {}", display_list(effects))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
mod displayutil;
|
||||
mod effects;
|
||||
mod tempo;
|
||||
mod voices;
|
||||
pub use effects::{display_effects, generate_effects, Effect};
|
||||
pub use tempo::Tempo;
|
||||
pub use voices::{generate_voice_adjective, Voice, display_voices};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SongIdea {
|
||||
pub tempo: Tempo,
|
||||
pub effects: Vec<Effect>,
|
||||
pub lead: Option<Voice>,
|
||||
pub bass: Option<Voice>,
|
||||
pub pad: Option<Voice>,
|
||||
pub vocals: Option<Voice>,
|
||||
pub kick: Option<Voice>,
|
||||
pub snare: Option<Voice>,
|
||||
pub hat: Option<Voice>,
|
||||
pub tom: Option<Voice>,
|
||||
pub cymbal: Option<Voice>,
|
||||
}
|
||||
|
||||
impl SongIdea {
|
||||
pub fn generate() -> Self {
|
||||
Self {
|
||||
tempo: rand::random(),
|
||||
effects: generate_effects(),
|
||||
lead: generate_voice_adjective("lead"),
|
||||
bass: generate_voice_adjective("bass"),
|
||||
pad: generate_voice_adjective("pad"),
|
||||
vocals: generate_voice_adjective("vocals"),
|
||||
kick: generate_voice_adjective("kick"),
|
||||
snare: generate_voice_adjective("snare"),
|
||||
hat: generate_voice_adjective("hat"),
|
||||
tom: generate_voice_adjective("tom"),
|
||||
cymbal: generate_voice_adjective("cymbal")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_ambient() -> Self {
|
||||
Self {
|
||||
tempo: rand::random(),
|
||||
effects: generate_effects(),
|
||||
lead: generate_voice_adjective("lead"),
|
||||
bass: generate_voice_adjective("bass"),
|
||||
pad: generate_voice_adjective("pad"),
|
||||
vocals: generate_voice_adjective("vocals"),
|
||||
kick: None,
|
||||
snare: None,
|
||||
hat: None,
|
||||
tom: None,
|
||||
cymbal: None
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SongIdea {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let voices: Vec<_> = [&self.lead, &self.bass, &self.pad, &self.vocals, &self.kick, &self.snare, &self.hat, &self.tom, &self.cymbal].iter().cloned().cloned().filter_map(|voice| voice).collect();
|
||||
write!(fmt, "{} at {}, {}", display_voices(&voices), self.tempo, display_effects(&self.effects))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use derive_more::Display;
|
||||
use rand::distributions::{Distribution, Standard};
|
||||
use rand::Rng;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Display, Serialize, Deserialize)]
|
||||
#[display(fmt="{} bpm, with {} swing", bpm, swing)]
|
||||
pub struct Tempo {
|
||||
bpm: u16,
|
||||
swing: Swing,
|
||||
}
|
||||
|
||||
impl Distribution<Tempo> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Tempo {
|
||||
// These parameters are static and known-good
|
||||
let raw_bpm_distr: f32 = rand_distr::SkewNormal::new(25.0, 75.0, 5.0).unwrap().sample(rng);
|
||||
let bpm: u16 =
|
||||
if raw_bpm_distr < 33. { 33 } else if raw_bpm_distr > 300.0 { 300 } else { raw_bpm_distr as u16 };
|
||||
let swing: Swing = Standard::sample(&self, rng);
|
||||
Tempo {
|
||||
bpm, swing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Display, Serialize, Deserialize)]
|
||||
pub enum Swing {
|
||||
#[display(fmt = "no")]
|
||||
None,
|
||||
#[display(fmt = "light")]
|
||||
Light,
|
||||
#[display(fmt = "heavy")]
|
||||
Heavy
|
||||
}
|
||||
|
||||
impl Distribution<Swing> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Swing {
|
||||
let index: u8 = rng.gen_range(0..6);
|
||||
match index {
|
||||
0 | 1 | 2=> Swing::None,
|
||||
3 | 4 => Swing::Light,
|
||||
5 => Swing::Heavy,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_tempo_struct() {
|
||||
assert_eq!(&format!("{}", Tempo { bpm: 80, swing: Swing::Light}), "80 bpm, with light swing");
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
use rand::prelude::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::displayutil::display_list;
|
||||
use derive_more::Display;
|
||||
use rand::distributions::{Distribution, Standard};
|
||||
use rand::Rng;
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Display)]
|
||||
pub enum VoiceAdjective {
|
||||
Glassy,
|
||||
Wide,
|
||||
Glitchy,
|
||||
Aggressive,
|
||||
Warm,
|
||||
Thick,
|
||||
Clean,
|
||||
Hard,
|
||||
Wet,
|
||||
Dry,
|
||||
Thin,
|
||||
Tinny,
|
||||
Splashy,
|
||||
Heavy,
|
||||
Light,
|
||||
Dark,
|
||||
Smooth,
|
||||
Plucky,
|
||||
Soft,
|
||||
Droning,
|
||||
Digital,
|
||||
Squishy,
|
||||
Analogue,
|
||||
Round,
|
||||
Sharp,
|
||||
Crunchy,
|
||||
Aliased,
|
||||
Punchy,
|
||||
Intense,
|
||||
Mild,
|
||||
Calm,
|
||||
Sonorous,
|
||||
Resonant,
|
||||
Buzzy,
|
||||
Dampened,
|
||||
Subdued,
|
||||
Rampant,
|
||||
Hot,
|
||||
Sweaty,
|
||||
Flooded,
|
||||
Plonky,
|
||||
Yoinky,
|
||||
Sploinky,
|
||||
Boofy,
|
||||
Middy,
|
||||
Grimy,
|
||||
Ugly,
|
||||
Soothing,
|
||||
Skittery,
|
||||
Harsh,
|
||||
Alien,
|
||||
Bubbly,
|
||||
Airy,
|
||||
Subterranean,
|
||||
Claustrophobic,
|
||||
Spacious,
|
||||
Ringing,
|
||||
Clicky,
|
||||
Acidic,
|
||||
Solid,
|
||||
Ethereal,
|
||||
Massive,
|
||||
Distant,
|
||||
Muffled,
|
||||
Garbled,
|
||||
Degraded,
|
||||
BlownOut,
|
||||
Sizzling,
|
||||
#[display(fmt = "In-your-face")]
|
||||
Inyouface,
|
||||
#[display(fmt = "Way Huge")]
|
||||
Wayhuge,
|
||||
ElectroHarmonic,
|
||||
Surfy,
|
||||
Nostalgic,
|
||||
Ancient,
|
||||
Angular,
|
||||
Fried,
|
||||
Ineffable,
|
||||
Ramshackle,
|
||||
Mercurial,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Voice {
|
||||
adjective: VoiceAdjective,
|
||||
voice: String,
|
||||
}
|
||||
|
||||
impl Distribution<VoiceAdjective> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> VoiceAdjective {
|
||||
use VoiceAdjective::*;
|
||||
let values = [
|
||||
Glassy,
|
||||
Wide,
|
||||
Glitchy,
|
||||
Aggressive,
|
||||
Warm,
|
||||
Thick,
|
||||
Clean,
|
||||
Hard,
|
||||
Wet,
|
||||
Dry,
|
||||
Thin,
|
||||
Tinny,
|
||||
Splashy,
|
||||
Heavy,
|
||||
Light,
|
||||
Dark,
|
||||
Smooth,
|
||||
Plucky,
|
||||
Soft,
|
||||
Droning,
|
||||
Digital,
|
||||
Squishy,
|
||||
Analogue,
|
||||
Round,
|
||||
Sharp,
|
||||
Crunchy,
|
||||
Aliased,
|
||||
Punchy,
|
||||
Intense,
|
||||
Mild,
|
||||
Calm,
|
||||
Sonorous,
|
||||
Resonant,
|
||||
Buzzy,
|
||||
Dampened,
|
||||
Subdued,
|
||||
Rampant,
|
||||
Hot,
|
||||
Sweaty,
|
||||
Flooded,
|
||||
Plonky,
|
||||
Yoinky,
|
||||
Sploinky,
|
||||
Boofy,
|
||||
Middy,
|
||||
Grimy,
|
||||
Ugly,
|
||||
Soothing,
|
||||
Skittery,
|
||||
Harsh,
|
||||
Alien,
|
||||
Bubbly,
|
||||
Airy,
|
||||
Subterranean,
|
||||
Claustrophobic,
|
||||
Spacious,
|
||||
Ringing,
|
||||
Clicky,
|
||||
Acidic,
|
||||
Solid,
|
||||
Ethereal,
|
||||
Massive,
|
||||
Distant,
|
||||
Muffled,
|
||||
Garbled,
|
||||
Degraded,
|
||||
BlownOut,
|
||||
Sizzling,
|
||||
Inyouface,
|
||||
Wayhuge,
|
||||
ElectroHarmonic,
|
||||
Surfy,
|
||||
Nostalgic,
|
||||
Ancient,
|
||||
Angular,
|
||||
Fried,
|
||||
Ineffable,
|
||||
Ramshackle,
|
||||
Mercurial,
|
||||
];
|
||||
values.choose(rng).unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_voice_adjective(voice: &'static str) -> Option<Voice> {
|
||||
if rand::random() {
|
||||
None
|
||||
} else {
|
||||
Some(Voice {
|
||||
adjective: rand::random(),
|
||||
voice: voice.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_voices(list: &[Voice]) -> String {
|
||||
let voices_formatted: Vec<_> = list
|
||||
.iter()
|
||||
.map(|voice| format!("{} {}", voice.adjective, voice.voice).to_lowercase())
|
||||
.collect();
|
||||
display_list(&voices_formatted)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/target
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "sigweb"
|
||||
version = "0.1.0"
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "sigweb"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
libsig = { path = "../libsig" }
|
||||
|
||||
[dependencies.rocket]
|
||||
version = "0.5.0-rc.1"
|
||||
features = ["json"]
|
|
@ -0,0 +1,49 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
use libsig;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::serde::json::Json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[get("/api")]
|
||||
fn api_version_available() -> Json<Value> {
|
||||
Json(json! ({
|
||||
"versions": [ "v1" ]
|
||||
}))
|
||||
}
|
||||
|
||||
#[get("/api/v1")]
|
||||
fn api_v1_index() -> Json<Value> {
|
||||
Json(json! ({
|
||||
"endpoints": [ "generate", "generate_ambient" ]
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Output {
|
||||
data: libsig::SongIdea,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[get("/api/v1/generate")]
|
||||
fn generate() -> Json<Output> {
|
||||
let idea = libsig::SongIdea::generate();
|
||||
Json(Output {
|
||||
data: idea.clone(),
|
||||
text: idea.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/api/v1/generate_ambient")]
|
||||
fn generate_ambient() -> Json<Output> {
|
||||
let idea = libsig::SongIdea::generate_ambient();
|
||||
Json(Output {
|
||||
data: idea.clone(),
|
||||
text: idea.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build().mount("/", routes![api_version_available, api_v1_index, generate, generate_ambient])
|
||||
}
|
Loading…
Reference in New Issue