rustaudio / dasp Goto Github PK
View Code? Open in Web Editor NEWThe fundamentals for Digital Audio Signal Processing. Formerly `sample`.
License: Other
The fundamentals for Digital Audio Signal Processing. Formerly `sample`.
License: Other
I might be vastly underestimating the work and complexity involved in this, but it would be an excellent feature to have and is undoubtably a fundamental tool of DSP.
I could find three current pure-rust fourier transform implementations that might be useful for reference:
The Fourier Transform Wikipedia
Circles, Sines and Signals - An awesome explanation on how DFT works.
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:
Rc
https://github.com/RustAudio/sample/blob/4a4c679268605e539342f52b702a21189534526a/src/signal.rs#L581Vec
and VecDeque
https://github.com/RustAudio/sample/blob/4a4c679268605e539342f52b702a21189534526a/src/signal.rs#L590Rc
https://github.com/RustAudio/sample/blob/4a4c679268605e539342f52b702a21189534526a/src/signal.rs#L601It'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.
They already live in lib.rs
, and can be accessed as sample::Float
and sample::Signed
.
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.
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!
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.
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");
}
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!
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.
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.
See the trigint library (an integer based trigonometry library) for inspiration.
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
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
.
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:
delay
to use a ring buffer, and move the current implementation to a lazy_delay
method?Delay
generic over usize
(a number of frames) and ring_buffer::Fixed
(buffered delay).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.
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.
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!
Whoops. PR on the way.
also I try dasp_interpolate, and i got
use dasp_interpolate::linear::Linear;
| ^^^^^^ could not find linear
in dasp_interpolate
The linear interpolation used by rate::Converter
is fast but not loses information, acting as a poor low-pass filter for many conversions. It would be useful to implement more interpolation types so that a user has more control over the performance/quality trade-off.
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!
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;
| ^^^^^
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.
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.
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.
This library uses the wrong name for the common Hann
window , a common mistake due to the simillar sounding Hamming
window. See https://en.wikipedia.org/wiki/Hann_function
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
.
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:
Interpolator
.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.
Right now it only takes a slice, which unfortunately means that it can't handle windowing continuous streams.
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...
Some other audio-related packages that I'm using for my application both only support up to f32
(C's float
) when handling data:
On Windows, my pipeline looks like:
F32
, convert to f32
here48_000
as required by opus
and rnnoise
f32
-> f64
as required by sample::Interpolator
.f32
once donernnoise
opus
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 ๐
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 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`
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();
}
}
}
}
}
}
This would just match the rest of the library a little better.
Should there be variants of sample::[Sample,Frame]::[add_amp,mul_amp] which use saturating arithmetic?
I believe I've found a bug in the Sinc
interpolate. When calling interpolate with x
= 0.0, NaN
is returned. Is this intended?
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.
These should likely be opt-in features, as they are unneeded in the general case.
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:
Window
as a struct
implementing Iterator
Window
will yield Frame
s, 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
mul_amp
on their own time to multiply the two signalsThis 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 Frame
s. 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 Frame
s.
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
.
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?
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.
Few supertrait would be really usefull for Sample in order to use it as generic parameter:
buffer.resize(1024, S::default())
;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>
.
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.
This will allow for compile-time multi-channel genericness and remove the need for a lot of dynamic checks.
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?
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.
I can't find any docs for the Ring Buffer implementation on your github.io site. But if I generate the docs locally with cargo doc
they're included.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.