use js_sys::{Array, Object, Reflect};
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
use crate::core::{
base::{HasDescription, HasName, HasPreciseName, HasStaticName, Parsable, PlaybackHandle, Res},
chord::{Chord, Chordable, HasChord, HasExtensions, HasInversion, HasIsCrunchy, HasModifiers, HasRoot, HasScale, HasSlash},
interval::Interval,
named_pitch::HasNamedPitch,
note::{HasPrimaryHarmonicSeries, Note},
octave::{HasOctave, Octave},
pitch::HasFrequency,
};
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc<'_> = wee_alloc::WeeAlloc::INIT;
pub type JsRes<T> = Result<T, JsValue>;
#[derive(Clone, Debug)]
#[wasm_bindgen]
pub struct KordNote {
inner: Note,
}
impl From<Note> for KordNote {
fn from(note: Note) -> Self {
KordNote { inner: note }
}
}
impl From<KordNote> for Note {
fn from(kord_note: KordNote) -> Self {
kord_note.inner
}
}
#[wasm_bindgen]
impl KordNote {
#[wasm_bindgen]
pub fn parse(name: String) -> JsRes<KordNote> {
Ok(Self { inner: Note::parse(&name).to_js_error()? })
}
#[cfg(feature = "analyze_base")]
#[wasm_bindgen(js_name = fromAudio)]
pub fn from_audio(data: &[f32], length_in_seconds: u8) -> JsRes<Array> {
let notes = Note::try_from_audio(data, length_in_seconds).to_js_error()?.into_iter().map(KordNote::from);
Ok(notes.into_js_array())
}
#[cfg(all(feature = "ml_infer", feature = "analyze_base"))]
#[wasm_bindgen(js_name = fromAudioMl)]
pub fn from_audio_ml(data: &[f32], length_in_seconds: u8) -> JsRes<Array> {
let notes = Note::try_from_audio_ml(data, length_in_seconds).to_js_error()?.into_iter().map(KordNote::from);
Ok(notes.into_js_array())
}
#[wasm_bindgen]
pub fn name(&self) -> String {
self.inner.name()
}
#[allow(clippy::inherent_to_string)]
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.name()
}
#[wasm_bindgen]
pub fn pitch(&self) -> String {
self.inner.named_pitch().static_name().to_string()
}
#[wasm_bindgen]
pub fn octave(&self) -> u8 {
self.inner.octave() as u8
}
#[wasm_bindgen]
pub fn frequency(&self) -> f32 {
self.inner.frequency()
}
#[wasm_bindgen(js_name = addInterval)]
pub fn add_interval(&self, interval: Interval) -> KordNote {
let note = self.inner + interval;
Self { inner: note }
}
#[wasm_bindgen(js_name = subInterval)]
pub fn subtract_interval(&self, interval: Interval) -> KordNote {
let note = self.inner - interval;
Self { inner: note }
}
#[wasm_bindgen(js_name = distanceTo)]
pub fn distance_to(&self, other: KordNote) -> Interval {
self.inner - other.inner
}
#[wasm_bindgen(js_name = harmonicSeries)]
pub fn harmonic_series(&self) -> Array {
let series = self.inner.primary_harmonic_series();
series.into_iter().map(KordNote::from).into_js_array()
}
#[wasm_bindgen]
pub fn copy(&self) -> KordNote {
self.clone()
}
}
#[derive(Clone, Debug)]
#[wasm_bindgen]
pub struct KordChord {
inner: Chord,
}
impl From<Chord> for KordChord {
fn from(chord: Chord) -> Self {
KordChord { inner: chord }
}
}
impl From<KordChord> for Chord {
fn from(kord_chord: KordChord) -> Self {
kord_chord.inner
}
}
#[wasm_bindgen]
impl KordChord {
#[wasm_bindgen]
pub fn parse(name: String) -> JsRes<KordChord> {
Ok(Self {
inner: Chord::parse(&name).to_js_error()?,
})
}
#[wasm_bindgen(js_name = fromNotesString)]
pub fn from_notes_string(notes: String) -> JsRes<Array> {
let notes = notes.split_whitespace().map(|note| Note::parse(note).to_js_error()).collect::<JsRes<Vec<Note>>>()?;
let candidates = Chord::try_from_notes(¬es).to_js_error()?.into_iter().map(KordChord::from);
Ok(candidates.into_js_array())
}
#[wasm_bindgen(js_name = fromNotes)]
pub fn from_notes(notes: Array) -> JsRes<Array> {
let notes: Vec<Note> = notes.cloned_into_vec_inner::<KordNote, Note>()?;
let candidates = Chord::try_from_notes(¬es).to_js_error()?.into_iter().map(KordChord::from);
Ok(candidates.into_js_array())
}
#[wasm_bindgen]
pub fn name(&self) -> String {
self.inner.name()
}
#[wasm_bindgen(js_name = preciseName)]
pub fn precise_name(&self) -> String {
self.inner.precise_name()
}
#[allow(clippy::inherent_to_string)]
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.inner.precise_name()
}
#[wasm_bindgen]
pub fn description(&self) -> String {
self.inner.description().to_string()
}
#[wasm_bindgen]
pub fn display(&self) -> String {
self.inner.to_string()
}
#[wasm_bindgen]
pub fn root(&self) -> String {
self.inner.root().name()
}
#[wasm_bindgen]
pub fn slash(&self) -> String {
self.inner.slash().name()
}
#[wasm_bindgen]
pub fn inversion(&self) -> u8 {
self.inner.inversion()
}
#[wasm_bindgen(js_name = isCrunchy)]
pub fn is_crunchy(&self) -> bool {
self.inner.is_crunchy()
}
#[wasm_bindgen]
pub fn chord(&self) -> Array {
self.inner.chord().into_iter().map(KordNote::from).into_js_array()
}
#[wasm_bindgen(js_name = chordString)]
pub fn chord_string(&self) -> String {
self.inner.chord().iter().map(|n| n.name()).collect::<Vec<_>>().join(" ")
}
#[wasm_bindgen]
pub fn scale(&self) -> Array {
self.inner.scale().into_iter().map(KordNote::from).into_js_array()
}
#[wasm_bindgen(js_name = scaleString)]
pub fn scale_string(&self) -> String {
self.inner.scale().iter().map(|n| n.name()).collect::<Vec<_>>().join(" ")
}
#[wasm_bindgen]
pub fn modifiers(&self) -> Array {
self.inner.modifiers().iter().map(|m| m.static_name()).into_js_array()
}
#[wasm_bindgen]
pub fn extensions(&self) -> Array {
self.inner.extensions().iter().map(|e| e.static_name()).into_js_array()
}
#[wasm_bindgen(js_name = withInversion)]
pub fn with_inversion(&self, inversion: u8) -> Self {
KordChord {
inner: self.inner.clone().with_inversion(inversion),
}
}
#[wasm_bindgen(js_name = withSlash)]
pub fn with_slash(&self, slash: &KordNote) -> Self {
KordChord {
inner: self.inner.clone().with_slash(slash.inner),
}
}
#[wasm_bindgen(js_name = withOctave)]
pub fn with_octave(&self, octave: u8) -> JsRes<KordChord> {
Ok(KordChord {
inner: self.inner.clone().with_octave(Octave::try_from(octave)?),
})
}
#[wasm_bindgen(js_name = withCrunchy)]
pub fn with_crunchy(&self, is_crunchy: bool) -> Self {
KordChord {
inner: self.inner.clone().with_crunchy(is_crunchy),
}
}
#[wasm_bindgen]
#[cfg(feature = "audio")]
pub async fn play(&self, delay: f32, length: f32, fade_in: f32) -> JsRes<()> {
use crate::core::base::Playable;
use anyhow::Context;
use gloo_timers::future::TimeoutFuture;
use std::time::Duration;
let delay = Duration::from_secs_f32(delay);
let length = Duration::from_secs_f32(length);
let fade_in = Duration::from_secs_f32(fade_in);
let _handle = self.inner.play(delay, length, fade_in).context("Could not start the playback.").to_js_error()?;
TimeoutFuture::new(length.as_millis() as u32).await;
Ok(())
}
#[wasm_bindgen]
pub fn copy(&self) -> KordChord {
self.clone()
}
}
#[wasm_bindgen]
pub struct KordPlaybackHandle {
_inner: PlaybackHandle,
}
#[derive(Clone, Debug)]
#[wasm_bindgen]
pub enum KordModifier {
Minor,
Flat5,
Augmented5,
Major7,
Dominant7,
Dominant9,
Dominant11,
Dominant13,
Flat9,
Sharp9,
Sharp11,
Diminished,
}
trait ToJsError<T> {
fn to_js_error(self) -> JsRes<T>;
}
impl<T> ToJsError<T> for Res<T> {
fn to_js_error(self) -> JsRes<T> {
self.map_err(|e| JsValue::from_str(&e.to_string()))
}
}
trait IntoJsArray {
fn into_js_array(self) -> Array;
}
impl<I, T> IntoJsArray for I
where
I: IntoIterator<Item = T>,
T: Into<JsValue>,
{
fn into_js_array(self) -> Array {
Array::from_iter(self.into_iter().map(Into::into))
}
}
trait ClonedIntoVec {
fn cloned_into_vec<T>(self) -> JsRes<Vec<T>>
where
T: RefFromJsValue + RefFromWasmAbi + Clone;
}
impl ClonedIntoVec for Array {
fn cloned_into_vec<T>(self) -> JsRes<Vec<T>>
where
T: RefFromJsValue + RefFromWasmAbi + Clone,
{
let mut result = Vec::with_capacity(self.length() as usize);
for k in 0..self.length() {
let value = self.get(k);
let value = T::ref_from_js_value(&value)?.clone();
result.push(value);
}
Ok(result)
}
}
trait ClonedIntoVecInner {
fn cloned_into_vec_inner<T, I>(self) -> JsRes<Vec<I>>
where
T: RefFromJsValue + RefFromWasmAbi + Clone,
I: From<T>;
}
impl ClonedIntoVecInner for Array {
fn cloned_into_vec_inner<T, I>(self) -> JsRes<Vec<I>>
where
T: RefFromJsValue + RefFromWasmAbi + Clone,
I: From<T>,
{
let mut result = Vec::with_capacity(self.length() as usize);
for k in 0..self.length() {
let value = self.get(k);
let value = T::ref_from_js_value(&value)?.clone();
let value = I::from(value);
result.push(value);
}
Ok(result)
}
}
trait RefFromJsValue {
fn ref_from_js_value(abi: &JsValue) -> JsRes<Self::Anchor>
where
Self: Sized + RefFromWasmAbi;
}
impl RefFromJsValue for KordNote {
fn ref_from_js_value(abi: &JsValue) -> JsRes<<KordNote as RefFromWasmAbi>::Anchor>
where
Self: Sized + RefFromWasmAbi,
{
let ptr = Reflect::get(abi, &JsValue::from_str("ptr"))?.as_f64().ok_or("Could not cast pointer to f64.")? as u32;
let object = abi.dyn_ref::<Object>().ok_or("Value is not an object.")?;
if object.constructor().name() != "KordNote" {
return Err("Invalid object type.".into());
}
let value = unsafe { KordNote::ref_from_abi(ptr) };
Ok(value)
}
}
impl RefFromJsValue for KordChord {
fn ref_from_js_value(abi: &JsValue) -> JsRes<<KordChord as RefFromWasmAbi>::Anchor>
where
Self: Sized + RefFromWasmAbi,
{
let ptr = Reflect::get(abi, &JsValue::from_str("ptr"))?.as_f64().ok_or("Could not cast pointer to f64.")? as u32;
let object = abi.dyn_ref::<Object>().ok_or("Value is not an object.")?;
if object.constructor().name() != "KordChord" {
return Err("Invalid object type.".into());
}
let value = unsafe { KordChord::ref_from_abi(ptr) };
Ok(value)
}
}
#[wasm_bindgen]
impl KordChord {
#[wasm_bindgen]
pub fn minor(&self) -> Self {
KordChord { inner: self.inner.clone().minor() }
}
#[wasm_bindgen]
pub fn flat5(&self) -> Self {
KordChord { inner: self.inner.clone().flat5() }
}
#[wasm_bindgen]
pub fn aug(&self) -> Self {
KordChord { inner: self.inner.clone().aug() }
}
#[wasm_bindgen]
pub fn maj7(&self) -> Self {
KordChord { inner: self.inner.clone().maj7() }
}
#[wasm_bindgen]
pub fn seven(&self) -> Self {
KordChord { inner: self.inner.clone().dominant7() }
}
#[wasm_bindgen]
pub fn nine(&self) -> Self {
KordChord { inner: self.inner.clone().dominant9() }
}
#[wasm_bindgen]
pub fn eleven(&self) -> Self {
KordChord { inner: self.inner.clone().dominant11() }
}
#[wasm_bindgen]
pub fn thirteen(&self) -> Self {
KordChord { inner: self.inner.clone().dominant13() }
}
#[wasm_bindgen]
pub fn flat9(&self) -> Self {
KordChord { inner: self.inner.clone().flat9() }
}
#[wasm_bindgen]
pub fn sharp9(&self) -> Self {
KordChord { inner: self.inner.clone().sharp9() }
}
#[wasm_bindgen]
pub fn sharp11(&self) -> Self {
KordChord { inner: self.inner.clone().sharp11() }
}
#[wasm_bindgen]
pub fn dim(&self) -> Self {
KordChord { inner: self.inner.clone().dim() }
}
#[wasm_bindgen(js_name = halfDim)]
pub fn half_dim(&self) -> Self {
KordChord { inner: self.inner.clone().half_dim() }
}
#[wasm_bindgen]
pub fn sus2(&self) -> Self {
KordChord { inner: self.inner.clone().sus2() }
}
#[wasm_bindgen]
pub fn sus4(&self) -> Self {
KordChord { inner: self.inner.clone().sus4() }
}
#[wasm_bindgen]
pub fn flat11(&self) -> Self {
KordChord { inner: self.inner.clone().flat11() }
}
#[wasm_bindgen]
pub fn flat13(&self) -> Self {
KordChord { inner: self.inner.clone().flat13() }
}
#[wasm_bindgen]
pub fn sharp13(&self) -> Self {
KordChord { inner: self.inner.clone().sharp13() }
}
#[wasm_bindgen]
pub fn add2(&self) -> Self {
KordChord { inner: self.inner.clone().add2() }
}
#[wasm_bindgen]
pub fn add4(&self) -> Self {
KordChord { inner: self.inner.clone().add4() }
}
#[wasm_bindgen]
pub fn add6(&self) -> Self {
KordChord { inner: self.inner.clone().add6() }
}
#[wasm_bindgen]
pub fn add9(&self) -> Self {
KordChord { inner: self.inner.clone().add9() }
}
#[wasm_bindgen]
pub fn add11(&self) -> Self {
KordChord { inner: self.inner.clone().add11() }
}
#[wasm_bindgen]
pub fn add13(&self) -> Self {
KordChord { inner: self.inner.clone().add13() }
}
}