趣味で宇宙開発を行う団体「リーマンサット・プロジェクト」がお送りする新春アドベントカレンダーです。
私はRSP-01という超小型人工衛星のチャット機能、地上システムを担当しております。
今年もショートケーキを作ったという話です。
ショートケーキの定義
https://jflute.hatenadiary.jp/entry/20180223/mastercurrent
昨年のショートケーキ
https://qiita.com/rsp-dgkz/items/83268377f8309ddb10f3
今年のショートケーキ
今回の記事は、モールス信号の必要性と少しだけWeb Audio APIについて書いています。
作ったショートケーキ
今回のショートケーキはモールス信号の勉強サイトです。
モールス信号なんて今の時代必要なの?と思われるかも知れませんが、必要なんです。一部の民にとって。
https://rsp-daigo.github.io/morse-manabu/
※Windows Chrome、iPhone Safariでのみ動作確認しています。
なぜ必要なのか
リーマンサットが趣味で創った人工衛星であるRSP-01は、2021年に打ち上がります。
宇宙に放出された暁には、衛星と通信したいじゃないですか。自分の手で。
この「自分の手」がポイントで、衛星へコマンドを送るには3級以上のアマチュア無線(以降3アマ)の資格が必要です。
3アマの試験問題には、モールス信号が出てきます。
巷にはモールス信号を覚えるためのサイトはいくつかあるのですが、私にフィットするものが無かったので作りました。試験が終わった後に。
人工衛星からはモールス信号が送信される
多くの人工衛星からはCWやビーコンと呼ばれるモールス信号が送信されています。
僕らのRSP-01もモールス信号を出します。
なぜモールス信号を使うかというと、主には以下の理由です。
- 特別なハードやソフトウェアが無くても可聴化できるため、公開すべき/したい情報に適している。聞こうと思えば誰でも聞ける。
- 電波状態が悪くとも人間の耳で解読できれば、情報を得ることができる。
- FMパケット通信に比べて、CWは衛星側の送信時の電力が少なくて済む。つまり、衛星の状態など確実に地上へ送信したい情報の送信に適している。
従って、人工衛星を運用するものにとってモールス信号は、3アマを取得した後も実際に必要なのです。
僕たち一部の必要な民!
ご要件
基本的な事項
- ブラウザで学習できる
- スマホで学習できる
出題方法
- モールス信号を文字化したものを表示する
- 合わせて音も出力する
解答方法
音の出力
今回はWeb Audio APIを使って、任意のモールス信号を出力することにしました。
ブラウザで音をオーディオを使うのなんて楽勝っしょ!なんて思ってましたが中々でした。
iPhoneで音が出なくなる
Mobile Safariでは、ユーザ操作を起点とした実行コンテキスでオシレータのstart()を呼ばなければならない制約があるとの事です。
出典(Apple公式の出展はサクッと見つけられませんでした)
iOSのMobile Safari でWeb Audio API を利用したサウンドが再生されない (タッチ制約による制限) - JavaScript プログラミング
開発の当初からこの情報は入手していたので意識してコーディングしていたのですが、この「実行コンテキスト」がどこかで切れてしまうのか、iPhoneでの連続した再生に難儀しました。
最終的には、タップ操作から出来るだけ近しい処理で window.webkitAudioContext を new することで安定しました。
以下が音を再生する箇所のソースです。
// 再生速度(現在は固定)
const MORSE_SPEED = 100;
// 長点
const MORSE_LONG = '-';
// Sleepメソッド化
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
export default class MorsePlayer {
constructor() {
this.aurdioRunnable = false;
this.audioReset = false;
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.oscillator = null;
}
/**
* 生成中か返す
*/
isAurdioRunnable() {
return this.aurdioRunnable;
}
/**
* 中断要求中か返す
*/
isAudioReset() {
return this.audioReset;
}
/**
* モールス信号を再生する
*/
async playMorseSignal(morseText) {
// 点と線でばらしてループ
const ch = Array.from(morseText);
const gainList = [];
for (const item of ch) {
// 長点は短点の3倍
let playTime = 1;
if (item === MORSE_LONG) {
playTime = 3;
}
// 再生速度の設定
playTime *= MORSE_SPEED;
gainList.push({ playTime, vol: 0.9 });
// 要素間のインターバル
const interval = 1 * MORSE_SPEED;
gainList.push({ playTime: interval, vol: 0 });
}
// 1セット再生し終えた後のインターバル
gainList.push({ playTime: 1000, vol: 0 });
// 再生実行
await this.playAudio(gainList);
}
/**
* オーディオを再生する
*/
async playAudio(gainList) {
try {
this.aurdioRunnable = true;
this.oscillator = this.audioCtx.createOscillator();
this.oscillator.type = 'sine';
this.oscillator.frequency.value = 440;
const gain = this.audioCtx.createGain();
gain.connect(this.audioCtx.destination);
this.oscillator.start();
this.oscillator.connect(gain);
// 指定要求があったら抜ける
while (!this.audioReset) {
let totalPlayTime = 0;
const baseTime = this.audioCtx.currentTime;
for (const gainItem of gainList) {
gain.gain.setValueAtTime(gainItem.vol, baseTime + totalPlayTime);
totalPlayTime += gainItem.playTime / 1000;
}
// 停止要求をチェックするため再生時間分Slepp
await sleep(totalPlayTime * 1000);
}
this.oscillator.stop();
this.oscillator = null;
this.audioReset = false;
this.aurdioRunnable = false;
} catch (e) {
alert(e);
}
}
/**
* 再生の停止を要求する
* @param {function} callback コールバック
*/
async stop(callback) {
console.log('stopAudio');
// 停止要求を通知
this.audioReset = true;
// 停止を待つ
while (this.audioReset) {
await sleep(100);
}
// 停止後のコールバック呼び出し
callback();
}
}
playMorseSignal()に「・--」などのモールス信号を文字列化したものを渡します。
「・--」を分割し、以下の配列を作成します。
[0] 100ms再生(・)
[1] 100ms無音
[2] 300ms再生(-)
[3] 100ms無音
[4] 300ms再生(-)
[5] 300ms無音(次のモールスとのインターバル)
配列をplayAudio()に渡し、停止要求があるまで繰り返し再生し続けます。
出来てしまえば簡単ですが、やはり色々とありますね。
ソースはこちらです。
https://github.com/rsp-daigo/morse-manabu
明日は @hokke_mirin さんの「パソコンの自作って難しいのか?」です。
リーマンサット・プロジェクトは「普通の人が集まって宇宙開発しよう」を合言葉に活動をしている民間団体です。
興味を持たれた方は https://www.rymansat.com/join からお気軽にどうぞ。
超小型人工衛星の開発の1から10までを経験できるという恐ろしいほど甘美な団体です。