morseplayer
Table of Contents
IBM BASIC 2.0's PLAY statement
Q: 演奏データの意味を解説して。
A1: IBM BASIC 2.0の`PLAY`文は、音楽を演奏するためのコマンドで、文字列内の音符やコマンドによって音階やリズムを指定できます。あなたが示した音楽データは、バッハの「トッカータとフーガ ニ短調」を模したメロディが`PLAY`文を使って表現されています。
### 各部分の解説:
- `msl16`:
- 「M」コマンドは音符の長さを指定します。
- `msl16`はスラー(連続する音符を滑らかに演奏)を適用しつつ、16分音符の長さで音を演奏することを意味します。
- `old`:
- 「O」コマンドはオクターブを指定します。
- `o`はオクターブ、`l`は音の長さの指定で、`old`は低めのオクターブで演奏されていることを示しています。
- `cd4`:
- `C`と`D`は音符の音階を表します。Cはド、Dはレです。`d4`はDの音を4分音符として演奏することを意味します。
- `mll8pcb-agf+4`:
- `M`コマンドは音符の長さを変更します。`mll8`は8分音符の長さに切り替える指示です。
- `p`は休符(音を鳴らさない)。
- `cb-ag`はC、B、A、G音を連続して演奏することを示します。
- `f+4`はF#(嬰ヘ音)を4分音符で演奏します。
- `.g4p4`:
- `.`は音符の長さを1.5倍にします(付点音符)。
- `g4`はG音を4分音符で演奏する指示で、`p4`は4分休符を意味します。
- `<msl16dcd4mll8pa.a+f+4p16g4`:
- `<`はオクターブを1つ下げる指示です。
- その後は、先ほどと同様にD、C、Dの音を演奏し、さらにA、F#を含む音符が続いています。
### 全体のまとめ: このデータは、`PLAY`文によってバッハの「トッカータとフーガ ニ短調」の 一部を再現するためのものであり、オクターブ、音符の長さ、休符などの指示 を含む音楽データです。
A2: このIBM BASIC 2.0の`PLAY`文による演奏データは、Bachの「トッカータとフーガ ニ短調」を再現しようとするもので、`PLAY`文を使用して音楽を再生します。`PLAY`文は、文字列内に音の高さ、音の長さ、オクターブの変更、テンポなどを指定し、コンピュータの内蔵音源で音楽を演奏させるものです。この例では、`msl16oldcd4mll8pcb-agf+4.g4p4<msl16dcd4mll8pa.a+f+4p16g4`という文字列に基づいて音楽が生成されます。それぞれのコマンドが何を意味しているかを説明します。
### コマンドの詳細:
- `msl16`: 音符の長さを16分音符に設定します。
- `o`: オクターブを指定します。`o`に続く数字がオクターブの番号です。`o`の後の数字(例: `l`, `d`)が音符の高さやオクターブの変更を指示します。
- `l`: 長さを指定します。`l8`は8分音符、`l16`は16分音符です。
- `c`: 音階の「ド」を意味します。アルファベット(a〜g)はそれぞれ音階(ラからソ)に対応しています。
- `d`: 音階の「レ」を表しています。
- `p`: 休符を表します。`p4`は4分休符、`p16`は16分休符の意味です。
- `b-`: 音階の「シ♭」(Bフラット)を示します。`-`は音程が半音低いことを示しています。
- `f+`: 音階の「ファ#」(Fシャープ)を示します。`+`は音程が半音高いことを示しています。
- `<`: オクターブを下げる指示です。
- `>`: オクターブを上げる指示です。
### 解釈例:
- `msl16` で16分音符がデフォルトの音符の長さに設定され、`o`でオクターブが指定されています。
- `d4` は4分音符の「レ」を演奏します。
- `p4` は4分休符です。
- `f+4` は4分音符の「ファ#」を演奏します。
- `<` はオクターブを一つ下げる指示です。
これらのコマンドが組み合わさって、音の高さ、長さ、休符、オクターブの移動などが指示されており、Bachの「トッカータとフーガ ニ短調」の一部が再現されることになります。
まとめると、このデータはBASIC言語を使用してコンピュータで音楽を生成す るための具体的な命令が書かれており、Bachの「トッカータとフーガ」の一部 をシミュレートしているものです。
ChatGPTに質問したら、2つの回答を並列表示してくれたぞ。もったいないので、 そのまま載せてみた。
これが共通化されると MIDI になるんだな。昔、iMacを使っていた時、ローラン ドのキーボードをMIDI接続して、生演奏させたな。懐しい。
どうでもいいけど、 < ってスラーの事、> ってゴーヘィ ってのは、建築重機のクレーンのオペレー タの用語なんだね。NHKの解体キングダムによく出てきていた。
Interpret and play an item from a notation string
/sys/dev/speaker/spkr.c に解釈ルーチンが内蔵されてた。
static int pitchtab[] = { /* C C# D D# E F F# G G# A A# B*/ /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, }; playstring(char *cp, size_t slen) { : switch (c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': /* compute pitch */ pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; /* this may be followed by an accidental sign */ if (cp[1] == '#' || cp[1] == '+') { ++pitch; ++cp; slen--; } else if (cp[1] == '-') { --pitch; ++cp; slen--; } :
morseplayer
前回はFreeBSDのBEEPでモールスした。でも、予告されているように、BEEPは 消えゆく運命にある。ならば、オルターネィティブとしてOpenBSD界では、ど うなっているか調べておきたい。
趣味と実益を兼ねて、BEEPに拘らず、morseを探してみる。デフォでは入いいっ ていないので、portsをガサ入れ
ob$ cd /usr/ports/ ob$ make search key=morse Port: morseplayer-1.4p1 Path: audio/morseplayer Info: morse player Maint: The OpenBSD ports mailing-list <ports@openbsd.org> Index: audio L-deps: B-deps: R-deps: Archs: any HOMEPAGE= https://github.com/wrigjl/morseplayer (from Makefile)
幸いな事に、単独なアプリだった。安心して、 pkg_add
したよ。manすると、
ちょっとデフォがオイラーの感性と合致してない事が判明。
-d device use device for audio output instead of the default -f freq use freq for the wave frequency instead of the default: 720.0Hz -w wpm use wpm words per minute output; default is 5.
パッチが当ててあるmainの中で、pitch=500 wpm=15 に変更。
ob$ cc morseplayer.c -lm -lsndio ob$ echo hello CW world | ./a.out
sndio
OpenBSDの音制御は独特なので、簡単に利用できる様にsndioと言うライブラリィ が用意されている。morseplayerのソースもそれを利用しているので、パッチ が提供されてた。以下は、その一部。
/usr/ports/audio/morseplayer/patch-morseplayerc
- r = write(pars->sp_fd, e->pl_ptr, res); - if (r == -1) + r = sio_write(pars->hdl, e->pl_ptr, res); + if (r == 0) - if (afname == NULL) - afname = "/dev/audio"; + pars.hdl = sio_open(afname, SIO_PLAY, 0); + if (pars.hdl == NULL) + errx(1, "Could not open sndio device"); - f = open(afname, O_WRONLY, 0); - if (f == -1) - err(1, "open %s", afname); + sio_initpar(&par); + par.rate = 22050; + par.sig = 0; + par.bits = 8; + par.pchan = 1; + if (!sio_setpar(pars.hdl, &par)) + errx(1, "sio_setpar failed"); + if (!sio_getpar(pars.hdl, &par)) + errx(1, "sio_getpar failed");
afnameはコマンドラインからの引数。一般のOSに実装されている音システムと の違いが結構有るな。
それから、もっと大事な事は、/usr/bin/sndiod (と、その子分 sndiod: helper (sndiod)) すなわち、サウンドサーバー が裏でrunしてる事だ。走っていないと、
ob$ doas rcctl stop sndiod sndiod(ok) ob$ cat AB | ./a.out a.out: Could not open sndio device ob$ doas rcctl start sndiod sndiod(ok) ob$ cat AB | ./a.out ob$
sndiod
下記の様に4つのデバイスをサポートしてるみたい。同時使用は可能なのかな?
ob$ ls -l /dev/audio* crw-rw---- 1 root _sndiop 42, 0 Sep 26 08:46 /dev/audio0 crw-rw---- 1 root _sndiop 42, 1 Apr 13 06:26 /dev/audio1 crw-rw---- 1 root _sndiop 42, 2 Apr 13 06:26 /dev/audio2 crw-rw---- 1 root _sndiop 42, 3 Apr 13 06:26 /dev/audio3 crw-rw---- 1 root _sndiop 42, 192 Apr 13 06:26 /dev/audioctl0 crw-rw---- 1 root _sndiop 42, 193 Apr 13 06:26 /dev/audioctl1 crw-rw---- 1 root _sndiop 42, 194 Apr 13 06:26 /dev/audioctl2 crw-rw---- 1 root _sndiop 42, 195 Apr 13 06:26 /dev/audioctl3
これの元ネタ。
ob$ dmesg | grep az azalia0 at pci0 dev 27 function 0 "Intel 82801I HD Audio" rev 0x03: msi azalia0: codecs: Realtek ALC269, Intel/0x2802, using Realtek ALC269 audio0 at azalia0
sample
何はサンプルが欲しい。そこで思い出したのは、音の万能薬soxである。GUI屋 さんなら迷わずに、audadityなんだろうけど。。
ob$ sox -r 8000 -n output.wav synth 3 sine 300-3300 ob$ play output.wav output.wav: File Size: 96.1k Bit Rate: 256k Encoding: Signed PCM Channels: 1 @ 32-bit Samplerate: 8000Hz Replaygain: off Duration: 00:00:03.00 In:100% 00:00:03.00 [00:00:00.00] Out:24.0k [ | ] Hd:0.0 Clip:0 ob$ aucat -n -i output.wav -h raw -o z.raw
300ー3300Hzまで3秒かけてスウィープする正弦波を発生。それをplayしてみた。 この音源って、ピンクレディーのUFOに挿入されてた未来的な音だな、って 昭和におじさんが、懐かしがってます。 次にそれをaucatでヘッダー無しに変換してみた。
debug
aucatにはデバッグ・フラグが用意されてる。-dを重ねると、より詳細に挙動 を報告してくれる。
ob$ aucat -d -d -d -i output.wav output.wav: skipped unknown chunk output.wav,pst=cfg: play, chan 0:0, 8000Hz, s32le, bytes 80..96080, vol 8388608 default: 8000Hz, play 0:0, 33 blocks of 80 frames output.wav,pst=cfg: allocated 2640 frame buffer cmap: nch = 1, ostart = 0, onext = 0, istart = 0, inext = 0 dec: s32le, 1 channels output.wav,pst=ini: chain initialized output.wav,pst=run: started started default: cycle, prime = 33 dec: copying 80 frames cmap: adding 80 frames default: cycle, prime = 33 : default: cycle, prime = 33 output.wav,pst=stp: drained, done output.wav,pst=ini: stopped stopped output.wav,pst=ini: closed
上でrawに変換した。そしたら、あろう事かファイルが肥大しちゃった。どん な風に変換された?
ob$ aucat -dd -i z.raw z.raw,pst=cfg: play, chan 0:1, 48000Hz, s24le4msb, vol 8388608 default: 48000Hz, play 0:1, 36 blocks of 480 frames z.raw,pst=cfg: allocated 17280 frame buffer cmap: nch = 2, ostart = 0, onext = 0, istart = 0, inext = 0 dec: s24le4msb, 2 channels z.raw,pst=ini: chain initialized z.raw,pst=run: started started z.raw,pst=run: eof reached, stopping z.raw,pst=stp: drained, done z.raw,pst=ini: stopped stopped z.raw,pst=ini: closed
ヘッダーが除去されてしまうと、ただのデータだ。これでは演奏も何も有った ものじゃない。そこでaucatはローカルルールを適用するんだな。48Kサンプリ ング、ステレオ、サイン付きの24Bitリトルエンディアン。これ世の中一般の フォーマットなんでしょうか?
ちょいとgdbしてみると、このあたりでグルグルしてる。 playrec_cycle
() at aucat.c:1118
sndio hints on writing & porting audio code
こんな資料を発見した。ならば、、、
sample
#include <fcntl.h> #include <unistd.h> #include <sndio.h> #define SZ 9600 // from par.appbufsz int buf[SZ]; int main(){ struct sio_hdl *my; struct sio_par par; char *afname = "default"; char *sndf = "z.raw"; int fd; my = sio_open(afname, SIO_PLAY, 0); fd = open(sndf, O_RDONLY); sio_initpar(&par); par.rate = 48000; par.sig = 1; par.le = 1; par.msb = 0; par.bits = 24; par.pchan = 2; par.xrun = 0; sio_setpar(my, &par); sio_getpar(my, &par); sio_start(my); while(read(fd, buf, SZ)) { sio_write(my, buf, SZ); } sio_stop(my); close(fd); sio_close(my); }
例によって、全くエラーチェックをしていないと言う恐ろしいプログラム。コ ンパイルは、cc -g -O0 sample.c -lsndio
特徴的なのは、パラメータの設定部分。まず、initparして、初期値を準備(全 てが最大値になる)。変更したい部分を設定してから、ネゴシエーションされ た値を再確認(してないけど)。
それからは、スタートして、音ファイルから読んで、それをハードに転送、を、 EOFになるまで繰り返し。stopを呼ぶと、ハードに転送されてるデータが残っ ていたら音出しする。それが完了したら、デバイスを閉じて終了。
aucatのソースを見ると、 sio_pollfd()
sio_revents()
を駆使して、
bufferへ上手にデータを送り込んでいるようだ。オイラーの奴は、呆れるぐら
い手抜きバージョンです。まあ、それがハックですと自慢しておこう。
エラー祭り
どんな挙動をするか、ktrace/kdumpしてみた。
93592 a.out CALL read(4,0x3b1d2138,0x2580) 93592 a.out RET read 9600/0x2580 93592 a.out CALL write(3,0x7360dbf8,0x28) 93592 a.out RET write 40/0x28 93592 a.out CALL write(3,0x3b1d2138,0x780) 93592 a.out RET write 1920/0x780 93592 a.out CALL poll(0xcf7f2080,1,INFTIM) 93592 a.out RET poll 1 93592 a.out CALL read(3,0x7360dbd0,0x28) 93592 a.out RET read 40/0x28 93592 a.out CALL read(3,0x7360dbd0,0x28) 93592 a.out RET read 40/0x28 93592 a.out CALL read(3,0x7360dbd0,0x28) 93592 a.out RET read -1 errno 35 Resource temporarily unavailable
音ファイルから指示された9600バイトを読み出し、それをハードに転送してる んだけど、事情が有って、全部を転送できない。何度がやってるうちに、poll を使った交通整理が出てきた(sndioのボランティアだな)。そのうちに、整理ができなくなって、OSが文句 を言い出した。
音を聞いてる分には、分からないのだけどね。。。
で、errno 35 なんて言ってるので、sys/errno.h で、#define EAGAIN 35 を取得。そして、read(2) すると、
[EAGAIN] The file was marked for non-blocking I/O, and no data were ready to be read.
sys_read
の中で表われるかと思ったら、出てきていない。もっと下部かな?
ob$ grep EAGAIN -l *.c kern_fork.c kern_rwlock.c kern_sig.c subr_extent.c subr_suspend.c sys_futex.c sys_pipe.c sys_process.c sysv_msg.c sysv_sem.c sysv_shm.c uipc_syscalls.c vfs_lockf.c
さあ、何処でしょう? それより、open(2)に助け船は無いか?
O_RDONLY Open for reading only. O_WRONLY Open for writing only. O_RDWR Open for reading and writing. Any combination of the following flags may additionally be used: O_NONBLOCK Do not block on open or for data to become available. :
わざわざ O_NONBLOCK
を付けていないので、ちゃんとブロックするはずだけ
どなあ。ググったら、OpenBSD 7.5 で、これがでるようになったって報告が散
見された。何か変更でも有ったのだろうか?
エラー祭 (2)
ob$ hexdump z.raw : 01193e0 5a00 5f99 0000 0000 9800 58a8 0000 0000 01193f0 5c00 512a 0000 0000 ad00 492a 0000 0000
触れ込みでは、24Bitって事だけど、実際は32Bitじゃん。それからステレオっ て事だけど、右チャンネルには、音が入いっていない。そんな物なのか。
my = sio_open(afname, SIO_PLAY, 1); fd = open(sndf, O_RDONLY | O_NONBLOCK);
両ポートをノンブロックングしてみた。
ob$ time ./a.out 0m03.01s real 0m00.00s user 0m00.02s system ob$ time ./a.out 0m00.01s real 0m00.00s user 0m00.00s system
上の結果は、ブロッキングした時としない時のもの。ノンブロッキングだと、 ほとんど直ぐに終了。実際に音システムへの書き込みは、ほとんど実行されて いないので、音はしない結果になった。
次は、32Bitにしてみた。音がより忠実に再生されたぞ。まろやかである。 にわかオーディオ評論家じゃん。プラグは金メッキされた物を使用すると、音 が良くなりますと一緒だな。世の中に32BitのA/Dコンバーターなんて存在する んか? ハルネーション 幻覚である。 ハイレゾオーディオ
今度は、ステレオを止めてモノラルにしてみた。(par.pchan = 1)
ob$ time ./a.out 0m06.01s real 0m00.00s user 0m00.03s system
当然の事ながら、実行時間は2倍になった。それから、音も低くなった。再生 スピードが遅くなるんで、低温の方へシフトするのは、当然か。
par.le = 0 として、無理にビッグエンディアンとして解釈させると、もはや 雑音ですよ。ピンクレディーの華々しさは消え失せてしまいました。嗚呼、昭 和は遠くなりにけり。
だから、エンディアンにも気を使ってください、とAC JAPAN からのお願いで した。
beep
OpenBSD方面でbeepはどうなっているか? dev/pckbc/pckbd.c
/* * Keyboard can't beep directly; we have an * externally-provided global hook to do this. */ pckbd_bell(d->pitch, d->period, d->volume, 0);
一応スピーカーは存在してる。
ob$ ls -l /dev/sp* crw------- 1 root wheel 27, 0 Apr 13 06:26 /dev/speaker
SPKR(4) Device Drivers Manual SPKR(4) NAME speaker, spkr - console speaker device driver SYNOPSIS spkr0 at pcppi? DESCRIPTION The speaker device driver allows applications to control the built-in speaker on machines providing a PCPPI speaker interface. Only one process may have this device open at any given time; open(2) and close(2) are used to lock and relinquish it. An attempt to open() when another process has the device locked will return -1 with an EBUSY error indication. Writes to the device are interpreted as "play strings" in a simple ASCII melody notation. An ioctl() for tone generation at arbitrary frequencies is also supported.