123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- var RTTY = RTTY || {};
-
- var RTTY = function(props) {
-
- this.mark = props.mark;
- this.space = props.space;
- this.baudrate = props.baudrate;
-
- this.bufferEmptyCallback = null;
- this.characterDoneCallback = null;
-
- this.fadeDoneCallback = null;
-
- this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
-
- this.osc = 0;
- this.freq = this.mark;
-
- this.buffer = [];
- this.bufferOffset = 0;
-
- this.idleBits = 0;
-
- this.bit = 8;
- this.ct = {};
- this.figsmask = 1<<5;
- this.currentchar = 0x00;
- this.pause = false;
- this.suspend = false;
- this.source = null;
- this.figures = true;
- this.unshift = false;
- this.idle = true;
- this.bufferEmptyCalled = false;
- this.flush = null;
- this.amplitude = 1;
- this.fade = 0;
-
- this.initTables();
-
- this.scriptNode = this.audioCtx.createScriptProcessor(4096, 1, 1);
-
- this.scriptNode.onaudioprocess = (function(audioProcessingEvent) {
- var inputBuffer = audioProcessingEvent.inputBuffer;
- var outputBuffer = audioProcessingEvent.outputBuffer;
- var inputData = inputBuffer.getChannelData(0);
- var outputData = outputBuffer.getChannelData(0);
- for (var sample = 0; sample < inputBuffer.length; sample++) {
- if (!this.suspend && inputData[sample]==1) { // bit marker
- if (this.idleBits > 0) {
- this.idleBits--;
- if (this.idleBits==0) {
- if (this.characterDoneCallback != null && typeof this.characterDoneCallback == "function") {
- this.characterDoneCallback(bufferLen);
- }
- this.idle = true;
- }
- } else if (this.pause && this.idle) {
- this.suspend = true;
- this.pause = false;
- } else if (this.idle) {
- if (this.flush != null) {
- this.buffer = [];
- this.bufferOffset = 0;
- if (typeof this.flush == "function") {
- this.flush();
- }
- this.flush = null;
- }
- var bufferLen = this.buffer.length - this.bufferOffset;
- if (bufferLen > 0) {
- this.idle = false;
- if (this.bufferEmptyCalled) {
- this.bufferEmptyCalled = false;
- }
- var next = this.buffer[this.bufferOffset];
- if (++this.bufferOffset * 2 >= this.buffer.length){
- this.buffer = this.buffer.slice(this.bufferOffset);
- this.bufferOffset = 0;
- }
- switch (next.type) {
- case "char":
- this.currentchar = next.val;
- break;
- case "pause":
- this.idleBits = next.val;
- break;
- case "callback":
- next.val();
- this.idle = true;
- break;
- }
- } else if (!this.bufferEmptyCalled) {
- if (this.bufferEmptyCallback != null && typeof this.bufferEmptyCallback == "function") {
- this.bufferEmptyCallback();
- this.bufferEmptyCalled = true;
- }
- }
- } else {
- if (this.bit==0) { // start
- this.freq = this.space;
- }
- if (this.bit >= 1 && this.bit <= 5) { // data bits
- this.freq = (this.currentchar&1)?this.mark:this.space;
- this.currentchar=this.currentchar>>1;
- }
- if (this.bit==6||this.bit==7) { // stop bits
- this.freq = this.mark;
- }
- if (this.bit==8) {
- this.bit=0;
- this.idle=true;
- if (this.characterDoneCallback != null && typeof this.characterDoneCallback == "function") {
- this.characterDoneCallback(bufferLen);
- }
- } else {
- this.bit++;
- }
- }
- }
-
- outputData[sample] = this.waveTable[this.osc]*this.amplitude;
- this.osc = (this.osc+this.freq)%this.audioCtx.sampleRate;
-
- if (this.fade > 0 && (this.amplitude + this.fade) > 1) {
- this.fade = 0;
- this.amplitude = 1;
- if (this.fadeDoneCallback != null && typeof this.fadeDoneCallback == "function") {
- this.fadeDoneCallback();
- this.fadeDoneCallback = null;
- }
- } else if (this.fade < 0 && (this.amplitude + this.fade) < 0) {
- this.fade = 0;
- this.amplitude = 0;
- if (this.fadeDoneCallback != null && typeof this.fadeDoneCallback == "function") {
- this.fadeDoneCallback();
- this.fadeDoneCallback = null;
- }
- }
- this.amplitude += this.fade;
- }
- }).bind(this);
-
- }
-
- RTTY.prototype.initTables = function() {
- this.waveTable = new Float32Array(this.audioCtx.sampleRate);
- for (var i=0;i<this.audioCtx.sampleRate;i++) {
- this.waveTable[i] = Math.sin((i/this.audioCtx.sampleRate)*2*Math.PI);
- }
- this.ct = {
- "0": 0x36, "1": 0x37, "2": 0x33, "3": 0x21, "4": 0x2a, "5": 0x30, "6": 0x35,
- "7": 0x25, "8": 0x26, "9": 0x38, "A": 0x03, "B": 0x19, "C": 0x0e, "D": 0x09,
- "E": 0x01, "F": 0x0d, "G": 0x1a, "H": 0x14, "I": 0x06, "J": 0x0b, "K": 0x0f,
- "L": 0x12, "M": 0x1c, "N": 0x0c, "O": 0x18, "P": 0x16, "Q": 0x17, "R": 0x0a,
- "S": 0x05, "T": 0x10, "U": 0x07, "V": 0x1e, "W": 0x13, "X": 0x1d, "Y": 0x15,
- "Z": 0x11, "\r": 0x02, "\n": 0x08, "'": 0x2b, "-": 0x23, "_": 0x23, ",": 0x2c,
- "!": 0x2d, ":": 0x2e, "(": 0x2f, "+": 0x31, ")": 0x32, "#": 0x34, "?": 0x39,
- "&": 0x3a, ".": 0x3c, "/": 0x3d, ";": 0x3e, "$": 0x29
- }
- };
-
-
- RTTY.prototype.setFade = function(fade, callback) {
- this.fade = fade;
- this.fadeDoneCallback = callback;
- }
-
- RTTY.prototype.pauseModulation = function() {
- this.pause = true;
- }
-
- RTTY.prototype.resumeModulation = function() {
- this.suspend = false;
- }
-
-
-
- RTTY.prototype.startSound = function() {
- if (this.source == null) {
- this.idle = true;
- this.amplitude = 0;
- var samplesPerSymbol = Math.floor(this.audioCtx.sampleRate/this.baudrate);
- var myArrayBuffer = this.audioCtx.createBuffer(1, samplesPerSymbol, this.audioCtx.sampleRate);
- this.source = this.audioCtx.createBufferSource();
- this.source.buffer = myArrayBuffer;
- this.source.loop = true;
-
- var tickTrack = myArrayBuffer.getChannelData(0);
- tickTrack[0] = 1;
-
- this.freq = this.mark;
-
- this.source.connect(this.scriptNode);
- this.scriptNode.connect(this.audioCtx.destination);
- this.source.start();
-
- this.setFade(.01, null);
- }
- }
-
- RTTY.prototype.stopSound = function() {
- if (this.source != null) {
- var me = this
- this.flushBuffer(function(){
- me.setFade(-.01, function(){
- setTimeout(function(){
- me.source.stop();
- me.scriptNode.disconnect();
- me.source = null;
- }, 500);
- });
- });
- }
- }
-
- RTTY.prototype.flushBuffer = function(callback) {
- if (callback == null) {
- callback=function(){};
- }
- this.flush = callback;
- }
-
- RTTY.prototype.invert = function() {
- var x = this.mark;
- if (this.freq==this.mark) {
- this.freq=this.space;
- } else {
- this.freq=this.mark;
- }
- this.mark = this.space;
- this.space = x;
- }
-
-
- RTTY.prototype.modulate = function(str) {
- for (var i = 0, len = str.length; i < len; i++) {
- this.modulateChar(str[i]);
- }
- }
-
- RTTY.prototype.modulateChar = function(c) {
- var baudot
-
- var c = c.toUpperCase();
-
- switch (c) {
- case 0x00: // null
- this.pushChar(0x00); // null
- break;
- case 0x0a: // lf
- this.pushChar(0x02); // cr
- this.pushChar(0x08); // lf
- break;
- case ' ': // space
- // if we print a space in figures mode, terminals set to
- // unshift on space will unshift. Remember that, so that if a
- // figures character needs to be printed, we can turn figures
- // mode back on before printing it.
- if (this.figures) {
- this.unshift = true;
- }
- this.pushChar(0x04);
- break;
- case '@': // Change @ to (at)
- this.figs();
- this.pushChar(this.ct['(']);
- this.ltrs();
- this.pushChar(this.ct['A']);
- this.pushChar(this.ct['T']);
- this.figs();
- this.pushChar(this.ct[')']);
- break;
- case '"': // use two single quotes as a double quote
- this.figs();
- this.pushChar(this.ct["'"]);
- this.pushChar(this.ct["'"]);
- break;
- default:
- baudot=this.ct[c];
- if (baudot!=undefined) {
- // correction for unshift on space
- if ((baudot&this.figsmask)&&this.figures&&this.unshift) {
- this.figs();
- }
- if ((baudot&this.figsmask)&&!this.figures) {
- this.figs();
- }
- if (!(baudot&this.figsmask)&&this.figures) {
- this.ltrs();
- }
- this.pushChar(baudot);
- }
- break;
- }
-
- }
-
- RTTY.prototype.pushChar = function(c) {
- this.buffer.push({
- type: "char",
- val: c
- });
-
- }
-
- RTTY.prototype.pushPause = function(p) {
- this.buffer.push({
- type: "pause",
- val: p
- });
- }
-
- RTTY.prototype.pushCallback = function(callback) {
- this.buffer.push({
- type: "callback",
- val: callback
- });
- }
-
-
- RTTY.prototype.figs = function() {
- this.pushChar(0x1b); // figs
- this.figures=true;
- this.unshift=false;
- }
-
- RTTY.prototype.ltrs = function() {
- this.pushChar(0x1f); // ltrs
- this.figures=false;
- this.unshift=false;
- }
-
-
|