mirror of
https://github.com/CKegel/Web-SSTV.git
synced 2025-06-05 19:15:34 +00:00
initial commit
This commit is contained in:
commit
b14b4624fc
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
39
index.html
Normal file
39
index.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
<title>Web-SSTV</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<h1>Web SSTV</h1>
|
||||||
|
<h3>Send SSTV signals from your browser</h3>
|
||||||
|
<select id="modeSelect">
|
||||||
|
<option value="none" selected disabled hidden>Select a mode</option>
|
||||||
|
<option value="M1">Martin M1</option>
|
||||||
|
<option value="M2">Martin M2</option>
|
||||||
|
<option value="S1">Scottie 1</option>
|
||||||
|
<option value="S2">Scottie 2</option>
|
||||||
|
<option value="SDX">Scottie DX</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label id="imgLabel" for="imgPicker">Select an Image</label>
|
||||||
|
<input type="file" name="imgPicker" id="imgPicker"/>
|
||||||
|
<center id="imgArea" class="container">
|
||||||
|
<canvas id="imgCanvas" width="320" height="256"></canvas>
|
||||||
|
<br>
|
||||||
|
<span id="warningText"></span>
|
||||||
|
</center>
|
||||||
|
<button id="startButton" class="contrast" disabled>Encode</button>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer class="container">
|
||||||
|
<p>Currently supports encoding (sending) in Martin and Scottie formats. SSTV signal decoding and more encoding modes coming soon.</p>
|
||||||
|
<p>© 2023 Christian Kegel - Available under the MIT License</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</html>
|
508
script.js
Normal file
508
script.js
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 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.
|
||||||
|
*/
|
||||||
|
//---------- Encoding Constants ----------//
|
||||||
|
|
||||||
|
const PREFIX_PULSE_LENGTH = 0.1; //100 ms
|
||||||
|
const HEADER_PULSE_LENGTH = 0.3; //300 ms
|
||||||
|
const HEADER_BREAK_LENGTH = 0.01; //10 ms
|
||||||
|
const VIS_BIT_LENGTH = 0.03; //30 ms
|
||||||
|
const SYNC_PULSE_FREQ = 1200;
|
||||||
|
const BLANKING_PULSE_FREQ = 1500;
|
||||||
|
const VIS_BIT_FREQ = {
|
||||||
|
ONE: 1100,
|
||||||
|
ZERO: 1300,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Format {
|
||||||
|
|
||||||
|
#numScanLines;
|
||||||
|
#vertResolution;
|
||||||
|
#blankingInterval;
|
||||||
|
#scanLineLength;
|
||||||
|
#syncPulseLength;
|
||||||
|
#VISCode;
|
||||||
|
#preparedImage = [];
|
||||||
|
|
||||||
|
constructor(numScanLines, vertResolution, blankingInterval, scanLineLength, syncPulseLength, VISCode) {
|
||||||
|
this.#numScanLines = numScanLines;
|
||||||
|
this.#vertResolution = vertResolution;
|
||||||
|
this.#blankingInterval = blankingInterval;
|
||||||
|
this.#scanLineLength = scanLineLength;
|
||||||
|
this.#syncPulseLength = syncPulseLength;
|
||||||
|
this.#VISCode = VISCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRGBValueAsFreq(data, scanLine, vertPos) {
|
||||||
|
const index = scanLine * (this.#vertResolution * 4) + vertPos * 4;
|
||||||
|
let red = data[index] * 3.137 + 1500;
|
||||||
|
let green = data[index + 1] * 3.137 + 1500;
|
||||||
|
let blue = data[index + 2] * 3.137 + 1500;
|
||||||
|
return [red, green, blue];
|
||||||
|
}
|
||||||
|
|
||||||
|
encodePrefix(oscillator, startTime) {
|
||||||
|
let time = startTime;
|
||||||
|
|
||||||
|
oscillator.frequency.setValueAtTime(1900, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1500, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1900, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1500, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(2300, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1500, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(2300, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1500, time);
|
||||||
|
time += PREFIX_PULSE_LENGTH;
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeHeader(oscillator, startTime) {
|
||||||
|
let time = startTime;
|
||||||
|
|
||||||
|
//----- Format Header -----//
|
||||||
|
oscillator.frequency.setValueAtTime(1900, time);
|
||||||
|
time += HEADER_PULSE_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time);
|
||||||
|
time += HEADER_BREAK_LENGTH;
|
||||||
|
oscillator.frequency.setValueAtTime(1900, time);
|
||||||
|
time += HEADER_PULSE_LENGTH;
|
||||||
|
|
||||||
|
//-----VIS Code-----//
|
||||||
|
//--- Start Bit ---//
|
||||||
|
oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time);
|
||||||
|
time += VIS_BIT_LENGTH;
|
||||||
|
//--- 7 Bit Format Code ---//
|
||||||
|
let parity = 0;
|
||||||
|
let bitFreq;
|
||||||
|
this.#VISCode.reverse().forEach((bit) => {
|
||||||
|
if(bit){
|
||||||
|
bitFreq = VIS_BIT_FREQ.ONE;
|
||||||
|
++parity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
bitFreq = VIS_BIT_FREQ.ZERO;
|
||||||
|
oscillator.frequency.setValueAtTime(bitFreq, time)
|
||||||
|
time += VIS_BIT_LENGTH;
|
||||||
|
});
|
||||||
|
//--- Even Parity Bit ---//
|
||||||
|
bitFreq = parity % 2 == 0 ? VIS_BIT_FREQ.ZERO : VIS_BIT_FREQ.ONE;
|
||||||
|
oscillator.frequency.setValueAtTime(bitFreq, time)
|
||||||
|
time += VIS_BIT_LENGTH;
|
||||||
|
//--- End Bit ---//
|
||||||
|
oscillator.frequency.setValueAtTime(SYNC_PULSE_FREQ, time);
|
||||||
|
time += VIS_BIT_LENGTH;
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareImage(data) {
|
||||||
|
this.#preparedImage = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeSSTV(oscillator, startTime) {
|
||||||
|
throw new Error("Must be defined by a subclass");
|
||||||
|
}
|
||||||
|
|
||||||
|
get numScanLines() {
|
||||||
|
return this.#numScanLines;
|
||||||
|
}
|
||||||
|
get vertResolution() {
|
||||||
|
return this.#vertResolution;
|
||||||
|
}
|
||||||
|
get blankingInterval() {
|
||||||
|
return this.#blankingInterval;
|
||||||
|
}
|
||||||
|
get scanLineLength() {
|
||||||
|
return this.#scanLineLength;
|
||||||
|
}
|
||||||
|
get syncPulseLength() {
|
||||||
|
return this.#syncPulseLength;
|
||||||
|
}
|
||||||
|
get VISCode() {
|
||||||
|
return this.#VISCode;
|
||||||
|
}
|
||||||
|
get preparedImage(){
|
||||||
|
return this.#preparedImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------- Format Encode Implementation ----------//
|
||||||
|
|
||||||
|
class MartinMOne extends Format {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let numScanLines = 256;
|
||||||
|
let vertResolution = 320;
|
||||||
|
let blankingInterval = 0.000572;
|
||||||
|
let scanLineLength = 0.146432;
|
||||||
|
let syncPulseLength = 0.004862;
|
||||||
|
let VISCode = [false, true, false, true, true, false, false];
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let numScanLines = 256;
|
||||||
|
let vertResolution = 320;
|
||||||
|
let blankingInterval = 0.000572;
|
||||||
|
let scanLineLength = 0.073216;
|
||||||
|
let syncPulseLength = 0.004862;
|
||||||
|
let VISCode = [false, true, false, true, false, false, false];
|
||||||
|
|
||||||
|
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 ScottieOne extends Format {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let numScanLines = 256;
|
||||||
|
let vertResolution = 320;
|
||||||
|
let blankingInterval = 0.0015;
|
||||||
|
let scanLineLength = 0.138240;
|
||||||
|
let syncPulseLength = 0.009;
|
||||||
|
let VISCode = [false, true, true, true, true, false, false];
|
||||||
|
|
||||||
|
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;
|
||||||
|
let blankingInterval = 0.0015;
|
||||||
|
let scanLineLength = 0.088064;
|
||||||
|
let syncPulseLength = 0.009;
|
||||||
|
let VISCode = [false, true, true, true, false, false, false];
|
||||||
|
|
||||||
|
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;
|
||||||
|
let blankingInterval = 0.0015;
|
||||||
|
let scanLineLength = 0.3456;
|
||||||
|
let syncPulseLength = 0.009;
|
||||||
|
let VISCode = [true, false, false, true, true, false, false];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------- Frontend Controls ----------//
|
||||||
|
|
||||||
|
const audioCtx = new AudioContext();
|
||||||
|
let imageLoaded = false;
|
||||||
|
|
||||||
|
let modeSelect = document.getElementById("modeSelect")
|
||||||
|
let startButton = document.getElementById("startButton");
|
||||||
|
let imgPicker = document.getElementById("imgPicker");
|
||||||
|
let warningText = document.getElementById("warningText");
|
||||||
|
|
||||||
|
let canvas = document.getElementById("imgCanvas");
|
||||||
|
let canvasCtx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
imgPicker.addEventListener("change", (e) => {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(event){
|
||||||
|
var img = new Image();
|
||||||
|
img.onload = function(){
|
||||||
|
canvas.width = 320;
|
||||||
|
canvas.height = 256;
|
||||||
|
canvasCtx.drawImage(img,0,0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
img.src = event.target.result;
|
||||||
|
if(modeSelect.value != "none"){
|
||||||
|
warningText.textContent = "";
|
||||||
|
startButton.disabled = false;
|
||||||
|
imageLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(e.target.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
modeSelect.addEventListener("change", (e) => {
|
||||||
|
if(modeSelect.value != "none"){
|
||||||
|
warningText.textContent = "";
|
||||||
|
startButton.disabled = !imageLoaded;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
startButton.onclick = () => {
|
||||||
|
let format;
|
||||||
|
if(modeSelect.value == "M1")
|
||||||
|
format = new MartinMOne();
|
||||||
|
else if(modeSelect.value == "M2")
|
||||||
|
format = new MartinMTwo();
|
||||||
|
else if(modeSelect.value == "S1")
|
||||||
|
format = new ScottieOne();
|
||||||
|
else if(modeSelect.value == "S2")
|
||||||
|
format = new ScottieTwo();
|
||||||
|
else if(modeSelect.value == "SDX")
|
||||||
|
format = new ScottieDX();
|
||||||
|
else {
|
||||||
|
warningText.textContent = "You must select a mode";
|
||||||
|
startButton.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!imageLoaded){
|
||||||
|
warningText.textContent = "You must upload an image";
|
||||||
|
startButton.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvasData = canvasCtx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
warningText.textContent = "";
|
||||||
|
if (audioCtx.state === "suspended") {
|
||||||
|
audioCtx.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
let oscillator = audioCtx.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
|
||||||
|
oscillator.connect(audioCtx.destination);
|
||||||
|
|
||||||
|
format.prepareImage(canvasData.data);
|
||||||
|
format.encodeSSTV(oscillator, audioCtx.currentTime + 1);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user