Skip to content

Commit

Permalink
Use Web Audio API to generate the morse audio
Browse files Browse the repository at this point in the history
  • Loading branch information
ozdemirburak committed Jun 20, 2017
1 parent 10b4c7f commit b5ad93f
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 83 deletions.
49 changes: 27 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![npm-version]][npm] [![npm-downloads]][npm] [![travis-ci]][travis]

Morse code encoder and decoder with no dependencies supports Latin, Cyrillic, Greek, Hebrew,
Arabic, Persian, Japanese, and Korean characters with audio generation functionality.
Arabic, Persian, Japanese, and Korean characters with audio generation functionality using the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API).

## Installation

Expand All @@ -24,19 +24,23 @@ $ yarn add morsify
```js
var morsify = require('morsify');

morsify.encode('SOS'); // .../---/...
morsify.decode('.../---/...'); // S O S
morsify.audio('SOS'); // will return base64 encoded audio/wav data
var encoded = morsify.encode('SOS'); // .../---/...
var decoded = morsify.decode('.../---/...'); // S O S
var audio = morsify.audio('SOS'), oscillator = audio.oscillator; // OscillatorNode
audio.play(); // play audio
audio.stop(); // stop audio
```

Or alternatively, you can also use the library directly with including the source file.

```html
<script src="https://rawgit.com/ozdemirburak/morsify/master/index.js"></script>
<script>
console.log(morsify.encode('SOS')); // .../---/...
console.log(morsify.decode('.../---/...')); // S O S
console.log(morsify.audio('SOS')); // will return base64 encoded audio/wav data
var encoded = morsify.encode('SOS'); // .../---/...
var decoded = morsify.decode('.../---/...'); // S O S
var audio = morsify.audio('SOS'), oscillator = audio.oscillator; // OscillatorNode
audio.play(); // play audio
audio.stop(); // stop audio
</script>
```

Expand All @@ -52,7 +56,7 @@ Set the priority option according to the list below.
- 1 => ASCII (Default)
- 2 => Numbers
- 3 => Punctuation
- 4 => Latin Extended (Turkish, Polski etc.)
- 4 => Latin Extended (Turkish, Polish etc.)
- 5 => Cyrillic
- 6 => Greek
- 7 => Hebrew
Expand All @@ -62,18 +66,21 @@ Set the priority option according to the list below.
- 11 => Korean

```js
morsify.encode('Ленинград', { priority: 5 }) // .-.././-./../-./--./.-./.-/-..
morsify.decode('.../.-/--./.-/.--./.--', { priority: 6 }) // Σ Α Γ Α Π Ω
morsify.decode('––– –... ––– –. ––. .. .–.. –––', { dash: '', dot: '.', space: ' ', priority: 7 }) // ה ב ה נ ג י ל ה
morsify.audio('البُراق‎‎', { // generates the morse .-/.-../-.../.-./.-/--.- then generates the audio from it
channels: 1,
sampleRate: 1012,
bitDepth: 16,
unit: 0.1,
frequency: 440.0,
volume: 32767,
priority: 8
})
var cyrillic = morsify.encode('Ленинград', { priority: 5 }) // .-.././-./../-./--./.-./.-/-..
var greek = morsify.decode('.../.-/--./.-/.--./.--', { priority: 6 }) // Σ Α Γ Α Π Ω
var hebrew = morsify.decode('––– –... ––– –. ––. .. .–.. –––', { dash: '', dot: '.', space: ' ', priority: 7 }) // ה ב ה נ ג י ל ה
var arabicAudio = morsify.audio('البُراق‎‎', { // generates the morse .-/.-../-.../.-./.-/--.- then generates the audio from it
unit: 0.1, // period of one unit, in seconds, 1.2 / c where c is speed of transmission, in words per minute
oscillator: {
type: 'sine', // sine, square, sawtooth, triangle
frequency: 500, // value in hertz
onended: function () { // event that fires when the tone has stopped playing
console.log('ended');
},
}
}), oscillator = arabicAudio.oscillator; // OscillatorNode
arabicAudio.play(); // will start playing morse audio
arabicAudio.stop(); // will stop playing morse audio
```

## Contributing and Known Issues
Expand All @@ -84,8 +91,6 @@ Currently, as a major drawback, Chinese characters are missing. Someone with the
[Chinese telegraph code](https://en.wikipedia.org/wiki/Chinese_telegraph_code) can help to implement it. Also someone who is proficient in Thai can help
to include [Thai alphabet](https://th.wikipedia.org/wiki/รหัสมอร์ส).

On the other hand, audio generated by the script is playable in Firefox, however, Chrome support is currently missing.

## License
The MIT License (MIT). Please see [License File](LICENSE) for more information.

Expand Down
93 changes: 37 additions & 56 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'Ș': '1111', 'Š': '1111', 'Ŝ': '00010', 'ß': '000000', 'Þ': '01100', 'Ü': '0011',
'Ù': '0011', 'Ŭ': '0011', 'Ž': '11001', 'Ź': '110010', 'Ż': '11001'
},
'5': { // Cyrilic Alphabet => https://en.wikipedia.org/wiki/Russian_Morse_code
'5': { // Cyrillic Alphabet => https://en.wikipedia.org/wiki/Russian_Morse_code
'А': '01', 'Б': '1000', 'В': '011', 'Г': '110', 'Д': '100', 'Е': '0',
'Ж': '0001', 'З': '1100', 'И': '00', 'Й': '0111', 'К': '101','Л': '0100',
'М': '11', 'Н': '10', 'О': '111', 'П': '0110', 'Р': '010', 'С': '000',
Expand Down Expand Up @@ -105,18 +105,19 @@

var getOptions = function (options) {
options = options || {};
options.oscillator = options.oscillator || {};
options = {
dash: options.dash || '-',
dot: options.dot || '.',
space: options.space || '/',
invalid: options.invalid || '#',
priority: options.priority || 1,
channels: options.channels || 1,
sampleRate: options.sampleRate || 1012,
bitDepth: options.bitDepth || 16,
unit: options.unit || 0.1,
frequency: options.frequency || 440.0,
volume: options.volume || 32767
unit: options.unit || 0.08, // period of one unit, in seconds, 1.2 / c where c is speed of transmission, in words per minute
oscillator: {
type: options.oscillator.type || 'sine', // sine, square, sawtooth, triangle
frequency: options.oscillator.frequency || 500, // value in hertz
onended: options.oscillator.onended || null, // event that fires when the tone has stopped playing
}
};
characters[0] = characters[options.priority];
return options;
Expand All @@ -141,29 +142,24 @@
}).join(' ').replace(/\s+/g, ' ');
};

// Source: https://github.com/mattt/Morse.js
var audio = function (text, opts) {
var options = getOptions(opts), morse = encode(text, opts), data = [], samples = 0,
pack = function (e) {
for (var b = '', c = 1, d = 0; d < e.length; d++) {
var f = e.charAt(d), a = arguments[c++];
b += f === 'v' ? String.fromCharCode(a & 255, a >> 8 & 255) : String.fromCharCode(a & 255, a >> 8 & 255, a >> 16 & 255, a >> 24 & 255);
}
return b;
}, tone = function (length) {
for (var i = 0; i < options.sampleRate * options.unit * length; i++) {
for (var c = 0; c < options.channels; c++) {
var v = options.volume * Math.sin((2 * Math.PI) * (i / options.sampleRate) * options.frequency);
data.push(pack('v', v)); samples++;
}
}
}, silence = function (length) {
for (var i = 0; i < options.sampleRate * options.unit * length; i++) {
for (var c = 0; c < options.channels; c++) {
data.push(pack('v', 0)); samples++;
}
}
};
var options = getOptions(opts), morse = encode(text, opts),
AudioContext = window.AudioContext || window.webkitAudioContext, ctx = new AudioContext(),
t = ctx.currentTime, oscillator = ctx.createOscillator(), gainNode = ctx.createGain();

oscillator.type = options.oscillator.type;
oscillator.frequency.value = options.oscillator.frequency;
oscillator.onended = options.oscillator.onended;

gainNode.gain.setValueAtTime(0, t);

var tone = function (i) {
gainNode.gain.setValueAtTime(1, t);
t += i * options.unit;
}, silence = function (i) {
gainNode.gain.setValueAtTime(0, t);
t += i * options.unit;
};

for (var i = 0; i <= morse.length; i++) {
if (morse[i] === options.space) {
Expand All @@ -179,34 +175,19 @@
}
}

var chunk1 = [
'fmt ',
pack('V', 16),
pack('v', 1),
pack('v', options.channels),
pack('V', options.sampleRate),
pack('V', options.sampleRate * options.channels * options.bitDepth / 8),
pack('v', options.channels * options.bitDepth / 8),
pack('v', options.bitDepth)
].join(''),
chunk2 = [
'data',
pack('V', samples * options.channels * options.bitDepth / 8),
data.join('')
].join(''),
header = [
'RIFF',
pack('V', 4 + (8 + chunk1.length) + (8 + chunk2.length)),
'WAVE'
].join('');
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);

if (typeof btoa === 'undefined') {
global.btoa = function (str) {
return new Buffer(str).toString('base64');
};
}

return 'data:audio/wav;base64,' + encodeURI(btoa([header, chunk1, chunk2].join('')));
return {
play: function () {
oscillator.start();
oscillator.stop(t);
},
stop: function () {
oscillator.stop();
},
oscillator: oscillator
};
};

return {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "morsify",
"version": "0.1.0",
"version": "0.2.0",
"description": "Encodes and decodes morse code, creates morse code audio from text.",
"keywords": [
"morse",
Expand Down
4 changes: 0 additions & 4 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,4 @@ describe('morsify', function () {
t.equal(morsify.decode('---/.---/./../-/...', options), 'ㅍ ㅎ ㅏ ㅑ ㅓ ㅕ');
t.equal(morsify.decode('.-/-./..../.-./-../..-', options), 'ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ');
});
it('creates audio', function () {
t.equal('data:audio/wav;base64', morsify.audio('SOS').substr(0, 21));
t.equal('UklGRsK8IAAAV0FWRWZtdCAQAAAAAQABAMO0AwAAw6gHAAACABAAZGF0YcKIIAAAAADDvjJ1wqLCm3hOwoDCpXE6wq/CiCJtEX/CvcKRaMKvwoJRfW/Cl8KBQsKTw654w53DhlBbwo7Csn9lwofCi10Cw40AAMO+MnXCosKbeE7CgMKlcTrCr8KIIm0Rf8K9wpFowq/CglF9b8KXwoFCwpPDrnjDncOGUFvCjsKyf2XCh8KLXQLDjQAAw74ydcKiwpt4TsKAwqVxOsKvwogibRF/wr3CkWjCr8KCUX1vwpfCgULCk8OueMOdw4ZQW8KOwrJ/ZcKHwotdAsONAADDvjJ1wqLCm3hOwoDCpXE6wq/CiCJtEX/CvcKRaMKvwoJRfW/Cl8KBQsKTw654w53DhlBbwo7Csn9lwofCi10Cw40AAMO+MnXCosKbeE7CgMKlcTrCr8KIIm0Rf8K', morsify.audio('SOS').substr(22, 463));
});
});

0 comments on commit b5ad93f

Please sign in to comment.