pulseplot
Version:
Pulse data viewer JS library
409 lines (362 loc) • 12 kB
JavaScript
/**
@file Pulse Builder JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
/*eslint no-console: "off"*/
import { Bitbuffer } from './bitbuffer.js'
function parsePulseString(pulseStr) {
if (Array.isArray(pulseStr)) {
return pulseStr
}
let pulses = pulseStr.split(/\D/).map(x => parseInt(x, 10))
return pulses
}
export class PulseBuilder {
constructor(args) {
Object.assign(this, args)
this.build()
}
build() {
this.pulses = []
// warmup
if (this.warmup_raw) {
this.addRaw(this, this.warmup_raw)
}
const repeat = this.repeat || 1
for (let j = 0; j < repeat; ++j) {
// packet gap
if (this.gap && j > 0) {
this.pulses.push(0) // mark
this.pulses.push(this.gap) // space
}
// preamble
if (this.preamble) {
const bits = new Bitbuffer(this.preamble)
this.addCode(this, bits)
}
if (this.preamble_raw) {
this.addRaw(this, this.preamble_raw)
}
// sync raw
if (this.sync_raw) {
this.addRaw(this, this.sync_raw)
}
// syncword
if (this.syncword) {
const bits = new Bitbuffer(this.syncword)
this.addCode(this, bits)
}
// payload
const bits = new Bitbuffer(this.payload)
this.addCode(this, bits)
// postamble
if (this.postamble) {
const bits = new Bitbuffer(this.postamble)
this.addCode(this, bits)
}
if (this.postamble_raw) {
this.addRaw(this, this.postamble_raw)
}
}
this.coalesce()
// jitter
if (this.jitter) {
this.addJitter(this)
}
return this
}
addCode(arg, bits) {
if (arg.modulation == 'PCM')
this.buildPCM(arg, bits)
else if (arg.modulation == 'MC')
this.buildMC(arg, bits)
else if (arg.modulation == 'PPM')
this.buildPPM(arg, bits)
else if (arg.modulation == 'PWM')
this.buildPWM(arg, bits)
else if (arg.modulation == 'DM')
this.buildDM(arg, bits)
else if (arg.modulation == 'NRZI')
this.buildNRZI(arg, bits)
else if (arg.modulation == 'CMI')
this.buildCMI(arg, bits)
else if (arg.modulation == 'PIWM')
this.buildPIWM(arg, bits)
else
console.log('Unknown modulation!')
return arg
}
addRaw(arg, pulses) {
pulses = parsePulseString(pulses)
// ensure even (mark/space) count
if (pulses.length % 2)
pulses.push(0)
arg.pulses = arg.pulses.concat(pulses)
return arg
}
coalesce() {
let pulses = []
let pulse = 0
for (let j = 0; j < this.pulses.length; j += 2) {
const m = this.pulses[j] || 0 // mark
const s = this.pulses[j + 1] || 0 // space
if (!m && !s) {
// skip nulls
}
else if (!pulse && !m && pulses.length) {
// no pulse, append to last
pulses[pulses.length - 1] += ~~s
}
else if (!s) {
// no space, buffer
pulse += m
}
else {
pulses.push(~~(m + pulse))
pulses.push(~~(s))
pulse = 0
}
}
// flush buffered pulse
if (pulse) {
pulses.push(~~pulse)
pulses.push(0)
}
this.pulses = pulses
return this
}
addJitter(arg) {
const jitter = arg.jitter
let pulses = []
for (let j = 0; j < arg.pulses.length; ++j) {
const w = arg.pulses[j] // mark or space
if (w == 0) {
pulses.push(w) // keep empty pulses
} else if (jitter < 1) {
// relative jitter
const s = Math.random() * 2 * jitter + 1 - jitter
pulses.push(~~(w * s))
} else {
// absolute jitter
const s = Math.random() * 2 * jitter - jitter
pulses.push(~~(w + s))
}
}
arg.pulses = pulses
return arg
}
// returned hints array contains triples of start,end,symbol
/// Pulse-code modulation (PCM)
/// https://en.wikipedia.org/wiki/Pulse-code_modulation
/// either NRZ or RZ
buildPCM(arg, bits) {
if (!arg.long || arg.long == arg.short) {
return this.buildNRZ(arg, bits)
} else {
return this.buildRZ(arg, bits)
}
}
/// NRZ(L) NRZL Non-return-to-zero level
/// https://en.wikipedia.org/wiki/Non-return-to-zero
buildNRZ(arg, bits) {
// TODO: wip
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
pulses.push(short)
pulses.push(0)
}
else {
pulses.push(0)
pulses.push(short)
}
}
return arg
}
/// Return-to-zero level
/// https://en.wikipedia.org/wiki/Return-to-zero
buildRZ(arg, bits) {
// TODO: wip
const short = arg.short
const long = arg.long
const short_gap = long - short
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
pulses.push(short)
pulses.push(short_gap)
}
else {
pulses.push(0)
pulses.push(long)
}
}
return arg
}
/// Pulse-position modulation (PPM)
/// https://en.wikipedia.org/wiki/Pulse-position_modulation
buildPPM(arg, bits) {
const pulse = arg.pulse || ~~(arg.short / 3)
const short = arg.short
const long = arg.long
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
pulses.push(pulse) // mark
if (bit[j]) {
pulses.push(short) // space
} else {
pulses.push(long) // space
}
}
return arg
}
/// Pulse-width modulation (PWM)
/// https://en.wikipedia.org/wiki/Pulse-width_modulation
buildPWM(arg, bits) {
const short = arg.short
const long = arg.long
const short_gap = arg.short_gap || arg.long
const long_gap = arg.long_gap || arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
pulses.push(short) // mark
pulses.push(short_gap) // space
} else {
pulses.push(long) // mark
pulses.push(long_gap) // space
}
}
return arg
}
/// Manchester code (MC)
/// https://en.wikipedia.org/wiki/Manchester_code
buildMC(arg, bits) {
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
pulses.push(short) // mark
pulses.push(short) // space
} else {
pulses.push(0) // mark
pulses.push(short) // space
pulses.push(short) // mark
pulses.push(0) // space
}
}
return arg
}
/// Differential Manchester Encoding (DM) aka Biphase Mark Code (CC)
/// https://en.wikipedia.org/wiki/Differential_Manchester_encoding
buildDM(arg, bits) {
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
let state = false
for (let j = 0; j < bit.length; ++j) {
const b = bit[j]
if (!b && !state) {
pulses.push(short) // mark
pulses.push(short) // space
} else if (!b && state) {
pulses.push(0) // mark
pulses.push(short) // space
pulses.push(short) // mark
pulses.push(0) // space
} else if (b && !state) {
pulses.push(short * 2) // mark
pulses.push(0) // space
state = !state
} else if (b && state) {
pulses.push(0) // mark
pulses.push(short * 2) // space
state = !state
}
}
return arg
}
/// Non-return-to-zero, inverted (NRZI) https://en.wikipedia.org/wiki/Non-return-to-zero#NRZI
/// NRZ(I) NRZI Non-return-to-zero inverted Refers to either an NRZ(M) or NRZ(S) code.
/// NRZ(M) NRZM Non-return-to-zero mark Serializer mapping {0: constant, 1: toggle}.
/// NRZ(S) NRZS Non-return-to-zero space Serializer mapping {0: toggle, 1: constant}.
/// A 1 is transmitted as a transition, and a 0 is transmitted as no transition.
buildNRZI(arg, bits) {
// TODO: wip
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
let state = !bit[0] // force an edge at start
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
state = !state
}
if (state) {
pulses.push(short) // mark
pulses.push(0) // space
} else {
pulses.push(0) // mark
pulses.push(short) // space
}
}
return arg
}
/// Coded Mark Inversion (CMI) https://en.wikipedia.org/wiki/Coded_mark_inversion
/// encodes zero bits as a half bit time of zero followed by a half bit time of one,
/// and one bits are encoded as a full bit time of a constant level,
/// the level used for one bits alternates each time one is coded.
buildCMI(arg, bits) {
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
let state = false
for (let j = 0; j < bit.length; ++j) {
if (!bit[j]) {
pulses.push(0) // mark
pulses.push(short) // space
pulses.push(short) // mark
pulses.push(0) // space
} else if (!state) {
pulses.push(short * 2) // mark
pulses.push(0) // space
state = !state
} else { // if (state)
pulses.push(0) // mark
pulses.push(short * 2) // space
state = !state
}
}
return arg
}
/// Pulse-Interval-Width Modulation (PIWM)
/// Exotic differential coding
buildPIWM(arg, bits) {
const short = arg.short
let pulses = arg.pulses
const bit = bits.toBitArray()
for (let j = 0; j < bit.length; ++j) {
if (bit[j]) {
pulses.push(short) // mark or space
} else {
pulses.push(short * 2) // mark or space
}
}
// add a long trailing space if the bit count was uneven
if (bit.length % 2) {
pulses.push(short * 3) // space
}
return arg
}
}