From a65f3082162e38e4362e99f60c4c1f2427d25b45 Mon Sep 17 00:00:00 2001 From: ckegel <57967583+CKegel@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:48:22 -0400 Subject: [PATCH] Add all PD Modes. --- README.md | 6 +- decode.html | 18 +- encode.html | 13 +- encode.js | 502 ++++++++++++++++++++++-------------------------- index.html | 9 +- learn.html | 2 +- sstv-decoder.js | 35 ++++ style.css | 5 + 8 files changed, 303 insertions(+), 287 deletions(-) create mode 100644 sstv-decoder.js diff --git a/README.md b/README.md index 6e53e82..fc7ac10 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## Summary Web SSTV aims to both encode and decode SSTV using plain JavaScript and Web Audio API. Web SSTV can be run entirely offline (without styling), and on any platform from Chromebooks to phones, so long as they support JavaScript and Web Audio. By making SSTV readily available on many platforms, we aim to create educational opportunities and introduce more people to STEM and amateur radio. Web SSTV is currently hosted at https://ckegel.github.io/Web-SSTV/. ## Current State -Currently Web SSTV only supports encoding images using the Martin or Scottie formats, along with a few PD formats. Support for more formats and recieving SSTV signals is actively being developed. We welcome any pull requests. +Currently Web SSTV supports encoding images using the Martin, Scottie, PD, and WRASSE SC2-180 formats. Support for transmitting in the Robot format and in black and white underway. Decoding has proven to be a greater challenge. I am currently in the process of writing a custom Web Audio Worklet that leverages the Goertzel Algorithm to detect VIS headers and sync pulses. Pull requests are welcome. ## Sources -Both the [SSTV Handbook](https://www.sstv-handbook.com/) and [JL Barber's (N7CXI) paper](http://www.barberdsp.com/downloads/Dayton%20Paper.pdf) were heavily referenced when implementing support for the Martin and Scottie formats. \ No newline at end of file +Both the [SSTV Handbook](https://www.sstv-handbook.com/) and [JL Barber's (N7CXI) Proposal for SSTV Mode Specifications ](http://www.barberdsp.com/downloads/Dayton%20Paper.pdf) were heavily referenced when implementing support for the Martin and Scottie formats. +## License +Web-SSTV is available freely under the MIT license. Should you decide to host your own instance of WebSSTV without substantial modification, please provide a link to this repository and a copy of the MIT license, including the original copyright statement. \ No newline at end of file diff --git a/decode.html b/decode.html index 7f8aa3e..303e1b7 100644 --- a/decode.html +++ b/decode.html @@ -3,7 +3,8 @@ - + + Web-SSTV @@ -17,16 +18,27 @@
  • Learn
  • + +
    -

    Stand by for SSTV decoding (coming soon...)

    +
    +

    Waiting for Audio Feed...

    +
    + +
    +

    SSTV decoding is currently under development and not currently operational. Encountered an issue or unexpected behavior? Create an issue on GitHub.

    +
    + + + \ No newline at end of file diff --git a/encode.html b/encode.html index c6c8a14..2179978 100644 --- a/encode.html +++ b/encode.html @@ -3,7 +3,7 @@ - + Web-SSTV @@ -29,15 +29,22 @@ + + + + + + +
    - +
    @@ -58,7 +65,7 @@
    Check out the project on Github
    - © 2023 Christian Kegel - Available under the MIT License + © Christian Kegel - Available under the MIT License diff --git a/encode.js b/encode.js index 6fec941..1c486e5 100644 --- a/encode.js +++ b/encode.js @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2023 Christian Kegel +Copyright (c) 2024 Christian Kegel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -13,6 +13,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ + //---------- Encoding Constants ----------// const PREFIX_PULSE_LENGTH = 0.1; //100 ms @@ -46,6 +47,12 @@ class Format { this.#VISCode = VISCode; } + getGreyscaleFreq(data, scanLine, vertPos) { + const index = scanLine * (this.#vertResolution * 4) + vertPos * 4; + let grey = data[index] * 0.299 + 0.587 * data[index + 1] + 0.114 * data[index + 2] + return grey * COLOR_FREQ_MULT + 1500 + } + getRGBValueAsFreq(data, scanLine, vertPos) { const index = scanLine * (this.#vertResolution * 4) + vertPos * 4; let red = data[index] * COLOR_FREQ_MULT + 1500; @@ -160,9 +167,49 @@ class Format { } //---------- Format Encode Implementation ----------// +class MartinBase extends Format { + prepareImage(data) { + let preparedImage = []; + for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ + let red = []; + let green = []; + let blue = []; + for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ + let freqs = this.getRGBValueAsFreq(data, scanLine, vertPos); + red.push(freqs[0]); + green.push(freqs[1]); + blue.push(freqs[2]); + } + preparedImage.push([green, blue, red]); + } -class MartinMOne extends Format { + super.prepareImage(preparedImage); + } + encodeSSTV(oscillator, startTime) { + let time = startTime; + + time = super.encodePrefix(oscillator, time); + time = super.encodeHeader(oscillator, time); + + for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ + oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); + time += super.syncPulseLength; + oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); + time += super.blankingInterval; + for(let dataLine = 0; dataLine < 3; ++dataLine) { + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); + time += super.scanLineLength; + oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); + time += super.blankingInterval; + } + } + + oscillator.start(startTime); + oscillator.stop(time); + } +} +class MartinMOne extends MartinBase { constructor() { let numScanLines = 256; let vertResolution = 320; @@ -173,50 +220,8 @@ class MartinMOne extends Format { super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } - - prepareImage(data) { - let preparedImage = []; - for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ - let red = []; - let green = []; - let blue = []; - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let freqs = this.getRGBValueAsFreq(data, scanLine, vertPos); - red.push(freqs[0]); - green.push(freqs[1]); - blue.push(freqs[2]); - } - preparedImage.push([green, blue, red]); - } - - super.prepareImage(preparedImage); - } - - encodeSSTV(oscillator, startTime) { - let time = startTime; - - time = super.encodePrefix(oscillator, time); - time = super.encodeHeader(oscillator, time); - - for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - for(let dataLine = 0; dataLine < 3; ++dataLine) { - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - } - } - - oscillator.start(startTime); - oscillator.stop(time); - } } -class MartinMTwo extends Format { - +class MartinMTwo extends MartinBase { constructor() { let numScanLines = 256; let vertResolution = 320; @@ -227,7 +232,9 @@ class MartinMTwo extends Format { super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } +} +class ScottieBase extends Format { prepareImage(data) { let preparedImage = []; for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ @@ -252,16 +259,19 @@ class MartinMTwo extends Format { time = super.encodePrefix(oscillator, time); time = super.encodeHeader(oscillator, time); + oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); + time += super.syncPulseLength; + for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; for(let dataLine = 0; dataLine < 3; ++dataLine) { - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); - time += super.scanLineLength; + if(dataLine == 2){ + oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); + time += super.syncPulseLength; + } oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); time += super.blankingInterval; + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); + time += super.scanLineLength; } } @@ -269,8 +279,7 @@ class MartinMTwo extends Format { oscillator.stop(time); } } -class ScottieOne extends Format { - +class ScottieOne extends ScottieBase { constructor() { let numScanLines = 256; let vertResolution = 320; @@ -281,53 +290,8 @@ class ScottieOne extends Format { super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } - - prepareImage(data) { - let preparedImage = []; - for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ - let red = []; - let green = []; - let blue = []; - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let freqs = this.getRGBValueAsFreq(data, scanLine, vertPos); - red.push(freqs[0]); - green.push(freqs[1]); - blue.push(freqs[2]); - } - preparedImage.push([green, blue, red]); - } - - super.prepareImage(preparedImage); - } - - encodeSSTV(oscillator, startTime) { - let time = startTime; - - time = super.encodePrefix(oscillator, time); - time = super.encodeHeader(oscillator, time); - - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - - for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ - for(let dataLine = 0; dataLine < 3; ++dataLine) { - if(dataLine == 2){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - } - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); - time += super.scanLineLength; - } - } - - oscillator.start(startTime); - oscillator.stop(time); - } } class ScottieTwo extends Format { - constructor() { let numScanLines = 256; let vertResolution = 320; @@ -338,53 +302,8 @@ class ScottieTwo extends Format { super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } - - prepareImage(data) { - let preparedImage = []; - for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ - let red = []; - let green = []; - let blue = []; - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let freqs = this.getRGBValueAsFreq(data, scanLine, vertPos); - red.push(freqs[0]); - green.push(freqs[1]); - blue.push(freqs[2]); - } - preparedImage.push([green, blue, red]); - } - - super.prepareImage(preparedImage); - } - - encodeSSTV(oscillator, startTime) { - let time = startTime; - - time = super.encodePrefix(oscillator, time); - time = super.encodeHeader(oscillator, time); - - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - - for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ - for(let dataLine = 0; dataLine < 3; ++dataLine) { - if(dataLine == 2){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - } - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); - time += super.scanLineLength; - } - } - - oscillator.start(startTime); - oscillator.stop(time); - } } class ScottieDX extends Format { - constructor() { let numScanLines = 256; let vertResolution = 320; @@ -395,7 +314,146 @@ class ScottieDX extends Format { super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } +} +class PDBase extends Format { + prepareImage(data) { + let preparedImage = []; + for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ + let Y = []; + let RY = []; + let BY = []; + for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ + let freqs = this.getYRYBYValueAsFreq(data, scanLine, vertPos); + Y.push(freqs[0]); + RY.push(freqs[1]); + BY.push(freqs[2]); + } + preparedImage.push([Y, RY, BY]); + } + for(let scanLine = 0; scanLine < this.numScanLines; scanLine += 2){ + for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ + let RY = preparedImage[scanLine][1][vertPos] + preparedImage[scanLine + 1][1][vertPos] + preparedImage[scanLine][1][vertPos] = RY / 2; + let BY = preparedImage[scanLine][2][vertPos] + preparedImage[scanLine + 1][2][vertPos] + preparedImage[scanLine][2][vertPos] = BY / 2; + } + } + super.prepareImage(preparedImage); + } + + encodeSSTV(oscillator, startTime) { + let time = startTime; + + time = super.encodePrefix(oscillator, time); + time = super.encodeHeader(oscillator, time); + + for(let scanLine = 0; scanLine < super.numScanLines; scanLine += 2){ + oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); + time += super.syncPulseLength; + oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); + time += super.blankingInterval; + + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][0], time, super.scanLineLength); + time += super.scanLineLength; + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][1], time, super.scanLineLength); + time += super.scanLineLength; + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][2], time, super.scanLineLength); + time += super.scanLineLength; + oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine + 1][0], time, super.scanLineLength); + time += super.scanLineLength; + } + + oscillator.start(startTime); + oscillator.stop(time); + } +} +class PD50 extends PDBase { + constructor() { + let numScanLines = 256; + let vertResolution = 320; + let blankingInterval = 0.00208; + let scanLineLength = 0.091520; + let syncPulseLength = 0.02; + let VISCode = [true, false, true, true, true, false, true]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD90 extends PDBase { + constructor() { + let numScanLines = 256; + let vertResolution = 320; + let blankingInterval = 0.00208; + let scanLineLength = 0.170240; + let syncPulseLength = 0.02; + let VISCode = [true, true, false, false, false, true, true]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD120 extends PDBase { + constructor() { + let numScanLines = 496; + let vertResolution = 640; + let blankingInterval = 0.00208; + let scanLineLength = 0.121600; + let syncPulseLength = 0.02; + let VISCode = [true, false, true, true, true, true, true]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD160 extends PDBase { + constructor() { + let numScanLines = 400; + let vertResolution = 512; + let blankingInterval = 0.00208; + let scanLineLength = 0.195584; + let syncPulseLength = 0.02; + let VISCode = [true, true, false, false, true, false, false]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD180 extends PDBase { + constructor() { + let numScanLines = 496; + let vertResolution = 640; + let blankingInterval = 0.00208; + let scanLineLength = 0.18304; + let syncPulseLength = 0.02; + let VISCode = [true, true, false, false, false, false, false]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD240 extends PDBase { + constructor() { + let numScanLines = 496; + let vertResolution = 640; + let blankingInterval = 0.00208; + let scanLineLength = 0.24448; + let syncPulseLength = 0.02; + let VISCode = [true, true, false, false, false, false, true]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} +class PD290 extends PDBase { + constructor() { + let numScanLines = 616; + let vertResolution = 800; + let blankingInterval = 0.00208; + let scanLineLength = 0.2288; + let syncPulseLength = 0.02; + let VISCode = [true, false, true, true, true, true, false]; + + super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); + } +} + +class WrasseSC2 extends Format { prepareImage(data) { let preparedImage = []; for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ @@ -408,7 +466,7 @@ class ScottieDX extends Format { green.push(freqs[1]); blue.push(freqs[2]); } - preparedImage.push([green, blue, red]); + preparedImage.push([red, green, blue]); } super.prepareImage(preparedImage); @@ -420,17 +478,12 @@ class ScottieDX extends Format { time = super.encodePrefix(oscillator, time); time = super.encodeHeader(oscillator, time); - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - for(let scanLine = 0; scanLine < super.numScanLines; ++scanLine){ + oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); + time += super.syncPulseLength; + oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); + time += super.blankingInterval; for(let dataLine = 0; dataLine < 3; ++dataLine) { - if(dataLine == 2){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - } - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][dataLine], time, super.scanLineLength); time += super.scanLineLength; } @@ -440,133 +493,17 @@ class ScottieDX extends Format { oscillator.stop(time); } } -class PD50 extends Format { - +class WrasseSC2180 extends WrasseSC2 { constructor() { let numScanLines = 256; let vertResolution = 320; - let blankingInterval = 0.00208; - let scanLineLength = 0.091520; - let syncPulseLength = 0.02; - let VISCode = [true, false, true, true, true, false, true]; + let blankingInterval = 0.0005; + let scanLineLength = 0.235; + let syncPulseLength = 0.0055225; + let VISCode = [false, true, true, false, true, true, true]; super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); } - - prepareImage(data) { - let preparedImage = []; - for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ - let Y = []; - let RY = []; - let BY = []; - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let freqs = this.getYRYBYValueAsFreq(data, scanLine, vertPos); - Y.push(freqs[0]); - RY.push(freqs[1]); - BY.push(freqs[2]); - } - preparedImage.push([Y, RY, BY]); - } - for(let scanLine = 0; scanLine < this.numScanLines; scanLine += 2){ - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let RY = preparedImage[scanLine][1][vertPos] + preparedImage[scanLine + 1][1][vertPos] - preparedImage[scanLine][1][vertPos] = RY / 2; - let BY = preparedImage[scanLine][2][vertPos] + preparedImage[scanLine + 1][2][vertPos] - preparedImage[scanLine][2][vertPos] = BY / 2; - } - } - super.prepareImage(preparedImage); - } - - encodeSSTV(oscillator, startTime) { - let time = startTime; - - time = super.encodePrefix(oscillator, time); - time = super.encodeHeader(oscillator, time); - - for(let scanLine = 0; scanLine < super.numScanLines; scanLine += 2){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][0], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][1], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][2], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine + 1][0], time, super.scanLineLength); - time += super.scanLineLength; - } - - oscillator.start(startTime); - oscillator.stop(time); - } -} -class PD90 extends Format { - - constructor() { - let numScanLines = 256; - let vertResolution = 320; - let blankingInterval = 0.00208; - let scanLineLength = 0.170240; - let syncPulseLength = 0.02; - let VISCode = [true, true, false, false, false, true, true]; - - super(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode); - } - - prepareImage(data) { - let preparedImage = []; - for(let scanLine = 0; scanLine < this.numScanLines; ++scanLine){ - let Y = []; - let RY = []; - let BY = []; - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let freqs = this.getYRYBYValueAsFreq(data, scanLine, vertPos); - Y.push(freqs[0]); - RY.push(freqs[1]); - BY.push(freqs[2]); - } - preparedImage.push([Y, RY, BY]); - } - for(let scanLine = 0; scanLine < this.numScanLines; scanLine += 2){ - for(let vertPos = 0; vertPos < this.vertResolution; ++vertPos){ - let RY = preparedImage[scanLine][1][vertPos] + preparedImage[scanLine + 1][1][vertPos] - preparedImage[scanLine][1][vertPos] = RY / 2; - let BY = preparedImage[scanLine][2][vertPos] + preparedImage[scanLine + 1][2][vertPos] - preparedImage[scanLine][2][vertPos] = BY / 2; - } - } - super.prepareImage(preparedImage); - } - - encodeSSTV(oscillator, startTime) { - let time = startTime; - - time = super.encodePrefix(oscillator, time); - time = super.encodeHeader(oscillator, time); - - for(let scanLine = 0; scanLine < super.numScanLines; scanLine += 2){ - oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time); - time += super.syncPulseLength; - oscillator.frequency.setValueAtTime(BLANKING_PULSE_FREQ, time); - time += super.blankingInterval; - - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][0], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][1], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine][2], time, super.scanLineLength); - time += super.scanLineLength; - oscillator.frequency.setValueCurveAtTime(super.preparedImage[scanLine + 1][0], time, super.scanLineLength); - time += super.scanLineLength; - } - - oscillator.start(startTime); - oscillator.stop(time); - } } //---------- Frontend Controls ----------// @@ -648,6 +585,23 @@ modeSelect.addEventListener("change", (e) => { sstvFormat = new PD50(); else if(modeSelect.value == "PD90") sstvFormat = new PD90(); + else if(modeSelect.value == "PD120") + sstvFormat = new PD120(); + else if(modeSelect.value == "PD160") + sstvFormat = new PD160(); + else if(modeSelect.value == "PD180") + sstvFormat = new PD180(); + else if(modeSelect.value == "PD240") + sstvFormat = new PD240(); + else if(modeSelect.value == "PD290") + sstvFormat = new PD290(); + else if(modeSelect.value == "RobotBW8") + sstvFormat = new RobotBW8(); + else if(modeSelect.value == "WrasseSC2180") + sstvFormat = new WrasseSC2180(); + + if(imageLoaded) + drawPreview(); }); startButton.onclick = () => { diff --git a/index.html b/index.html index bcd72c6..72bd227 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + Web-SSTV @@ -14,7 +14,7 @@ @@ -22,7 +22,8 @@

    About:

    Web SSTV aims to both encode and decode SSTV using plain JavaScript and Web Audio API. Web SSTV can be run entirely offline (without styling), and on any platform from Chromebooks to phones, so long as they support JavaScript and Web Audio. By making SSTV readily available on many platforms, we aim to create educational opportunities and introduce more people to STEM and amateur radio.

    Changelog:

    -

    October 2024 - Restructured the site in preperation for decoding. Added the ability to mark images with the users call sign.

    +

    October 16, 2024 - Added all PD Modes, patched several usability issues, and updated the styling.

    +

    October 13, 2024 - Restructured the site in preperation for decoding. Added the ability to mark images with the users call sign.

    December 2023 - Initial site published via Github pages.

    \ No newline at end of file diff --git a/learn.html b/learn.html index ee44416..de204ef 100644 --- a/learn.html +++ b/learn.html @@ -3,7 +3,7 @@ - + Web-SSTV diff --git a/sstv-decoder.js b/sstv-decoder.js new file mode 100644 index 0000000..99f0b70 --- /dev/null +++ b/sstv-decoder.js @@ -0,0 +1,35 @@ +/* +MIT License + +Copyright (c) 2024 Christian Kegel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +*/ +class SSTVDecoder extends AudioWorkletProcessor { + leaderToneDetected = false; + sstvFormat; + + constructor(){ + super(); + } + + process(inputs, outputs, parameters){ + //Apply the Goertzel algorithm (more performant/save battery) to idenify the VIS Header, but use FFT (for now) + + //Detect a mode and load appropriate parameters + + //Apply Goertzel algorithm to recognize sync pulses, and call `onLineRecieved` Callback with freq data + //Note: We don't have to live decode the freq data, just collect it between sync pulses and measure the time to see if we missed a sync + return true; + } + +} +registerProcessor('sstv-decoder', SSTVDecoder); \ No newline at end of file diff --git a/style.css b/style.css index a0170da..990493e 100644 --- a/style.css +++ b/style.css @@ -6,6 +6,11 @@ color: red; } +#previewArea { + padding: 10px; + background: black; +} + .rounded { border-radius: 10px; }