use crate::{
flux::{flux_revolution::FluxRevolution, FluxStats, FluxTransition},
format_ms,
format_us,
DiskDataEncoding,
DiskDataRate,
};
use bit_vec::BitVec;
use std::io::Write;
const BASE_CLOCK: f64 = 2e-6;
const SHORT_TRANSITION: f64 = 4.0e-6; const MEDIUM_TRANSITION: f64 = 6.0e-6; const LONG_TRANSITION: f64 = 8.0e-6; const TOLERANCE: f64 = 0.5e-6;
pub struct PllDecodeStatEntry {
pub time: f64,
pub len: f64,
pub predicted: f64,
pub clk: f64,
pub window_min: f64,
pub window_max: f64,
pub phase_err: f64,
pub phase_err_i: f64,
}
#[allow(dead_code)]
pub enum PllPreset {
Aggressive,
Conservative,
}
pub struct PllDecodeResult {
pub transitions: Vec<FluxTransition>,
pub bits: BitVec,
pub flux_stats: FluxStats,
pub pll_stats: Vec<PllDecodeStatEntry>,
pub markers: Vec<usize>,
}
pub struct Pll {
pub pll_default_rate: f64,
pub pll_rate: f64,
pub pll_period: f64,
pub working_period: f64,
pub period_factor: f64,
pub max_adjust: f64,
pub density_factor: f64,
pub clock_gain: f64,
pub phase_gain: f64,
}
impl Pll {
pub fn new() -> Self {
Pll {
pll_default_rate: u32::from(DiskDataRate::Rate250Kbps(1.0)) as f64 * 2.0,
pll_rate: u32::from(DiskDataRate::Rate250Kbps(1.0)) as f64 * 2.0,
pll_period: BASE_CLOCK, working_period: BASE_CLOCK,
period_factor: 1.0,
max_adjust: 0.15, density_factor: 2.0,
clock_gain: 0.05,
phase_gain: 0.65,
}
}
pub fn from_preset(preset: PllPreset) -> Pll {
match preset {
PllPreset::Aggressive => Pll::new(),
PllPreset::Conservative => Pll::new(),
}
}
pub fn set_clock(&mut self, rate: f64, max_adj: Option<f64>) {
self.pll_default_rate = rate;
self.pll_rate = rate;
self.pll_period = 1.0 / rate;
self.working_period = self.pll_period;
if let Some(adj) = max_adj {
self.max_adjust = adj;
}
assert!(self.pll_rate > 1.0);
log::debug!(
"Pll::set_clock(): Setting clock rate to {:.2}, max adjust: {:.2} new period: {}",
self.pll_rate,
self.max_adjust,
format_us!(self.pll_period)
);
}
pub fn get_clock(&mut self) -> f64 {
self.pll_rate
}
pub fn reset_clock(&mut self) {
self.pll_rate = self.pll_default_rate;
self.pll_period = 1.0 / self.pll_rate;
self.working_period = self.pll_period;
log::debug!(
"Pll::reset_clock(): Resetting clock to default rate: {} period: {}",
self.pll_rate,
format_us!(self.pll_period)
);
}
pub fn adjust_clock(&mut self, factor: f64) {
let old_rate = self.pll_rate;
self.pll_rate *= factor;
self.pll_period = 1.0 / self.pll_rate;
self.working_period = self.pll_period;
log::debug!(
"Pll::adjust_clock(): Adjusting clock by {:.4}% factor: {:.4} old: {:.4} new: {:.4} period: {}",
factor * 100.0,
factor,
old_rate,
self.pll_rate,
format_us!(self.pll_period)
);
}
#[allow(dead_code)]
pub fn decode_transitions(&mut self, stream: &FluxRevolution) -> Vec<FluxTransition> {
let mut transitions = Vec::new();
let mut valid_deltas = 0;
let mut delta_avg = 0.0;
for t in stream.delta_iter() {
if *t > 0.0 {
delta_avg += *t;
valid_deltas += 1;
}
let transition = self.classify_transition(*t);
transitions.push(transition);
}
let delta_avg = delta_avg / valid_deltas as f64;
let other_ct = transitions.iter().filter(|t| **t == FluxTransition::Other).count();
log::warn!(
"Pll::decode_transitions(): {} avg transition time, {} unclassified transitions",
delta_avg,
other_ct
);
transitions
}
#[allow(dead_code)]
pub fn classify_transition(&self, duration: f64) -> FluxTransition {
if (duration - SHORT_TRANSITION).abs() <= TOLERANCE {
FluxTransition::Short
}
else if (duration - MEDIUM_TRANSITION).abs() <= TOLERANCE {
FluxTransition::Medium
}
else if (duration - LONG_TRANSITION).abs() <= TOLERANCE {
FluxTransition::Long
}
else {
FluxTransition::Other
}
}
pub fn decode(&mut self, stream: &FluxRevolution, encoding: DiskDataEncoding) -> PllDecodeResult {
match encoding {
DiskDataEncoding::Mfm => self.decode_mfm(stream),
DiskDataEncoding::Fm => self.decode_fm(stream),
_ => {
log::error!("Unsupported encoding: {:?}", encoding);
self.decode_mfm(stream)
}
}
}
fn decode_mfm(&mut self, stream: &FluxRevolution) -> PllDecodeResult {
let mut output_bits = BitVec::with_capacity(stream.flux_deltas.len() * 3);
let mut error_bits = BitVec::with_capacity(stream.flux_deltas.len() * 3);
let mut pll_stats = Vec::with_capacity(stream.flux_deltas.len());
let mut phase_error: f64 = 0.0;
let mut phase_adjust: f64 = 0.0;
let mut transitions = Vec::new();
let mut this_flux_time;
let mut time = self.pll_period / 2.0;
let mut last_flux_time = 0.0;
let mut clock_ticks: u64 = 0;
let mut clock_ticks_since_flux: u64 = 0;
let mut shift_reg: u64 = 0;
let mut markers = Vec::new();
let mut zero_ct = 0;
let min_clock = self.working_period - (self.working_period * self.max_adjust);
let max_clock = self.working_period + (self.working_period * self.max_adjust);
self.working_period = self.pll_period;
let p_term = self.pll_period * self.phase_gain;
let mut flux_stats = FluxStats {
total: stream.flux_deltas.len() as u32,
..FluxStats::default()
};
let mut last_bit = false;
let mut adjust_gate: i32 = 0;
for (flux_ct, &delta_time) in stream.delta_iter().enumerate() {
flux_stats.shortest_flux = delta_time.min(flux_stats.shortest_flux);
flux_stats.longest_flux = delta_time.max(flux_stats.longest_flux);
if flux_ct == 0 {
flux_stats.shortest_flux = delta_time;
log::debug!(
"decode_mfm(): first flux transition: {} @({})",
format_us!(delta_time),
format_ms!(time)
);
}
this_flux_time = last_flux_time + delta_time;
time += phase_adjust;
while time < this_flux_time {
time += self.working_period;
clock_ticks_since_flux += 1;
clock_ticks += 1;
}
let flux_length = clock_ticks_since_flux;
if flux_length < 2 {
flux_stats.too_short += 1;
}
else if flux_length > 4 {
log::trace!(
"decode_mfm(): Too slow flux detected: #{} @({}), dt: {}, clocks: {}",
flux_ct,
format_ms!(time),
delta_time,
clock_ticks_since_flux,
);
flux_stats.too_long += 1;
flux_stats.too_slow_bits += (flux_length - 4) as u32;
}
match flux_length {
2 => {
flux_stats.short_time += delta_time;
flux_stats.short += 1;
transitions.push(FluxTransition::Short);
}
3 => {
flux_stats.medium += 1;
transitions.push(FluxTransition::Medium);
}
4 => {
flux_stats.long += 1;
transitions.push(FluxTransition::Long);
}
_ => {}
}
if flux_length > 0 {
for _ in 0..flux_length - 1 {
zero_ct += 1;
output_bits.push(false);
last_bit = false;
shift_reg <<= 1;
error_bits.push(zero_ct > 3);
}
zero_ct = 0;
error_bits.push(last_bit);
output_bits.push(true);
last_bit = true;
shift_reg <<= 1;
shift_reg |= 1;
}
if shift_reg & 0xFFFF_FFFF_FFFF_0000 == 0x4489_4489_4489_0000 {
log::trace!(
"decode_mfm(): Marker detected at {}, bitcell: {}",
format_ms!(time),
flux_ct - 64
);
markers.push(output_bits.len() - 64);
}
if zero_ct > 16 {
}
let predicted_flux_time =
last_flux_time + phase_adjust + (clock_ticks_since_flux as f64 * self.working_period);
let window_max = (time - this_flux_time) + delta_time;
let window_min = window_max - self.working_period;
let window_center = window_max - self.working_period / 2.0;
let last_phase_error = phase_error;
phase_error = delta_time - window_center;
if phase_error < 0.0 {
if adjust_gate < 0 {
adjust_gate -= 1;
}
else {
adjust_gate = -1;
}
}
else if phase_error >= 0.0 {
if adjust_gate > 0 {
adjust_gate += 1;
}
else {
adjust_gate = 1;
}
}
let phase_delta_error = phase_error - last_phase_error;
let min_phase_error = if phase_error.abs() < last_phase_error.abs() {
phase_error
}
else {
last_phase_error
};
pll_stats.push(PllDecodeStatEntry {
time,
len: delta_time,
predicted: window_min + phase_adjust,
clk: self.working_period,
window_min,
window_max,
phase_err: phase_error,
phase_err_i: phase_adjust,
});
phase_adjust = 0.65 * min_phase_error;
if flux_ct == 0 {
log::debug!(
"decode_mfm(): first phase error: {} @({:.9})",
format_us!(phase_error),
time
);
}
let mut clk_adjust = 0.0;
if adjust_gate.abs() > 1 {
clk_adjust = 0.05 * phase_error;
}
self.working_period += clk_adjust;
self.working_period = self.working_period.clamp(min_clock, max_clock);
clock_ticks_since_flux = 0;
last_flux_time = this_flux_time;
}
_ = std::io::stdout().flush();
log::debug!(
"decode_mfm(): Completed decoding of MFM flux stream. Total clocks: {} markers: {} FT stats: {}",
clock_ticks,
markers.len(),
flux_stats
);
PllDecodeResult {
transitions,
bits: output_bits,
flux_stats,
pll_stats,
markers,
}
}
fn decode_fm(&mut self, stream: &FluxRevolution) -> PllDecodeResult {
let mut output_bits = BitVec::with_capacity(stream.flux_deltas.len() * 3);
let pll_stats = Vec::with_capacity(stream.flux_deltas.len());
let mut phase_accumulator: f64 = 0.0;
let mut last_flux_time = 0.0;
let mut next_flux_time;
self.working_period = self.pll_period * 2.0;
let min_clock = self.working_period - (self.working_period * self.max_adjust);
let max_clock = self.working_period + (self.working_period * self.max_adjust);
let mut time = -self.working_period / 2.0;
let mut clock_ticks: u64 = 0;
let mut clock_ticks_since_flux: u64 = 0;
let mut shift_reg: u64 = 0;
let mut markers = Vec::new();
log::debug!(
"decode_fm(): normal period: {} working period: {} min: {} max: {}",
format_us!(self.pll_period),
format_us!(self.working_period),
format_us!(min_clock),
format_us!(max_clock)
);
let mut flux_stats = FluxStats {
total: stream.flux_deltas.len() as u32,
..FluxStats::default()
};
for (flux_ct, &delta_time) in stream.delta_iter().enumerate() {
flux_stats.shortest_flux = delta_time.min(flux_stats.shortest_flux);
flux_stats.longest_flux = delta_time.max(flux_stats.longest_flux);
if flux_ct == 0 {
flux_stats.shortest_flux = delta_time;
log::debug!("first flux transition: {} @({:.9})", format_us!(delta_time), time);
}
next_flux_time = last_flux_time + delta_time;
while (time + phase_accumulator) < next_flux_time {
time += self.working_period;
clock_ticks_since_flux += 1;
clock_ticks += 1;
}
time += phase_accumulator;
phase_accumulator = 0.0;
let flux_length = clock_ticks_since_flux;
log::trace!("flux length: {}", flux_length);
match flux_length {
0 => {
flux_stats.too_short += 1;
}
1 => {
flux_stats.short_time += delta_time;
flux_stats.short += 1;
}
2 => {
flux_stats.long += 1;
}
_ => {
flux_stats.too_long += 1;
flux_stats.too_slow_bits += (flux_length - 4) as u32;
}
}
if flux_length == 0 {
}
else {
for _ in 0..flux_length.saturating_sub(1) {
output_bits.push(false);
shift_reg <<= 1;
}
output_bits.push(true);
shift_reg <<= 1;
shift_reg |= 1;
}
if shift_reg & 0xAAAA_AAAA_AAAA_AAAA == 0xAAAA_AAAA_AAAA_A02A {
log::debug!(
"decode_fm(): Marker detected at {}, bitcell: {}",
format_ms!(time),
flux_ct - 16
);
markers.push(output_bits.len() - 16);
}
let predicted_flux_time = last_flux_time + (clock_ticks_since_flux as f64 * self.working_period);
let phase_error = next_flux_time - predicted_flux_time;
let p_term = (self.phase_gain * phase_error) / self.working_period;
self.working_period += p_term;
self.working_period = self.working_period.clamp(min_clock, max_clock);
phase_accumulator += phase_error;
if phase_accumulator.abs() > self.working_period {
phase_accumulator %= self.working_period;
}
clock_ticks_since_flux = 0;
last_flux_time = next_flux_time;
}
log::debug!(
"Completed decoding of FM flux stream. Total clocks: {} FT stats: {}",
clock_ticks,
flux_stats
);
PllDecodeResult {
transitions: Vec::new(),
bits: output_bits,
flux_stats,
pll_stats,
markers,
}
}
}