1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details.

//! Provides an implementation for a shared feedback buffer for the DSP node graph.
//! It is used for instance by the `FbWr` and `FbRd` nodes to implement their functionality.
//!
//! See also [crate::NodeGlobalData] which provides the [SharedFeedback] to the nodes.

use crate::dsp::MAX_BLOCK_SIZE;
use std::sync::Arc;
use synfx_dsp::AtomicFloat;

pub const FB_DELAY_LENGTH_MS: f32 = 3.14;

/// The SharedFeedback is a feedback delay buffer for the `FbWr` and `FbRd` nodes.
///
/// They have a fixed delay of 3.14ms, which should be equal for all sample rates above 42kHz.
/// Below that the delay might be longer to accomodate the [crate::dsp::MAX_BLOCK_SIZE].
///
/// See also [crate::NodeGlobalData] which provides the [SharedFeedback] to the DSP nodes.
#[derive(Debug, Clone)]
pub struct SharedFeedback {
    buffer: Arc<Vec<AtomicFloat>>,
    delay_sample_count: usize,
}

impl SharedFeedback {
    pub fn new(sample_rate: f32) -> Self {
        let mut buf = vec![];
        let delay_sample_count = ((sample_rate * FB_DELAY_LENGTH_MS) / 1000.0) as usize;

        // Ensure we got at least MAX_BLOCK_SIZE though!
        let delay_sample_count = delay_sample_count.max(MAX_BLOCK_SIZE);

        // Multiply by 3, to make ample space for the FB_DELAY_LENGTH_MS,
        // probably 2*delay_sample_count would be fine too,
        // but I'm anxious about off by one bugs :-)
        buf.resize_with(3 * delay_sample_count, || AtomicFloat::new(0.0));

        Self { buffer: Arc::new(buf), delay_sample_count }
    }
}

/// This instance writes into the [SharedFeedback] buffer.
///
/// Even though it's safe to have multiple writers of this will not work
/// or produce any meaningful results. The goal is really, that one `FbWr` DSP node
/// in the audio thread writes the buffer, and one (or multiple) `FbRd` DSP nodes
/// read from that [SharedFeedback] buffer via a [SharedFeedbackReader].
#[derive(Debug, Clone)]
pub struct SharedFeedbackWriter {
    buffer: Arc<Vec<AtomicFloat>>,
    write_ptr: usize,
    delay_sample_count: usize,
}

impl SharedFeedbackWriter {
    pub fn new(sfb: &SharedFeedback) -> Self {
        let buffer = sfb.buffer.clone();
        Self {
            buffer,
            delay_sample_count: sfb.delay_sample_count,
            write_ptr: sfb.delay_sample_count,
        }
    }

    /// Write the next sample in to the feedback buffer.
    ///
    /// Even though it's safe to have multiple writers of this will not work
    /// or produce any meaningful results. The goal is really, that one `FbWr` DSP node
    /// on the audio thread writing the buffer per buffer iteration. And then one or more
    /// `FbRd` DSP node reading from that buffer.
    pub fn write(&mut self, s: f32) {
        self.buffer[self.write_ptr].set(s);
        self.write_ptr = (self.write_ptr + 1) % self.delay_sample_count;
    }
}

/// A reader for the [SharedFeedback] buffer, used to implement the `FbRd` DSP node.
///
/// Multiple readers are okay, and you can even read from the buffer across the threads.
/// It is sound to read from another thread. But keep in mind, that this is not a ring buffer
/// and you will get partially written buffer contents. There is also only a per sample reading
/// API, that means without the current sample rate you will not know how many samples the 3.14ms
/// buffer is big.
#[derive(Debug, Clone)]
pub struct SharedFeedbackReader {
    buffer: Arc<Vec<AtomicFloat>>,
    read_ptr: usize,
    delay_sample_count: usize,
}

impl SharedFeedbackReader {
    pub fn new(sfb: &SharedFeedback) -> Self {
        Self { buffer: sfb.buffer.clone(), delay_sample_count: sfb.delay_sample_count, read_ptr: 0 }
    }

    /// Read the next sample from the buffer. Wraps around after some internal buffer
    /// size (that is consistent with the [SharedFeedback] buffer size). Used by `FbRd` DSP node
    /// to do it's functionality.
    pub fn read(&mut self) -> f32 {
        let ret = self.buffer[self.read_ptr].get();
        self.read_ptr = (self.read_ptr + 1) % self.delay_sample_count;
        ret
    }
}