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|09|10|11|12|

2004-07-13(Tue) ジュークボックスアプリ公開

  快進撃を続けてきたジュークボックスアプリの開発だが、本日をもってバージョン1.00の完成としてしまうのであった。で、先日の約束どおりGPLとしてここに公開する。以下、超適当であるがマニュアルである。

  必要環境

Windows + Cygwin(Ruby/tk)

  各ファイル説明

carjuke.rbRuby/tkで書かれたジュークボックスアプリ本体。GUI関係のコード。
jukedir.rbcarjuke.rbが利用するRubyライブラリ。メディアファイルを管理する関係のコード。
juked.rbRubyで書かれたジュークボックスデーモン。TCPで要求を受け、mediaplay.exe等を呼び出す。
juked.conf.rbjuked.rbの設定ファイル。
mediaplay.cpp先日公開したCUIのメディアファイル再生ツールのソース。
使い方はコマンドラインにwav, mp3, avi, mpegファイル等を与えるだけ。
mediaplay.exe上記の実行形式バイナリ。
makemediaplay.cppからmediaplay.exeを作るバッチファイル
noimage.gifジャケット画像がない場合に表示する画像。以下で説明する親ディレクトリに置いておくとよい。
licence.txtGPLのお約束

  導入時設定

carjuke.rbの冒頭にhomeDirという変数があるので、メディアファイルの存在する親ディレクトリをcygwin形式で指定する。D:\My_Soundsにあるなら'/cygdrive/d/My_Sounds'と(必ずcygdrive経由で)指定する。

  メディアファイルの配置

親ディレクトリの下にディレクトリを作り、その中にメディアファイルを置く。ちなみに一階層限定である。メディアファイルのほかにjacket.gifというファイルを置くとジャケットとして画像が表示され、songs.lstというファイルを置くと曲名が表示される。songs.lstのフォーマットは以下の例にならうこと。

OutRun2 SOUND TRACKS/SEGA
track01.cdda.wav.mp3,SPLASH WAVE
track02.cdda.wav.mp3,MAGICAL SOUND SHOWER
track03.cdda.wav.mp3,PASSING BREEZE
track04.cdda.wav.mp3,RISKY RIDE
track05.cdda.wav.mp3,SHINY WORLD
track06.cdda.wav.mp3,NIGHT FLIGHT
track07.cdda.wav.mp3,LIFE WAS A BORE
track08.cdda.wav.mp3,ENDING-A
track09.cdda.wav.mp3,ENDING-B
track10.cdda.wav.mp3,ENDING-C
track11.cdda.wav.mp3,ENDING-D
track12.cdda.wav.mp3,ENDING-E
track13.cdda.wav.mp3,LAST WAVE
track14.cdda.wav.mp3,OPENING
track15.cdda.wav.mp3,SPLASH WAVE (1986)
track16.cdda.wav.mp3,MAGICAL SOUND SHOWER (1986)
track17.cdda.wav.mp3,PASSING BREEZE (1986) 
track18.cdda.wav.mp3,LAST WAVE (1986)

  起動方法

cygwinのbashから./juked.rb &とし、ジュークボックスデーモンを起動。

続けて./carjuke.rbとしてGUIフロントエンドを起動。

  操作方法

起動すると、ものがカーステだけに勝手に再生が始まる。'6'キーで次の曲、'4'で前の曲、'8'で次のアルバム、'2'で前のアルバム、'0'で現在の曲を最初から演奏しなおす。

  ……などと、まとめていたら、あっという間に深夜になってしまった。今日はこのほかにも、車のリア側の変換ボックスの回路の修正を行い、外部からのPC起動を可能にしたりしていた。しかし、一応できたことはできたのだが、配線を間違ってしまい、再修正が必要となってしまった。あー、めんどくさ。それから以前より、CDチェンジャーに使われていたケーブル内の13本の線のうち1本だけ線が余っていたのだが、本日、突然それに割り付ける信号を思いついた。で、追加した。ふふふ。これもとりあえず、秘密にしておこう。以前の写真と以下の写真を見比べればカンのいい人はわかるかもしれない。

  画像の説明

  関係ないが、夕方にツタヤでなにげに上戸彩のCDレンタルしてきた。よって今日はサービスカット(?)をつけてしまう。残念なことにこのCDはCCCDなのだが、おいらはアナログコピーでPCに取り込み、自作の怪しいスクリプトでサクサク切ってmp3化してしまうのでウハウハである。CCCDはムカつくので決して買ったりはしないが、だからといって負けてもいないのである。レンタルしたCDをコピーするのは合法なのだ。余談だが、以下の画面を作るのに、jacket.gifとsongs.lstはamazonを利用してゴニョゴニョしているので、スキャンしたり手打ちしてはいない(よく見たらミスタイプあるじゃねーか)。せっかくなので、そのうちこれらのスクリプトも公開しようと思っている。では、おやすみである。

  画像の説明


2005-07-13(Wed) 中年力で勝ってみる

  しょーりゅーけんッ!! しょーりゅーけぇーんッ!! ……はぁはぁ。まだ拳から火が出るようにならないが、オレも歳だしこんなもんだろう。

  そう、次の事例は「ストリートファイターII」だ。これは、直接に友人に指摘された。「オマエ、体力ゲージがほとんど残ってないのに、そこからネバるなぁ」と。そうなのだ。以前のオイラなら、体力ゲージが1/4あたりを割った時点で、そのラウンドを捨てていた気がするのだ。そして、既にヒトツ黒星が付いていたら、そのゲームをアキらめていた気がするのだ。しかし、気が付けば、体力がキッカリゼロ。つまり、必殺技で削られるだけでKOされる状態にも関わらず、勝負を捨てないオイラがいるのである。これも中年力である。

  逆境に置かれても、決して最後まで勝負を捨てない。今なら理解できるぞ。ハーロックの「男には負けるとわかっていても戦わなければならない時がある」というセリフの真の意味が。そして、負けたとしても、自分の理解のなかで負ける。ベストを尽くしたのならば、それで仕方ないではないかと。そして、それは次の戦いにフィードバックされるのだ。負けた時に、自分の理解のなかで、潔く負けられるように、今、ベストを尽くしておこうとッ!!

  具体的になにがどうとは書かないが、以前、ある高い目標を掲げ、2ヶ月以上という長いスパンを戦い抜いた時も、自らの中年力を認識した瞬間である。思うように事が運ばず、ヘコんだコトもあった。多少のゲインのあと、長く停滞する期間が続いたときは、心が折れそうもになった。若い頃ならば、アセる気持ちを抑えられずに、イチかバチかの行動に出て失敗したり、いっそアキらめてその後の努力をやめてしまっていたことだろう。そこを、耐え抜くことができたのも中年力である。

  体勢が悪いときは、何もしないというのも、正しい選択肢のひとつであるコトと知ったのも中年力である。先日のアルカノイドでいえば、最後にひとつ残ったブロックは「狙う」のでなく「待つ」という意識だ。ボールを落とさなければ、必ず当たる。そう考えるのだ。こう考えるとアルカノイドは中年力養成ギプスである。アルカノイドのプレイぶりで、性格判断ができる。御社でも、入社試験にアルカノイド、どうすか?

  とはいっても、地球は自分を中心に回っているわけではない。リアルはゲームと異なり、必ずエンディングにたどり着き、勝利できるという保障はないのである。しかし、そこが、おもしろい。おもしろいと思えるようになったのも中年力である。だから、自分のできる範囲でベストを尽くすのだ。そして、勝利をこの手に掴み取るのだだだだッ!!

  さて、そんなこのSVX日記の運営も「Powered by CHU-NEN」である。始めた当初、ぜんぜんカウンタが回らなくても、ヘコまないのである。毎日の日記のクオリティにバラツキがあっても、気にしないのである。どうしても書けないのならば、書かずに済ます勇気もまた、中年力なのである。ちゃんと毎日更新することを自らに義務付け、守れずに自分に腹を立て、ヤメてしまってはどうにもならない。そこはファジーにゴマかし、ゴマかしてしまうことで、継続を可能にし、質×量で最高点を目指す。オイラのベストはそこにある。それに気づき、実践できるようになった、これこそまさに中年力なのである。

  最後に、ココまできて、かなり名文を書いてしまったような気がして、こんなコトなら最初から「中年力」なんてヘンなコトバじゃなく、もっとカッチョエエ単語、例えば「年巧力」とかにしとけばよかったとか、思い返してしまうのに、そこをアキらめてしまうのも、これもまたひとつの「中年力」の形である。むはっは。



2013-07-13(Sat) 書・作・走

  うちのガキが書道教室に行っているのを見て、自分も書いてみたくなり、かなり前に100円ショップで道具を揃えていたのだが、ようやく着手できた。

  画像の説明 画像の説明

  ここ数年、ちゃんとした文字を書いたことがないし、書道の心得はまったくないのだが、思ったよりもいい感じに書けた。小学生の頃は、書道なんてまったく楽しくなかった気がするが、自分で書きたい文字を好きなように書いていいのなら、これはかなり楽しい。

  画像の説明 画像の説明

  手本もなしに思いつきで書いただけで、こんなに味が出せるなら、もっと真剣に取り組んでみようか知らん。「竜巻旋風脚」とか、床の間に飾ってみたい。しかし、少なくとも筆だけは買い直さないといかんようだ。

  画像の説明 画像の説明

  最近、とんと工作してないので、リハビリを兼ねて、小型の露出計を作り始めた。CdSの出力をEV値でLED表示する仕様だ。

  キーホルダー程度の大きさにまとめたかったので、基板は使わず、空中配線する。なんか、調子が出てきた感じだ。


2024-07-13(Sat) SSOのOSSでSOS

  mod_auth_mellonを使いたい、っていう客が現れた。なんだそのフルーティな認証モジュールは!? と、思ったらApacheでSSO(シングルサインオン)を実現するものらしい。

  認証にはあまり興味がないのだが、DKIMだの、Sinatraへ追加だの、S/MIMEだの、OAuth2.0だの、意外とアレコレと取り組んでいる。まぁSSOはユーザの負担を軽減するための妥当な仕組みであるし、悪いのものではないよな……と、調べ始めるといろいろ出てくる新事実。

  • mod_auth_mellonはApacheをSAMLに対応させるためのクライアントモジュール
  • SAMLはSecurity Assertion Markup LanguageというSSOのオープンな規格
  • SAMLはクライアント/サーバの形であり、サーバ側が認証を司る
  • サーバ側で一度認証すれば、すべてのクライアントでアクセス許可される
  • 代表的なサーバは、商用サイト「OneLogin」や、OSSの「Keycloak」

  そういやKeycloakってどこかで聞いたような名前だな。mod_auth_mellonを試すにはサーバが必要だが、Keycloakが使えるならそっちの味見もできて一石二鳥である。とりあえずKeycloakをコンテナで上げてみる……

  画像の説明

  ……と、それがドハマリな日々の始まりであった。

  まぁ、概念がわからない。ウェブ上に文書はあるが、読んでもわからん。Sinatraに実装して動かすのが目的なのだから、どうにか少しでも動かして、それを元に理解を進めたいのだが、にっちもさっちもよっちもごっちもろっちもしっちもはっちもくっちもじっちも動かない。

  画像の説明

  急がば回れと、OneLoginのお試しアカウントを作ってサーバを替えてみたり、mod_auth_mellonのハウツーを柄にもなく実直に辿ってみたりもした。結果mod_auth_mellonは動きはしたがそれだけだ。やっぱりわからん。

  ここまでガッツリとハマったのは人生で初めてかもしれん。なにしろ、Keycloakのログイン画面に遷移しないのだ。Keycloakはエラーログを出すのだが、内容が具体的でないので、試行錯誤するほかない。ググると似たような感じでハマっている書き込みは見つかるものの、解決してそうに見えない。結局、秘密鍵や証明書の指定をアレコレしまくって、ようやくログイン画面にこぎ着けたが、今度はSAMLのライブラリがエラーを吐く。終いにはruby-samlのコードにデバッグ行を入れまくり、どうにかSinatra側に戻すところまで持ってきたが、なんだか設定が微妙すぎて、もう何が正しいのかわからない。

  ログインができたらログアウトもできなければならない。そしてまたKeycloakのエラーログだ。意味不明……さすがにちょっとログの出し方がアホなんじゃないかと思い始める。またもや証明書の指定をアレコレしまくって、どうにか抜けたかと思ったら、またもやSAMLのライブラリがエラーを吐く。再びruby-samlのコードにデバッグ行を入れまくるが、限りなくバグくさいコードをアチラコチラに見つけてしまう……さすがにちょっとライブラリの品質もクソなんじゃないかと思い始める。

  どうにかログアウトもできたが、そこで気づく。基本的にhttpにはセッションの概念がないのに、どうやって各ページで認証を行うんだ? IdPから受け取ったアサーション(認証情報)をSPに提示し、SPが認証するのはわかるが、どうやってアクセスの都度アサーションを渡すのか。POSTしなければならないサイズだよな。それってAタグによるページ遷移はできなくなるってことなんじゃ?

  うーん、なんだか急激に興味が薄れてきたな。そんな実装以外にやりようはなかったんかいな。まぁ、散々コネくりまわしたのでソコソコ概念は理解できたが、手に入ったのは山盛りのバッドノウハウという気しかしない。Sinatraへの実装は断念するか……もう疲れたんだよパトラッシュ……しかしなぁ、そこは無理をしてでもブラウザのBasic認証やCookieの機構の上に載せるべきだったんじゃないのかねぇ……知らねえけれど、さよ〜ならまたいつか〜♪


2025-07-13(Sun) Embeddings APIって、そういうことだったんですかい

  どうもAIを素直に好きになれない自分がいる。スゲェな、と思う場面がある反面、同時に悔しさも感じてしまい、どうしても敵対的に見てしまうのだ。

  なので「AIコーディングツールは生産性を下げる」などという記事を見つけたときは、思わず鼻を鳴らしてしまった。そらそうだろうよ。概ね合ってるコードが一番始末が悪いんだから。なんで人間様がデバッグ係なんだよ。つうても、数行のサンプルコードを書いてもらう限りは、非常に有用。結局は、使い方次第だ。

  つうわけで、$5課金してからというもの、自作した「リナ」をちょいちょい便利に使っているのだが、ハテ、いくら分ぐらい使ったものやら……と、確認するとまだ$2以上残っていた。自分の使い方だと、無限に使える感じだな。

  と、なにげにパネルを見ていたら「Vector stores」という表示を見つけた。ん? ベクトルストア? そんなんあるなら、相手側の資源でRAGを実現できるんじゃないの? そう思って、ChatGPT自身に「どうなの?」と訊いたのだがなんだかハッキリしない。「APIからChatGPTの Vector Store機能を使いたい場合には、OpenAIの Embeddings API + 自前でのベクトルストア管理 が基本になります」だと。なんか言ってることが矛盾してね?

  その延長で、あれこれ調べているうちに「Embeddings API」の意味にようやく気づいた。単語や文章を与えると、それを1536次元のベクトル値に変換して返してくれるAPIなのだということに。Embeddings APIって、ひいては、RAGって、そういうことだったんですかい。

  ここしばらく、RAGを自作してみたいものの、自らGPUを用意するつもりはないので、どうしたものかと思っていたのだが、計算負荷の高いベクトル化を相手側の資源で行えるなら、それでイケるんじゃないか。しかも、変換コストは非常に安いとくる。

  問題はベクトルDBだが……PostgreSQLにベクトル演算を拡張するpgvectorなんてものがあるらしい。なんとも都合のいいことに、コンテナイメージもあり、既存のdocker-compose.ymlの「docker.io/postgres」を「docker.io/pgvector/pgvector:0.8.0-pg17」に書き換えただけで動いてしまった。PostgreSQLなら、割と使い慣れているから助かる。

  役者が揃ったところで、以前に作ったlibngs.rbに、Embeddings APIに対応するコードを追加する。既存のChat Completions APIとの共用部分が多い反面、共用部分を共用化するためには、抽象クラスに取り出す必要があったりして、余計に手間がかかった気がするが、それがまた楽しくもあり。

  結局、以下のようなコードで、ベクトル値のストア、近いベクトル値を持つレコードの検索ができるようになった。要するに、任意の文章を与え、登録してある文章の中から、最も近い意味合いの文章を引き出すことができるようになった、ということだ。

  when('initdb')
    @pgc = setup_pg_conn
    [   "CREATE EXTENSION IF NOT EXISTS vector;",
        "CREATE TABLE embeddings (id SERIAL PRIMARY KEY, text TEXT, embedding VECTOR(1536));",
    ].each {|sql|
        p pgr = @pgc.exec(sql)
    }
 
  when('insertdb')
    response = ngs[0].embedding(text = $stdin.read.chomp)
    @pgc = setup_pg_conn
    [   "INSERT INTO embeddings (text, embedding) VALUES ('%s', '%s');" % [text, response.embedding.inspect]
    ].each {|sql|
        p pgr = @pgc.exec(sql)
    }
 
  when('selectdb')
    response = ngs[0].embedding(text = $stdin.read.chomp)
    @pgc = setup_pg_conn
    [   "SELECT id, text, embedding <=> '%s' AS distance FROM embeddings ORDER BY distance LIMIT 5;" % [response.embedding.inspect]
    ].each {|sql|
        p pgr = @pgc.exec(sql)
        pgr.each {|r|
            p r
        }
    }

  こんな風に使う。まずは、pgvectorコンテナを立ち上げ、初期化する。

$ ./embed initdb
"CREATE EXTENSION IF NOT EXISTS vector;"
#<PG::Result:0x00007f687772dc60 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>
"CREATE TABLE embeddings (id SERIAL PRIMARY KEY, text TEXT, embedding VECTOR(1536));"
#<PG::Result:0x00007f687772d878 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>

  AIに作ってもらった「架空のニュースヘッドライン」30件をDBにストアする。

$ echo '火星の地下湖に生命の痕跡 探査機が微生物反応検出' | ./embed insertdb
"INSERT INTO embeddings (text, embedding) VALUES ('火星の地下湖に生命の痕跡 探査機が微生物反応検出', '[0.008627256, 0.04008983, 0.022950277, 0.053258..."
#<PG::Result:0x00007fda5cab54a8 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=1>
$ echo '日本政府、空飛ぶ自動車の市販許可を正式発表' | ./embed insertdb
"INSERT INTO embeddings (text, embedding) VALUES ('日本政府、空飛ぶ自動車の市販許可を正式発表', '[0.03220249, 0.011132977, -0.020613244, 0.00199744..."
#<PG::Result:0x00007f40460b18b8 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=1>
$ echo 'AIが初の小説家デビュー 芥川賞ノミネートへ' | ./embed insertdb
"INSERT INTO embeddings (text, embedding) VALUES ('AIが初の小説家デビュー 芥川賞ノミネートへ', '[0.030073598, -0.02538003, -0.042647723, 6.115188..."
#<PG::Result:0x00007fc085637188 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=1>
  :

  任意の文章を与え、近い意味合いを持つニュースを検索させる。

$ echo 'ドラえもんが実用化' | ./embed selectdb
"SELECT id, text, embedding <=> '[0.023070822, 0.038914356, -0.037266437, 0.0132892635, -0.00953437, -0.023659363, -0.0114589,..."
#<PG::Result:0x00007f8aca269d30 status=PGRES_TUPLES_OK ntuples=5 nfields=3 cmd_tuples=5>
{"id"=>"8", "text"=>"ネコ語翻訳機、精度90%超えで商品化スタート", "distance"=>"0.6139296889305115"}
{"id"=>"28", "text"=>"四次元ポケットの実用化に成功 国際特許を申請", "distance"=>"0.6229722573144583"}
{"id"=>"16", "text"=>"消える道路標識、実は新型AR技術の実験だった", "distance"=>"0.6906551009026104"}
{"id"=>"14", "text"=>"老舗和菓子屋がメタバースに進出 VRで試食体験", "distance"=>"0.7033616127587321"}
{"id"=>"20", "text"=>"鏡の中に映らない猫、科学的解明に一歩前進", "distance"=>"0.7042705416679382"}
 
$ echo '未曾有の大災害' | ./embed selectdb
"SELECT id, text, embedding <=> '[0.023535717, 0.021849712, 0.025312858, 0.046182863, -0.008014219, -0.018546054, 0.0101217255..."
#<PG::Result:0x00007ff3dcf960a0 status=PGRES_TUPLES_OK ntuples=5 nfields=3 cmd_tuples=5>
{"id"=>"6", "text"=>"東京湾に巨大未確認生物出現 各地で目撃情報相次ぐ", "distance"=>"0.6341621497772836"}
{"id"=>"12", "text"=>"富士山に新たな火口 専門家「噴火の兆候なし」", "distance"=>"0.6510091035956672"}
{"id"=>"11", "text"=>"無人島が突如移動 GPS地図が大混乱に", "distance"=>"0.6530257281034317"}
{"id"=>"24", "text"=>"京都の神社で時空のゆがみ報告 観光客パニック", "distance"=>"0.6767845241088242"}
{"id"=>"30", "text"=>"地球に最接近する月、巨大化の理由は未解明", "distance"=>"0.7187142539790712"}
 
$ echo '宇宙人とコンタクト' | ./embed selectdb
"SELECT id, text, embedding <=> '[0.0039180126, -0.007993211, -0.007061737, 0.025079938, -0.023228632, -0.027175754, -0.036723..."
#<PG::Result:0x00007f029cbc4800 status=PGRES_TUPLES_OK ntuples=5 nfields=3 cmd_tuples=5>
{"id"=>"15", "text"=>"宇宙からのラジオ信号にモールス信号の痕跡発見", "distance"=>"0.594331335345663"}
{"id"=>"24", "text"=>"京都の神社で時空のゆがみ報告 観光客パニック", "distance"=>"0.6794118690140118"}
{"id"=>"21", "text"=>"国連、地球外文明との外交専門部署を新設", "distance"=>"0.6888121498841258"}
{"id"=>"30", "text"=>"地球に最接近する月、巨大化の理由は未解明", "distance"=>"0.6926628282860291"}
{"id"=>"26", "text"=>"東京地下に謎の空洞都市 調査隊が内部撮影成功", "distance"=>"0.7025683167658209"}

  完璧とは言えないものの、決して全文検索では引き出せない結果が得られている。よろしいのではないでしょうか!

  例によって、各物件をhttp://itline.jp/git/ngs, http://itline.jp/git/pgvector_dockerhubに置いておく。