]> code.octet-stream.net Git - m17rt/blobdiff - m17core/src/modem.rs
Implement a fair bit of SoftModulator
[m17rt] / m17core / src / modem.rs
index 2f92ba9a7318a01c5fbf45399df50bfad43be476..fa06af6c8f4bfe24855d46096784408871d72156 100644 (file)
@@ -1,7 +1,7 @@
 use crate::decode::{
     parse_lsf, parse_packet, parse_stream, sync_burst_correlation, SyncBurst, SYNC_THRESHOLD,
 };
 use crate::decode::{
     parse_lsf, parse_packet, parse_stream, sync_burst_correlation, SyncBurst, SYNC_THRESHOLD,
 };
-use crate::protocol::Frame;
+use crate::protocol::{Frame, LsfFrame, PacketFrame, StreamFrame};
 use crate::shaping::RRC_48K;
 use log::debug;
 
 use crate::shaping::RRC_48K;
 use log::debug;
 
@@ -158,6 +158,208 @@ impl Default for SoftDemodulator {
     }
 }
 
     }
 }
 
+pub trait Modulator {
+    /// Inform the modulator how many samples remain pending for output and latency updates.
+    ///
+    /// For the buffer between `Modulator` and the process which is supplying samples to the
+    /// output sound card, `samples_to_play` is the number of bytes which the modulator has
+    /// provided that have not yet been picked up, and `capacity` is the maximum size we can
+    /// fill this particular buffer, i.e., maximum number of samples.
+    ///
+    /// Furthermore we attempt to track and account for the latency between the output
+    /// soundcard callback, and when those samples will actually be on the wire. CPAL helpfully
+    /// gives us an estimate. The latest estimate of latency is converted to a duration in terms
+    /// of number of samples and provided as `output_latency`.
+    ///
+    /// Finally, we give the modem a monotonic timer expressed in the number of samples of time
+    /// that have elapsed since the modem began operation. When the modulator advises the TNC
+    /// exactly when a transmission is expected to complete, it should be expressed in terms of
+    /// this number.
+    ///
+    /// Call this whenever bytes have been read out of the buffer, or the TNC may have something
+    /// new to send.
+    fn update_output_buffer(
+        &mut self,
+        samples_to_play: usize,
+        capacity: usize,
+        output_latency: usize,
+        now_samples: u64,
+    );
+
+    /// Supply the next frame available from the TNC, if it was requested.
+    fn next_frame(&mut self, frame: Option<ModulatorFrame>);
+
+    /// Calculate and write out output samples for the soundcard.
+    ///
+    /// Returns the number of bytes valid in `out`. Should generally be called in a loop until
+    /// 0 is returned.
+    fn read_output_samples(&mut self, out: &mut [i16]) -> usize;
+
+    /// Run the modulator and receive actions to process.
+    ///
+    /// Should be called in a loop until it returns `None`.
+    fn run(&mut self) -> Option<ModulatorAction>;
+}
+
+pub enum ModulatorAction {
+    /// If true, once all samples have been exhausted output should revert to equilibrium.
+    ///
+    /// If false, failure to pick up enough samples for output sound card is an underrun error.
+    SetIdle(bool),
+
+    /// Check with the TNC if there is a frame available for transmission.
+    ///
+    /// Call `next_frame()` with either the next frame, or `None` if TNC has nothing more to offer.
+    GetNextFrame,
+
+    /// Modulator wishes to send samples to the output buffer - call `read_output_samples`.
+    ReadOutput,
+
+    /// Advise the TNC that we will complete sending End Of Transmission at the given time and
+    TransmissionWillEnd(u64),
+}
+
+/// Frames for transmission, emitted by the TNC and received by the Modulator.
+///
+/// The TNC is responsible for all timing decisions, making sure these frames are emitted in the
+/// correct order, breaks between transmissions, PTT and CSMA. If the modulator is given a
+/// `ModulatorFrame` value, its job is to transmit it immediately by modulating it into the output
+/// buffer, or otherwise directly after any previously-supplied frames.
+///
+/// The modulator controls the rate at which frames are drawn out of the TNC. Therefore if the send
+/// rate is too high (or there is too much channel activity) then the effect of this backpressure is
+/// that the TNC's internal queues will overflow and it will either discard earlier frames in the
+/// current stream, or some packets awaiting transmission.
+pub enum ModulatorFrame {
+    Preamble {
+        /// TNC's configured TxDelay setting, increments of 10ms.
+        ///
+        /// TNC fires PTT and it's up to modulator to apply the setting, taking advantage of whatever
+        /// buffering already exists in the sound card to reduce the artificial delay.
+        tx_delay: u8,
+    },
+    Lsf(LsfFrame),
+    Stream(StreamFrame),
+    Packet(PacketFrame),
+    // TODO: BertFrame
+    EndOfTransmission,
+}
+
+struct SoftModulator {
+    /// Next modulated frame to output - 1920 samples for 40ms frame plus 80 for ramp-up/ramp-down
+    next_transmission: [i16; 2000],
+    /// How much of next_transmission should in fact be transmitted
+    next_len: usize,
+    /// How much of next_transmission has been read out
+    next_read: usize,
+    /// How many pending zero samples to emit to align start of preamble with PTT taking effect
+    tx_delay_padding: usize,
+
+    /// Do we need to update idle state?
+    update_idle: bool,
+    /// What is that idle status?
+    idle: bool,
+
+    /// Do we need to report that a transmission end time? (True after we encoded an EOT)
+    report_tx_end: bool,
+    /// What will that end time be?
+    tx_end_time: u64,
+
+    /// Circular buffer of incoming samples for calculating the RRC filtered value
+    filter_win: [i16; 81],
+    /// Current position in filter_win
+    filter_cursor: usize,
+
+    /// Should we ask the TNC for another frame. True after each call to update_output_buffer.
+    try_get_frame: bool,
+
+    now: u64,
+    output_latency: usize,
+    samples_in_buf: usize,
+    buf_capacity: usize,
+}
+
+impl Modulator for SoftModulator {
+    fn update_output_buffer(
+        &mut self,
+        samples_to_play: usize,
+        capacity: usize,
+        output_latency: usize,
+        now_samples: u64,
+    ) {
+        self.now = now_samples;
+        self.output_latency = output_latency;
+        self.buf_capacity = capacity;
+        self.samples_in_buf = samples_to_play;
+
+        if capacity - samples_to_play >= 2000 {
+            self.try_get_frame = true;
+        }
+    }
+
+    fn next_frame(&mut self, frame: Option<ModulatorFrame>) {
+        match frame {
+            Some(_f) => {}
+            None => {
+                self.try_get_frame = false;
+            }
+        }
+
+        // this is where we write it to next_transmission
+
+        // this is where we set report_tx_end
+        todo!()
+    }
+
+    fn read_output_samples(&mut self, out: &mut [i16]) -> usize {
+        let mut written = 0;
+
+        // if we have pre-TX padding to accommodate TxDelay then expend that first
+        if self.tx_delay_padding > 0 {
+            let len = out.len().max(self.tx_delay_padding);
+            self.tx_delay_padding -= len;
+            for x in 0..len {
+                out[x] = 0;
+            }
+            written += len;
+        }
+
+        // then follow it with whatever might be left in next_transmission
+        let next_remaining = self.next_len - self.next_read;
+        if next_remaining > 0 {
+            let len = (out.len() - written).max(next_remaining);
+            out[written..(written + len)]
+                .copy_from_slice(&self.next_transmission[self.next_read..(self.next_read + len)]);
+            self.next_read += len;
+            written += len;
+        }
+
+        written
+    }
+
+    fn run(&mut self) -> Option<ModulatorAction> {
+        if self.next_read < self.next_len {
+            return Some(ModulatorAction::ReadOutput);
+        }
+
+        if self.update_idle {
+            self.update_idle = false;
+            return Some(ModulatorAction::SetIdle(self.idle));
+        }
+
+        if self.report_tx_end {
+            self.report_tx_end = false;
+            return Some(ModulatorAction::TransmissionWillEnd(self.tx_end_time));
+        }
+
+        if self.try_get_frame {
+            return Some(ModulatorAction::GetNextFrame);
+        }
+
+        None
+    }
+}
+
 #[derive(Debug)]
 pub(crate) struct DecodeCandidate {
     burst: SyncBurst,
 #[derive(Debug)]
 pub(crate) struct DecodeCandidate {
     burst: SyncBurst,