-
Notifications
You must be signed in to change notification settings - Fork 27
/
signal_generator.rs
211 lines (183 loc) · 6.17 KB
/
signal_generator.rs
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
use miniconf::{Leaf, Tree};
use rand_core::{RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
/// Types of signals that can be generated.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub enum Signal {
Cosine,
Square,
Triangle,
WhiteNoise,
}
/// Basic configuration for a generated signal.
///
/// # Miniconf
/// `{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}`
///
/// Where `<signal>` may be any of [Signal] variants, `frequency` specifies the signal frequency
/// in Hertz, `symmetry` specifies the normalized signal symmetry which ranges from 0 - 1.0, and
/// `amplitude` specifies the signal amplitude in Volts.
#[derive(Copy, Clone, Debug, Tree, Serialize, Deserialize)]
pub struct BasicConfig {
/// The signal type that should be generated. See [Signal] variants.
pub signal: Leaf<Signal>,
/// The frequency of the generated signal in Hertz.
pub frequency: Leaf<f32>,
/// The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal.
/// At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this
/// symmetry is the duty cycle.
pub symmetry: Leaf<f32>,
/// The amplitude of the output signal in volts.
pub amplitude: Leaf<f32>,
/// The phase of the output signal in turns.
pub phase: Leaf<f32>,
}
impl Default for BasicConfig {
fn default() -> Self {
Self {
frequency: 1.0e3.into(),
symmetry: 0.5.into(),
signal: Signal::Cosine.into(),
amplitude: 0.0.into(),
phase: 0.0.into(),
}
}
}
/// Represents the errors that can occur when attempting to configure the signal generator.
#[derive(Copy, Clone, Debug)]
pub enum Error {
/// The provided amplitude is out-of-range.
InvalidAmplitude,
/// The provided symmetry is out of range.
InvalidSymmetry,
/// The provided frequency is out of range.
InvalidFrequency,
}
impl BasicConfig {
/// Convert configuration into signal generator values.
///
/// # Args
/// * `sample_period` - The time in seconds between samples.
/// * `full_scale` - The full scale output voltage.
pub fn try_into_config(
self,
sample_period: f32,
full_scale: f32,
) -> Result<Config, Error> {
let symmetry_complement = 1.0 - *self.symmetry;
// Validate symmetry
if *self.symmetry < 0.0 || symmetry_complement < 0.0 {
return Err(Error::InvalidSymmetry);
}
const NYQUIST: f32 = (1u32 << 31) as _;
let ftw = *self.frequency * sample_period * NYQUIST;
// Validate base frequency tuning word to be below Nyquist.
if ftw < 0.0 || 2.0 * ftw > NYQUIST {
return Err(Error::InvalidFrequency);
}
// Calculate the frequency tuning words.
// Clip both frequency tuning words to within Nyquist before rounding.
let phase_increment = [
if *self.symmetry * NYQUIST > ftw {
ftw / *self.symmetry
} else {
NYQUIST
} as i32,
if symmetry_complement * NYQUIST > ftw {
ftw / symmetry_complement
} else {
NYQUIST
} as i32,
];
let amplitude = *self.amplitude * (i16::MIN as f32 / -full_scale);
if !(i16::MIN as f32..=i16::MAX as f32).contains(&litude) {
return Err(Error::InvalidAmplitude);
}
let phase = *self.phase * (1u64 << 32) as f32;
Ok(Config {
amplitude: amplitude as i16,
signal: *self.signal,
phase_increment,
phase_offset: phase as i32,
})
}
}
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// The type of signal being generated
pub signal: Signal,
/// The full-scale output code of the signal
pub amplitude: i16,
/// The frequency tuning word of the signal. Phase is incremented by this amount
pub phase_increment: [i32; 2],
/// The phase offset
pub phase_offset: i32,
}
impl Default for Config {
fn default() -> Self {
Self {
signal: Signal::Cosine,
amplitude: 0,
phase_increment: [0, 0],
phase_offset: 0,
}
}
}
#[derive(Debug)]
pub struct SignalGenerator {
phase_accumulator: i32,
config: Config,
rng: XorShiftRng,
}
impl SignalGenerator {
/// Construct a new signal generator with some specific config.
///
/// # Args
/// * `config` - The config to use for generating signals.
///
/// # Returns
/// The generator
pub fn new(config: Config) -> Self {
Self {
config,
phase_accumulator: 0,
rng: XorShiftRng::from_seed([0; 16]), // zeros will initialize with XorShiftRng internal seed
}
}
/// Update waveform generation settings.
pub fn update_waveform(&mut self, new_config: Config) {
self.config = new_config;
}
/// Clear the phase accumulator.
pub fn clear_phase_accumulator(&mut self) {
self.phase_accumulator = 0;
}
}
impl core::iter::Iterator for SignalGenerator {
type Item = i16;
/// Get the next value in the generator sequence.
fn next(&mut self) -> Option<i16> {
let phase = self
.phase_accumulator
.wrapping_add(self.config.phase_offset);
let sign = phase.is_negative();
self.phase_accumulator = self
.phase_accumulator
.wrapping_add(self.config.phase_increment[sign as usize]);
let scale = match self.config.signal {
Signal::Cosine => idsp::cossin(phase).0 >> 16,
Signal::Square => {
if sign {
i16::MIN as i32
} else {
-(i16::MIN as i32)
}
}
Signal::Triangle => i16::MIN as i32 + (phase >> 15).abs(),
Signal::WhiteNoise => self.rng.next_u32() as i32 >> 16,
};
// Calculate the final output result as an i16.
Some(((self.config.amplitude as i32 * scale) >> 15) as _)
}
}