nu_plugin_audio_hook 0.105.1

A nushell plugin to make and play sounds
Documentation
use nu_plugin::{self, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, Example, LabeledError, Signature, Span, SyntaxShape, Value};
use rodio::source::{SineWave, Source};
use rodio::{OutputStream, Sink};

use std::time::Duration;

use crate::Sound;

pub struct SoundMakeCmd;

impl SimplePluginCommand for SoundMakeCmd {
    type Plugin = Sound;

    fn name(&self) -> &str {
        "sound make"
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::new("sound make")
            .required("Frequency", SyntaxShape::Float, "Frequency of the noise")
            .required("duration", SyntaxShape::Duration, "duration of the noise")
            .named(
                "amplify",
                SyntaxShape::Float,
                "amplify the sound by given value",
                Some('a'),
            )
            .category(Category::Experimental)
    }
    fn examples(&self) -> Vec<Example> {
        vec![
            Example {
                description: "create a simple noise frequency",
                example: "sound make 1000 200ms",
                result: None,
            },
            Example {
                description: "create a simple noise sequence",
                example:
                    "[ 300.0, 500.0,  1000.0, 400.0, 600.0 ] | each { |it| sound make $it 150ms }",
                result: None,
            },
        ]
    }
    fn description(&self) -> &str {
        "creates a noise with given frequency and duration"
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &nu_plugin::EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, nu_protocol::LabeledError> {
        make_sound(call)
    }
}

pub struct SoundBeepCmd;

impl SimplePluginCommand for SoundBeepCmd {
    type Plugin = Sound;

    fn name(&self) -> &str {
        "sound beep"
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::new("sound beep").category(Category::Experimental)
    }
    fn examples(&self) -> Vec<Example> {
        vec![Example {
            description: "create a simple beep sound",
            example: "sound beep",
            result: None,
        }]
    }
    fn description(&self) -> &str {
        "creates a beep noise"
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &nu_plugin::EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, nu_protocol::LabeledError> {
        sine_wave(1000.0, Duration::from_millis(300), 1.0);
        return Ok(Value::nothing(call.head));
    }
}

fn make_sound(call: &EvaluatedCall) -> Result<Value, LabeledError> {
    let (frequency_value, duration_value, amplify_value) = match load_values(call) {
        Ok(value) => value,
        Err(value) => return value,
    };

    sine_wave(frequency_value, duration_value, amplify_value);
    Ok(Value::nothing(call.head))
}

fn sine_wave(frequency_value: f32, duration_value: Duration, amplify_value: f32) {
    let (_stream, stream_handle) = OutputStream::try_default().unwrap();
    let sink = Sink::try_new(&stream_handle).unwrap();
    let source = SineWave::new(frequency_value)
        .take_duration(duration_value)
        .amplify(amplify_value);
    sink.append(source);
    sink.sleep_until_end();
}

fn load_values(call: &EvaluatedCall) -> Result<(f32, Duration, f32), Result<Value, LabeledError>> {
    let frequency: Value = match call.req(0) {
        Ok(value) => value,
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string())
                .with_label("Frequency value not found", call.head)))
        }
    };
    let frequency_value: f32 = match frequency.as_float() {
        Ok(value) => value as f32,
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string()).with_label(
                "Frequency value must be of type Float (f32)",
                frequency.span(),
            )))
        }
    };
    let duration: Value = match call.req(1) {
        Ok(value) => value,
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string())
                .with_label("Duration value not found", call.head)))
        }
    };
    let duration_value = match duration {
        Value::Duration { val, .. } => Duration::from_nanos(val.try_into().unwrap_or(0)),
        _ => {
            return Err(Err(LabeledError::new(
                "cannot parse duration value as Duration",
            )))
        }
    };

    let amplify: Value = match call.get_flag("amplify") {
        Ok(value) => match value {
            Some(value) => value,
            None => Value::float(1.0, Span::unknown()),
        },
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string())
                .with_label("Duration value not found", call.head)))
        }
    };
    let amplify_value: f32 = match amplify.as_float() {
        Ok(value) => value as f32,
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string()).with_label(
                "Amplify value must be of type Float (f32)",
                amplify.span(),
            )))
        }
    };
    Ok((frequency_value, duration_value, amplify_value))
}