2026年 02月 05日

ScreenToGifで最新のffmpegを使う

ScreenToGifはffmpeg同梱してるが、v4.4.1 とだいぶ古い。

PS C:\Users\cho45> C:\ProgramData\ScreenToGif\ffmpeg.exe
ffmpeg version 4.4.1-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers

winget install ffmpeg

winget install ffmpeg

で簡単に入れられるので、フルパスしらべて

(base) PS C:\Users\cho45> Get-Command ffmpeg

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ffmpeg.exe                                         0.0.0.0    C:\Users\cho45\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.0.1-full_build\bin\ffmpeg.exe

これを ScreenToGif の設定画面で指定してあげる。

animated avif

Animated AVIF の出力選択はあるのだけど、ffmpeg が古いせいか avif への出力に失敗したので新しくした。

animated avif と AV1 +webm って何がちがうのかという感じはするが (コンテナが違うというのはそうだけど)

hackrf-sweep-webusbの更新

盆栽。地味に自分でも定期的に使っており、自分で作ったやつの中では使用頻度が高い。ということで盆栽がて、いろいろ古かったのでなおした。

ひさしぶりに見たらHackRF One だいぶ高いね…… とおもった。

やったこと

Rust 環境アップデート・コードをまともに

  • edition 更新
  • テスト (rust レベル + node )
  • 実装修正
    • ゼロアロケーションに

JS側

  • Vue2 やめて Vue3 に
  • Vue Material やめて Vanilla CSS に
  • バンドプリセット
2026年 02月 03日

続 Conformer モールスデコーダ

-11dB(2500Hz BW) ぐらいまで90%、-15dB で50%デコードぐらいまではきたけどもういいかな……

モデル構造もちょっと変えた。Signal Head とかを Conformer の途中から出すようにして、2層分の Conformer をCTC Head だけに使うようにしてみた。ただあんまり意味があるのかは…… Conformerの層ごとの寄与度的なやつを出してみたけど、ほぼ Conv。Attn が意外に最初のほうで使われていてよくわからない。

あと内部の次元数も半分(256→128)にした。あんまり変わらないみたい。もっと減らして深くしたほうがいいのかも。わからない。

200ms 分の推論がスマフォ(Pixel7)で20ms前後。ちゃんと実用的に10バッチ同時にやるとかできたら格好いいかもだけどそこまでのモチベがなし……


人間だと -15dB (2500Hz BW) までは完コピできるみたいな論文?がでてくるんだけど、絶対盛ってるだろと思う。100Hz のフィルタ入れて 15wpm ならなんとか多少聞き覚えのある単語がわかるぐらい。CQはわかるけどコールは聞きとれない。https://cho45.github.io/morse-decoder-2026/demo.html で -15dB 設定して聞いてみてほしい。

2026年 02月 01日

builderscon::3DP #1

からくりすとさん来るぞというので、ビタビタのターゲティングを勝手に感じ取り絶対に行くぞと思い行ってきた。大変良かった。

カメラ付きでアラート出すプリンタとか、マルチカラーのプリンタとか羨ましいと思いつつ、まあそこまで今のプリンタに不満ないんだよなという……


Fusion の派生機能を知らなかったので試してみたい。エクスポートしたときどうなるのか不安に思ったけど個別に書かれるらしい。


転職ドラフトのビールスポンサーすごいなと思った。なんとなく世の中景気悪いイメージだったけど少人数(5人から)から支援しているとのこと。

会場のアンドパッド案内板がおしゃれだった。関係ないけどビルのエレベーターがうちのリビンクの広さぐらいあり、でかすぎて怖かった。


からくりすとさん、よく知らないCADを使ってるなと思ってたけど、 Rhinocerosらしい。ある程度シミュレーションしてるのかな?と思ってたけど、「動かせないCADなのでからくり作るには向かない」と言っていてびっくりした。なんとなく動くだろうい頭のなかでやってるらしい。天才すぎる。

動画にはなってない実用品の3Dプリントも格好良くて良かった。ますますファンになった。

あとよく軸に使っている虫ピンみたいなの、なんだろうなと思ってついに直接訊いてみたら、真鍮釘とのこと。釘で調べたことなかった


あと小さい3Dプリンタの話、3Dプリンタは「大は小を兼ねない」という感じの話もしていて良かった。完全に同意見です。自分もVoron0.1気に入ってる。

2026年 01月 31日

reload した後 h2oの古いプロセスが残り続ける場合

h2o は graceful restart をするのだが、稀に古いプロセスがずっと残ることがあって困ってた。(正確には start_server が restart させていて、h2o 自体は graceful shutdown しているだけ)

一例としてはこういう感じ (これは単に SIGHUP 直後なだけだけど、ときどき reload するとプロセスが残っていく)

 $  sudo systemctl status h2o
● h2o.service - H2O HTTP Server
     Loaded: loaded (/etc/systemd/system/h2o.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-01-10 17:36:22 JST; 2 weeks 4 days ago
    Process: 902234 ExecReload=/bin/kill -s HUP $MAINPID (code=exited, status=0/SUCCESS)
   Main PID: 1288 (perl)
      Tasks: 23 (limit: 2274)
     Memory: 53.0M (peak: 1015.0M swap: 12.2M swap peak: 95.1M)
        CPU: 1h 38min 58.048s
     CGroup: /system.slice/h2o.service
             ├─  1288 perl -x /usr/local/share/h2o/start_server --pid-file=/var/run/h2o.pid --port=0.0.0.0:80 "--port=[::]:80" --port=0.0.0.0:u443 "--port=[::]:u443" --port=0.0.0.0:443 "--port=[::]:443" -- /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─897714 /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─897716 neverbleed
             ├─897735 perl -x /usr/local/share/h2o/annotate-backtrace-symbols
             ├─902237 /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─902238 neverbleed
             └─902257 perl -x /usr/local/share/h2o/annotate-backtrace-symbols

結論: http*-graceful-shutdown-timeout を設定しよう

まず以下のような設定をし忘れないように。この例だと30秒で強制切断。デフォルトが0(無効)

http2-graceful-shutdown-timeout: 30
http3-graceful-shutdown-timeout: 30

メモ: タイムアウトの仕組み

HTTP/2 (lib/http2/connection):
  1. initiate_graceful_shutdown → 最初のGOAWAY送信
  2. 1000ms後 → graceful_shutdown_resend_goaway → 2回目のGOAWAY送信
  3. もし http2-graceful-shutdown-timeout > 0なら、その時間後に強制終了
HTTP/3 (lib/http3/server.c):
  1. 同様に1000ms後に2回目のGOAWAY
  2. もし http3-graceful-shutdown-timeout > 0なら、その時間後に強制終了
HTTP/1 (lib/http1.c):
  1. Keep-Alive をオフにしてレスポンス終了時 close

とにかくタイムアウトは全部設定しておこう!

調査

ただこの2つを設定していても接続が残るケースが発生して悩んだ

$  sudo ss -tapn | grep 902501                                                                                                                                                                                                              
ESTAB      0      0                              127.0.0.1:54080                                 127.0.0.1:5001  users:(("h2o",pid=902501,fd=66))                                                                                             
ESTAB      0      0                              127.0.0.1:46754                                 127.0.0.1:3000  users:(("h2o",pid=902501,fd=72))                                                                                             
CLOSE-WAIT 1      0                              127.0.0.1:52768                                 127.0.0.1:80    users:(("h2o",pid=902501,fd=74))                                                                                               

CLOSE-WAIT が残っている。

→ 最終的にこれは grafana の websocket への接続がずっと残っているというものだった。ブラウザの grafana タブを閉じたらプロセスが消えた。なんで CLOSE-WAIT?

websocket の proxy.timeout.io はデフォルト30秒らしいけど、通信が続くと一生切断されないのかも?

2026年 01月 30日

モールス(時系列データ)の機械学習

機械に機械学習のモデルを設計してもらう(何もわからない)の続きで、モールスの機械学習をちまちま諦めずにやってる。

PCEN (Per-Channel Energy Normalization)

音声データは非常にダイナミックレンジが広いので、なんらかの方法でノーマライズしないと、いざ実世界の、スケール 40dBぐらい違うデータを推論しようとしてもうまくいかない。

PCEN はなんかそういう圧縮してスケールする正規化をモデルの中に組込むというやつ。ちゃんと実装できているかはわかなないが挙動からするとできているのだろう……

もう少し実践的なデモ

実際にオーディオストリームから広域をFFTしつつ、一部をクロップして連続推論するというデモをつくった (マイク入力に対応)。

「しばらく動かすと推論がエラーで止まる」という現象が発生してこまった。onnx のエラーが出るのだけど、WASM のポインタアドレスが throw されてくるのでまるで情報がない。

Python 側では起きないので、ずっとJS側の実装でメモリ管理がおかしいことを疑っていたが、結局 Claude Opus 君が「モデルのマスク管理がおかしいです!」ということに気付いてくれてなおすことができた。ひさびさに Claude に感動したわ。Python 側で再現しなかったのは長時間連続のストリーム推論することがないからだった。

先読みの実装

当初から「先読み」を実装していたつもりだったのに、この期に及んで、まったく先読みできていなかったことが発覚。わたくし衝撃です。先読みせずにそこそこ精度が出てたのはすごいが…… 一応文字が確定するタイミングでCTC発火するように調整していたので、非因果的なのは長点・短点・単語間空白などを予測するヘッドだけだったはずではある。機械学習、こういう間違った実装されてても、ある程度はなんとかなっちゃうことがあるので本当に怖い。

ということで、時系列データの場合とにかく因果性というのが問題になって、常に頭を悩ませる。こんがらがってくる。ラベル自体をオフセットするのか? アテンションに未来を見せるのか? そして、コーディングエージェントは時系列というものが苦手なので、人間がちゃんと理解してテストを書かせてないとダメなのだ…… 普通につらい。

前回も言ったけどコーディングエージェントはとにかくテストを書きたがらない。いくら言ってもダメ。AGENTS で指定しても意味あるのは初回ぐらい。テストで動作を保証する発想が一切ない。ゴミスクリプトをチョチョッっと作って実行してはゴミ掃除もせず放置する。

2026年 01月 28日

WebAudio-Signal-Generator

むか〜し書いた WebAudio-Signal-Generatorをモダンにした。AngularJS 1.2 + Bootstrap 3 だったのを Vue3 + Vanilla CSS に。というかそもそも ScriptProcessorNode 使っててマジで古かった。

ブラウンノイズ こんな感じで設定をURLに反映するようにして、リンクを貼れるように。

まぁもうこんなの欲しいとおもったときにLLMに書かせりゃいいという気はするけど

2026年 01月 25日

機械に機械学習のモデルを設計してもらう(何もわからない)

主に Gemini を使って、LLM主体でモデルの設計と学習をなるべくさせてみるということに数日とりくんでた。

自分が知らないことをLLMにやらせてみようというのを時々やっている。

課題

モールス符号の音響受信をさせる。7年前にKerasを使って頑張って似たようなことをやった覚えがある。その情報は渡さずに「今こういうのやるならどういうモデルがいいかな」という相談からはじめた。

Gemini いわく「 CTC損失を用いたStreaming Conformer (CNN + Transformer)」がいいのではということだった。よくわからないが全く理解しないままこの方向ですすんでみることにする。(賢いあなたは、わからないことがあったらその時点で必ず解消しましょう)。

リアルタイム音声認識ということでこれを提案してきたのだろう……

できたもの

https://cho45.github.io/morse-decoder-2026/demo.html

一応計画の段階で「最終的にブラウザでリアルタイムにストリーム処理できること」は入れてたので、いろいろ問題はありつつも動くものはできた。

pytorch でモデルを作り、最終的に onnx に変換して onnx web で動いている (wasm)。WebGPU でも動きはするけど、なんかちゃんとやってないのでパフォーマンスがでない。

「SNRいくつまでデコードができる」みたいなことを言いたいが、SNRの定義がなんともいえない (信号のある瞬間の振幅基準) ので難しい。このSNRで-15dBぐらいまでギリデコードができる。もうちょっと頑張ってほしいけど……

モデル構造

こんな感じ。CNN + Transformer のことを Conformerというらしい。まんなかの4層がそれ。ただのモールス復号のモデルにしてはデカいはずだけど、もっと少ない状態だとまったくうまくいかなかった。

入力はスペクトログラムを特定ピーク中心で切り出したものになっている。つまりベースバンド波形ではなく画像として見てる。別途ピーク検出と切り出しを行ってからこのモデルに入れる想定。位相が不要だから画像として扱うほうがいいっぽい?

しかし疑問点

モデルの内容には、ほぼ口を出さずにやれるところまでやったけど、そもそもいくつか疑問があるので、今更訊いた。

なぜ入力にメルスペクトログラム?

(意図: 特定周波数付近を切り出して渡しているので、通常のスペクトログラムで十分だし実用上は楽なのでは?)

A:「メルスペクトログラムは、一般的な音声認識(ASR)モデルの標準的な入力形式であり、情報の圧縮とノイズ耐性に優れているため採用されています。狭帯域にクリップする場合、メル尺度による圧縮の恩恵は薄れるため、本アプリにおいては線形スペクトログラムの方が解像度を維持でき、より合理的である可能性が高いです。」

「リアルタイム音声認識」にひっぱられてあんまり考えずに決めてるな……

出力がCTCなのはなぜ?

Q: 「今回のモールス学習の場合、かなり厳密にラベルがつけられるはずですよね。そして実際そうしているし Signal Head として出力もしている。それでもなおCTCを使う理由は?」

A:「Signal Head による物理的な信号認識を補助としつつ、CTC を使うことで「文字」としての文脈や区切りを柔軟に扱い、デコード時の頑健性を高めるためです。」

Q:「今回の場合ラベルの柔軟性というよりは出力の簡略化の側面がつよい?」

A:「はい、その通りです。Signal Headのように「全フレームを分類」するのではなく、CTCを使うことで「重複除去とblank削除」という単純なルールだけで、複雑な後処理なしに最終的なテキストを直接得られるという、出力の簡略化と利便性の側面が非常に強いです。」

フーン……

出力に CTC 以外に Signal Logits と Boundary Logits がある。これらはCTC出力からだけの学習で一切上手くいかなかったため、苦肉の策として追加した出力になっている。(ここは自分の指示……)

  • Signal Logits: モールスの「短点」「長点」「符号間空白」「文字間空白」「単語間空白」「ブランク」を予想させる -- モールスの形状を強制的に学習させる
  • Boundary Logits: モールスにおいて、符号が確定する瞬間に1になる。つまり文字符号の後の空白1ユニットの直後に発火する -- CTCが発火すべきポイントを間接的に学習させる意図 --- これそのままCTCへの入力に使ったら楽や~んと思ったけど、それだとCTCがチートして、Boundary Logits が高かったら適当に文字出すようになってしまって一敗

これが正しいことかはわからないが、Gemini 的には学習を進めるうえで有効ですとはいっていた。まぁ結果としてうまくはいったけど、これのせいでモデルサイズが大きくなっている面がある気がする。

得られたもの

やはり自分で正解知らないことをボーっとやらせるのは非常に難しい。結局急がば回れになることが多い…… わかってるんだよ。わかってるけどわからないまま作りたいんだ!

機械学習の修正タスク投げると、いくら事前に指示をしていてもテストを書かないし実行してくれない。最終的には「平均的なエンジニアはテストを書かず本体のコードだけに集中している。そこから学んでいるので自分もそういう傾向にあるのだ」という開きなおりをされた。

2026年 01月 20日

このサイト開くと、便利情報として右上に IPv4 か IPv6 か表示するようにしてあるんだけど、ここに今繋いでる HTTP のバージョンも出すようにしてみた。

/.ip としてシンプルな情報を出すエンドポイントが前からあるのでここに足しただけ

      "/.ip":
        header.set: "Cache-Control: private"
        mruby.handler: |
          lambda do |env|
            ipv6 = env["REMOTE_ADDR"].include?(":")
            proto = {"HTTP/2" => "h2", "HTTP/3" => "h3"}.fetch(env["SERVER_PROTOCOL"], "")
            [200, {'content-type' => 'text/plain'}, [ "#{ipv6 ? 'IPv6' : 'IPv4'} #{proto}" ]]
          end


直接関係ないけど、h2o からproxy.reverse.url のバックエンドに直接こういう変数を入れたカスタムしたヘッダを送るのはあんまり簡単でなさそう? http_request 使うぐらい?

2026年 01月 19日

Chromeの開発者ツールでそのプロトコルが選ばれた理由が表示されることに気付いた

ずーーーと「なんでh3にならんのかな〜」ということをやってたけど、この開発者ツールのプロトコルの部分にホバーすると理由が出るということにようやく気付いた。ただ出ないこともある

HTTPS レコードで勝った場合

`Chrome` used a `HTTP/3` connection due to the `DNS record` indicating `HTTP/3` support, which won a race against establishing a connection using a different `HTTP` version.

HTTPSレコードちゃんと使ってるんだ。

ただレースして勝ったから使っていると書いてある。従来のTCP接続も同時に投げているようだ。

おそらく初回接続だけで、次からは Alt-Svc 優先になると思われる。(シークレットウィンドウで初回アクセスするとこれになる)

Alt-Svc ヘッダで勝った場合

`Chrome` used a `HTTP/3` connection induced by an `Alt-Svc` header without racing against establishing a connection using a different `HTTP` version.

2回目移行の接続。こっちはレースしないで決め打ちで繋ぐんだなあ