Skip to content

Commit b5ad93f

Browse files
committed
Use Web Audio API to generate the morse audio
1 parent 10b4c7f commit b5ad93f

File tree

4 files changed

+65
-83
lines changed

4 files changed

+65
-83
lines changed

README.md

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![npm-version]][npm] [![npm-downloads]][npm] [![travis-ci]][travis]
44

55
Morse code encoder and decoder with no dependencies supports Latin, Cyrillic, Greek, Hebrew,
6-
Arabic, Persian, Japanese, and Korean characters with audio generation functionality.
6+
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).
77

88
## Installation
99

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

27-
morsify.encode('SOS'); // .../---/...
28-
morsify.decode('.../---/...'); // S O S
29-
morsify.audio('SOS'); // will return base64 encoded audio/wav data
27+
var encoded = morsify.encode('SOS'); // .../---/...
28+
var decoded = morsify.decode('.../---/...'); // S O S
29+
var audio = morsify.audio('SOS'), oscillator = audio.oscillator; // OscillatorNode
30+
audio.play(); // play audio
31+
audio.stop(); // stop audio
3032
```
3133

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

3436
```html
3537
<script src="https://rawgit.com/ozdemirburak/morsify/master/index.js"></script>
3638
<script>
37-
console.log(morsify.encode('SOS')); // .../---/...
38-
console.log(morsify.decode('.../---/...')); // S O S
39-
console.log(morsify.audio('SOS')); // will return base64 encoded audio/wav data
39+
var encoded = morsify.encode('SOS'); // .../---/...
40+
var decoded = morsify.decode('.../---/...'); // S O S
41+
var audio = morsify.audio('SOS'), oscillator = audio.oscillator; // OscillatorNode
42+
audio.play(); // play audio
43+
audio.stop(); // stop audio
4044
</script>
4145
```
4246

@@ -52,7 +56,7 @@ Set the priority option according to the list below.
5256
- 1 => ASCII (Default)
5357
- 2 => Numbers
5458
- 3 => Punctuation
55-
- 4 => Latin Extended (Turkish, Polski etc.)
59+
- 4 => Latin Extended (Turkish, Polish etc.)
5660
- 5 => Cyrillic
5761
- 6 => Greek
5862
- 7 => Hebrew
@@ -62,18 +66,21 @@ Set the priority option according to the list below.
6266
- 11 => Korean
6367

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

7986
## Contributing and Known Issues
@@ -84,8 +91,6 @@ Currently, as a major drawback, Chinese characters are missing. Someone with the
8491
[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
8592
to include [Thai alphabet](https://th.wikipedia.org/wiki/รหัสมอร์ส).
8693

87-
On the other hand, audio generated by the script is playable in Firefox, however, Chrome support is currently missing.
88-
8994
## License
9095
The MIT License (MIT). Please see [License File](LICENSE) for more information.
9196

index.js

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

106106
var getOptions = function (options) {
107107
options = options || {};
108+
options.oscillator = options.oscillator || {};
108109
options = {
109110
dash: options.dash || '-',
110111
dot: options.dot || '.',
111112
space: options.space || '/',
112113
invalid: options.invalid || '#',
113114
priority: options.priority || 1,
114-
channels: options.channels || 1,
115-
sampleRate: options.sampleRate || 1012,
116-
bitDepth: options.bitDepth || 16,
117-
unit: options.unit || 0.1,
118-
frequency: options.frequency || 440.0,
119-
volume: options.volume || 32767
115+
unit: options.unit || 0.08, // period of one unit, in seconds, 1.2 / c where c is speed of transmission, in words per minute
116+
oscillator: {
117+
type: options.oscillator.type || 'sine', // sine, square, sawtooth, triangle
118+
frequency: options.oscillator.frequency || 500, // value in hertz
119+
onended: options.oscillator.onended || null, // event that fires when the tone has stopped playing
120+
}
120121
};
121122
characters[0] = characters[options.priority];
122123
return options;
@@ -141,29 +142,24 @@
141142
}).join(' ').replace(/\s+/g, ' ');
142143
};
143144

144-
// Source: https://github.com/mattt/Morse.js
145145
var audio = function (text, opts) {
146-
var options = getOptions(opts), morse = encode(text, opts), data = [], samples = 0,
147-
pack = function (e) {
148-
for (var b = '', c = 1, d = 0; d < e.length; d++) {
149-
var f = e.charAt(d), a = arguments[c++];
150-
b += f === 'v' ? String.fromCharCode(a & 255, a >> 8 & 255) : String.fromCharCode(a & 255, a >> 8 & 255, a >> 16 & 255, a >> 24 & 255);
151-
}
152-
return b;
153-
}, tone = function (length) {
154-
for (var i = 0; i < options.sampleRate * options.unit * length; i++) {
155-
for (var c = 0; c < options.channels; c++) {
156-
var v = options.volume * Math.sin((2 * Math.PI) * (i / options.sampleRate) * options.frequency);
157-
data.push(pack('v', v)); samples++;
158-
}
159-
}
160-
}, silence = function (length) {
161-
for (var i = 0; i < options.sampleRate * options.unit * length; i++) {
162-
for (var c = 0; c < options.channels; c++) {
163-
data.push(pack('v', 0)); samples++;
164-
}
165-
}
166-
};
146+
var options = getOptions(opts), morse = encode(text, opts),
147+
AudioContext = window.AudioContext || window.webkitAudioContext, ctx = new AudioContext(),
148+
t = ctx.currentTime, oscillator = ctx.createOscillator(), gainNode = ctx.createGain();
149+
150+
oscillator.type = options.oscillator.type;
151+
oscillator.frequency.value = options.oscillator.frequency;
152+
oscillator.onended = options.oscillator.onended;
153+
154+
gainNode.gain.setValueAtTime(0, t);
155+
156+
var tone = function (i) {
157+
gainNode.gain.setValueAtTime(1, t);
158+
t += i * options.unit;
159+
}, silence = function (i) {
160+
gainNode.gain.setValueAtTime(0, t);
161+
t += i * options.unit;
162+
};
167163

168164
for (var i = 0; i <= morse.length; i++) {
169165
if (morse[i] === options.space) {
@@ -179,34 +175,19 @@
179175
}
180176
}
181177

182-
var chunk1 = [
183-
'fmt ',
184-
pack('V', 16),
185-
pack('v', 1),
186-
pack('v', options.channels),
187-
pack('V', options.sampleRate),
188-
pack('V', options.sampleRate * options.channels * options.bitDepth / 8),
189-
pack('v', options.channels * options.bitDepth / 8),
190-
pack('v', options.bitDepth)
191-
].join(''),
192-
chunk2 = [
193-
'data',
194-
pack('V', samples * options.channels * options.bitDepth / 8),
195-
data.join('')
196-
].join(''),
197-
header = [
198-
'RIFF',
199-
pack('V', 4 + (8 + chunk1.length) + (8 + chunk2.length)),
200-
'WAVE'
201-
].join('');
178+
oscillator.connect(gainNode);
179+
gainNode.connect(ctx.destination);
202180

203-
if (typeof btoa === 'undefined') {
204-
global.btoa = function (str) {
205-
return new Buffer(str).toString('base64');
206-
};
207-
}
208-
209-
return 'data:audio/wav;base64,' + encodeURI(btoa([header, chunk1, chunk2].join('')));
181+
return {
182+
play: function () {
183+
oscillator.start();
184+
oscillator.stop(t);
185+
},
186+
stop: function () {
187+
oscillator.stop();
188+
},
189+
oscillator: oscillator
190+
};
210191
};
211192

212193
return {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "morsify",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Encodes and decodes morse code, creates morse code audio from text.",
55
"keywords": [
66
"morse",

test/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,4 @@ describe('morsify', function () {
158158
t.equal(morsify.decode('---/.---/./../-/...', options), 'ㅍ ㅎ ㅏ ㅑ ㅓ ㅕ');
159159
t.equal(morsify.decode('.-/-./..../.-./-../..-', options), 'ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ');
160160
});
161-
it('creates audio', function () {
162-
t.equal('data:audio/wav;base64', morsify.audio('SOS').substr(0, 21));
163-
t.equal('UklGRsK8IAAAV0FWRWZtdCAQAAAAAQABAMO0AwAAw6gHAAACABAAZGF0YcKIIAAAAADDvjJ1wqLCm3hOwoDCpXE6wq/CiCJtEX/CvcKRaMKvwoJRfW/Cl8KBQsKTw654w53DhlBbwo7Csn9lwofCi10Cw40AAMO+MnXCosKbeE7CgMKlcTrCr8KIIm0Rf8K9wpFowq/CglF9b8KXwoFCwpPDrnjDncOGUFvCjsKyf2XCh8KLXQLDjQAAw74ydcKiwpt4TsKAwqVxOsKvwogibRF/wr3CkWjCr8KCUX1vwpfCgULCk8OueMOdw4ZQW8KOwrJ/ZcKHwotdAsONAADDvjJ1wqLCm3hOwoDCpXE6wq/CiCJtEX/CvcKRaMKvwoJRfW/Cl8KBQsKTw654w53DhlBbwo7Csn9lwofCi10Cw40AAMO+MnXCosKbeE7CgMKlcTrCr8KIIm0Rf8K', morsify.audio('SOS').substr(22, 463));
164-
});
165161
});

0 commit comments

Comments
 (0)