SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|

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を起動する。

$ ./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のパラメータがおかしいって?

  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を起動する。

$ ./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

  あで? 再びioctlのパラメータがおかしいって? そんなわけないんだが……デバッグモードで走らせてみる。

$ 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

  ソースビルドに必要なパッケージが大量に不足している。yum installで全部入れたら、ソースビルドに成功した。修正すべきコードを探す。

# 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 }

  最後のところで32ビット幅で切ってしまおう。パッチを作る。

# 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-* 

  5度目の正直。うりゃッ!!

$ padsp -d ./melod

  ようやく、動いたっぽい。改めて、PCのキーボードを、そのままピアノのキーボード化する「konk」を立ち上げる。

$ ./konk

  音が出たッ……と思った矢先にmelod側が落ちた。バッファサイズが足りなかったらしい。適当に倍に増やす。

 	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

  MMLにコードを追記して、MIDIファイルにエンコードする。

$ cat connect.mml | ./plexor

  と、MIDIファイルを生成したところでフト気づいた。Linuxで、MIDIファイルを演奏させたい場合、誰にやらせればいいんだ? 試しにファイルをOperaにツッコんだところ、Totemらしいヤツが出てきてgstreamer-plugins-bad-free-extrasを入れろと言われた。

# yum install gstreamer-plugins-bad-free-extras

  これまた、大量のパッケージが必要に。全部入れ……ようとしたら、gstreamer-plugins-goodと衝突して入らない。なんじゃそりゃ。

  そっち方面をあきらめ、いろいろと探すと、timidityという単独のMIDIデコーダが見つかった。入れてみる。

# yum install timidity++

  再生してみる。

$ timidity midi/connect.mid

  おぉ、ちゃんと音が出た。悪くない。悪くはないぞぉ。<midiを再生する>

  ……というようなことをやりつつ、ゴールデンウィークは過ぎゆくのであった。でも、実はこれは「あること」に対する前哨戦だったりする。つづく。