Нет описания

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. var RTTY = RTTY || {};
  2. var RTTY = function(props) {
  3. this.mark = props.mark;
  4. this.space = props.space;
  5. this.baudrate = props.baudrate;
  6. this.bufferEmptyCallback = null;
  7. this.characterDoneCallback = null;
  8. this.fadeDoneCallback = null;
  9. this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  10. this.osc = 0;
  11. this.freq = this.mark;
  12. this.buffer = [];
  13. this.bufferOffset = 0;
  14. this.idleBits = 0;
  15. this.bit = 8;
  16. this.ct = {};
  17. this.figsmask = 1<<5;
  18. this.currentchar = 0x00;
  19. this.pause = false;
  20. this.suspend = false;
  21. this.source = null;
  22. this.figures = true;
  23. this.unshift = false;
  24. this.idle = true;
  25. this.bufferEmptyCalled = false;
  26. this.flush = null;
  27. this.amplitude = 1;
  28. this.fade = 0;
  29. this.initTables();
  30. this.scriptNode = this.audioCtx.createScriptProcessor(4096, 1, 1);
  31. this.scriptNode.onaudioprocess = (function(audioProcessingEvent) {
  32. var inputBuffer = audioProcessingEvent.inputBuffer;
  33. var outputBuffer = audioProcessingEvent.outputBuffer;
  34. var inputData = inputBuffer.getChannelData(0);
  35. var outputData = outputBuffer.getChannelData(0);
  36. for (var sample = 0; sample < inputBuffer.length; sample++) {
  37. if (!this.suspend && inputData[sample]==1) { // bit marker
  38. if (this.idleBits > 0) {
  39. this.idleBits--;
  40. if (this.idleBits==0) {
  41. if (this.characterDoneCallback != null && typeof this.characterDoneCallback == "function") {
  42. this.characterDoneCallback(bufferLen);
  43. }
  44. this.idle = true;
  45. }
  46. } else if (this.pause && this.idle) {
  47. this.suspend = true;
  48. this.pause = false;
  49. } else if (this.idle) {
  50. if (this.flush != null) {
  51. this.buffer = [];
  52. this.bufferOffset = 0;
  53. if (typeof this.flush == "function") {
  54. this.flush();
  55. }
  56. this.flush = null;
  57. }
  58. var bufferLen = this.buffer.length - this.bufferOffset;
  59. if (bufferLen > 0) {
  60. this.idle = false;
  61. if (this.bufferEmptyCalled) {
  62. this.bufferEmptyCalled = false;
  63. }
  64. var next = this.buffer[this.bufferOffset];
  65. if (++this.bufferOffset * 2 >= this.buffer.length){
  66. this.buffer = this.buffer.slice(this.bufferOffset);
  67. this.bufferOffset = 0;
  68. }
  69. switch (next.type) {
  70. case "char":
  71. this.currentchar = next.val;
  72. break;
  73. case "pause":
  74. this.idleBits = next.val;
  75. break;
  76. case "callback":
  77. next.val();
  78. this.idle = true;
  79. break;
  80. }
  81. } else if (!this.bufferEmptyCalled) {
  82. if (this.bufferEmptyCallback != null && typeof this.bufferEmptyCallback == "function") {
  83. this.bufferEmptyCallback();
  84. this.bufferEmptyCalled = true;
  85. }
  86. }
  87. } else {
  88. if (this.bit==0) { // start
  89. this.freq = this.space;
  90. }
  91. if (this.bit >= 1 && this.bit <= 5) { // data bits
  92. this.freq = (this.currentchar&1)?this.mark:this.space;
  93. this.currentchar=this.currentchar>>1;
  94. }
  95. if (this.bit==6||this.bit==7) { // stop bits
  96. this.freq = this.mark;
  97. }
  98. if (this.bit==8) {
  99. this.bit=0;
  100. this.idle=true;
  101. if (this.characterDoneCallback != null && typeof this.characterDoneCallback == "function") {
  102. this.characterDoneCallback(bufferLen);
  103. }
  104. } else {
  105. this.bit++;
  106. }
  107. }
  108. }
  109. outputData[sample] = this.waveTable[this.osc]*this.amplitude;
  110. this.osc = (this.osc+this.freq)%this.audioCtx.sampleRate;
  111. if (this.fade > 0 && (this.amplitude + this.fade) > 1) {
  112. this.fade = 0;
  113. this.amplitude = 1;
  114. if (this.fadeDoneCallback != null && typeof this.fadeDoneCallback == "function") {
  115. this.fadeDoneCallback();
  116. this.fadeDoneCallback = null;
  117. }
  118. } else if (this.fade < 0 && (this.amplitude + this.fade) < 0) {
  119. this.fade = 0;
  120. this.amplitude = 0;
  121. if (this.fadeDoneCallback != null && typeof this.fadeDoneCallback == "function") {
  122. this.fadeDoneCallback();
  123. this.fadeDoneCallback = null;
  124. }
  125. }
  126. this.amplitude += this.fade;
  127. }
  128. }).bind(this);
  129. }
  130. RTTY.prototype.initTables = function() {
  131. this.waveTable = new Float32Array(this.audioCtx.sampleRate);
  132. for (var i=0;i<this.audioCtx.sampleRate;i++) {
  133. this.waveTable[i] = Math.sin((i/this.audioCtx.sampleRate)*2*Math.PI);
  134. }
  135. this.ct = {
  136. "0": 0x36, "1": 0x37, "2": 0x33, "3": 0x21, "4": 0x2a, "5": 0x30, "6": 0x35,
  137. "7": 0x25, "8": 0x26, "9": 0x38, "A": 0x03, "B": 0x19, "C": 0x0e, "D": 0x09,
  138. "E": 0x01, "F": 0x0d, "G": 0x1a, "H": 0x14, "I": 0x06, "J": 0x0b, "K": 0x0f,
  139. "L": 0x12, "M": 0x1c, "N": 0x0c, "O": 0x18, "P": 0x16, "Q": 0x17, "R": 0x0a,
  140. "S": 0x05, "T": 0x10, "U": 0x07, "V": 0x1e, "W": 0x13, "X": 0x1d, "Y": 0x15,
  141. "Z": 0x11, "\r": 0x02, "\n": 0x08, "'": 0x2b, "-": 0x23, "_": 0x23, ",": 0x2c,
  142. "!": 0x2d, ":": 0x2e, "(": 0x2f, "+": 0x31, ")": 0x32, "#": 0x34, "?": 0x39,
  143. "&": 0x3a, ".": 0x3c, "/": 0x3d, ";": 0x3e, "$": 0x29
  144. }
  145. };
  146. RTTY.prototype.setFade = function(fade, callback) {
  147. this.fade = fade;
  148. this.fadeDoneCallback = callback;
  149. }
  150. RTTY.prototype.pauseModulation = function() {
  151. this.pause = true;
  152. }
  153. RTTY.prototype.resumeModulation = function() {
  154. this.suspend = false;
  155. }
  156. RTTY.prototype.startSound = function() {
  157. if (this.source == null) {
  158. this.idle = true;
  159. this.amplitude = 0;
  160. var samplesPerSymbol = Math.floor(this.audioCtx.sampleRate/this.baudrate);
  161. var myArrayBuffer = this.audioCtx.createBuffer(1, samplesPerSymbol, this.audioCtx.sampleRate);
  162. this.source = this.audioCtx.createBufferSource();
  163. this.source.buffer = myArrayBuffer;
  164. this.source.loop = true;
  165. var tickTrack = myArrayBuffer.getChannelData(0);
  166. tickTrack[0] = 1;
  167. this.freq = this.mark;
  168. this.source.connect(this.scriptNode);
  169. this.scriptNode.connect(this.audioCtx.destination);
  170. this.source.start();
  171. this.setFade(.01, null);
  172. }
  173. }
  174. RTTY.prototype.stopSound = function() {
  175. if (this.source != null) {
  176. var me = this
  177. this.flushBuffer(function(){
  178. me.setFade(-.01, function(){
  179. setTimeout(function(){
  180. me.source.stop();
  181. me.scriptNode.disconnect();
  182. me.source = null;
  183. }, 500);
  184. });
  185. });
  186. }
  187. }
  188. RTTY.prototype.flushBuffer = function(callback) {
  189. if (callback == null) {
  190. callback=function(){};
  191. }
  192. this.flush = callback;
  193. }
  194. RTTY.prototype.invert = function() {
  195. var x = this.mark;
  196. if (this.freq==this.mark) {
  197. this.freq=this.space;
  198. } else {
  199. this.freq=this.mark;
  200. }
  201. this.mark = this.space;
  202. this.space = x;
  203. }
  204. RTTY.prototype.modulate = function(str) {
  205. for (var i = 0, len = str.length; i < len; i++) {
  206. this.modulateChar(str[i]);
  207. }
  208. }
  209. RTTY.prototype.modulateChar = function(c) {
  210. var baudot
  211. var c = c.toUpperCase();
  212. switch (c) {
  213. case 0x00: // null
  214. this.pushChar(0x00); // null
  215. break;
  216. case 0x0a: // lf
  217. this.pushChar(0x02); // cr
  218. this.pushChar(0x08); // lf
  219. break;
  220. case ' ': // space
  221. // if we print a space in figures mode, terminals set to
  222. // unshift on space will unshift. Remember that, so that if a
  223. // figures character needs to be printed, we can turn figures
  224. // mode back on before printing it.
  225. if (this.figures) {
  226. this.unshift = true;
  227. }
  228. this.pushChar(0x04);
  229. break;
  230. case '@': // Change @ to (at)
  231. this.figs();
  232. this.pushChar(this.ct['(']);
  233. this.ltrs();
  234. this.pushChar(this.ct['A']);
  235. this.pushChar(this.ct['T']);
  236. this.figs();
  237. this.pushChar(this.ct[')']);
  238. break;
  239. case '"': // use two single quotes as a double quote
  240. this.figs();
  241. this.pushChar(this.ct["'"]);
  242. this.pushChar(this.ct["'"]);
  243. break;
  244. default:
  245. baudot=this.ct[c];
  246. if (baudot!=undefined) {
  247. // correction for unshift on space
  248. if ((baudot&this.figsmask)&&this.figures&&this.unshift) {
  249. this.figs();
  250. }
  251. if ((baudot&this.figsmask)&&!this.figures) {
  252. this.figs();
  253. }
  254. if (!(baudot&this.figsmask)&&this.figures) {
  255. this.ltrs();
  256. }
  257. this.pushChar(baudot);
  258. }
  259. break;
  260. }
  261. }
  262. RTTY.prototype.pushChar = function(c) {
  263. this.buffer.push({
  264. type: "char",
  265. val: c
  266. });
  267. }
  268. RTTY.prototype.pushPause = function(p) {
  269. this.buffer.push({
  270. type: "pause",
  271. val: p
  272. });
  273. }
  274. RTTY.prototype.pushCallback = function(callback) {
  275. this.buffer.push({
  276. type: "callback",
  277. val: callback
  278. });
  279. }
  280. RTTY.prototype.figs = function() {
  281. this.pushChar(0x1b); // figs
  282. this.figures=true;
  283. this.unshift=false;
  284. }
  285. RTTY.prototype.ltrs = function() {
  286. this.pushChar(0x1f); // ltrs
  287. this.figures=false;
  288. this.unshift=false;
  289. }