2 SYNC_THRESHOLD
, SyncBurst
, parse_lsf
, parse_packet
, parse_stream
, sync_burst_correlation
,
5 encode_lsf
, encode_packet
, encode_stream
, generate_end_of_transmission
, generate_preamble
,
7 use crate::protocol
::{Frame
, LsfFrame
, PacketFrame
, StreamFrame
};
8 use crate::shaping
::RRC_48K
;
11 pub trait Demodulator
{
12 /// Handle the next sample.
14 /// If a frame can be decoded, return it, along with an indication of many errors were fixed by FEC.
15 fn demod(&mut self, sample
: i16) -> Option
<(Frame
, u8)>;
16 /// Does somebody else appear to be transmitting at the moment?
17 fn data_carrier_detect(&self) -> bool
;
20 /// Converts a sequence of samples into frames.
21 pub struct SoftDemodulator
{
22 /// Circular buffer of incoming samples for calculating the RRC filtered value
23 filter_win
: [i16; 81],
24 /// Current position in filter_win
26 /// Circular buffer of shaped samples for performing decodes based on the last 192 symbols
28 /// Current position in rx_cursor
30 /// A position that we are considering decoding due to decent sync
31 candidate
: Option
<DecodeCandidate
>,
32 /// How many samples have we received?
34 /// Remaining samples to read in before attempting to decode the current candidate
35 samples_until_decode
: Option
<u16>,
36 /// Do we think there is a data carrier, i.e., channel in use? If so, at what sample does it expire?
40 impl SoftDemodulator
{
41 pub fn new() -> Self {
43 filter_win
: [0i16; 81],
49 samples_until_decode
: None
,
55 impl SoftDemodulator
{
56 fn dcd_until(&mut self, end_sample
: u64) {
57 if self.dcd
.is
_none
() {
58 debug
!("SoftDemodulator DCD on");
60 self.dcd
= Some(end_sample
);
63 fn check_dcd(&mut self) {
64 if let Some(end_sample
) = self.dcd
{
65 if self.sample
> end_sample
{
67 debug
!("SoftDemodulator DCD off");
73 impl Demodulator
for SoftDemodulator
{
74 fn demod(&mut self, sample
: i16) -> Option
<(Frame
, u8)> {
75 self.filter
_w
in
[self.filter
_c
ursor
] = sample
;
76 self.filter
_c
ursor
= (self.filter
_c
ursor
+ 1) % 81;
77 let mut out
: f32 = 0.0;
79 let filter_idx
= (self.filter
_c
ursor
+ i
) % 81;
80 out
+= RRC_48K
[i
] * self.filter
_w
in
[filter_idx
] as f32;
83 self.rx_win
[self.rx_cursor
] = out
;
84 self.rx_cursor
= (self.rx_cursor
+ 1) % 1920;
89 if let Some(samples_until_decode
) = self.samples_until_decode
{
90 let sud
= samples_until_decode
- 1;
92 self.samples_until_decode
= Some(sud
);
95 self.samples_until_decode
= None
;
97 if let Some(c
) = self.candidate
.take() {
98 // we have capacity for 192 symbols * 10 upsamples
99 // we have calculated that the ideal sample point for 192nd symbol is right on the edge
100 // so take samples from the 10th slot all the way through.
101 let start_idx
= self.rx_cursor
+ 1920 + 9;
102 let mut pkt_samples
= [0f32; 192];
104 let rx_idx
= (start_idx
+ i
* 10) % 1920;
105 pkt_samples
[i
] = (self.rx_win
[rx_idx
] - c
.shift
) / c
.gain
;
109 if let Some((frame
, errors
)) = parse_lsf(&pkt_samples
) {
110 return Some((Frame
::Lsf(frame
), errors
));
116 SyncBurst
::Stream
=> {
117 if let Some((frame
, errors
)) = parse_stream(&pkt_samples
) {
118 return Some((Frame
::Stream(frame
), errors
));
121 SyncBurst
::Packet
=> {
122 if let Some((frame
, errors
)) = parse_packet(&pkt_samples
) {
123 return Some((Frame
::Packet(frame
), errors
));
126 SyncBurst
::Preamble
| SyncBurst
::EndOfTransmission
=> {
127 // should never be chosen as a candidate
133 let mut burst_window
= [0f32; 8];
135 let c
= (self.rx_cursor
+ 1920 - 1 - ((7 - i
) * 10)) % 1920;
136 burst_window
[i
] = self.rx_win
[c
];
139 for burst
in [SyncBurst
::Preamble
, SyncBurst
::EndOfTransmission
] {
140 let (diff
, _
, _
) = sync_burst_correlation(burst
.target(), &burst_window
);
141 if diff
< SYNC_THRESHOLD
{
142 // arbitrary choice, 240 samples = 5ms
143 // these bursts keep repeating so it will keep pushing out the DCD end time
144 self.dcd_until(self.sample
+ 240);
154 let (diff
, max
, shift
) = sync_burst_correlation(burst
.target(), &burst_window
);
155 if diff
< SYNC_THRESHOLD
{
156 let mut new_candidate
= true;
157 if let Some(c
) = self.candidate
.as_mut() {
160 new_candidate
= false;
164 self.candidate
= Some(DecodeCandidate
{
173 if diff
>= SYNC_THRESHOLD
177 .map(|c
| c
.burst
== burst
)
180 // wait until the rest of the frame is in the buffer
181 let c
= self.candidate
.as_ref().unwrap
();
182 self.samples_until_decode
= Some((184 * 10) - (c
.age
as u16));
184 "Found {:?} at sample {} diff {}",
186 self.sample
- c
.age
as u64,
189 // After any of these frame types you would expect to see a full EOT
190 self.dcd_until(self.sample
+ 1920 + 1920);
197 fn data_carrier_detect(&self) -> bool
{
202 impl Default
for SoftDemodulator
{
203 fn default() -> Self {
208 pub trait Modulator
{
209 /// Inform the modulator how many samples remain pending for output and latency updates.
211 /// For the buffer between `Modulator` and the process which is supplying samples to the
212 /// output sound card, `samples_to_play` is the number of bytes which the modulator has
213 /// provided that have not yet been picked up, and `capacity` is the maximum size we can
214 /// fill this particular buffer, i.e., maximum number of samples.
216 /// Furthermore we attempt to track and account for the latency between the output
217 /// soundcard callback, and when those samples will actually be on the wire. CPAL helpfully
218 /// gives us an estimate. The latest estimate of latency is converted to a duration in terms
219 /// of number of samples and provided as `output_latency`. Added to this is the current
220 /// number of samples we expect remain to be processed from the last read.
222 /// Call this whenever bytes have been read out of the buffer.
223 fn update_output_buffer(
225 samples_to_play
: usize,
227 output_latency
: usize,
230 /// Supply the next frame available from the TNC, if it was requested.
231 fn provide_next_frame(&mut self, frame
: Option
<ModulatorFrame
>);
233 /// Calculate and write out output samples for the soundcard.
235 /// Returns the number of bytes valid in `out`. Should generally be called in a loop until
237 fn read_output_samples(&mut self, out
: &mut [i16]) -> usize;
239 /// Run the modulator and receive actions to process.
241 /// Should be called in a loop until it returns `None`.
242 fn run(&mut self) -> Option
<ModulatorAction
>;
245 pub enum ModulatorAction
{
246 /// If true, once all samples have been exhausted output should revert to equilibrium.
248 /// If false, failure to pick up enough samples for output sound card is an underrun error.
251 /// Check with the TNC if there is a frame available for transmission.
253 /// Call `next_frame()` with either the next frame, or `None` if TNC has nothing more to offer.
256 /// Modulator wishes to send samples to the output buffer - call `read_output_samples`.
259 /// Advise the TNC that we will complete sending End Of Transmission after the given number of
260 /// samples has elapsed, and therefore PTT should be deasserted at this time.
261 TransmissionWillEnd(usize),
264 /// Frames for transmission, emitted by the TNC and received by the Modulator.
266 /// The TNC is responsible for all timing decisions, making sure these frames are emitted in the
267 /// correct order, breaks between transmissions, PTT and CSMA. If the modulator is given a
268 /// `ModulatorFrame` value, its job is to transmit it immediately by modulating it into the output
269 /// buffer, or otherwise directly after any previously-supplied frames.
271 /// The modulator controls the rate at which frames are drawn out of the TNC. Therefore if the send
272 /// rate is too high (or there is too much channel activity) then the effect of this backpressure is
273 /// that the TNC's internal queues will overflow and it will either discard earlier frames in the
274 /// current stream, or some packets awaiting transmission.
275 pub enum ModulatorFrame
{
277 /// TNC's configured TxDelay setting, increments of 10ms.
279 /// TNC fires PTT and it's up to modulator to apply the setting, taking advantage of whatever
280 /// buffering already exists in the sound card to reduce the artificial delay.
290 pub struct SoftModulator
{
291 // TODO: 2000 was overflowing around EOT, track down why
292 /// Next modulated frame to output - 1920 samples for 40ms frame plus 80 for ramp-down
293 next_transmission
: [i16; 4000],
294 /// How much of next_transmission should in fact be transmitted
296 /// How much of next_transmission has been read out
298 /// How many pending zero samples to emit to align start of preamble with PTT taking effect
299 tx_delay_padding
: usize,
301 /// Do we need to update idle state?
303 /// What is that idle status?
306 /// Do we need to calculate a transmission end time?
308 /// (True after we encoded an EOT.) We will wait until we get a precise timing update.
309 calculate_tx_end
: bool
,
310 /// Do we need to report a transmission end time?
312 /// This is a duration expressed in number of samples.
313 report_tx_end
: Option
<usize>,
315 /// Circular buffer of most recently output samples for calculating the RRC filtered value.
317 /// This should naturally degrade to an oldest value plus 80 zeroes after an EOT.
318 filter_win
: [f32; 81],
319 /// Current position in filter_win
320 filter_cursor
: usize,
322 /// Should we ask the TNC for another frame. True after each call to update_output_buffer.
325 /// Expected delay beyond the buffer to reach the DAC
326 output_latency
: usize,
327 /// Number of samples we have placed in the buffer for the output soundcard not yet picked up.
328 samples_in_buf
: usize,
329 /// Total size to which the output buffer is allowed to expand.
334 pub fn new() -> Self {
336 next_transmission
: [0i16; 4000],
340 // TODO: actually set this to false when we are worried about underrun
343 calculate_tx_end
: false,
345 filter_win
: [0f32; 81],
347 try_get_frame
: false,
354 fn push_sample(&mut self, dibit
: f32) {
355 // TODO: 48 kHz assumption again
357 // Right now we are encoding everything as 1.0-scaled dibit floats
358 // This is a bit silly but it will do for a minute
359 // Max possible gain from the RRC filter with upsampling is about 0.462
360 // Let's bump everything to a baseline of 16383 / 0.462 = 35461
361 // For normal signals this yields roughly 0.5 magnitude which is plenty
363 self.filter
_w
in
[self.filter
_c
ursor
] = dibit
* 35461.0;
365 self.filter
_w
in
[self.filter
_c
ursor
] = 0.0;
367 self.filter
_c
ursor
= (self.filter
_c
ursor
+ 1) % 81;
368 let mut out
: f32 = 0.0;
370 let filter_idx
= (self.filter
_c
ursor
+ i
) % 81;
371 out
+= RRC_48K
[i
] * self.filter
_w
in
[filter_idx
];
373 self.next_transmission
[self.next_len
] = out
as i16;
378 fn request_frame_if_space(&mut self) {
379 if self.buf_capacity
- self.samples_in_buf
>= 2000 {
380 self.try_get_frame
= true;
385 impl Modulator
for SoftModulator
{
386 fn update_output_buffer(
388 samples_to_play
: usize,
390 output_latency
: usize,
392 self.output_latency
= output_latency
;
393 self.buf_capacity
= capacity
;
394 self.samples_in_buf
= samples_to_play
;
396 if self.calculate_tx_end
{
397 self.calculate_tx_end
= false;
398 // next_transmission should already have been read out to the buffer by now
399 // so we don't have to consider it
400 self.report_tx_end
= Some(self.samples_in_buf
+ self.output_latency
);
403 self.request_frame_if_space();
406 fn provide_next_frame(&mut self, frame
: Option
<ModulatorFrame
>) {
407 let Some(frame
) = frame
else {
408 self.try_get_frame
= false;
416 ModulatorFrame
::Preamble
{ tx_delay
} => {
417 // TODO: Stop assuming 48 kHz everywhere. 24 kHz should be fine too.
418 let tx_delay_samples
= tx_delay
as usize * 480;
419 // Our output latency gives us a certain amount of unavoidable TxDelay
420 // So only introduce artificial delay if the requested TxDelay exceeds that
421 self.tx_delay_padding
= tx_delay_samples
.saturating_sub(self.output_latency
);
423 // We should be starting from a filter_win of zeroes
424 // Transmission is effectively smeared by 80 taps and we'll capture that in EOT
425 for dibit
in generate_preamble() {
426 self.push_sample(dibit
);
429 ModulatorFrame
::Lsf(lsf_frame
) => {
430 for dibit
in encode_lsf(&lsf_frame
) {
431 self.push_sample(dibit
);
434 ModulatorFrame
::Stream(stream_frame
) => {
435 for dibit
in encode_stream(&stream_frame
) {
436 self.push_sample(dibit
);
439 ModulatorFrame
::Packet(packet_frame
) => {
440 for dibit
in encode_packet(&packet_frame
) {
441 self.push_sample(dibit
);
444 ModulatorFrame
::EndOfTransmission
=> {
445 for dibit
in generate_end_of_transmission() {
446 self.push_sample(dibit
);
449 // This is not a real symbol value
450 // However we want to flush the filter
451 self.push_sample(0f32);
453 self.calculate_tx_end
= true;
458 fn read_output_samples(&mut self, out
: &mut [i16]) -> usize {
461 // if we have pre-TX padding to accommodate TxDelay then expend that first
462 if self.tx_delay_padding
> 0 {
463 let len
= out
.len().min(self.tx_delay_padding
);
464 self.tx_delay_padding
-= len
;
471 // then follow it with whatever might be left in next_transmission
472 let next_remaining
= self.next_len
- self.next_read
;
473 if next_remaining
> 0 {
474 let len
= (out
.len() - written
).min(next_remaining
);
475 out
[written
..(written
+ len
)]
476 .copy_from_slice(&self.next_transmission
[self.next_read
..(self.next_read
+ len
)]);
477 self.next_read
+= len
;
484 fn run(&mut self) -> Option
<ModulatorAction
> {
485 // Time-sensitive for accuracy, so handle it first
486 if let Some(end
) = self.report_tx_end
.take() {
487 return Some(ModulatorAction
::TransmissionWillEnd(end
));
490 if self.next_read
< self.next_len
{
491 return Some(ModulatorAction
::ReadOutput
);
494 if self.update
_idle
{
495 self.update
_idle
= false;
496 return Some(ModulatorAction
::SetIdle(self.idle
));
499 if self.try_get_frame
{
500 return Some(ModulatorAction
::GetNextFrame
);
507 impl Default
for SoftModulator
{
508 fn default() -> Self {
514 pub(crate) struct DecodeCandidate
{