mirror of
https://github.com/CKegel/Web-SSTV.git
synced 2025-06-02 09:30:23 +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