<html>
<head>
<title>すすーっすっすーっっすっーっすすーっすっすっーっっすすーすっっすっーっすすっすーすっすすっーすすすっすーっすすすーっーっすっすっーっすっっーっすっすっーすすすっすーすっすすっーっすすっーすすすっすー</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
原文<br />
<div style="font-size: x-small;">
全角カタカナで入力してください<br />
ひらがなからカタカナへの変換などは行いません。<br />
半角カナはサポートしていません。<br />
</div>
<textarea id="text" rows="10" cols="40">アンナチャン…</textarea><br />
</div>
<div>
<button id="btn_encode">↓エンコード</button> <button id="btn_decode">↑デコード</button><br />
</div>
<div>
ヨロモールス <button id="play" style="font-size: xx-small;">再生</button><br />
<textarea id="yoro" rows="10" cols="40">ススッススーッスッスッーッスッーッッスッーッススーッスッスッー…</textarea><br />
</div>
<script>
/**
* @var Map<String, String> table カタカナとモールス信号の対照表。すっじゃなくて10で書いてるのは、単純に視認性が良かったから。
*/
var table = {
'ア': '11011',
'カ': '0100',
'サ': '10101',
'タ': '10',
'ナ': '010',
'ハ': '1000',
'マ': '1001',
'ヤ': '011',
'ラ': '000',
'ワ': '101',
'イ': '01',
'キ': '10100',
'シ': '11010',
'チ': '0010',
'ニ': '1010',
'ヒ': '11001',
'ミ': '00101',
'リ': '110',
'ヰ': '01001',
'ウ': '001',
'ク': '0001',
'ス': '11101',
'ツ': '0110',
'ヌ': '0000',
'フ': '1100',
'ム': '1',
'ユ': '10011',
'ル': '10110',
'ン': '01010',
'オ': '01000',
'ケ': '1011',
'セ': '01110',
'テ': '01011',
'ネ': '1101',
'ヘ': '0',
'メ': '10001',
'レ': '111',
'ヱ': '01100',
'、': '010101',
'エ': '10111',
'コ': '1111',
'ソ': '1110',
'ト': '00100',
'ノ': '0011',
'ホ': '100',
'モ': '10010',
'ヨ': '11',
'ロ': '0101',
'ヲ': '0111',
'。': '010100',
'0': '11111',
'1': '01111',
'2': '00111',
'3': '00011',
'4': '00001',
'5': '00000',
'6': '10000',
'7': '11000',
'8': '11100',
'9': '11110',
'゛': '00',
'ー': '01101',
'゜': '00110',
};
/**
* @var Map<String, String> revTable 逆検索用のテーブルを作ってキャッシュしたもの
*/
var revTable = {};
$.each(table, function (k, v) { revTable[v] = k; });
$('#btn_encode').on('click', function () {
// テキストを正規化する
// 濁点と半濁点を分離。小音を普通の大きさにする。
var text = $('#text').val();
text = text.replace(/ガ/g, 'カ゛').replace(/ギ/g, 'キ゛').replace(/グ/g, 'ク゛').replace(/ゲ/g, 'ケ゛').replace(/ゴ/g, 'コ゛');
text = text.replace(/ザ/g, 'サ゛').replace(/ジ/g, 'シ゛').replace(/ズ/g, 'ス゛').replace(/ゼ/g, 'セ゛').replace(/ゾ/g, 'ソ゛');
text = text.replace(/ダ/g, 'タ゛').replace(/ヂ/g, 'チ゛').replace(/ヅ/g, 'ツ゛').replace(/デ/g, 'テ゛').replace(/ド/g, 'ト゛');
text = text.replace(/バ/g, 'ハ゛').replace(/ビ/g, 'ヒ゛').replace(/ブ/g, 'フ゛').replace(/ベ/g, 'ヘ゛').replace(/ボ/g, 'ホ゛');
text = text.replace(/バ/g, 'ハ゛').replace(/ビ/g, 'ヒ゛').replace(/ブ/g, 'フ゛').replace(/ベ/g, 'ヘ゛').replace(/ボ/g, 'ホ゛');
text = text.replace(/ァ/g, 'ア').replace(/ィ/g, 'イ').replace(/ゥ/g, 'ウ').replace(/ェ/g, 'エ').replace(/ォ/g, 'オ');
text = text.replace(/ッ/g, 'ツ').replace(/ャ/g, 'ヤ').replace(/ュ/g, 'ユ').replace(/ョ/g, 'ヨ').replace(/ヮ/g, 'ワ');
// 1文字ずつ分離して処理する
var yoro = '';
$.each(text.split(''), function (pos, c) {
var m = table[c];
if (m == undefined) {
// モールスに無い文字はそのまま書く
m = c;
} else {
// 1101 => すすっすー
m = m.replace(/1/g, 'す').replace(/0/g, 'っ');
m += 'ー';
}
yoro += m;
});
$('#yoro').val(yoro);
});
$('#btn_decode').on('click', function () {
var yoromors = $('#yoro').val();
// ヨロモールス側は、カタカナ、ひらがな、半角カナを受け入れる
// すすっすーっっすっー => 1101,0010
var mors = yoromors.replace(/[すスス]/g, '1').replace(/[っッッ]/g, '0').replace(/[ー─-ー]/g, ',');
var text = '';
// 1グループごとに処理する
$.each(mors.split(','), function (pos, mor) {
// モールスパターンとの完全一致検索
var c = revTable[mor];
// 一致するものが無い場合は、モールスパターンをそのまま出す。
if (c == undefined) c = mor;
text += c;
});
$('#text').val(text);
});
/**
* モールス信号再生
* @see https://qiita.com/kurehajime/items/27c98de56fd06f93d0b8
*/
var audio = new Audio();
audio.src = 'http://xiidec.appspot.com/etc/pi.wav';
$('#play').on('click', function () {
// ヨロモールスを文字単位に分解
var yoromors = $('#yoro').val().split('');
// オーディオリセット
audio.currentTime = 0;
/**
* @var Integer remain 次のオーディオ割り込みまでの残り周期
*/
var remain = 0;
/**
* @var String mors モールスのグループを覚えておく変数。再生中の原文の逐次出力に使う。
*/
var mors = '';
// 50ミリ秒ごとに割り込み処理
var timer = setInterval(function () {
if (remain > 0) {
// まだ割り込まない
remain--;
if (remain == 0) {
audio.pause();
audio.currentTime = 0;
}
return;
}
// オーディオを停止してリセット
audio.pause();
audio.currentTime = 0;
if (yoromors.length <= 0) {
// 終わり
clearInterval(timer);
return;
}
var onoff = yoromors.shift();
$('#yoro').val($('#yoro').val() + onoff);
if (onoff.match(/[すスス]/)) {
// 長音を再生する
audio.play();
remain = 4;
mors += '1';
} else if (onoff.match(/[っッッ]/)) {
// 単音を再生する
audio.play();
remain = 2;
mors += '0';
} else if (onoff.match(/[ー─-ー]/)) {
// 区切りなので、再生せず無音を作る
remain = 3;
// 1グループ分のモールスをデコードして、原文テキストに追記
$('#text').val($('#text').val() + revTable[mors]);
mors = '';
} else {
// モールスとして解読できなかった文字はそのまま原文テキストに転記
$('#text').val($('#text').val() + onoff);
}
}, 50);
$('#text').val('');
$('#yoro').val('');
});
</script>
</body>
</html>