SVX日記
2011-05-10(Tue) チープなDTMアプリLinuxへ
ふと思うところがあって、5年近く以前に自作したDTMアプリを引っ張り出してきた。当時は、まだWindowsを使っていたっけなぁ。主にRubyで書き、Cygwin上で動かし、実際に作曲に利用したアプリだ。プログラムの構成どころか、使い方も含めて、かなり記憶が曖昧になっているが、そういう場合こそブログの記録が役に立つ。自分のブログからパッケージをダウンロードして、説明を読みながらLinux上で動かしてみる。
$ ./melod
./melod:17:in `initialize': No such file or directory - /dev/dsp (Errno::ENOENT)
from ./melod.org:17:in `open'
from ./melod.org:17
# ls /dev/dsp
ls: cannot access /dev/dsp: そのようなファイルやディレクトリはありません
素直に動かネェだろうなぁ、と思ったら、いきなり動かネェ。そもそも/dev/dsp自体がネェってさ。/dev/dspって、ALSAの前身の、OSSってヤツが提供しているんじゃなかったっけ? デバイスファイルなんだから、カーネルモジュールなんじゃネェの?
# find /lib/modules/`uname -r` -name "*oss*"
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/drivers/scsi/osst.ko
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/sound/core/oss
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/sound/core/oss/snd-pcm-oss.ko
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/sound/core/oss/snd-mixer-oss.ko
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/sound/core/seq/oss
/lib/modules/2.6.32.16-150.fc12.x86_64/kernel/sound/core/seq/oss/snd-seq-oss.ko
# modprobe snd-pcm-oss
# ls /dev/dsp
crw-rw----+ 1 root audio 14, 3 2011-05-09 09:42 /dev/dsp
# chmod 666 /dev/dsp
……って思ったら、思った通りだった。それっぽいモジュールをロードしたら、アッサリと/dev/dspが出現した。ちなみに/dev/dspは、音の波形(PCM)データを書き込むと音が鳴る、という概念のデバイスファイルだ。
$ ./melod
7fff0009
./melod:24:in `ioctl': Invalid argument - /dev/dsp (Errno::EINVAL)
from ./melod.org:24
from ./melod.org:17:in `open'
from ./melod.org:17
ioctlってのは、デバイスに対する「読み書き以外のアレコレ」を一手に引き受ける、ジャンク箱みたいなヤツだ。Cygwinの環境と、Linuxの環境とでは、要求コードのマジックナンバが違っているとか? 定義を探してみる。
$ grep -r SNDCTL_DSP_SETFRAGMENT /usr/include
/usr/include/linux/soundcard.h:#define SNDCTL_DSP_SETFRAGMENT _SIOWR('P',10, int)
/usr/include/linux/soundcard.h:#define SOUND_PCM_SETFRAGMENT SNDCTL_DSP_SETFRAGMENT
$ vi /usr/include/linux/soundcard.h
536 #define SNDCTL_DSP_RESET _SIO ('P', 0)
537 #define SNDCTL_DSP_SYNC _SIO ('P', 1)
538 #define SNDCTL_DSP_SPEED _SIOWR('P', 2, int)
539 #define SNDCTL_DSP_STEREO _SIOWR('P', 3, int)
540 #define SNDCTL_DSP_GETBLKSIZE _SIOWR('P', 4, int)
541 #define SNDCTL_DSP_SAMPLESIZE SNDCTL_DSP_SETFMT
542 #define SNDCTL_DSP_CHANNELS _SIOWR('P', 6, int)
543 #define SOUND_PCM_WRITE_CHANNELS SNDCTL_DSP_CHANNELS
544 #define SOUND_PCM_WRITE_FILTER _SIOWR('P', 7, int)
545 #define SNDCTL_DSP_POST _SIO ('P', 8)
546 #define SNDCTL_DSP_SUBDIVIDE _SIOWR('P', 9, int)
547 #define SNDCTL_DSP_SETFRAGMENT _SIOWR('P',10, int)
$ vi ioctl_resolv.c
#include <stdio.h>
#include <linux/soundcard.h>
int main() {
printf("%x\n", SNDCTL_DSP_SETFRAGMENT);
printf("%x\n", SNDCTL_DSP_SPEED);
printf("%x\n", SNDCTL_DSP_SETFMT);
printf("%x\n", SNDCTL_DSP_CHANNELS);
printf("%x\n", SNDCTL_DSP_GETFMTS);
printf("%x\n", SNDCTL_DSP_GETOSPACE);
}
$ cc -o ioctl_resolv ioctl_resolv.c
$ ./ioctl_resolv
c004500a
c0045002
c0045005
c0045006
8004500b
8010500c
$ ./melod
./melod:17:in `initialize': Device or resource busy - /dev/dsp (Errno::EBUSY)
from ./melod:17:in `open'
from ./melod:17
ビジー出た。これも、やや想定通り。最近のLinuxはALSAの上にPulseAudioというサウンドデーモンが載っており、各アプリの音声出力をソフト的にミキシングしているので、直接に/dev/dspにアクセスするのはよろしくない。
RubyでPulseAudioに対応するには……と調べたところ、OSS用のラッパーが用意されており、そいつを噛ませば、従来のOSS向けコードを、そのままPulseAudio対応にすることができるらしい。
$ padsp ./melod
7fff0009
./melod:25:in `ioctl': Invalid argument - /dev/dsp (Errno::EINVAL)
from ./melod:25
from ./melod:17:in `open'
from ./melod:17
$ padsp -d ./melod
utils/padsp.c: dsp_open()
utils/padsp.c: fd_info_new()
utils/padsp.c: dsp_open() succeeded, fd=3
7fff0009
utils/padsp.c: unknown ioctl 0xffffffffc004500a ★
utils/padsp.c: freeing fd info (fd=-1)
utils/padsp.c: Draining.
./melod:25:in `ioctl': Invalid argument - /dev/dsp (Errno::EINVAL)
from ./melod:25
from ./melod:17:in `open'
from ./melod:17
なんか、頭に「f」がいっぱい付いてしまっている。ioctlの引数って、32ビット幅じゃないんか? 勝手に64bit拡張して渡してしまうRubyもRubyだが、そのまま受け取って処理しようとするpadspもpadspって感じ。
正直、これには参った。Ruby側でいろいろやってみたが、どうにもならない。どっちが原因かわからんが、どっちかをどうにかする必要がある。Rubyをイジるとオオゴトになりそうなので、padsp側をどうにかしてみることにする。要するに、padsp側のソースコードをイジって、処理の直前で32ビット幅で切ってしまうように修正し、パッケージを作り直すわけだ。ソースをダウンロードしてビルドする。
$ which padsp
/usr/bin/padsp
$ rpm -qf /usr/bin/padsp
pulseaudio-utils-0.9.21-4.fc12.x86_64
# yumdownloader --source pulseaudio-utils
# rpm -ivh pulseaudio-0.9.21-6.fc12.src.rpm
# rpmbuild -bp rpmbuild/SPECS/pulseaudio.spec
# cd rpmbuild/BUILD/pulseaudio-0.9.21/
# ctags -R *
# grep -r "unknown ioctl" *
src/utils/padsp.c: debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request);
# vi src/utils/padsp.c
1497 static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
1498 int ret = -1;
1499
1500 switch (request) {
1501 case SOUND_MIXER_READ_DEVMASK :
1502 debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_DEVMASK\n");
1503
1504 *(int*) argp = SOUND_MASK_PCM | SOUND_MASK_IGAIN;
1505 break;
1638 default:
1639 debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request);
1640
1641 *_errno = EINVAL;
1642 goto fail;
1643 }
1644
1645 ret = 0;
1646
1647 fail:
1648
1649 return ret;
1650 }
1912 static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
1913 int ret = -1;
1914
1925 switch (request) {
1926 case SNDCTL_DSP_SETFMT: {
1927 debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFMT: %i\n", *(int*) argp);
1928
1929 pa_threaded_mainloop_lock(i->mainloop);
1930
1931 if (*(int*) argp == AFMT_QUERY)
1932 *(int*) argp = map_format_back(i->sample_spec.format);
1933 else {
1934 map_format((int*) argp, &i->sample_spec);
1935 free_streams(i);
1936 }
1937
1938 pa_threaded_mainloop_unlock(i->mainloop);
1939 break;
1940 }
2020 case SNDCTL_DSP_SETFRAGMENT:
2021 debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFRAGMENT: 0x%08x\n", *(int*) argp);
2022
2023 pa_threaded_mainloop_lock(i->mainloop);
2024
2025 i->fragment_size = 1 %lt;%lt; ((*(int*) argp) & 31);
2026 i->n_fragments = (*(int*) argp) >> 16;
2027
2028 /* 0x7FFF means that we can set whatever we like */
2029 if (i->n_fragments == 0x7FFF)
2030 i->n_fragments = 12;
2031
2032 free_streams(i);
2033
2034 pa_threaded_mainloop_unlock(i->mainloop);
2035
2036 break;
2304 default:
2305 /* Mixer ioctls are valid on /dev/dsp aswell */
2306 return mixer_ioctl(i, request, argp, _errno);
2307
2308 inval:
2309 *_errno = EINVAL;
2310 goto fail;
2311 }
2312
2313 ret = 0;
2314
2315 fail:
2316
2317 return ret;
2318 }
2323 int ioctl(int fd, unsigned long request, ...) {
2347 if (i->type == FD_INFO_MIXER)
2348 r = mixer_ioctl(i, request, argp, &_errno);
2349 else
2350 r = dsp_ioctl(i, request, argp, &_errno);
2359 return r;
2360 }
# cd ..
# mv pulseaudio-0.9.21 pulseaudio-0.9.21.my
# rpmbuild -bp /root/rpmbuild/SPECS/pulseaudio.spec
# mv pulseaudio-0.9.21 pulseaudio-0.9.21.org
# cd pulseaudio-0.9.21.my/
# vi src/utils/padsp.c
# cd ..
# diff -r -U 3 pulseaudio-0.9.21.org pulseaudio-0.9.21.my
diff -r -U 3 pulseaudio-0.9.21.org/src/utils/padsp.c pulseaudio-0.9.21.my/src/utils/padsp.c
--- pulseaudio-0.9.21.org/src/utils/padsp.c 2011-05-09 12:46:22.000000000 +0900
+++ pulseaudio-0.9.21.my/src/utils/padsp.c 2011-05-09 12:48:46.000000000 +0900
@@ -2345,9 +2345,9 @@
}
if (i->type == FD_INFO_MIXER)
- r = mixer_ioctl(i, request, argp, &_errno);
+ r = mixer_ioctl(i, request & 0xffffffff, argp, &_errno);
else
- r = dsp_ioctl(i, request, argp, &_errno);
+ r = dsp_ioctl(i, request & 0xffffffff, argp, &_errno);
fd_info_unref(i);
# vi ../SPECS/pulseaudio.spec
# cd ../SPECS/
# cp pulseaudio.spec pulseaudio.spec.org
# vi pulseaudio.spec
# diff -U 3 pulseaudio.spec.org pulseaudio.spec
--- pulseaudio.spec.org 2010-11-21 08:43:14.000000000 +0900
+++ pulseaudio.spec 2011-05-09 12:53:34.000000000 +0900
@@ -69,6 +69,7 @@
Patch60: 0061-esd-simple-use-pa_memblockq_pop_missing.patch
Patch61: 0062-core-rework-how-stream-volumes-affect-sink-volumes.patch
Patch62: 0063-legacy-dir.patch
+Patch63: 0064-for-melod.patch
URL: http://pulseaudio.org/
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: m4
@@ -342,6 +343,7 @@
%patch60 -p1
%patch61 -p1
%patch62 -p1 -b .legacyDir
+%patch63 -p1
%build
autoreconf
# cd ../BUILD
# diff -r -U 3 pulseaudio-0.9.21.org pulseaudio-0.9.21.my > ../SOURCES/0064-for-melod.patch
# rpmbuild -ba /root/rpmbuild/SPECS/pulseaudio.spec
# rpm -Fvh ../RPMS/x86_64/pulseaudio-*
$ padsp -d ./melod
$ ./konk
ringbuf = Array.new(fragsize * 24, 128) # リングバッファ(みたいに使う)
とりあえず★マギカとして、ClariSの「コネクト」の冒頭部分について「konk」を使って音を採ってみる。コードはここを参考にして……と、思ったら「C6」とか「Dsus4」とか未対応のコードがあるではないか。この辺りの概念を作ったのはずいぶん前のことなので、音に関する知識が脳からページアウトしてしまっている。Wikipediaを読んで和音や音程についての情報の再呼び出しを図る。
*** chord.rb.u8.org 2011-05-09 13:19:48.000000000 +0900
--- chord.rb 2011-05-06 00:46:15.000000000 +0900
***************
*** 32,43 ****
--- 32,50 ----
# Minor-6th-chord :Cm6 根音+短3度+長3度+長2度 (長6度)
# Aug.-7th-chord :Caug7 C7+5 根音+長3度+長3度+短3度 (短7度?)
+ # Suspended-4th-chord :Csus4 根音+完全4度+長2度
+ # 7th-Suspended-4th-chord :C7sus4 根音+完全4度+長2度++短3度
+
@@RULES = Hash[
'', [0, 4, 3],
'm', [0, 3, 4],
'7', [0, 4, 3, 3],
'M7', [0, 4, 3, 4],
'm7', [0, 3, 4, 3],
+ '6', [0, 4, 3, 2],
+ 'm6', [0, 3, 4, 2],
+ 'sus4', [0, 5, 2],
+ '7sus4', [0, 5, 2, 3],
]
@@OCTAVES = Array.new
$ cat connect.mml | ./plexor
と、MIDIファイルを生成したところでフト気づいた。Linuxで、MIDIファイルを演奏させたい場合、誰にやらせればいいんだ? 試しにファイルをOperaにツッコんだところ、Totemらしいヤツが出てきてgstreamer-plugins-bad-free-extrasを入れろと言われた。
# yum install gstreamer-plugins-bad-free-extras
# yum install timidity++
$ timidity midi/connect.mid
おぉ、ちゃんと音が出た。悪くない。悪くはないぞぉ。<midiを再生する>