Giter VIP home page Giter VIP logo

dasp's People

Contributors

0xcaff avatar alextmjugador avatar andrewcsmith avatar chohner avatar default-username-852 avatar emberian avatar est31 avatar foolswood avatar gereeter avatar herschel avatar jdanford avatar linclelinkpart5 avatar manorhos avatar mitchmindtree avatar ollpu avatar rawler avatar sof3 avatar vaffeine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dasp's Issues

Add no_std option

You claim "no dependencies", but std is actually a quite large dependency in some environments. Most of this crate can do without std and use only core (ie, function overloading traits)`.

There's a few bits which can't:

It's a bit unfortunate trying to use alloc and collections, as they are currently unstable. I recommend adding a no_std feature that explicitly depends on those crates, with internal aliases for Rc, Vec, and VecDeque that switch between std and alloc/collections based on #[cfg(feature = "no_std")]. This does imply that no_std is unstable.

If you're happy with this, I can send a PR doing these changes.

Remove src/rate.rs (?)

Suggested for the 0.7 milestone. Should be superceded by my new stuff.

Edit: Give me the OK and I'll get to work exorcising the rate module.

Making `Iterator<Item=Frame>` and `Signal` play together nicely.

One common occurence that I run into is the desire to "lift" a Signal operation/method into an Iterator<Item=Frame>. As was originally pointed out by @tuxmark5 in #54, this is almost always the case when you want the exhaustiveness of Iterator with the Signal API.

As mentioned in #54, a nice building block to begin with would be some alternative to signal::from_iter which provides access to a flag indicating that the inner iterator was exhausted.

A type like this could be used within some function that takes an Iterator<Item=Frame>, a function to apply to a Signal and returns a new Iterator<Item=Frame>. E.g.

signal::lift(frame_iter, |signal| signal.add_amp(other_signal))

Chaining multiple signal methods might look like this:

signal::lift(frame_iter, |signal| {
    signal.add_amp(other_signal)
        .delay(delay_duration)
        .clip_amp(threshold)
});

The name lift comes from the idea of "lifting" the given signal function into the Iterator. Open to better name suggestions!

An alternative approach to offering a function might be to offer a second trait along these lines:

pub trait ExhaustiveSignal: Iterator<Item=Frame> {
    fn lift<F, S>(self, signal_fn: F) -> ExhaustiveSignalIterator<Self, S>
        F: FnOnce(FromIterator<Self>) -> S,
        S: Signal<Frame=Self::Item>,
    {
        // ...
    }
}

where MapSignal is an Iterator which becomes exhausted when Self becomes exhausted.

Both of these approaches should also be friendly to changes in frequency as the resulting Iterator simply starts returning None once the inner Iterator does.

Totally open to better names/suggestions if anyone has any other ideas!

Broken links in documentation

On the home page of the API documentation under the section modules, there are three links in the descriptions ("Frame", "Converter type", "Signal trait"), all three are broken.

Resampling to the same sample rate produces NaNs

Hello.

I came across a possible bug or strangeness. I have some code where I attempt to always resample the input wav file to 44100Hz. When my input is 48000, the result is OK (48000 -> 44100). If I resample 44100 -> 48000, also, it works fine. But in the case of 44100 to 44100, every sample is NaN.

The code is copied from https://github.com/RustAudio/sample/blob/master/examples/resample.rs

Good:

shanssian:jitters $ cargo run --example wav_sender ./188692__arseniiv__pianoa-100bpm.wav
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/examples/wav_sender ./188692__arseniiv__pianoa-100bpm.wav`
Resampling from 44100 to 48000
i: 0, frame: [
    NaN
]
i: 1, frame: [
    0.0
]
i: 2, frame: [
    0.0
]
...
i: 330, frame: [
    -0.002826701526415574
]
i: 331, frame: [
    -0.03558936043031568
]
i: 332, frame: [
    0.00041085714707058275
]
...

Also playing the resampled clip sounds fine.

Bad:

shanssian:jitters $ cargo run --example wav_sender ./188692__arseniiv__pianoa-100bpm.wav
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/examples/wav_sender ./188692__arseniiv__pianoa-100bpm.wav`
Resampling from 44100 to 44100
i: 0, frame: [
    NaN
]
i: 1, frame: [
    NaN
]
i: 2, frame: [
    NaN
]
...
i: 330, frame: [
    NaN
]
i: 331, frame: [
    NaN
]
i: 332, frame: [
    NaN
]
...

Code:

let mut reader = WavReader::open(&args[1]).unwrap();
let file_spec = reader.spec();
let mut target_spec = file_spec;
target_spec.sample_rate = JITTERS_SAMPLE_RATE;
let mut rtp_stream = RtpOutStream::new(file_spec.channels);

let samples = reader.into_samples().filter_map(Result::ok).map(i16::to_sample::<f64>);
let signal = signal::from_interleaved_samples_iter(samples);

let ring_buffer = ring_buffer::Fixed::from([[0.0]; 100]);
let sinc = interpolate::Sinc::new(ring_buffer);

//if file_spec.sample_rate != JITTERS_SAMPLE_RATE {
println!("Resampling from {:#?} to {:#?}", file_spec.sample_rate, target_spec.sample_rate);
let new_signal = signal.from_hz_to_hz(sinc, file_spec.sample_rate as f64, target_spec.sample_rate as f64);


let mut writer = WavWriter::create("fake_wav.wav", target_spec).unwrap();
for (i, frame) in new_signal.until_exhausted().enumerate() {
    println!("i: {:#?}, frame: {:#?}", i, frame);
    writer.write_sample(frame[0].to_sample::<i16>()).unwrap();
    //
    //let sample = frame[0].to_sample::<i16>();
    //let time_in_s = ((i as f64) * ((1.0 / JITTERS_SAMPLE_RATE) * (frame.len() as f64))) as u32;
    //let next_packet = rtp_stream.next_packet(sample, time_in_s);
    //udp_sock.send_to(&next_packet, "127.0.0.1:1337");
}

Possibility of incorporating fourier transform function into dasp?

Hey! I wanted to ask if FFTs are in scope for this project? I'd be happy to help try to implement if so, since I've been working on a couple of audio projects using dasp, and it feels natural to include FFTs in what seems to be a core DSP crate and would be incredibly useful in my opinion!

Once stabilised, use the `doc_cfg` feature to automatically tag items with their required features in documentation

See here for info about the attribute.

I tried to get this going as a part of #120, however it seems that the attribute is ignored within crate dependencies. That is, when documenting dasp, the re-exported dependencies did not provide any information about their own features, only providing tags for the dasp-level features.

There's an issue following development of the feature here rust-lang/rust#43781.

Ring Buffer type

It would be great to make our own ring buffer (basically, something like VecDeque) that uses a <F: Frame> &mut [F] in its constructor. (It's impossible to do this with VecDeque. I would consider this a 1.0 milestone feature for being able to pitch this library.

More examples

Hi!
For the MultiDSP project, I would like to rely on this crate for most things in order to participate and contribute to the Rust audio effort.
Looking into how to use ring_buffer to write a simple threaded ALSA loopback:
My first goal is to copy the input buffer filled by ALSA capture into the ring buffer in one thread, and read from it to write to ALSA playback in another thread.

However I find difficult to get started.
May I ask if the authors could write more examples, especially involving threads?
Thanks!

Here is something close to what I am trying to do, from the rb crate: https://docs.rs/rb/0.3.1/rb/struct.SpscRb.html

Fix README doc on Converter + signal

Before 0.7 lands (with rate mod removed) someone needs to fix up the README doc. It might also be worthwhile to rework my work to involve some type-level shortcuts on the signal module, rather than explicitly instantiating a struct impl Interpolator.

Change `Signal::delay` to use `ring_buffer::Fixed`?

Currently Signal::delay simply uses a frame countdown to delay yielding frames from the inner signal. Only once the countdown reaches 0 does it begin requesting frames from the delayed signal.

One potential issue with this is that next is not called on the inner signal until the delay countdown is complete. This can be a problem if the inner signal has some kind of side-effect, e.g. if it monitors the signal in some way and sends values to another thread, a large delay will cause monitoring to hault for the duration of the delay.

This could be fixed by using a fixed-length ring buffer. In this case, a frame could be requested from the inner signal every time next is called (rather than just after the delay is finished) and instead of yielding them immediately they are pushed to the back of the ring buffer and the frame at the front of the ring buffer is returned instead. This way the output would have the same behaviour as the current delay, but it would ensure that next is called on the inner signal every frame.

An alternative approach might be to offer both? The current implementation is much more memory efficient, especially in the case of long delays, and so would surely be preferable in some cases. I can see a couple of options for this:

  1. Provide two methods. E.g. change delay to use a ring buffer, and move the current implementation to a lazy_delay method?
  2. Make Delay generic over usize (a number of frames) and ring_buffer::Fixed (buffered delay).

Realtime-friendly Bus implementation

While Bus is pretty cool, the use of VecDeque (and the possibility that it will reallocate mid-sample) means that it's not suitable for realtime audio. I'm working on a (very circumscribed) replacement that is suitable.

Monitoring `Signal`s from different threads of varying rates.

I often come across the desire to monitor a Signal that is running on the audio thread, from the GUI thread. Normally the kinds of signals I want to monitor are control signals (like Peak or Rms) generated by an adaptor around some signal.

I normally end up designing a custom Signal adaptor to do this depending on the task.

I often want to sample the signal I'm monitoring at a much lower rate than the audio sample rate. E.g. if I want to monitor a 44100hz audio signal in a GUI that is running at 60hz, I only want a "snapshot" of the audio signal every 735 audio frames as I can't physically see the monitored values faster than this anyway.

This rate issue is a large contributor to the awkwardness involved with these monitoring implementations and I think could possibly be addressed at a lower level. That is, it would be nice to have abstractions for safely and predictably pulling values from a Signal at varying rates.

Signal::bus could be considered a step in this direction, but has caveats. Firstly, Bus is designed for use on a single thread (it uses Rc and RefCell internally for sharing the ring buffer between output nodes). Secondly, when requesting from a Bus's Output nodes at different rates, the internal ring buffer will grow infinitely large with values that have not yet been collected by the slower Output.

One could work around the second issue by reducing the sample rate (e.g. calling Signal::from_hz_to_hz) on the slower Output node to the rate at which it should be called. This would go a long way to fixing the issue, but the same problem will occur if the rate is inconsistent/unreliable or if some drift occurs (which will almost always be the case if frames are being requested from different threads). This same problem applies when a user requires outputting a single audio signal to more than one audio hardware device.

The `Sample` and `Frame` traits would greatly benefit from the "associated consts" and "integer generics" features

Sample could benefit from EQUILIBRIUM and IDENTITY associated constants, allowing us to remove the methods that currently fill this role.

Frame could benefit from, NUM_CHANNELS, EQUILIBRIUM and IDENTITY associated constants, allowing us to remove the n_channels and equilibrium methods.

Frame would also massively benefit from integer generics of some sort in order to get rid of the NumChannels associated type, the related trait and the macro-generated implementations for every frame size up to 32 (an arbitrary, unnecessary limit).

It looks like associated consts at least will land soonish as the feature gate was recently removed. I'm not sure what the current state of integer generics is.

cc @andrewcsmith I'm leaning towards making this a 1.0 blocker as it will cause quite a significant impact on the API. Otherwise I guess we could aim to stabilise earlier and then release a 2.0 in the future when these features arrive. Looking into the progress on integer generics will probably help us to make a decision!

How to initialize ring_buffer

I want to use the sample library to create a class for calculating the auto-correlation function of a signal. In principle, the implementation should be fairly similar to rms.rs. However, I'm having some trouble creating a ring_buffer.

I'm definitely a rust beginner, so I am certain I'm just doing something wrong!

Here is the best I've come up with so far:

extern crate sample;

use sample::ring_buffer::Slice;
use std::marker::PhantomData;

#[derive(Clone)]
struct DiffSquaredAcf<F, S> where F: sample::Frame, S: sample::ring_buffer::Slice<Element = F::Float>
{
    frame: PhantomData<F>,
    frame_buffer: sample::ring_buffer::Fixed<S>,
    window_size: usize,
    max_t: usize,
}
 
impl<F, S> DiffSquaredAcf<F, S>
where
    F: sample::Frame,
    S: sample::ring_buffer::Slice<Element = F::Float>,
{
    pub fn new(window_size: usize, max_t: usize) -> Self {
        let buffer_size = window_size + max_t;
        let frame_vec = vec![F::equilibrium().to_float_frame(); buffer_size];
        let mut frame_buffer = sample::ring_buffer::Fixed::from(frame_vec);
        DiffSquaredAcf {
            frame: PhantomData,
            // This line fails with a type error
            frame_buffer: frame_buffer,
            window_size: window_size,
            max_t: max_t,
        }
    }

    pub fn add_frame(&mut self, frame: F) {
        //...
    }
}

I think I'm close, but I just can't figure out how to create the ring_buffer in a generic way. In the above example, I get the following error:

error[E0308]: mismatched types
  --> /Users/kevin/Projects/kgreenek/rzero/src/diff_squared_acf.rs:26:27
   |
26 |             frame_buffer: frame_buffer,
   |                           ^^^^^^^^^^^^ expected type parameter, found struct `std::vec::Vec`
   |
   = note: expected type `sample::ring_buffer::Fixed<S>`
              found type `sample::ring_buffer::Fixed<std::vec::Vec<<F as sample::Frame>::Float>>`

error: aborting due to previous error

error: Could not compile `rzero`.

To learn more, run the command again with --verbose.

I would think this should work because there is an impl for Vec for the ring_buffer::Slice trait. But it doesn't :(

It's similar to the Rms class, but in my case, I don't want to have my ring_buffer passed to the constructor as an argument, I want to create it in the new method. The DiffSquaredAcf struct will obviously have more members; right now I'm just trying to figure out how to create this internal ring_buffer!

Also, I'd love to contribute this upstream when it's ready, if it's something you'd be interested in. The sample module is great!

Compiler error in rustc-nightly 2020-10-06

dasp_window has a compiler error in the latest rustc nightly:

rustc --version
rustc 1.49.0-nightly (98edd1fbf 2020-10-06)


C:\...\dasp_window-0.11.0\src\hanning\mod.rs:25:41
   |
25 |         let v = phase.to_float_sample().to_sample() * PI_2;
   |                                         ^^^^^^^^^ cannot infer type for type parameter `S` declared on the associated function `to_sample`
   |
help: consider specifying the type argument in the method call
   |
25 |         let v = phase.to_float_sample().to_sample::<S>() * PI_2;
   |                                                  ^^^^^

Graph: Add EdgeWeight to Input

I'm currently trying to get some simple modular synth running using dasp_graph.
For that it would be required, to be able to distinguish the inputs of a node in the method, computing the node.

Seeing, that the definition of input currently looks like this:

/// A reference to another node that is an input to the current node.โ€จ
///
โ€จ/// *TODO: It may be useful to provide some information that can uniquely identify the input node.
โ€จ/// This could be useful to allow to distinguish between side-chained and regular inputs forโ€จ
/// example.*
โ€จpub struct Input {โ€จ
    buffers_ptr: *const Buffer,โ€จ
    buffers_len: usize,โ€จ
}

was a bit unfortunate.

I dug around a little and pentagraph supports EdgeWeights, wich are generics, that can hold information on the edge.
It would be nice to be able to define EdgeWeights when creating the graph and have them put into the input.

I tried around, but the process method does not directly use the graph, so I'm a bit stuck.
The edge_weight() function is available, so if one could get the edge_id, the data can be read.
However, I don't see a possibility to get the edge_id as the find_edge() function is not available.

pub fn process<G, T>(processor: &mut Processor<G>, graph: &mut G, node: G::NodeId)
where
    G: Data<NodeWeight = NodeData<T>> + DataMapMut + Visitable,
    for<'a> &'a G: GraphBase<NodeId = G::NodeId> + IntoNeighborsDirected,
    T: Node,
{
    const NO_NODE: &str = "no node exists for the given index";
    processor.dfs_post_order.reset(Reversed(&*graph));
    processor.dfs_post_order.move_to(node);
    while let Some(n) = processor.dfs_post_order.next(Reversed(&*graph)) {
        let data: *mut NodeData<T> = graph.node_weight_mut(n).expect(NO_NODE) as *mut _;
        processor.inputs.clear();
        for in_n in (&*graph).neighbors_directed(n, Incoming) {
            // Skip edges that connect the node to itself to avoid aliasing `node`.
            if n == in_n {
                continue;
            }
            
            // ADDED Get Edge Information
            let edgeId = graph.find_edge(in_n, n).unwrap(); // this is not available
            let edge_weight = graph.edge_weight(edgeId);
            
            let input_container = graph.node_weight(in_n).expect(NO_NODE);
            let input = node::Input::new(&input_container.buffers); // The EdgeWeight would be added to this constructor
            processor.inputs.push(input);
        }
        // Here we deference our raw pointer to the `NodeData`. The only references to the graph at
        // this point in time are the input references and the node itself. We know that the input
        // references do not alias our node's mutable reference as we explicitly check for it while
        // looping through the inputs above.
        unsafe {
            (*data)
                .node
                .process(&processor.inputs, &mut (*data).buffers);
        }
    }
}

If somebody can point me in the right direction, I would gladly finish this up and prepare a pull request.
Or if somebody has already solved this in a different way, that would also be great to hear.

Inversion around equilibrium

Basically, Sample should have a built-in function to switch from 1.0 to -1.0. It's sort of "negation" but in reality we're talking about an inversion around the equilibrium point, so it makes sense that it would live in the Sample crate.

Add a `equilibrium_padded()` constructor to each of the interpolators.

Currently all interpolator implementations require being constructed with the initial samples used for inteprolation. In some cases, it might be more convenient to just construct the interpolator with equilibrium values to begin to avoid the need for the user to sample from the source signal.

The `Signal` trait should return `Self::Frame` rather than `Option<Self::Item>`

Currently the Signal trait depends on the Iterator<Item=F> trait (where F is some Frame type).

While this is really convenient in terms of implementation (every Iterator that returns a Frame type automatically implements Signal), the Option around the next method return type does not really make sense.

Most signals are infinite/continuous (e.g. periodic waveforms like sine, square, noise, etc) and the Option is an unnecessary hinderance and in some cases a performance hit. Even for finite-length sample playback, it would probably feel more natural for the signal to return silence rather than None.

I think the crate would benefit from reworking the Signal trait so that it did not require Iterator and instead provided its own next method that returned an associated Frame type.

For convenience, we could could add a signal adaptor type that converted an Iterator<Item=F> into a Signal<Frame=F>, where the None case for the Iterator returns F::equilibrium.

cc @andrewcsmith

Add Benchmarks

It would be extremely useful to have a series of benchmarks for testing and demonstrating the feasibility of the sample crate for users.

Benchmarks that would be nice to have:

  • All sample format conversions.
  • Sample rate conversion speed for a large buffer of frames for each Interpolator.
  • Signal generators - sine, square, saw, noise, noise_walk - test varying frame sizes (mono, stereo, 6x, etc).
  • Adding and multiplying signals.

We should compare the performance of each task to other rust audio libraries that offer similar functionality. It would also be useful to add one or two well-known C/C++ libraries for comparison, though this might significantly complicate setup.

Linear with until_exhausted does not produce enough frames

The last 2 frames are missing:

#[test]
fn test_linear_converter_until_exhausted() {
    let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]];
    let mut source = signal::from_iter(frames.iter().cloned());
    let interp = Linear::from_source(&mut source);
    let conv = Converter::scale_playback_hz(source, interp, 0.5);

    let bar: Vec<_> = conv.until_exhausted().collect();
    assert_eq!(bar, [[0.0], [0.5], [1.0], [1.5], [2.0], [1.0]]);
}

Floor is not affected.

I'm not sure about Sinc but it is probably affected. I'm still trying to figure out how to get the correct initial samples...

Should Interpolator support f32?

Some other audio-related packages that I'm using for my application both only support up to f32 (C's float) when handling data:

Details

On Windows, my pipeline looks like:

  1. Get data from WASAPI
    • If device doesn't support F32, convert to f32 here
  2. Interpolate sample rate from the device rate to 48_000 as required by opus and rnnoise
    • Upscale from f32 -> f64 as required by sample::Interpolator.
    • Downscale back to f32 once done
    • (I'd like to remove these conversion steps^^^, if possible)
  3. Remove noise with rnnoise
  4. Encode with opus
  5. Send that sucker over the internet

This package has been invaluable to me - seeing in easy-to-understand rust an implementation of combining signals, interpolating sample rates and representing how formats look under-the-hood has been awesome! Thanks for your hard work ๐ŸŽ‰

Concatenating Frames

I need to concatenate frames, and obviously doing this in a type-safe way would be pretty cool. Of course it'll be a ton of macro impls.

I'm working on this in a separate crate and will push when ready; just a heads-up that this issue is in-progress.

dasp_window fails to compile with error[E0282]: type annotations needed

dasp_window fails to compile with rustc 1.50.0 / macOS 11.0.1:

error[E0282]: type annotations needed
  --> /Users/jsd/.cargo/registry/src/github.com-1ecc6299db9ec823/dasp_window-0.11.0/src/hanning/mod.rs:25:41
   |
25 |         let v = phase.to_float_sample().to_sample() * PI_2;
   |                                         ^^^^^^^^^ cannot infer type for type parameter `S` declared on the associated function `to_sample`
   |
help: consider specifying the type argument in the method call
   |
25 |         let v = phase.to_float_sample().to_sample::<S>() * PI_2;
   |                                                  ^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.
error: could not compile `dasp_window`

Lots of noise in downsampling from 48KHz to 16KHz

I'm downsampling a 48KHz sample buffer, generated from a hardware input using the cpal crate, to 16KHz for speech-to-text via deepspeech-rs, and I'm getting a lot of noise in the output audio (written out to .WAV via hound). Noisy recording attached: recorded.wav.gz.

I've tested to make sure that the noise is introduced by Converter. If I save out the source audio at 48KHz/f32 or non-resampled at 48KHz/i16 there are no noise artifacts, but 16KHz/f32 or 16KHz/i16 sounds like there's a lot of rounding errors (?). I don't have a lot of experience with DSP, so please be patient with my imprecise explanations.

I have resampled the source audio using Audacity and sox from 48KHz to 16KHz, and neither introduce noise, just the expected decrease in fidelity with reduced sample rate.

Here is a short section of the overall code. It's the callback function given to cpal::device.build_input_stream() that does all the work:

// ... callback passed to cpal like this (much setup code omitted)
let input_sample_rate = config.sample_rate();
device.build_input_stream(
    &config.into(),
    move |data, _| {
        write_resampled_input_data(data, input_sample_rate, &audio_buf_for_input_stream, &writer_2);
    }
);

const SAMPLE_RATE: u32 = 16_000;
type BufHandle = Arc<Mutex<Option<Vec<i16>>>>;
type WavWriterHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>;

fn write_resampled_input_data(input: &[f32], rate: cpal::SampleRate, audio_buf: &BufHandle, writer: &WavWriterHandle)
{
    let samples = input.iter().copied().map(|i| f64::from_sample(i));
    let interpolator = Linear::new([0.0f64], [1.0]);
    let conv = Converter::from_hz_to_hz(
        from_interleaved_samples_iter::<_, [f64; 1]>(samples),
        interpolator,
        rate.0.into(),
        SAMPLE_RATE.into()
    );
    if let Ok(mut guard) = audio_buf.try_lock() {
        if let Some(audio_buf) = guard.as_mut() {
            for sample in conv.until_exhausted().map(|f| f[0]) {
                let sample = i16::from_sample(sample);
                audio_buf.push(sample);
                // WRITE after resampling
                if let Ok(mut guard) = writer.try_lock() {
                    if let Some(writer) = guard.as_mut() {
                        writer.write_sample(sample).ok();
                    }
                }
            }
        }
    }
}

Bug in Sinc Interpolator

I believe I've found a bug in the Sinc interpolate. When calling interpolate with x = 0.0, NaN is returned. Is this intended?

Signed integer conversion overflows

These fail:

assert!((1.0 as f64).to_sample::<i8>() > 0);
assert!((1.0 as f64).to_sample::<i16>() > 0);
assert!((1.0 as f64).to_sample::<i32>() > 0);
assert!((1.0 as f64).to_sample::<i64>() > 0);
assert!((1.0 as f32).to_sample::<i8>() > 0);
assert!((1.0 as f32).to_sample::<i16>() > 0);
assert!((1.0 as f32).to_sample::<i32>() > 0);
assert!((1.0 as f32).to_sample::<i64>() > 0);

(A quick test seems to show that this isn't an issue for i24 and i48, but I wasn't very thorough in checking them.)

The range of i16 is -32,768 to 32,767 (inclusive) and the conversion in conv.rs multiplies by 32,768 when converting floats to i16.
This causes an overflow when the input is 1.0.

Changing the multiplier would fix this problem, but causes a new one:

assert_eq!((-32_768 as i16).to_sample::<f64>().to_sample::<i16>(), -32_768); // Fail.

Changing the multiplier for conversion in the other direction would fix that, but:

assert!((-32_768 as i16).to_sample::<f64>() >= -1.0); // Fail.

I can't think of a nice solution unfortunately; each of these failures will cause a problem for some use case.

Add Window Iterators

After our brief discussion on /r/rust, I thought that now is the time to move my Window functions to your sample crate. I'm having a hard time conceptualizing where to put this though. It seems like sample is focused on the time domain, and (in general) windowing is mostly used in the frequency domain.

Here's a limited, first-steps proposal:

  • Implement Window as a struct implementing Iterator
  • Window will yield Frames, allowing for a trivial impl of Signal
  • Window can be instantiated with a convenience function similar to signal::sine or signal::saw, with an argument of an Iterator that yields a Phase
  • Users can then call mul_amp on their own time to multiply the two signals

This is pretty simple. The catch is that this wouldn't actually implement windowing but only common window functions like Hanning/Hamming/Rectangular/etc. represented as oscillators.

Step two would be to implement a sliding window that (given a bin and hop) yields up slices of Frames. I believe this should be an Iterator called Windower where:

// WARNING: I haven't actually tried to compile any of this. Pseudo.
struct Windower<'a, F> {
    window: Window<F>,
    current_index: usize,
    hop: usize,
    input: &'a [F],
    data: &'a mut [F]
}

impl<'a, T: ToFrameSliceMut<'a, F>, F: Frame> Windower<'a, F> {
    fn new<'a>(type: WindowType, input: &'a [F], data_store: &'a mut [F]) -> Windower<'a, F> {
        // Create a `Window`-type `Signal` as outlined above, taking the `Phase` 
        // frequency from data_store.len()
    }
}

impl<'a, F: Frame> Iterator for Windower<'a, F> {
    type Item = &[F];
    fn next(&mut self) -> Self::Item {
        // update self.data with the product of the window value and input[(self.current_index*self.hop)..(self.current_index*self.hop+self.data.len())]
        // increment current_index by hop. (the bin is inferred from the size of self.data)
        // yield the value of self.data as an immutable reference
    }
}

This way, the Windower would be an iterator that would yield immutable slices of Frames.

Let me know if this looks like an all right implementation. I'm not quite sure what the state of spectral analysis in the sample crate is going to be, but it would be fantastic to at least have many of these functions be generalized over Sample and Frame.

Signals of audio frames of an unknown size (number of channels)

I'm currently writing an audio engine for an exhibition that will run over 100 channels of audio from a single audio server. Much of the audio content played throughout the exhibition will have different numbers of channels. Currently, there isn't really a nice way of working with signals of audio frames in the case where the number of channels is unknown or may vary widely.

It would be nice if the sample crate came with some abstraction for working with these kinds of signals that felt as nice as the Signal trait itself. That said, some of the functionality will inherently be more costly and limited due to the need for comparing numbers of channels at runtime rather than compile time. Some form of dependent types (or maybe integer generics?) could possibly keep this type-safe, but it looks like neither of those will land for a while at least.

I can imagine a trait that might look something like this:

// A signal of interleaved audio samples.
trait InterleavedSamples {
    // The type of samples yielded by the signal.
    type Sample: Sample;
    // An iterator yielding each sample for a single frame.
    type Frame: Iterator<Item=Self::Sample>;
    // The number of channels per frame.
    fn channels(&self) -> usize;
    // Yield the next frame as an iterator.
    fn next(&mut self) -> Self::Frame;

    // Provided methods:
    // - map
    // - zip_map
    // - offset_amp
    // - scale_amp
    // - mul_hz
    // - from_hz_to_hz
    // - scale_hz
    // - delay
    // - clip_amp
    // - inspect
    // - bus
    // - take
    // - by_ref
}

It would be a pity to require this much duplication of the Signal trait... It would be great if we could still somehow use the Signal trait. Perhaps it's possible by revisiting the Frame trait and splitting it into fixed-size and dynamically-sized functionality?

Add an example for working with signals with channels amount determined at runtime

I'm having serious difficulties with making my code work with frames with dynamic amount of channels (i.e. determined at runtime). When audio engine is initialized in my app, the amount of channels I have to work with is reported by the system. I then want to resample the signal, and when I'm trying to work with interleaved samples buffer I have to specify the frame type.
Surely, in theory, it's not required for the amount of channels to be known at compile type as a type parameter - it's enough for the Frame::zip_map to just function.

I'd like to know how I'm supposed to use the currently present API to handle my case. This problem must be very wide-spread, since in the real world we almost never hardcode the apps to work with just a predetermined amount of channels, and I must me missing something really simple.

Sample should have super traits

Few supertrait would be really usefull for Sample in order to use it as generic parameter:

  • Default: would ease container resize and have an initial zero value when using Sample as type bound for a generic parameter, such as buffer.resize(1024, S::default());
  • Add: simplify addition between samples;

Variable Hz object should take Signal

I could be wrong about this, but it seems like we missed a spot when removing the impl Iterator from Signal. Looks to me like the Hz method should take a Signal instead of I: Iterator<Item = f64>.

Instructive Documentation

The documentation doesn't make the reason you'd use each type clear, and leaves it to the user to learn about the purpose (and names) of the different parts of audio processing.

It's not really an issue with the library, but it could be nice to provide a Book-like explanation to users.

Split into several features

Currently, the sample crate has a high number of features, but if you just need a few of them, you need to compile them all.

Would you be open to a PR that defines a number of Cargo features that map to the features described in the README, so that a depending crate can just select the features it actually uses?

What happened to `sample`?

Recently the sample crate was renamed to dasp as a part of an overhaul aimed at improving the crate's modularity. See #120 for further rational.

Also be sure to visit v0.11 at the CHANGELOG for more details on precisely what has changed.

Also, be sure to visit the top-level docs of dasp to get an overview of the new structure. For the most part, the structure is very similar to the original sample structure.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.