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|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|07|08|

2005-03-07(Mon) FT232BM専用基板上に起つ!!

  今日は朝から再び花粉症対策の注射を打ちに行く。前回は注射のタイミングが早すぎたのか、花粉が飛ばないうちに薬が切れてしまったような気もするが、今はシッカリと鼻はズルズル、目はカユカユなのである。今度こそ薬の効き目が明らかになるであろ……ビャックショイ!! ズズーッ……くそー、花粉が憎けりゃ、杉山、杉本、杉良太郎、ケイン・コスギまで憎いわ。がるるる。

  帰りがけにつくば市内で対向車線をチラッと横切る、黄色のひらべったい……SVXッ!! 発見ッ!! 黄色という色も目に鮮やかだけど、視界の隅は「ひらべったい」という形状に反応したのであった。天井とトランクの上が黒だったので、余計にひらべったく感じるのかもしれん。そーか、SVXはひらべったかったのか。なるほどぉ。なんにせよ、もう少し観察したかったなぁ。

  それはそうと昨日、見事なタイミングで役者が揃ったので、今日はFT232BMを専用基板上に取り付けてみるのである。QFP-32というパッケージはピン間0.8mmという狭さであり、これだけの精密なハンダ付けはオイラの未経験の領域である。やはり昨日手配した新兵器のハンダゴテCXR-30を得物にとりゃーッ!!

  画像の説明

  意外とイケるじゃん!! というか、PCBにあらかじめ塗布してあるハンダを活用しつつ、コテ先に少しハンダを含ませ、各ピンをウリウリしただけである。ひととおり作業が終わった時点では、それでも何ピンか浮いていたが、ルーペで確認しては、ウリウリ、テスターでチェックしては、ウリウリするうち、そこそこキレイ(?)にハンダ付けができたようだ。

  画像の説明

  ちなみに、オイラはカメラも趣味なので、ルーペにはNikonのAi50mm1.8Fを逆からノゾいて代用にした。また、今回は基板の撮影に三脚とマクロスライダを動員してみた。道具が揃っていれば安いデジカメでもかなり本格的なマクロ撮影ができるってモンよ。

  画像の説明

  難関であるFT232BMの取り付けを完了したら、あとは残りの部品をチャチャっと取り付けて完成である。わぉ。なかなかシンプルかつコンパクトで美しい。初めて設計したPCBにしては、かなりよいデキだといえるのではないだろうか。

  そして意を決して、単体でPCにつないでみるのである。USB-BジャックにUSBケーブルを接続、拍手を3つ打った後、念仏を唱えながら、PC側のUSBポートに、イザ、接続ッ!!

  画像の説明

  ……カリカリカリ……オゴソかにノートPCのHDDが回転し「新しいハードウェアが見つかりました」出ましたッ!! デバイス名は「USB <-> Serial」ということで、とりあえず成功であろうッ!! 完全に動作確認したとはいえないが、ココまでは実にあっけなく到達である。いやー、楽しい。明日からは実際に通信をさせてみる予定である。うほほぃ。

  ちなみに、基板は一度に16枚もできてしまうので、たくさん余っている。欲しい人があったら必要な部品とセットで実費+手数料程度でお分けしようとも考えている。希望者はココまで……って、まだ動作確認すら終わってないのに気が早いっすか、そうすか……。


2006-03-07(Tue) 555を味見してみる

  帰りが遅いやらなんやらで、なかなか工作して遊ぶ時間がとれないのだが、それでも先日買ってきたLMC555を使ってみたいのである。電子工作を始めるに当たって3年弱ほど前に購入した「図解・わかる電子回路」だが、その中に「555は便利だよ」と笑っている犬のイラストが強烈に印象に残っていて、いつか555を使ってみたかったのである。

  画像の説明

  しかし、実際の本の記述は、なんだかいろいろと式が出てくるわ、実際のピンアサインが表に出てこないわで、どうも実感がなくて拒否反応を感じていたのだが、よくよく読んでみれば、そんなに難しいわけでもない。というか、本を読むより、データシートの回路サンプルを見つつ、事前知識として時定数、つまり抵抗とコンデンサを直列につないだ時に、与えた電圧の63%まで電圧が上がるまでの時間がrとcの積で求められる……具体的には1MΩと0.1uFだったら0.1秒とか……という知識を下敷きにしたらすぐに理解できた。

  で、RbとRaを適当に用意して、ブレッドボードで実験してみるのである。ブレッドボード上にたまたま残っていた抵抗とコンデンサ、10kΩ、220Ωと0.1uFを適当にプスプスと刺して、久々に出したペン型オシロで出力を確認してみる。

  画像の説明

  おぉ!! ちゃんと発振しとる!! こりゃ確かに555は便利であるッ!! ただ、こんな発振波形でいいんだっけ? なんとなく、時間と共に発振周波数が上がっていくんですが……例によって酔っ払ってきているので、オイラの周波数が下がってきてるからか……? というわけで、今日はココまで。


2007-03-07(Wed) USBメモリが熱いッ!!

  ひさびさの更新。自作サーバは完全移行を目指して暫定運用中なので、そっちのネタもないこたないが、それはもう少し事態が落ち着いてから書く予定。今日はいわゆるUSBメモリについて。

  画像の説明

  ずーっと前に購入した赤いUSBメモリ。デザインが痛く気に入っており、モバイルなcvsリポジトリを主な用途に、今日も毎日愛用している。愛用のあまり、キズだらけになっており、チェーンもブッちぎれてしまっているが、そのイモータルな感じは今も健在である。

  しかし、ずぅーッと気になっているコトがあった。それは発熱である。USBメモリっつったって、たかがメモリだ。それが挿してあるだけなのに、かなりホットになるのだ。持てないほどではないが、かなり熱い。外装で40度を超えている感じ。

  ずっと「PCとの相性が悪いのかな」などと思ってたが……ちょっと待てぇぃ!! んなワケがあるかッ!! USBメモリの温度を上げるPC側の要因なんて、電圧と読み書き動作の頻度くらいなもんだろう。USB端子の電圧は5V固定なのは言うまでもなく、たいして読み書きさせてないのも確認済み。つーことは、USBメモリに要因があるってことじゃねーか。だいたい、こちらも痛く気に入っており、愛用しているワイヤレスマウスのレシーバモジュールは、さっぱり熱くなったりはしない。

  なんで今まで気づかなかったんだろう。サクッとバッファローのサイトで仕様を確認してみる。ふむん。平均250mA以下ね。なるほ……

  「250mA!? しかも平均!?」

  ……そりゃ、高すぎだろうッ!? だって、一般的なUSBの給電容量はポートあたり500mAだ。最大値の半分が平均だなんて……ポータブルHDDみたいな回転モノで平均300mAってなら、まぁ納得もするケドもさ。そんな電力を消費して、コトリとも動きもしないUSBメモリなら、そりゃ発熱するしかないだろうさ。

  USB機器ということで、関連して調べてみる。わが社で扱っている(!?)USBシリアル変換チップの消費電流だ。松林コースのFT232BM、松原BコースのFT245BMでTyp.25mA。一世代前の松原コースのFT8U245AMでもMax.50mAだ。そりゃ、USB1.1のフルスピードに出さない仕様だし、メモリ自体の消費電力も入ってないが、チップ単体で25mAというのは、USB接続を前提とするチップとして常識の範囲内と思える。

  だいたい、ノートPCの電力は有限なのだ。250mAが稼働時間に及ぼす影響を考えてみる。オイラの使っているノートPCのバッテリの容量は定格11.1Vの5200mAhである。標準消費電力は18Wとあるので、18W / 11.1V = 1621mA、5200mAh / 1621mA = 3.207h、3時間チョイの稼働時間となり、だいたい計算が合う。

  ここで5Vで250mAなUSBメモリが挿さりっぱなしだとどうなるか。5V x 250mA = 1.25W……って、デカいなぁ、おい。19.25W / 11.1V = 1734mA、5200mAh / 1734mA = 2.998h、0.2時間。12分の短縮である。うーむ、思ったよりは小さいが、決して12分は小さくはないぞ。最初から12分短縮されるコトを知っていれば、挿しっぱなしにすることはタメラわれよう。

  こうなれば、消費電力の観点からUSBメモリを選んでみよう。しかし、そんな項目で製品を一覧することは価格.comでもできない……うまくググるのも困難……こんな時は人力検索はてなに質問してみようッ!!

  おぉ!! すごいッ!! 前回は回答が付かなかったが、今回はスゴく的確な回答が付いたッ!! 低消費電力を謳って消費電力が75mA以下なんていう製品があったんだねぇ。しかもアルミ外装。これで赤いバージョンがあってストラップ穴が付いている最高だが、この低消費電力の前には何も言うまい。

  画像の説明

  多分最大75mAということで、勝手に平均50mAと仮定すると、実に1/5の消費電力。つまり、ノートPCの稼働時間の短縮分は2分程度に抑えられる計算になる。これなら挿しっぱなしという選択肢もアリだろう。こりゃ、ポチッと行ってしまうしかないかと非常に悩む今日この頃である。


2009-03-07(Sat) XPort動き出します

  というわけで、調子の悪いヤツ、どうしてくれよう。面倒くさいが、仕方ない。ダメモトで、Windows機を立ち上げ、シリアル経由でのアップデートを試そう。アドホックに、シリアル接続する配線を加え、ヘンなWindows用ユーティリティを立ち上げ、アップデートしよ……うにも、ウンともスンともいわない。というか、通信速度が115200固定に見えるんですけどね。ユーティリティには変更できるような設定項目は見あたらない。

  画像の説明

  ……えぇい、なんかわからんが、同時にLANからも接続ッ!! ……って、アレ? もしかして、IPアドレスが付いてない? 特に何もしてないが動き出した。なんつぅ不安定な。トラブルシュートのために計測機をつなぐと、その時だけちゃんと動く、なんて事例を思い出した。telnetで10001番につなぐと、WindowsのTERATERMからのシリアル出力が受け取れてしまう。逆はダメだけど。

  なんでもいいや。動いているなら、その間に、せめて、ファームだけでも最新にしておこう。ファームウェアアップデートはtftpで行う。tftpなんて滅多に使わんから入ってない。入れる。

# yum install tftp

  オフィシャルサイトでは、Windows用のファームウェアアップデート手順しか載せてない。tftpの規格はRFCで決められているんだから、一般的な手順も書けよな。

Example command for firmware upgrade from a DOS command prompt type:
	1.  C:\ tftp -i  put xpt01upg.rom X1 
	2.  Wait 20 seconds
	3.  C:\ tftp -i  put xpte_6100.rom X4 
Example command for the new Web Manager upgrade from a DOS command prompt type:
	1. C:\ tftp -i  put xpt_webm_1300.cob WEB4

  てのは、Linuxのtftpだと、以下の手順になる。

$ tftp 192.168.7.100 -m binary -c put xpt01upg.rom X1

  これは、一瞬で終わるが、20秒待つ必要があるらしい。

$ tftp 192.168.7.100 -m binary -c put xpte_6503.rom X4

  これには、20秒ちょいかかる。telnetして、アップデートの様子を確認してみる。

$ telnet 192.168.7.100 9999
Trying 192.168.7.100...
Connected to 192.168.7.100.
Escape character is '^]'.
 
MAC address 00204A805218
Software version V6.5.0.3 (070403) XPTE
 
Press Enter for Setup Mode 
 
 
*** basic parameters 
Hardware: Ethernet TPI
IP addr - 0.0.0.0/DHCP/BOOTP/AutoIP, no gateway set,netmask 128.0.0.0
DHCP device name : not set
 
*** Security
SNMP is              enabled
SNMP Community Name: public
Telnet Setup is      enabled
TFTP Download is     enabled
Port 77FEh is        enabled
Web Server is        enabled
Web Setup is         enabled
ECHO is              disabled
Enhanced Password is disabled
Port 77F0h is        enabled
 
*** Channel 1
Baudrate 9600, I/F Mode 4C, Flow 00
Port 10001
Connect Mode : C0
Send '+++' in Modem Mode enabled
Show IP addr after 'RING' enabled
Auto increment source port disabled
Remote IP Adr: --- none ---, Port 00000
Disconn Mode : 00
Flush   Mode : 00
 
*** Expert
TCP Keepalive    : 45s
ARP cache timeout: 600s
Monitor Mode @ bootup : enabled
HTTP Port Number : 80
SMTP Port Number : 25
MTU Size: 1400
Alternate MAC: disabled
Ethernet connection type: auto-negotiate
 
*** E-mail
Mail server: 0.0.0.0
Unit       : 
Domain     : 
Recipient 1: 
Recipient 2: 
 
- Trigger 1 
Serial trigger input: disabled
  Channel: 1
  Match: 00,00
Trigger input1: X
Trigger input2: X
Trigger input3: X
Message : 
Priority: L
Min. notification interval: 1 s
Re-notification interval  : 0 s
 
- Trigger 2 
Serial trigger input: disabled
  Channel: 1
  Match: 00,00
Trigger input1: X
Trigger input2: X
Trigger input3: X
Message : 
Priority: L
Min. notification interval: 1 s
Re-notification interval  : 0 s
 
- Trigger 3 
Serial trigger input: disabled
  Channel: 1
  Match: 00,00
Trigger input1: X
Trigger input2: X
Trigger input3: X
Message : 
Priority: L
Min. notification interval: 1 s
Re-notification interval  : 0 s
 
 
Change Setup:
  0 Server
  1 Channel 1
  3 E-mail
  5 Expert
  6 Security
  7 Defaults
  8 Exit without save
  9 Save and exit            Your choice ? 8
 
exiting without save !
Connection closed by foreign host.

  確かに「V6.5.0.3」に上がっている。成功だ。ついでに、内蔵のWeb I/Fのファームも上げておく。

$ tftp 192.168.7.100 -m binary -c put xpt01_webm_1602.cob WEB4

  これには、30秒ちょいかかる。どれどれ。

  画像の説明

  「Version 1.6.0.2」となっている。苦労しただけに、その上の「Configuration」が「Congratulation」に空目したわい。

  さて、このファームのアップにて、安定して動くかどうかはわからんが、とりあえず先に進もう。うほ。


2013-03-07(Thu) ついにはロードブラスター

  オッサンホイホイネタはまだ続く。いつの間にか、あの「ロードブラスター」が、iOSのアプリとして登場していたのである。

  ロードブラスターといえば、ダライアスと並んで、オイラの一番好きなゲームなのである。中学の頃だと思うが、ゲームセンターでオープニングデモを一目見て魅了されたのだ。やや、ゲーム性には疑問を感じながらも、それには熱中し、かなりつぎ込んだ記憶がある。なんというか、あの車、あのステージに魅力がありすぎるのだよな。

  しかしながら、最終面である9面は、難度が飛び抜けていて、何度も9面に到達するも、ゲームセンターでは遂にエンディングを拝めなかった記憶がある。うまい人のプレイを後ろで見ていてエンディングを拝んだが、ある程度は覚えないと無理だと思ったな、あれは。

  その後、メガドライブ版、プレイステーション版も購入したが、メガドライブ版のオープニングデモの曲がヒドいことになっていたこと以外、あまり記憶にない。一度、通しでやってしまうと、そんなものかもしれない。

  が、ここに来てiOS版の登場だ。いままでと何が違うって、夢中になって、一緒に遊んでくれそうなガキが、ちょうど隣にいることだ。これはダウンロードしなくてはならない。iPodTouchはカミさんのものなので、コンビニでアプリのプリペイドカードを買ってきて、許可を得てインストール。

  懐かしい! 画面がキレイだ。ちょっとトリミングされていたり、画質をリファインしすぎている気がするが、悪くない。なかなかの好移植だ。あまり一度に遊んでしまうともったいない。今日は1面だけクリアしておこう。

  画像の説明

  ……と、次の日になったら、こんなことに。熱中しすぎだろ、ウチのガキ。しかし、なんだかアレだ。昔、熱中したゲームを、世代を超えて楽しめるって……もしかして、これがシアワセってヤツなのかいッ!?

  このことを同僚に話したら「450円は高い」といわれたが、何を言う! 当時のOh!mzをひっくり返して調べたら、当時ロードブラスターを楽しむのに必要な金額は以下だ。

  画像の説明 画像の説明

VHDプレーヤVP-2400149,800
インタフェイスユニットVO-20PC55,000
VHDコントローラボードVO-20PS27,000
VHD言語インタープリタVO-20IP5,000
VHDソフト6,800
PC用ソフト3,000

  定価ベースとはいえ、約25万だぞ。X1本体を入れたら+30万で55万だ。これを考えれば、まさに現在こそ、国民の義務としてロードブラスターを楽しまなければならない時代といえる。

  なんだかんだいって、1985年から約30年の刻を越え、いまでもある程度の話題性を持ち、実際に遊んで楽しめるという事実は、このゲームの持つポテンシャルの証明だ。いまも、このゲームが愛されていることが、心底、嬉しいぞ。

本日のツッコミ(全1件) [ツッコミを入れる]

すーぱーたーぼ [始めまして。 X1用VHDシステムの検索でたまたま見つけたのでコメントさせていただきます。 お子様が一緒に遊んでく..]


2025-03-07(Fri) ALSAでPulseAudioで音を鳴らす

  OSSアプリを書いたら、ALSAアプリも、PulseAudioアプリも書いてみたくなった。それを順に辿ることでサウンド機能の進化を体験できるかもしれない。

  しかし情報が少ない。Cで書くのは面倒だからRubyで書きたい。GEMにALSAのバインディングくらいあんじゃないの? えぇい、AIに聞いちゃえ。「RubyでALSAで音を鳴らすプログラムを書いてください」。そのまんまだ。

  出てきた……て、なんだこれ。GEMを使ってない。FFIてナニ? 調べたら「Foreign function interface」とある。え。Ruby上でバインディングを書けるってこと!? そういうのもあるのか! 知らんかった。

  前にもあったが、サンプルコード的なものを書かせると、AIはちゃんとしたものを出してくる。ほぼそのまま動いたが、あちこちオレ風にリライトして仕上げたのが以下。その作業を通じてコードはオレの血肉となるのだ。

#!/usr/bin/env ruby
 
# $ bundle add ffi
require 'bundler/setup'
 
require 'ffi'
include Math
 
module ALSA
    extend FFI::Library
    ffi_lib 'asound'
 
    # https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html
    attach_function :snd_pcm_open, [:pointer, :string, :int, :int], :int
    attach_function :snd_pcm_set_params, [:pointer, :int, :int, :uint, :uint, :int, :uint], :int
    attach_function :snd_pcm_avail, [:pointer], :int
    attach_function :snd_pcm_writei, [:pointer, :pointer, :ulong], :long
    attach_function :snd_pcm_close, [:pointer], :int
 
    SND_PCM_STREAM_PLAYBACK = 0
    SND_PCM_FORMAT_S16_LE = 2
    SND_PCM_ACCESS_RW_INTERLEAVED = 3
end
 
p_pcm = FFI::MemoryPointer.new(:pointer)
name = 'default'                                # 'hw:2,0', 'plughw:2,0'
ALSA.snd_pcm_open(p_pcm, name, ALSA::SND_PCM_STREAM_PLAYBACK, 0) == 0 or raise('snd_pcm_open failed.')
pcm = p_pcm.read_pointer
 
channels = 1
rate = 44100
ALSA.snd_pcm_set_params(pcm, ALSA::SND_PCM_FORMAT_S16_LE, ALSA::SND_PCM_ACCESS_RW_INTERLEAVED, channels, rate, 1, 500000) == 0 or raise('snd_pcm_set_params failed.')
 
freq = 440
duration = 2
p_buffer = FFI::MemoryPointer.new(:int16, frames = 2048)
omega = 2.0 * PI * freq / rate
theta = 0
(duration * rate).times {|p|
    p_buffer.put_int16(p % frames * 2, (sin(theta += omega) * 32700).to_i)
    if((p + 1) % frames == 0)
        a0 = ALSA.snd_pcm_avail(pcm)
        ALSA.snd_pcm_writei(pcm, p_buffer, frames) < 0 and raise('snd_pcm_writei failed.')  # blocking
        puts('%s: %5d -> %5d' % [Time.now.strftime('%H:%M:%S.%N'), a0, ALSA.snd_pcm_avail(pcm)])
    end
}
 
ALSA.snd_pcm_close(pcm)

  引き続き、PulseAudio版。こっちも情報が少ない。Simple APIのサンプルは見つかったが、発声中にブロッキングされる仕様では、ゲームに使えない。Asynchronous APIを使うべきだが……無闇に複雑な前処理が要るんだなぁ。結局、CのサンプルをRubyのFFI向けに書き直した。

#!/usr/bin/env ruby
 
# $ bundle add ffi
require 'bundler/setup'
 
require 'ffi'
include Math
 
module PulseAudio
    extend FFI::Library
    ffi_lib 'pulse'
 
    # https://freedesktop.org/software/pulseaudio/doxygen/mainloop_8h.html
 
    # pa_mainloop *pa_mainloop_new(void);
    attach_function :pa_mainloop_new, [], :pointer
    # pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m);
    attach_function :pa_mainloop_get_api, [:pointer], :pointer
    # pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name);
    attach_function :pa_context_new, [:pointer, :string], :pointer
 
    # void (*pa_context_notify_cb_t)(pa_context *c, void *userdata);
    callback :pa_context_notify_cb, [:pointer, :pointer], :void
    # void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata);
    attach_function :pa_context_set_state_callback, [:pointer, :pa_context_notify_cb, :pointer], :void
 
    # int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
    attach_function :pa_context_connect, [:pointer, :string, :int, :pointer], :int
 
    # pa_context_state_t pa_context_get_state(const pa_context *c);
    attach_function :pa_context_get_state, [:pointer], :int
 
    # pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map);
    attach_function :pa_stream_new, [:pointer, :string, :pointer, :pointer], :pointer
 
    # void (*pa_stream_request_cb_t)(pa_stream *p, size_t nbytes, void *userdata);
    callback :pa_stream_request_cb, [:pointer, :int, :pointer], :void
    # void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
    attach_function :pa_stream_set_write_callback, [:pointer, :pa_stream_request_cb, :pointer], :void
 
    # int pa_stream_connect_playback(pa_stream *s, const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream);
    attach_function :pa_stream_connect_playback, [:pointer, :string, :pointer, :int, :pointer, :pointer], :int
 
    # int pa_stream_write(pa_stream *p, const void *data, size_t nbytes, pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek);
    attach_function :pa_stream_write, [:pointer, :pointer, :int, :pointer, :int, :int], :int
 
    # int pa_mainloop_run(pa_mainloop *m, int *retval);
    attach_function :pa_mainloop_run, [:pointer, :pointer], :int
    # int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval);
    attach_function :pa_mainloop_iterate, [:pointer, :int, :pointer], :int
 
    PA_CONTEXT_NOAUTOSPAWN      = 1
 
    PA_CONTEXT_UNCONNECTED      = 0
    PA_CONTEXT_CONNECTING       = 1
    PA_CONTEXT_AUTHORIZING      = 2
    PA_CONTEXT_SETTING_NAME     = 3
    PA_CONTEXT_READY            = 4
    PA_CONTEXT_FAILED           = 5
    PA_CONTEXT_TERMINATED       = 6
 
    PA_SAMPLE_U8                = 0
    PA_SAMPLE_ALAW              = 1
    PA_SAMPLE_ULAW              = 2
    PA_SAMPLE_S16LE             = 3
    PA_SAMPLE_S16BE             = 4
 
    PA_SEEK_RELATIVE            = 0
    PA_SEEK_ABSOLUTE            = 1
    PA_SEEK_RELATIVE_ON_READ    = 2
    PA_SEEK_RELATIVE_END        = 3
end
 
class Pa_sample_spec < FFI::Struct
    layout(
        :format,    :int,
        :rate,      :uint32,
        :channels,  :uint8,
    )
end
 
class Userdata < FFI::Struct
    layout(
        :gain,      :int,
        :omega,     :double,
        :theta,     :double,
        :data,      :pointer,
    )
end
 
def pa_context_notify
    FFI::Function.new(:void, [:pointer, :pointer]) {|c, p_userdata|
        state = PulseAudio.pa_context_get_state(c)
        puts('state: %d' % state)
        case(state)
          when PulseAudio::PA_CONTEXT_UNCONNECTED
          when PulseAudio::PA_CONTEXT_CONNECTING
          when PulseAudio::PA_CONTEXT_AUTHORIZING
          when PulseAudio::PA_CONTEXT_SETTING_NAME
          when PulseAudio::PA_CONTEXT_READY
 
            ss = Pa_sample_spec.new
            ss[:format] = PulseAudio::PA_SAMPLE_S16LE
            ss[:rate] = 44100
            ss[:channels] = 1
 
            stream = PulseAudio.pa_stream_new(c, 'SineWave', ss, nil)
            puts('stream: 0x%016X' % stream)
 
            PulseAudio.pa_stream_set_write_callback(stream, pa_stream_request, p_userdata)
 
            r = PulseAudio.pa_stream_connect_playback(stream, nil, nil, 0, nil, nil)
            puts('connect_playback: %d' % r)
 
          when PulseAudio::PA_CONTEXT_FAILED
          when PulseAudio::PA_CONTEXT_TERMINATED
        end
    }
end
 
def pa_stream_request
    FFI::Function.new(:void, [:pointer, :int, :pointer]) {|p, nbytes, p_userdata|
        userdata = Userdata.new(p_userdata)
        data = userdata[:data]                                  # ループの外にある必要!?
        nbytes.times {|t|
            v = userdata[:gain] * sin(userdata[:theta] += userdata[:omega])
            data.put_int16(t * 2, v.to_i)
#           userdata[:data].put_int16(t * 2, v.to_i)            # これだとなぜか SEGV...
        }
        r = PulseAudio.pa_stream_write(p, userdata[:data], nbytes, nil, 0, PulseAudio::PA_SEEK_RELATIVE)
        puts('stream_write(%d): %d' % [nbytes, r])
    }
end
 
#-------------------------------------------------------------------------------
#
#   Main
#
mainloop = PulseAudio.pa_mainloop_new()
puts('mainloop: 0x%016X' % mainloop)
 
mainloop_api = PulseAudio.pa_mainloop_get_api(mainloop)
puts('mainloop_api: 0x%016X' % mainloop_api)
 
context = PulseAudio.pa_context_new(mainloop_api, 'SineWaveAsync')
puts('context: 0x%016X' % context)
 
userdata = Userdata.new
userdata[:gain] = 32700
userdata[:omega] = 2.0 * PI * 440 / 44100
userdata[:theta] = 0
userdata[:data] = FFI::MemoryPointer.new(:int16, 32768)
PulseAudio.pa_context_set_state_callback(context, pa_context_notify, userdata)
 
r = PulseAudio.pa_context_connect(context, nil, PulseAudio::PA_CONTEXT_NOAUTOSPAWN, nil)
puts('context connect: %d' % r)
 
#r = PulseAudio.pa_mainloop_run(mainloop, nil)
loop {
    r = PulseAudio.pa_mainloop_iterate(mainloop, 0, nil)
    print('.')
    sleep(0.01)
}

  んが、セグメンテーションフォルト連発。音は出るものの、すぐに落ちてしまう。うーむ。久々に動かない地獄を長々と這いずり回ってしまった。でも、PulseAudioで音を出す手順は習得できたし、それでよしとするか。常に同じ症状が出ないので、メモリの扱い周りに問題があるのだろうが、FFI特有のアレコレに振り回されるのは本意ではない。

  ちょっと今回は長すぎるグダグダで疲れてしまった。サウンド関係はしばらく倉庫に押し込んで別のことをやることにするよ、パトラッシュ。