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); }