Browse Source

Initial import of ham.js

converted to oldschool js
master
Konrad Beckmann 1 year ago
parent
commit
7c93bfaac1
4 changed files with 284 additions and 0 deletions
  1. +21
    -0
      app.js
  2. +6
    -0
      bootstrap.min.css
  3. +213
    -0
      ham.js
  4. +44
    -0
      index.html

+ 21
- 0
app.js View File

@ -0,0 +1,21 @@
var ham = new HamJS();
var cw = new HamJS.modulators.cw({frequency: 1000, gain:1.0});
document.getElementById('message').addEventListener('keypress', function(event) {
if (event.keyCode == 13) {
var text = document.getElementById('message').value;
var frequency = document.getElementById('frequency').value;
var wpm = document.getElementById('wpm').value;
var attack = document.getElementById('attack').value;
var decay = document.getElementById('decay').value;
if (frequency > 0) cw.setFrequency(frequency);
if (wpm > 0) cw.setWPM(wpm);
if (attack > 0) cw.setAttack(attack / 1000.0);
if (decay > 0) cw.setDecay(decay / 1000.0);
ham.use(cw);
ham.transmit(text, event.shiftKey);
}
});

+ 6
- 0
bootstrap.min.css
File diff suppressed because it is too large
View File


+ 213
- 0
ham.js View File

@ -0,0 +1,213 @@
var Utils = {
lerp: function (a, b, x) {
// map [0,1] linearily
return a + x * (b - a);
},
cosLerp: function (a, b, x) {
// map [0,1] to the smoothness of a (co)sinus curve
return this.lerp(a, b, 0.5 + 0.5 * Math.cos(Math.PI * x + Math.PI));
},
}
///////////////////////////////////////////////////////////////////////////////
// CW module
///////////////////////////////////////////////////////////////////////////////
var cwDefaultConfig = {
channels: 1,
frequency: 1000,
attack: 0.005,
decay: 0.005,
gain: 1.0,
wpm: 20,
};
var CWModulator = function(config) {
Object.assign(this, cwDefaultConfig, config);
this.setWPM(this.wpm);
this.morseBuffer = '';
};
var alphabet = {
'a': '.-', 'b': '-...', 'c': '-.-.', 'd': '-..',
'e': '.', 'f': '..-.', 'g': '--.', 'h': '....',
'i': '..', 'j': '.---', 'k': '-.-', 'l': '.-..',
'm': '--', 'n': '-.', 'o': '---', 'p': '.--.',
'q': '--.-', 'r': '.-.', 's': '...', 't': '-',
'u': '..-', 'v': '...-', 'w': '.--', 'x': '-..-',
'y': '-.--', 'z': '--..', ' ': ',',
'1': '.----', '2': '..---', '3': '...--', '4': '....-',
'5': '.....', '6': '-....', '7': '--...', '8': '---..',
'9': '----.', '0': '-----',
};
CWModulator.prototype.encode = function(data) {
return data
.split('')
.map(function(e){
return alphabet[e.toLowerCase()] || '';
})
.join(' ')
.replace(/ +/g, ' ')
.replace(/ , /g, ',');
};
CWModulator.prototype.setFrequency = function(value) {
this.frequency = value;
};
CWModulator.prototype.setWPM = function(value) {
this.wpm = value;
this.dotLength = 1.2 / this.wpm;
};
CWModulator.prototype.setAttack = function(value) {
this.attack = value;
};
CWModulator.prototype.setDecay = function(value) {
this.decay = value;
};
CWModulator.prototype.connect = function(audioContext) {
this.ac = audioContext;
this.sampleRate = this.ac.sampleRate;
};
CWModulator.prototype.insertTone = function(audioBuffer, offset, frequency, length, attack, decay) {
attack *= this.sampleRate;
decay *= this.sampleRate;
length *= this.sampleRate;
var i;
for (i = 0; i < length; i++) {
var ramp = this.gain;
if (i < attack) {
ramp = Utils.cosLerp(0.0, this.gain, i / attack);
}
else if (i > length - decay) {
ramp = Utils.cosLerp(0.0, this.gain, (length - i) / decay);
}
audioBuffer[i + offset] = Math.sin(this.frequency * i * Math.PI * 2 / this.sampleRate) * ramp;
}
return i;
};
CWModulator.prototype.getLength = function() {
var length = 0;
for (var i = 0; i < this.morseBuffer.length; i++) {
var c = this.morseBuffer[i];
switch (c) {
case '.':
length += 1 + 1;
break;
case '-':
length += 3 + 1;
break;
case ' ':
length += 2;
break;
case ',':
length += 6;
break;
}
}
return length * this.dotLength;
};
CWModulator.prototype.insertTones = function(channelData) {
var offset = 0;
for (var i = 0; i < this.morseBuffer.length; i++) {
var c = this.morseBuffer[i];
switch (c) {
case '.':
offset += this.insertTone(channelData, offset, this.frequency, this.dotLength, this.attack, this.decay);
offset += this.dotLength * this.sampleRate;
break;
case '-':
offset += this.insertTone(channelData, offset, this.frequency, this.dotLength * 3, this.attack, this.decay);
offset += this.dotLength * this.sampleRate;
break;
case ' ':
offset += 2 * this.dotLength * this.sampleRate;
break;
case ',':
offset += 6 * this.dotLength * this.sampleRate;
break;
}
}
};
CWModulator.prototype.modulate = function(data) {
this.morseBuffer = data;
this.length = this.getLength();
this.frameCount = this.channels * this.ac.sampleRate * this.length;
this.audioBuffer = this.ac.createBuffer(this.channels, this.frameCount, this.ac.sampleRate);
var channelData = this.audioBuffer.getChannelData(0);
this.insertTones(channelData);
return this.audioBuffer;
};
///////////////////////////////////////////////////////////////////////////////
// ham.js module
///////////////////////////////////////////////////////////////////////////////
var HamJS = function(audioContext) {
if (audioContext) {
this.ac = audioContext;
}
else {
this.ac = new (window.AudioContext || window.webkitAudioContext)();
}
this.sources = {};
};
HamJS.prototype.use = function(modem) {
this.modem = modem;
modem.connect(this.ac);
};
HamJS.prototype.stopAll = function() {
for (var x in this.sources) {
this.sources[x].stop();
delete this.sources[x];
}
this.currentSource = null;
};
HamJS.prototype.transmit = function(data, stopCurrent) {
console.log('radio.transmit(' + data + ')');
var encoded = this.modem.encode(data);
console.log('encoded = ' + encoded);
var buffer = this.modem.modulate(encoded);
console.log('buffer = ' + buffer);
if (stopCurrent) {
this.stopAll();
}
var source = this.ac.createBufferSource();
var now = Date.now();
this.sources[now] = source;
source.onended = function(){
delete this.sources[now];
if (this.currentSource === source) {
this.currentSource = null;
}
}.bind(this);
source.buffer = buffer;
source.connect(this.ac.destination);
source.start();
this.currentSource = source;
};
HamJS.modulators = {
cw: CWModulator,
};

+ 44
- 0
index.html View File

@ -0,0 +1,44 @@
<html>
<head>
<title>Morse all the things!</title>
<link rel="stylesheet" href="./bootstrap.min.css">
</head>
<body>
<h1>Morse is nice. Make some noise!</h1>
<p>Type your message below, press enter to transmit. Hold down shift to silence other transmitting signals.</p>
<form>
<input
type="text"
id="message"
placeholder="Write message, press enter to generate sound"
style="width:400"
/>
<input
type="text"
id="frequency"
placeholder="Hz (default:1000)"
style="width:150"
/>
<input
type="text"
id="wpm"
placeholder="Words/minute (default:100)"
style="width:200"
/>
<input
type="text"
id="attack"
placeholder="Attack (50)"
style="width:150"
/>
<input
type="text"
id="decay"
placeholder="Decay (50)"
style="width:150"
/>
</form>
<script type="text/javascript" src="./ham.js"></script>
<script type="text/javascript" src="./app.js"></script>
</body>
</html>

Loading…
Cancel
Save