FreeBSDでもモールス練習したいぞ
昼時に女房が居ないと、しょうがなしに自炊を強いられる。面倒だけど、腹が減ると 性格が豹変するらしいので、重い腰を上げる事になる。
近頃のマイブームは、Pasco社製の「麦のめぐみ・イングリッシュ・マフィン」だ。 4個入りのやつで、168円。一回で、2個食べるから、一食84円か。
これに、有り合わせのハムかチーズ。大体、一食で50円ぐらいだろうか。後は、 トマトかレタスか、無ければ、私の冷蔵庫と呼んでいるコンビニから、100円相当の サラダを仕入れてくる。
これだけだと、喉が詰まるので、理研のワカメスープ(一食30円)という献立だ。 えっと、これで幾らになるんだ? 264円か。外に食べに行くより安いし、死ぬほど 暑い車(orママチャリ)に乗る事も無いので、体にも家計にも優しいな。
マフィンをトースターで軽く焼くのと、お湯を沸かすのは、パラレルに行なう。 トースターは3分のタイマー、お湯は湧き上がると笛で教えてくれるので、Done Flag が上がってくるまでの間に、ハムを用意したり、皿を用意したりしてればいい。
3回ぐらい、自炊してやっとこの手順を会得したおいらは、コンカレント・プログラミングに向いていないのだろうか? まあ、一度手順が確立してしまえば、終了時間が 予測出来るんで、いいけど。(sakaeの3分間クッキング)
FreeBSDでもモールス可能?
と思って、Windowsで動いてた Haskellのコードを、GHC 6.8.3の環境へ持ってきました。まずは、Ehのモジュールをコンパイルしておくんだな。それが終ったら、morse.hsの コンパイルか。異なる環境でも動く事を確認しておかないと、たまたま動いたのが 正解だったと誤認しちゃいますので。
[sakae@fb ~/mo]$ ghc --make eh.hs
[1 of 1] Compiling Eh ( eh.hs, eh.o )
[sakae@fb ~/mo]$ ghc --make morse.hs
morse.hs:9:7:
Could not find module `Eh':
Use -v to see a list of the files searched for.
あぁー、言わんこっちゃない。何で? ちゃんと、eh.o と eh.hi は、出来上がってるよ 。 しゃーないので、ghcの解説をみてみました。そしたら、ふふふ、-i オプションで、 モジュールのあるdirを指定するんだと。デフォは、今居るdirだとさ。おかしくないかい?
まー、-i の威力を確認してみっか。
[sakae@fb ~/mo]$ ghc -i /home/sakae/mo --make morse.hs
<command line>:
Could not find module `/home/sakae/mo':
it is not a module in the current program, or in any known package.
少し、挙動が変わりましたね。で、ヒントっぽいのが出てる。しょうがないので、ヒントに従ってみるか。
[sakae@fb ~/mo]$ ghc -i /home/sakae/mo/eh --make morse.hs [2 of 2] Compiling Main ( morse.hs, morse.o ) morse.hs:30:26: Not in scope: `rawSystem'
一歩前進一歩後退で、足踏みしてます。rawSystem って、GHC 6.8.3には無いの?
Prelude> :mo System.Process Prelude System.Process> :bro
してみましたが、取り扱っていなかったよ。全く、油断も隙もあったもんじゃないな。
こうなったら、方針転換で、同じ関数言語科に属する、別のものにスイッチしちゃうか。この所、出番が無いmatzLispなんてのは、どうだろう。同じ科目だから、悩む事は無いよね。
こうやって、あちこちにほいほいと浮気してると、Haskellのあの人やSchemeのあの方や rubyのあの方に、愛想つかされるよ。いやいや、「汝の妻を平等に愛せよ」の博愛精神が 発露してるだけですから、浮気と違いまっせ。
考えてみたら、何を使うかなんて些細な問題よりも、何の方が問題なんだ。何何何?
それはね、Windows特有なAPIを使いまくりで作っちゃったモールス発信器(mosc)相当 を、FreeBSDへ移植出来るかって事。突き詰めれば、FreeBSDに、Beepとsleepが 有るか、なんだ。
FreeBSDでの調査
調査と言ったら、そりゃ man ですわな。
man beep
名称
beep, flash - ncurses ベルおよび画面フラッシュルーチン
書式
#include <ncurses.h>
int beep(void);
int flash(void);
はい、却下。sleepの方はどうだ。
man -k sleep sleep(1) - suspend execution for an interval of time sleep(3) - suspend process execution for an interval measured in seconds usleep(3) - suspend process execution for an interval measured in microseconds
こちらの方は、何とか見込みありそうだ。
だけど、beep相当が無いんじゃ、絶望的だあ。こういう時は、殻に籠っていないで、情報の海へと航海してみる事にします。
えっと、検索ワードは、"FreeBSD 音を鳴らす" ぐらいかな?
そしたら、色々な雑音に紛れて、かすかに信号らしきものが飛び込んできました。
echo CDE > /dev/speaker
これで、ドレミ と speaker が、鳴ってくれるそうです。俄には信じられません。
/dev/speaker
でも信じるしかないので、早速、man speaker します。
名称
speaker, spkr - コンソールスピーカのデバイスドライバ
書式
pseudo-device speaker
#include <machine/speaker.h>
解説
スピーカデバイスドライバは、 FreeBSD が走っている IBM-PC 互換 PC 上で、ア
プリケーションがコンソールスピーカを制御できるようにします。
いかなるときでも、このデバイスをオープンできるのはただ 1 つのプロセスだけ
です。このため、このデバイスのロックと解放には、 open(2) と close(2) を使
用します。他のプロセスがデバイスを独占している時にオープンしようとすると
、 EBUSY エラーを示して -1 を返します。デバイスへの書き込みは、 ASCII 文
字で単純にメロディを表記した演奏文字列 (`play string') として解釈されます
。 ioctl(2) リクエストによる任意の周波数の発音もサポートされています。
アプリケーションは、スピーカのファイル記述子に対して ioctl(2) 呼び出しを
することにより、スピーカドライバを直接制御可能です。 ioctl(2) インタフェ
ースについての定義は /usr/include/machine/speaker.h にあります。これらの
呼び出しに使われる tone_t 構造体には、周波数 (ヘルツ) と持続時間 (1/100
秒単位で) を指定する 2 つのフィールドがあります。周波数 0 は、休符と解釈
されます。
演奏文字列の語法は IBM Advanced BASIC 2.0 の PLAY 文の習慣を模倣していま
す。 PLAY 文の MB, MF, X 要素は時分割環境では役に立たないため除かれます。
`オクターブ追従' 機能とスラー記号は新しく追加されたものです。
へぇー、面白い機能があるんだ。これを手がかりに調べてみると、機能自身は、カーネルモジュールになっているので、rootになって、kldload speaker して、おく事。/etc/devfs.conf を編集して、一般ユーザーにも使えるようする事、が判明。(実際は、コメントを外すだけ)
スピーカーの試験としては、spkrtest(8) が、提供されてた。実行してみると、プレイリストが出てきて、どれかを選ぶと、曲を演奏してくれた。
って、事は、休符もあるわけで、そこらへんをどうやってるか、spkrtest のソースを 見れば分かるんじゃないかと、期待した。そしたら、shellスクリプトだったよ。 ちょっと悔しいけど、一部を載せておく。
/usr/bin/dialog --title "Speaker test" --checklist \
"Please select the melodies you wish to play (space for select)" \
-1 -1 10 \
reveille "Reveille" OFF \
contact "Contact theme from Close Encounters" OFF \
dance "Lord of the Dance (aka Simple Gifts)" OFF \
loony "Loony Toons theme" OFF \
sinister "Standard villain's entrance music" OFF \
rightstuff "A trope from 'The Right Stuff' score by Bill Conti" OFF \
toccata "Opening bars of Bach's Toccata and Fugue in D Minor" OFF \
startrek "Opening bars of the theme from Star Trek Classic" OFF \
2> ${choices} || cleanExit 0
...
toccata)
title="opening bars of Bach's Toccata and Fugue in D Minor"
music="msl16oldcd4mll8pcb-agf+4.g4p4<msl16dcd4mll8pa.a+f+4p16g4"
;;
startrek)
title="opening bars of the theme from Star Trek Classic"
music="l2b.f+.p16a.c+.p l4mn<b.>e8a2mspg+e8c+f+8b2"
;;
esac
echo "Title: ${title}"
echo ${music} > ${speaker}
しょうがないので、dev/speakerのソースを見てみる。
more spkr.c
tone(thz, centisecs)
unsigned int thz, centisecs;
{
divisor = timer_freq / thz;
spkr_set_pitch(divisor);
/* turn the speaker on */
ppi_spkr_on();
/*
* Set timeout to endtone function, then give up the timeslice.
* This is so other processes can execute while the tone is being
* emitted.
*/
timo = centisecs * hz / 100;
if (timo > 0)
tsleep(&endtone, SPKRPRI | PCATCH, "spkrtn", timo);
ppi_spkr_off();
.....
/**************** PLAY STRING INTERPRETER BEGINS HERE **********************
*
* Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
* M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
* tracking facility are added.
* Requires tone(), rest(), and endtone(). String play is not interruptible
* except possibly at physical block boundaries.
*/
内部発振機の周波数をセットしてから、スピーカーをONし、後は時間になるまで お休み、時間が来たら、スピーカーをOFFしてるだけだ。後、時間待ちのrestなる 関数も定義されてた。 後半部分と言うか、このコードの大部分は、BASICからのMML移植版か。
上のコード中に出てくる hz は、FreeBSDのRTC割り込み頻度だったな。1秒間に100回だった はずだから、周期に直すと 10msになる。ここから、設定出来る時間分解能は、10ms になるんだ。
文房具用OSである、Windowsで、Beepの時間分解能は、1msとなっているけど、CPU負荷に よって、大いにずれる事が観測されてる。メッセージキューに入った仕事の依頼を順番に処理する方式では、前の仕事が長びけば、後のやつは遅れてしまうのはしょうがない。 その点、FreeBSDの方が、ずっと正確っぽいぞ。
いよいよ移植
デバドラのソースまで見てしまったけど、正直 ioctlの使い方の参考になりそうなのは、ありませんでした。しょうがないので、また、情報の海をさ迷ってみると、かすかに、 FreeBSDにも、/usr/ports/audio/beep が、あるよと言う信号を受信できました。 で、カンニングさせて頂きました。答案用紙は、こちらです。
何点頂けますか? エラーチェックサボっているから、ー20点は、免れません。でも、よかれと思って APIを追加したので、+5点。以上が、事故評価です。どうでっしゃろ、裁判員の皆様。
// mosc (Morse generator) for FreeBSD
// ex. mosc '.- .- | -... ' ( morse code = "aa b" )
// . : dot, - : dash, SPACE : char separator, | : word separator
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <dev/speaker/speaker.h>
tone_t tone_description; // freq and duration
int freq = 1000; // tone freq in [Hz]
int t = 10; // dot length in x10 [MS]
int t3 = 30; // dash length in x10 [MS]
int t2 = 20; // char,word separator length in x10 [MS]
set_tn(int t){
t3 = t * 3;
t2 = t * 2;
}
dev_con(){
int fd;
fd = open("/dev/speaker", O_WRONLY);
ioctl(fd, SPKRTONE, &tone_description);
close(fd);
}
tone(int freq, int t){
tone_description.frequency = freq;
tone_description.duration = t;
dev_con();
}
osc(char *str){
int c;
while ( (c = *str++) ){
switch (c) {
case '.': // dot
tone(freq, t);
tone(0, t);
break;
case '-': // dash
tone(freq, t3);
tone(0, t);
break;
case '\'': case ' ': // char separator
tone(0, t2);
break;
case '!': case '|': // word separator
tone(0, t2);
break;
default:
exit(2); // Bad char, soon exit
break;
}
}
}
main(int argc, char *argv[]){
if (argc > 3){ freq = atoi(argv[3]); }
if (argc > 2){ t = atoi(argv[2]) / 10; set_tn(t); }
if (argc > 1){ osc(argv[1]); }
if (argc == 1){
printf("Usage: mosc '.- |' [speed freq]\n");
exit(1);
}
exit(0);
}