にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

このブログについて

文字・フォント・プログラム・技術・趣味などについて、Twitterでは書きづらい長い内容などをまとめるためのブログです。基本的には自分用のメモとして書いている部分が多いです。

リンク等

note https://note.com/nixeneko 残らなくてもいい記事
Pixiv https://pixiv.me/nixeneko
Tumblr https://nixeneko.tumblr.com/ 絵。Pixivと同じにしたいがサボリ気味
Pleroma @nixeneko@nixeneko.info Mastodonとかやってる人はフォローしてください
Twitter @nixeneko
GitHub https://github.com/nixeneko プログラムとか
Bookmeter https://bookmeter.com/users/9166 読書記録。たまに感想
ナナシスID ZhRYMnA
D4DJ oDgpcYR9
Amazon欲しい物リスト amazon.jp/registry/wishlist/1C43ZFBA4IL6Z

同人誌(無料公開)

http://nixeneko.hatenablog.com/entry/c88_russian_alphabethttp://nixeneko.hatenablog.com/entry/c90_greek_latin_cyrillichttp://nixeneko.hatenablog.com/entry/20170811_dentyu

NextDNSを使って深夜にTwitterにアクセスできないようにする

NextDNSを使って、特定の時間帯のみTwitterにアクセスできるように設定した。

モチベーション

深夜延々とツイッターを見てしまうということがある。人によってはTwitterでなくてYouTubeとかかもしれない。これをアクセス不可能にできればゆっくり眠れるのかなあと思うこともある。

特定の時間帯にアクセス不能にするというのは、DNSをカスタマイズすればできる。ただ、自分でDNSサーバーを立てるのは面倒だし、家のLAN/Wifi環境とそれ以外で統一的に利用できるようにするのは大変*1なので、なかなか難しい。

NextDNS

NextDNSというDNSサービスがあり、これは簡単にいうと自分で好きにカスタマイズできるDNSである。

この設定項目の中に「ペアレンタルコントロール」というものがあり、特定のサイトへのアクセスを制限でき、「娯楽時間」の設定で、接続を許可する時間を設定できる。

つまり、深夜帯以外を「娯楽時間」に設定することで、深夜はTwitterにアクセス不可にすることが可能だ。
また、システム全体に設定することで、家の中でも外でも同じDNSを使うことができる。

NextDNSは無料で30万クエリ/月まで利用でき、制限のないProプランだと250円/月となる。
まずは登録なしで一週間利用できるので試してみるといいのかもしれない。

以下、試してみたことと、感想を書いていく。

設定方法

NextDNSのトップページを開き、“Try it now”ボタンをクリックするといきなりDNSサービスが準備される。
これをデバイスに設定すると利用準備が完了する。

DNSの端末への設定

NextDNSの設定ページの「セットアップガイド」に従って準備する。

  • Windowsでは設定用のソフトをインストールして使用するか、自分で設定を変更するかのどちらかの方法があるらしい。
  • Androidでは「プライベート DNS」で設定するのが便利らしい。
  • ルーターに設定してもいいが、それに接続する端末すべてに影響しうるので、他にそのルータを使用して接続するすべての人の了解を得てからやった方がよい。自分だけが利用するルータを用意するというのも手ではある。

ペアレンタルコントロールの設定

「ペアレンタルコントロール」タブを開き、「ウェブサイト、アプリ、ゲーム」セクションで、アクセスを制限したいWebサイトやアプリを追加する。

「娯楽時間」セクションで時間を設定する。

f:id:nixeneko:20211007134855p:plain
娯楽時間の設定例

30分刻みでしか設定できないのと、終端の時間が23:30までしか設定できない、また複数時間帯を設定できないのが不便っぽい。

「ウェブサイト、アプリ、ゲーム」セクションで、設定したい項目の右の方にあるグレーの時計マーク🕓をクリックして有効化する。有効にすると緑色になる。

f:id:nixeneko:20211007135036p:plain
娯楽時間の有効化

設定の永続化

このままだと7日間で期限切れとなってまうので、継続して使用したければ登録する必要がある。
設定ページの上の「新規登録」ボタンを押すと登録画面に進むので、メールアドレスとパスワードを設定すればアカウントが作成される。

本当に効くのか?

さて、DNSによる名前解決の結果は端末にキャッシュされる。
そのため、アクセス制限しても、それまでに通信していた場合にしばらくは繋がることになるかもしれない。

実際にAndroid端末でやってみると、しばらくはTwitterは普通に読み込みできる。10分ほどすると、TwitterのWebサイトにつながらなくなる一方、アプリでは次第に(キャッシュされてない)画像等が表示されなくなるもののテキスト情報の読み込みはできる。1時間ほどして確認してみたらアプリで新しいツイートが読み込めなくなっていた。
もう一度やってみたら遮断開始時刻から1分ほどで画像が読み込めなくなり、10分程度で完全に読み込めなくなった。

YouTubeアプリでやってみたら、制限して5分ほどしたら新しく再生を開始した動画の再生が止まるようになった。

というわけで、時間差はあるがそのうち効いてくるという感じっぽい。なるほど。

課題点

  • リストにないサイト・アプリはペアレンタルコントロールに追加できるのか?
  • ペアレンタルコントロールの時間をもっと細かく設定できないか?
    • 6:00-10:00, 15:00-21:00とか、0:00-1:00, 5:00-24:00とかね…
    • 日を跨げないの厳しい
  • ルータで設定したDNSルーティング設定が使えない(それはそう)
  • 30万クエリ/月というのは心もとない。Android端末一つだけでも1000クエリ/時程度は行くので、複数端末使えば簡単に突破するのでは?
    • まともに使いたければ課金が必要そう。高くはないが…

感想

全体としては良い感じだと思うし、有用なケースはある。ただ、ペアレンタルコントロールの設定があまり柔軟でなく、もっと改善の余地があるなあと思った。

*1:これはLAN内でDNSサーバを動かした場合で、対策としてはVPSとか借りればいいわけですが、そこにそんなに金かけたくないし…

1次B-スプライン曲線の制御点列をそのまま利用して2次B-スプライン曲線で1次B-スプライン曲線を近似する

問題提起

1次B-スプライン曲線(linear B-spline curve)とはすなわち折れ線のことである。
これはシンプルで、隣り合う2制御点を順番に直線で結んでいけば求める曲線(というか折れ線)が描ける。

一方で、2次B-スプライン曲線(quadratic B-spline curve)はB-スプライン基底関数という2次関数を使って制御点の影響を計算して曲線を描く。
基本的に2次B-スプライン曲線では、連続する制御点を結んだ折れ線、つまり直線の連続を描くことができない。
1次B-スプライン曲線の隣り合う2制御点の中点*1を制御点として2点間に挿入し、ノットベクトル(後述)を[0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4]みたいにすると直線が表現できるが、制御点列としては別のものになる。


さて、ここで、1次B-スプライン曲線の制御点列をそのまま利用して、2次B-スプライン曲線で1次B-スプライン曲線を表現することはできるだろうか?

モチベーション

TrueTypeアウトラインの曲線部分は2次B-スプライン曲線の特殊な場合であるが、2次B-スプライン曲線は隣り合う2制御点を結ぶような折れ線、つまり1次B-スプライン曲線はふつう表現できない。そのため、TrueTypeアウトラインのcontourの制御点と2次B-スプライン曲線の制御点は同一ではないと考えていた。

一方で、もしそのような折れ線を2次B-スプラインで表現できるのであれば、直線部分も2次B-スプライン曲線として表現可能になり、任意のTrueTypeアウトラインは制御点を挿入することなしに2次B-スプライン曲線だと言えるのではないかと思う。

B-スプライン曲線の定義

n次のB-スプライン曲線とは、制御点列(control points) \{\boldsymbol P_i\}とノットベクトル(knot vector) \{ t_i \}で定義される曲線である。曲線の補間(制御点の影響の計算)にはn次のB-スプライン基底関数が用いられる。

n次のB-スプライン基底関数(n-th order B-spline basis function)  N_i^n(t)は、次のように定義される。(コンピュータグラフィックス編集委員会(2004), p.66 (3.29)式)
 \displaystyle N_i^0(t) = \left\{ \begin{array}{ll} 1 & (\text{if} \quad t_i \leq t < t_{i+1}) \\ 0 & (\text{if} \quad t_i \leq t < t_{i+1}) \end{array} \right.

 \displaystyle N_i^n(t) = \frac{t-t_i}{t_{i+n}-t_{i}} N_i^{n-1}(t) + \frac{t_{i+n+1}-t}{t_{i+n+1}-t_{i+1}} N_{i+1}^{n-1}(t) \quad (n\geq 1)

これを使って、L個のセグメントからなるn次B-スプライン曲線は次のように定義される。
 \displaystyle \boldsymbol{P}(t) = \sum_{i=0}^{n+L-1}\boldsymbol{P}_iN_i^n(t) \quad (t_n \leq t < t_{n+L})

1次B-スプライン曲線

さてここで、1次B-スプライン曲線の場合を見ていく。

L個のセグメントからなる1次B-スプライン曲線は次のように定義される。
 \displaystyle \boldsymbol{P}(t) = \sum_{i=0}^{L}\boldsymbol{P}_iN_i^1(t) \quad (t_1 \leq t < t_{L+1})

ここで、 0 \leq k < Lなる整数 kに関して、点 \boldsymbol{P}_k \boldsymbol{P}_{k+1}で定義される一つのセグメント ( t_{k+1} \leq t < t_{k+2})だけをみると、
 \displaystyle \boldsymbol{P}(t) = \boldsymbol{P}_kN_k^1(t) + \boldsymbol{P}_{k+1}N_{k+1}^1(t)

ここで、B-スプライン基底関数は次のようになる。
 \displaystyle N_k^0(t) = 0

 \displaystyle N_{k+1}^0(t) = 1

 \displaystyle N_{k+2}^0(t) = 0

 \displaystyle \begin{eqnarray*} N_k^1(t) &=& \frac{t-t_k}{t_{k+1}-t_k} N_k^0(t) + \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}} N_{k+1}^0(t) \\ &=& \frac{t-t_k}{t_{k+1}-t_k} \cdot 0 + \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}} \cdot 1 \\ &=& \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}} \end{eqnarray*}

 \displaystyle \begin{eqnarray*} N_{k+1}^1(t) &=& \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}} N_{k+1}^0(t) + \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} N_{k+2}^0(t) \\ &=& \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}} \cdot 1 + \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} \cdot 0 \\ &=& \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}} \end{eqnarray*}

したがって、
 \displaystyle \boldsymbol{P}(t) = \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}}\boldsymbol{P}_k + \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}}\boldsymbol{P}_{k+1}
というわけで2点間の線形補間になっている=直線であることがわかる。

2次B-スプライン曲線

さて、次に2次B-スプライン曲線の場合について考える。

2次B-スプライン曲線の1つのセグメントは3点の制御点からなる。そのため、1次B-スプライン曲線と同じ数のセグメントを用意するためには、制御点を最後に一個追加しないといけない。

L個のセグメントからなる2次B-スプライン曲線は次のように定義される。
 \displaystyle \boldsymbol{P}(t) = \sum_{i=0}^{L+1}\boldsymbol{P}_iN_i^2(t) \quad (t_2 \leq t < t_{L+2})

ここで、 0 \leq k < Lなる整数 kについて、点 \boldsymbol{P}_k \boldsymbol{P}_{k+1},  \boldsymbol{P}_{k+2}で定義される一つのセグメント t_{k+2} \leq t < t_{k+3}だけをみると、

 \displaystyle \boldsymbol{P}(t) = \boldsymbol{P}_kN_k^2(t) + \boldsymbol{P}_{k+1}N_{k+1}^2(t) + \boldsymbol{P}_{k+2}N_{k+2}^2(t)
となる。

1次B-スプライン曲線の近似

さて、ここでノットベクトルとして次のような公比を無限大に近づけた等比数列 \{ t_i \}を考える*2
 \displaystyle t_i = \lim_{r \to +\infty} ar^{i} ただし \displaystyle a>0

すると、 \displaystyle t_{k+2} \leq t < t_{k+3}において、B-スプライン基底関数は次のように計算される。

後の計算で必要となるもの

 \displaystyle \frac{t_{k+2}}{t_{k+3}} = \lim_{r \to +\infty}\frac{1}{r} = 0

 \displaystyle \frac{t_{k+3}}{t_{k+4}} = \lim_{r \to +\infty}\frac{1}{r} = 0

 \displaystyle \frac{t_{k+2}}{t_{k+4}} = \lim_{r \to +\infty}\frac{1}{r^2} = 0

 \displaystyle \frac{t_{k+2}}{t_{k+4}} \leq \frac{t}{t_{k+4}} < \frac{t_{k+3}}{t_{k+4}} \quad (\because t_{k+2} \leq t < t_{k+3})より \displaystyle \frac{t}{t_{k+4}} = 0

0次

 \displaystyle N_k^0(t) = 0

 \displaystyle N_{k+1}^0(t) = 0

 \displaystyle N_{k+2}^0(t) = 1

 \displaystyle N_{k+3}^0(t) = 0

 \displaystyle N_{k+4}^0(t) = 0

1次

 \displaystyle \begin{eqnarray*} N_k^1(t) &=& \frac{t-t_k}{t_{k+1}-t_k} N_k^0(t) + \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}} N_{k+1}^0(t) \\ &=& \frac{t-t_k}{t_{k+1}-t_k} \cdot 0 + \frac{t_{k+2}-t}{t_{k+2}-t_{k+1}} \cdot 0 = 0 \end{eqnarray*}

 \displaystyle \begin{eqnarray*} N_{k+1}^1(t) &=& \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}} N_{k+1}^0(t) + \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} N_{k+2}^0(t) \\ &=& \frac{t-t_{k+1}}{t_{k+2}-t_{k+1}} \cdot 0 + \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} \cdot 1 = \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} \end{eqnarray*}

 \displaystyle \begin{eqnarray*} N_{k+2}^1(t) &=& \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} N_{k+2}^0(t) + \frac{t_{k+4}-t}{t_{k+4}-t_{k+3}} N_{k+3}^0(t) \\ &=& \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} \cdot 1 + \frac{t_{k+4}-t}{t_{k+4}-t_{k+3}} \cdot 0 = \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}}  \end{eqnarray*}

 \displaystyle \begin{eqnarray*} N_{k+3}^1(t) &=& \frac{t-t_{k+3}}{t_{k+4}-t_{k+3}} N_{k+3}^0(t) + \frac{t_{k+5}-t}{t_{k+5}-t_{k+4}} N_{k+4}^0(t) \\ &=& \frac{t-t_{k+3}}{t_{k+4}-t_{k+3}} \cdot 0 + \frac{t_{k+5}-t}{t_{k+5}-t_{k+4}} \cdot 0 = 0 \end{eqnarray*}

2次

 \displaystyle \begin{eqnarray*} N_k^2(t) &=& \frac{t-t_k}{t_{k+2}-t_k} N_k^1(t) + \frac{t_{k+3}-t}{t_{k+3}-t_{k+1}} N_{k+1}^1(t) \\ &=& \frac{t-t_k}{t_{k+2}-t_k} \cdot 0 + \frac{t_{k+3}-t}{t_{k+3}-t_{k+1}} \cdot \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} \\ &=& \frac{t_{k+3}-t}{t_{k+3}-t_{k+1}} \cdot \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} \\ &=& \frac{1-\frac{t}{t_{k+3}}}{1-\frac{t_{k+1}}{t_{k+3}}} \cdot \frac{1-\frac{t}{t_{k+3}}}{1-\frac{t_{k+2}}{t_{k+3}}} \\ &=& \left(1-\frac{t}{t_{k+3}}\right)^2 \end{eqnarray*}

 \displaystyle \begin{eqnarray*} N_{k+1}^2(t) &=& \frac{t-t_{k+1}}{t_{k+3}-t_{k+1}} N_{k+1}^1(t) + \frac{t_{k+4}-t}{t_{k+4}-t_{k+2}} N_{k+2}^1(t) \\ &=& \frac{t-t_{k+1}}{t_{k+3}-t_{k+1}} \cdot \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} + \frac{t_{k+4}-t}{t_{k+4}-t_{k+2}} \cdot \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} \end{eqnarray*}

 \displaystyle \begin{eqnarray*} \frac{t-t_{k+1}}{t_{k+3}-t_{k+1}} \cdot \frac{t_{k+3}-t}{t_{k+3}-t_{k+2}} &=& \frac{\frac{t}{t_{k+3}}-\frac{t_{k+1}}{t_{k+3}}}{1-\frac{t_{k+1}}{t_{k+3}}} \cdot \frac{1-\frac{t}{t_{k+3}}}{1-\frac{t_{k+2}}{t_{k+3}}} \\ &=& \frac{t}{t_{k+3}} \left( 1-\frac{t}{t_{k+3}} \right) \end{eqnarray*}

 \displaystyle \begin{eqnarray*} \frac{t_{k+4}-t}{t_{k+4}-t_{k+2}} \cdot \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} &=& \frac{1-\frac{t}{t_{k+4}}}{1-\frac{t_{k+2}}{t_{k+4}}} \cdot \frac{\frac{t}{t_{k+3}}-\frac{t_{k+2}}{t_{k+3}}}{1-\frac{t_{k+2}}{t_{k+3}}} \\ &=& \left( 1-\frac{t}{t_{k+4}}\right) \cdot \frac{t}{t_{k+3}} = \frac{t}{t_{k+3}} \end{eqnarray*}
よって
 \displaystyle \begin{eqnarray*} N_{k+1}^2(t) &=& \frac{t}{t_{k+3}} \left( 1-\frac{t}{t_{k+3}} \right) + \frac{t}{t_{k+3}} \\ &=& \frac{t}{t_{k+3}} \left( 2-\frac{t}{t_{k+3}} \right) \end{eqnarray*}

また、
 \displaystyle \begin{eqnarray*} N_{k+2}^2(t) &=& \frac{t-t_{k+2}}{t_{k+4}-t_{k+2}} N_{k+2}^1(t) + \frac{t_{k+5}-t}{t_{k+5}-t_{k+3}} N_{k+3}^1(t) \\ &=& \frac{t-t_{k+2}}{t_{k+4}-t_{k+2}} \cdot \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} + \frac{t_{k+5}-t}{t_{k+5}-t_{k+3}} \cdot 0 \\ &=& \frac{t-t_{k+2}}{t_{k+4}-t_{k+2}} \cdot \frac{t-t_{k+2}}{t_{k+3}-t_{k+2}} \\ &=& \frac{\frac{t}{t_{k+4}}-\frac{t_{k+2}}{t_{k+4}}}{1-\frac{t_{k+2}}{t_{k+4}}} \cdot \frac{\frac{t}{t_{k+3}}-\frac{t_{k+2}}{t_{k+3}}}{1-\frac{t_{k+2}}{t_{k+3}}} \\ &=& \frac{t}{t_{k+4}} \cdot \frac{t}{t_{k+3}} = 0 \cdot \frac{t}{t_{k+3}} = 0 \end{eqnarray*}


ここから、 \displaystyle N_{k+2}^2(t) = 0なので \boldsymbol P_{k+2}の関与が完全になくなっていることがわかる。

また、 \displaystyle N_{k}^2(t) + N_{k+1}^2(t) = 1となることもわかる。

 y = \displaystyle N_{k}^2(t),  y = \displaystyle N_{k+1}^2(t)のグラフを描くとだいたい次のような感じになる*3
f:id:nixeneko:20210814154404j:plain

実際に描いてみた

プログラムを使って描いてみた。さすがに無限大は扱えないので適当に大きな値にしてみる。

描画に用いたPython3コードを次に示す*4STEPが公比rに相当する。
gist.github.com

描画結果

赤が正解となる1次B-スプライン曲線、黒が2次B-スプライン曲線である。

r=10

f:id:nixeneko:20210813092559p:plain

r=100

f:id:nixeneko:20210813092614p:plain

r=1000

f:id:nixeneko:20210813092622p:plain

r=10000

f:id:nixeneko:20210813092638p:plain
r=10000でほぼ一致している。
隣り合う2制御点をr:1で内分する点を通る曲線が描かれているので、r=10000のときは2点を10000:1に内分する点を通る曲線となる。誤差が1ピクセルより小さくなるので直線に見えるというわけである。
画像サイズが(1000, 1000)なので公比r=10000程度でも直線になっているが、ドローソフトで線を拡大していく場合などはそれでも足りなくなると思う。
r=10000の場合、制御点15個に対して最初のノット値と最後のノット値を比べると1064倍になっている。複雑な図形にしたらすぐにあふれそう。

考察

1次B-スプライン曲線を(制御点を変更せずに)2次B-スプライン曲線を使って表現できる(そのようなノットベクトルの取り方が存在する)ことがわかった。

つまり、適切にノットベクトルを用意すれば、TrueTypeアウトラインのcontourをそのまま2次B-スプラインを用いて描画可能なのでは?

というわけで試してみる。
用意するのは次の制御点列。onはTrueTypeにおけるon-curve点、offはoff-curve点を示す。

idx 座標 on/off
0 (10, 10) on
1 (10, 990) on
2 (250, 500) off
3 (500, 990) off
4 (750, 500) off
5 (990, 990) on
6 (990, 10) on
7 (500, 500) off
8 (10, 10) on

これに対して、
 \displaystyle \boldsymbol t = \lim_{r \to +\infty}\{r^0, r^0, r^0, r^1, r^2, 2r^2, 3r^2, 4r^2, r^3, r^4, r^4, r^4\}
みたいなノットベクトルを用意してみたらどうだろう。


r=10000として計算したのが次の画像である。この手法で計算された曲線が黒い線となっている。
f:id:nixeneko:20210814200729p:plain
制御点を青の点で示している。制御点列を結んだ直線が水色の直線である。
赤は正解となる2次B-スプライン曲線で、2連続するon-curve点の途中にその2点の中点をoff-curveな制御点として挿入してon-curve点の連続をなくし、ノットベクトルを[0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6]としたものであるが、黒い線の下に入ってほぼ見えなくなっている。

描画に使用したPython3コードは次のものである。


というわけで、こんな感じのわけわからんノットベクトルを用意すれば、同一の制御点列を用いつつも2次B-スプライン曲線はTrueTypeアウトラインのcontourに一致する気がする*5
ちゃんと計算すれば(上でやったのと同じような議論で)示せると思うけど面倒なので気になったら各自計算してね…

感想

無限大を導入すれば、任意のTrueTypeアウトラインのcontourの制御点列をそのまま2次B-スプライン曲線の制御点列として、同一のアウトラインを描けるノットベクトルの取り方が存在しそう、ということがわかった。無限大を導入すれば……

コンピュータで扱う数値計算に無限大を導入するな💢

実用としては素直に直線で計算するべし。

参考書籍

*1:別に内分点であればいいけど

*2:こういう数式の書き方で正しいのかわからないけど、とにかく公比をめちゃくちゃ大きくした等比数列だと思ってください。

*3:ガタガタしてるように見えるのは描くのが下手なだけので気にしないで…。普通の放物線です。

*4:このコード、めちゃくちゃ効率が悪いしあんまり汎用性もないです。あしからず。

*5:最後が直線になる場合はノット重ねるとベジエになっちゃうのでダミーの制御点追加しないといけないけど、一様B-スプライン曲線の描画と同様に制御点ループさせればいいので問題ないか…。

Pleromaのホームタイムラインが500エラー返すようになってた

PostgreSQLの実行でタイムアウトしていたので、VACUUM ANALYZEを実行したら解決した。

問題

以前Pleromaのサーバを移行したが、

その後、多少問題はあったが普通に動いていた。

環境はPleroma 2.3.0で、ソースからインストールしたものである。


しかし、今日急に、SubwayTooterでホームタイムラインを取得しようとした(GET /api/v1/timelines/home)場合に、500 Internal Server Errorが返ってくるようになった。(ブラウザからは普通に見れていたのでよくわからない…)

さてどうしたものか。

調査

サーバーにログインし、

sudo journalctl -fu pleroma.service

実行する。するとpleromaのログが流れてくる。

ここでSubwayTooterでホームタイムラインを取得しようとすると、次のようなログがでてくる。時刻やrequest_idなど先頭部分は省いている。

[info] GET /api/v1/timelines/home
[error] Postgrex.Protocol (#PID<■■■>) disconnected: ** (DBConnection.ConnectionError) client #PID<■■■> timed out because it queued and checked out the connection for longer than 15000ms
#PID<■■■> was at location:
    :prim_inet.recv0/3
    (postgrex) lib/postgrex/protocol.ex:2838: Postgrex.Protocol.msg_recv/4
    (postgrex) lib/postgrex/protocol.ex:1880: Postgrex.Protocol.recv_bind/3
    (postgrex) lib/postgrex/protocol.ex:1735: Postgrex.Protocol.bind_execute_close/4
    (db_connection) lib/db_connection/holder.ex:316: DBConnection.Holder.holder_apply/4
    (db_connection) lib/db_connection.ex:1272: DBConnection.run_execute/5
    (db_connection) lib/db_connection.ex:1359: DBConnection.run/6
    (db_connection) lib/db_connection.ex:613: DBConnection.execute/4
[error] Internal server error: %DBConnection.ConnectionError{message: "tcp recv: closed (the connection was closed by the pool, possibly due to a timeout or because the pool has been terminated)", reason: :error, severity: :error}
[info] Sent 500 in 15293ms
[error] #PID<■■■> running Pleroma.Web.Endpoint (connection #PID<■■■>, stream id 1) terminated
Server: nixeneko.info:80 (http)
Request: GET /api/v1/timelines/home?limit=80
** (exit) an exception was raised:
    ** (DBConnection.ConnectionError) tcp recv: closed (the connection was closed by the pool, possibly due to a timeout or because the pool has been terminated)
        (ecto_sql) lib/ecto/adapters/sql.ex:593: Ecto.Adapters.SQL.raise_sql_call_error/1
        (ecto_sql) lib/ecto/adapters/sql.ex:526: Ecto.Adapters.SQL.execute/5
        (ecto) lib/ecto/repo/queryable.ex:192: Ecto.Repo.Queryable.execute/4
        (ecto) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
        (pleroma) lib/pleroma/pagination.ex:40: Pleroma.Pagination.fetch_paginated/4
        (pleroma) lib/pleroma/web/activity_pub/activity_pub.ex:1172: Pleroma.Web.ActivityPub.ActivityPub.fetch_activities/3
        (pleroma) lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:59: Pleroma.Web.MastodonAPI.TimelineController.home/2
        (pleroma) lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:5: Pleroma.Web.MastodonAPI.TimelineController.action/2

ここから、PostgreSQLで時間がかかっていることがわかる。

対策

検索してたら次の投稿を見つけた。
pleroma.gidikroon.eu

I think I remember that once after a migration I needed to do an ANALYZE to stop the timeouts from happening.

そのため、データベースのVACUUM ANALYZEを実行してみた。

VACUUM ANALYZEの実行

cd /opt/pleroma
sudo service pleroma stop
sudo -Hu pleroma MIX_ENV=prod mix pleroma.database vacuum analyze
sudo service pleroma start

参考: Database maintenance tasks - Pleroma Documentation

vacuum analyzeの直前にpg_dumpでバックアップをしている。pleromaはデータベース名、pleroma_database_backup.pgdumpは出力ファイル

sudo -Hu postgres pg_dump -d pleroma --format=custom -f pleroma_database_backup.pgdump

結果

すると、ホームタイムラインが復活した。
副作用として、うまく動かなくなっていたパブリックタイムラインとハッシュタグタイムラインが動くようになった。

データベースを移行した時とか、定期的にVACUUM ANALYZEを実行するといいようだ。

Pythonでの長い文字列の連結は遅い

概要

Python 3で、長い文字列を格納した文字列変数に+=で連結して文字列を保持していると遅い。
文字列のリストとして保持しておいて最後に連結すると速い。

擬似コードでは

# text_iterは文字列を返すiterableオブジェクト
#遅い
out_text = ""
for text in text_iter: 
    out_text += text

#速い
out_list = []
for text in text_iter:
    out_list.append(text)
out_text = "".join(out_list)

背景

Python 3で、テキストファイルを読み込んで、指定の条件に合致した行だけ取り出すというプログラムを書いていた。だが、どうにも実行が遅い。

そのコードでは、ループで行を取り出し、それを文字列変数に+=で結合して保持して、最後にファイルに書き込んでいた。

もしかして、文字列の連結がよくないのでは?と思ったので実験してみた。

実験

沢山の行が含まれるテキストを、各行ごとにループを回し、行の文字列を変数に蓄積していくというプログラムを考える。

一行ずつループを回して、結果を格納していくが、

  1. 文字列変数にどんどん連結していく
  2. list変数にappendしていって、最後に"".joinで連結して文字列にする

という2つの方法が考えられる。この2つを比較してみる。

コード

テストに使ったテキストはhttps://dumps.wikimedia.org/jawiki/20210720/jawiki-20210720-all-titles.gzを解凍したもので、90MB程度、3778098行ある。これと同じディレクトリに次のpythonコードを入れて実行する。


コードを示す。

# coding: utf-8

import codecs
import time

TEXTFILE = "jawiki-20210720-all-titles"
REPORT_INTERVAL = 100000

if __name__ == '__main__':
    print("Start Processing...")
    out_text = ""
    with codecs.open(TEXTFILE, "r", "utf-8") as f:
        lines = f.readlines()
    
    # 1.
    start_time_1 = time.time()
    n_processed = 0
    out_text = ""
    for line in lines:
        out_text += line
        n_processed += 1
        if n_processed % REPORT_INTERVAL == 0:
            print(n_processed, "lines processed")
    elapsed_1 = time.time() - start_time_1
    print(len(out_text))

    # 2.
    out_list = []
    n_processed = 0
    start_time_2 = time.time()
    for line in lines:
        out_list.append(line)
        n_processed += 1
        if n_processed % REPORT_INTERVAL == 0:
            print(n_processed, "lines processed")
    out_text = "".join(out_list)
    elapsed_2 = time.time() - start_time_2
    print(len(out_text))
    
    print("str_concat:", elapsed_1, "sec")
    print("list:", elapsed_2, "sec")

これを実行する。


それで、最初に文字列の結合によるものをやっているが、出力の間隔を見ているとどんどん遅くなっていくのが分かる。
最後に出力される結果を見ると、

str_concat: 519.9358415603638 sec
list: 0.746680736541748 sec

…うそやろ!?…となっていて、378万回弱の文字列の連結は8分以上かかっている一方、リストへの追加は1秒未満で終わっていることがわかる。その差約700倍…

実験した環境は、

である。

ちなみに、同じPCでCygwin上のPython 3.8.9で実行した場合は

str_concat: 93.5771963596344 sec
list: 0.7298173904418945 sec

とかで、それでも100倍以上の差はある。

考察

文字列の+による連結は非破壊的操作なので、毎回結合後の文字列全体を新しい値として生成してメモリに格納してるので遅いということなのかなと思う。
list.appendは破壊的操作なので、新しく追加する部分だけ処理すればいいので速い。

結論

長いテキストを処理するときに、処理後のテキストを保持するのには、連結して文字列で保持すると遅く、文字列のlistで保持して後で"".joinした方が速い。

Windows 10+AnacondaでGPU版TensorFlow 2.3, 2.4, 2.5のインストール

Windows 10上のAnacondaに、TensorFlowのバージョン2.3.0, 2.4.1, 2.5.0のGPUサポート付きのものを、仮想環境ごとに併用可能な状態でインストールする。

概要

TensorFlowを導入しようとしてめんどくせ~って思ったのでインストール方法をメモしておく。

ディープラーニングライブラリごとに、またバージョンごとに、GPUサポートに必要とするCUDAのバージョンが異なっている。しかし、環境変数に同時に複数のバージョンのCUDAをPathに設定することはできない(複数設定しても一つしか参照されないので意味がない)。

そのため、仮想環境ごとにPathを変更したコマンドプロンプトを起動できるようにしたらいいのでは?と思った。これをすることで、違うバージョンを併用できる。

以下では、TensorFlowのバージョン2.3.0, 2.4.1, 2.5.0のGPUサポート付きのものを、仮想環境ごとに併用可能な状態でインストールする方法を書く。

環境

CUDAに必要なものをインストール

CUDA, cuDNNバージョンの確認

TensorFlowは、そのバージョンごとに、動作が確認されているPython, CUDAとcuDNNのバージョンがある。これに従うことで、CUDA等のバージョンに関連する問題を防ぐことができる。

テストされたPython, CUDA, cuDNNのバージョンは次のページで確認できる。

TensorFlowのバージョンごとにPython, cuDNN, CUDAのバージョンが書かれているので、それを使うようにする。

TensorFlow 2.3.0のインストール(condaコマンド使用)

仮想環境の作成

ここではtf230gpuという名前の仮想環境を作成する。TensorFlow 2.3.0はPython 3.5-3.8に対応しているようなので3.8を使うことにする。

Anaconda Promptを起動し、次のコマンドを実行する。

conda create -n tf230gpu python=3.8

すると、Anacondaのインストールディレクトリの中にenvs\tf230gpuディレクトリができているはず。

TensorFlow 2.3.0(GPU版)のインストール

Anaconda Promptから、仮想環境をtf230gpuに切り替える。

activate tf230gpu

その後、tensorflow-gpuのインストールを行う。

conda install tensorflow-gpu=2.3 tensorflow=2.3=mkl_py38h1fcfbd6_0

tensorflow=2.3=mkl_py38h1fcfbd6_0は本来要らないはずだが、これについてWindows 10ではビルドの自動選択にバグがあるっぽくて、ビルドを指定してインストールしないとGPUが使えない。

CUDA, cuDNNも一緒にインストールしてくれるので楽。


インストールが終わったら、

python

を実行し、

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

とか入力して、GPUが認識されているか試してみる。device_typeGPUXLA_GPUなどになっているデバイスがリストアップされていればOK。

仮想環境を起動するショートカットの作成(任意)

仮想環境が、Anacondaのインストールディレクトリの下のenvs\tf230gpuにあるので、Scripts\activate.datにこれを指定して起動すると、仮想環境に切り替わった状態になるらしい。


Windowsのスタートメニューの「Anaconda Prompt (anaconda3)」を右クリック→「ファイルの場所を開く」を選択し、開いたフォルダのAnaconda Prompt (anaconda3)のショートカットをコピーし、デスクトップとかに貼り付ける。
貼り付けたショートカットの名前をAnaconda tf230gpuとかの分かりやすい名前に変えておく。

貼り付けたショートカットの右クリックメニューから「プロパティ」を開き、「リンク先」の最後を
C:\Users\USERNAME\anaconda3C:\Users\USERNAME\anaconda3\envs\tf230gpu
のように変更し、

%windir%\System32\cmd.exe "/K" C:\Users\USERNAME\anaconda3\Scripts\activate.bat C:\Users\USERNAME\anaconda3\envs\tf230gpu

のようにする。ここで、USERNAMEWindowsのユーザー名で、環境により異なる。

これで、このショートカットをダブルクリックして起動すると、TensorFlow 2.3.0をインストールした仮想環境に切り替わるようになる。

TensorFlow 2.4.1のインストール(pipコマンド使用)

CUDA, cuDNNのインストール

tensorflow-2.4.0のテスト済みバージョンは

  • Python 3.6-3.8
  • cuDNN 8.0
  • CUDA 11.0

なので、CUDA Toolkit 11.0 Update1とcuDNN v8.0.5 (November 9th, 2020), for CUDA 11.0を入れるといいと思う。

CUDAは、CUDA Toolkitをダウンロードしてきて、実行ファイルを実行し、指示に従えばインストールされる。

cuDNNは、ダウンロードしてきた圧縮ファイルのcudaフォルダの中身を、CUDAのインストール先C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\以下に突っ込むとよい(手抜き)


その後、環境変数のPathからCUDAのパスを削除する(しなくても問題ないが、他で使わないので…)。

  • C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin
  • C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\libnvvp

仮想環境の作成

仮想環境tf241gpuを作成する。

conda create -n tf241gpu python=3.8

すると、Anacondaのインストールディレクトリの中にenvs\tf241gpuディレクトリができているはず。

PathにCUDAを追加した仮想環境を起動するショートカットの作成

Windowsのスタートメニューの「Anaconda Prompt (anaconda3)」を右クリック→「ファイルの場所を開く」を選択し、開いたフォルダのAnaconda Prompt (anaconda3)をコピーしてデスクトップかどこかに貼り付ける。
ショートカットを右クリック→「プロパティ」を開き、から開いたプロパティから「リンク先」をメモする。

デフォルトでインストールすると「リンク先」は次のようになっていると思う。ユーザー名の部分USERNAMEはそれぞれの環境で異なるので、以下適切に読み替える。

%windir%\System32\cmd.exe "/K" C:\Users\USERNAME\anaconda3\Scripts\activate.bat C:\Users\USERNAME\anaconda3

ここでは、C:\Users\USERNAME\anaconda3\Scripts\activate.batコマンドプロンプト起動時に実行されるバッチファイル、C:\Users\USERNAME\anaconda3がAnacondaのインストールディレクトリとなっている。

ここで、Anacondaのインストールディレクトリ以下のところに、
C:\Users\USERNAME\anaconda3\Scripts\activate_tf241gpu.batを次の内容で作成する。USERNAMEは(というか、仮想環境のパスは)適切なものに置き換える。

@set PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\extras\CUPTI\lib64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\include;%PATH%
@set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0
@CALL "%~dp0activate.bat" C:\Users\USERNAME\anaconda3\envs\tf241gpu

@は、コマンドの最初につけるとechoを抑制する。Pathを設定した後、仮想環境を指定してアクティベート用のバッチファイルを呼び出している。

先ほど貼り付けたショートカットを右クリック→「プロパティ」から、リンク先を

%windir%\System32\cmd.exe "/K" C:\Users\USERNAME\anaconda3\Scripts\activate_tf241gpu.bat

のように変更し、OKを押して閉じる。ショートカットの名前をAnaconda tf241gpuとかの分かりやすい名前に変えておくとよい。

これで、このショートカットをダブルクリックして起動すると、CUDAをPathに設定した上で、さっき作成した仮想環境に切り替わるようになる。

TensorFlow 2.4.1をpipでインストール

先ほど用意したショートカットをダブルクリックしてコマンドプロンプトを起動し、次のコマンドを実行する。

pip install tensorflow==2.4.1

インストールが終わったら、Python

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

を実行してみて、device_nameGPUになっているものがあればGPUが認識されていることがわかる。

TensorFlow 2.5.0のインストール(pipコマンド使用)

CUDA, cuDNNのインストール

tensorflow-2.5.0のテスト済みバージョンは

  • Python 3.6-3.9
  • cuDNN 8.1
  • CUDA 11.2

なので、CUDA Toolkit 11.2.2とcuDNN v8.1.1 (Feburary 26th, 2021), for CUDA 11.0,11.1 and 11.2を入れるといいと思う。

CUDAは、CUDA Toolkitをダウンロードしてきて、実行ファイルを実行し、指示に従えばインストールされる。

cuDNNは、ダウンロードしてきた圧縮ファイルのcudaフォルダの中身を、CUDAのインストール先C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\以下に突っ込むとよい(手抜き)


その後、環境変数のPathからCUDAのパスを削除する(しなくても問題ないが、他で使わないので…)。

  • C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\bin
  • C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\libnvvp

仮想環境の作成

仮想環境tf250gpuを作成する。なんとなく他と揃えてPython 3.8にしたが、3.9を使ってもいい。

conda create -n tf250gpu python=3.8

すると、Anacondaのインストールディレクトリの中にenvs\tf250gpuディレクトリができているはず。

PathにCUDAを追加した仮想環境を起動するショートカットの作成

Windowsのスタートメニューの「Anaconda Prompt (anaconda3)」を右クリック→「ファイルの場所を開く」を選択し、開いたフォルダのAnaconda Prompt (anaconda3)をコピーしてデスクトップかどこかに貼り付ける。
ショートカットを右クリック→「プロパティ」を開き、から開いたプロパティから「リンク先」をメモする。

デフォルトでインストールすると「リンク先」は次のようになっていると思う。ユーザー名の部分USERNAMEはそれぞれの環境で異なるので、以下適切に読み替える。

%windir%\System32\cmd.exe "/K" C:\Users\USERNAME\anaconda3\Scripts\activate.bat C:\Users\USERNAME\anaconda3

ここでは、C:\Users\USERNAME\anaconda3\Scripts\activate.batコマンドプロンプト起動時に実行されるバッチファイル、C:\Users\USERNAME\anaconda3がAnacondaのインストールディレクトリとなっている。

ここで、Anacondaのインストールディレクトリ以下のところに、
C:\Users\USERNAME\anaconda3\Scripts\activate_tf250gpu.batを次の内容で作成する。USERNAMEは(というか、仮想環境のパスは)適切なものに置き換える。

@set PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\extras\CUPTI\lib64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\include;%PATH%
@set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2
@CALL "%~dp0activate.bat" C:\Users\USERNAME\anaconda3\envs\tf250gpu

@は、コマンドの最初につけるとechoを抑制する。Pathを設定した後、仮想環境を指定してアクティベート用のバッチファイルを呼び出している。

先ほど貼り付けたショートカットを右クリック→「プロパティ」から、リンク先を

%windir%\System32\cmd.exe "/K" C:\Users\USERNAME\anaconda3\Scripts\activate_tf250gpu.bat

のように変更し、OKを押して閉じる。ショートカットの名前をAnaconda tf250gpuとかの分かりやすい名前に変えておくとよい。

これで、このショートカットをダブルクリックして起動すると、CUDAのディレクトリをPathに追加した上で、さっき作成した仮想環境に切り替わるようになる。

TensorFlow 2.5.0をpipでインストール

先ほど用意したショートカットをダブルクリックしてコマンドプロンプトを起動し、次のコマンドを実行する。

pip install tensorflow==2.5.0

インストールが終わったら、Python

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

を実行してみて、device_nameGPUになっているものがあればGPUが認識されていることがわかる。

Windows 10+AnacondaでGPU版TensorFlow 2.3.0がうまく入らなかった

概要

Windows 10上で、Anacondaでtensorflow-gpu 2.3.0をインストールしたが、GPUが認識されなかった。ビルドの自動選択に不具合があるらしく、インストール時にビルドを指定すると問題なく認識されるようになった。

問題

現在、AnacondaのcondaコマンドでインストールできるTensorFlowは2.3.0が最新っぽい。
普通であれば、次のコマンドでGPU版のTensorFlowがインストールできるはずである。

conda install tensorflow-gpu==2.3.0

しかし、Windows 10環境で、上のコマンドでインストールした場合に、python

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

を見てみると、GPUやXLA_GPUなどのdevice_typeをもつものが表示されず、GPUを認識していないようだった。

解決法

なぜかWindows 10のAnacondaのTensorFlow 2.3.0において、ビルドの自動選択がうまく動いていないらしい。
そのため、ビルドを自分で指定してやるとうまくインストールできる。

Python 3.8をインストールする場合、conda install tensorflow-gpu==2.3.0の代わりに
conda install tensorflow-gpu=2.3 tensorflow=2.3=mkl_py38h1fcfbd6_0とするといいらしい。例えば次のようなコマンドになる。

conda create -n tf230gpu python=3.8
conda activate tf230gpu
conda install tensorflow-gpu=2.3 tensorflow=2.3=mkl_py38h1fcfbd6_0

Raspberry Pi Zero Wを超A&G+音声再生機にする(2021年5月版)

Raspberry Pi Zero Wを超A&G+(AGQR)音声再生機にする。


以前書いた記事

が古くなってるので、今やってできる手順を書く。

環境

母艦

手順

Micro SDへのイメージ書き込み

  1. Operating SystemにRaspberry Pi OS (32-bit)を選択
    • SDカードの容量が小さければRaspberry Pi OS Lite (32-bit)とかでもOK
  2. StorageにSDカードを選択
  3. WRITEをクリック
  4. 書き込みが終わったらCONTINUEを押してウィンドウを閉じる
    • 後で見てみたら入ったバージョンはRaspbian GNU/Linux 10 (buster)だった

SSH, Wifiのセットアップ

(SDカードが認識されない場合はSDカードを指しなおす)
SDカード(bootという名前のパーティション)を開く

SSH有効化

sshという名前の空ファイルを作成

Wifi接続設定

wpa_supplicant.confという名前のファイルを作成し、次のような内容にする。

ctrl_interface=/var/run/wpa_supplicant
network={
    ssid="xxxxxxxxxxxxxxxxxxxx"
    psk=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    key_mgmt=WPA-PSK
}

ただし、

その後、PCからSDカードを取り外す。

起動

Raspberry Pi Zero WにSDカードを挿す。
電源に接続する。端の方にあるUSB端子が電源専用となっている。電源に繋ぐと起動し、自動的にWifiに繋がる。

SSHで接続する

IPアドレスRaspberry Pi Zero Wにディスプレイを繋げていればそこに表示されるが、分からない場合はローカルIPは限られているので順番に試せばそのうち繋がる。面倒ならAdvanced IP Scannerなどを利用して調べる。

CygwinのOpenSSHを入れてるので次のようなコマンドでつなぐ。(Cygwinを使ってない場合はTera Termとかを利用して接続する)
Raspberry Pi Zero WのIPが192.168.11.10だとすると

ssh pi@192.168.11.10
  • Are you sure you want to continue connecting (yes/no/[fingerprint])と聞かれたらyesと入力してEnter
  • 初期パスワードraspberryを入力してログイン

各種設定

パスワード変更

外に公開するわけではないといえ、一応パスワード位は変えた方がいいかも?

passwd

現在のパスワードを1回、変更後のパスワードを2回入力する。

ソフトを最新に更新
sudo apt update
sudo apt -y upgrade
タイムゾーンを東京にする
sudo raspi-config

で設定を開き、

  • 5 Localisation OptionsTimezoneAsiaTokyoFinish

にする。

ラジオにする

pHAT DACを使った。しかしpHAT DACはすでにディスコンなので、新しく用意するには代わりにPirate Audioを使うといいっぽい。次のようなやつ。セットアップはPirate Audioの説明に従うとよい。

pHAT DACセットアップ

まず、Raspberry Pi Zero W側にピンヘッダ(別売り)を、pHAT DAC側にピンソケットをはんだ付けし、それらを接続する。
また、pHAT DACのミニジャックにスピーカーなどを接続しておく。ヘッドフォンを繋ぐ場合、音量が結構大きいので注意。音量コントロールのついているものを接続するのがよいと思う。

次にソフトウェアのインストールを行う。次のコマンドを実行:

curl https://get.pimoroni.com/phatdac | bash

yでインストールする。
その後、再起動する。

スピーカーから流れる謎の音声メッセージを消す

スピーカーから“To install screen reader, press Ctrl-Alt-Space”って流れてうるさいので消す。

sudo rm /etc/xdg/autostart/piwiz.desktop
sudo reboot now

参考: update - How do I stop the audio message "To install the screen reader press control alt spce"? - Raspberry Pi Stack Exchange

超A&G+(AGQR)音声の再生

Raspberry Pi OS Liteの場合はffmpegのインストールが必要かも。

ffplay -nodisp "https://fms2.uniqueradio.jp/agqr10/aandg1.m3u8"

を実行すると再生される。

起動時に自動再生するように設定

再生用シェルスクリプトの用意
nano playagqr.sh

playagqr.shを次の内容で作成

#!/bin/bash

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ffplay -nodisp "https://fms2.uniqueradio.jp/agqr10/aandg1.m3u8"

実行属性をつけて/opt/にコピーする。

chmod +x playagqr.sh
sudo cp playagqr.sh /opt/
systemdを利用した自動起動

systemdを利用して起動時に自動実行されるようにする。

sudo nano /etc/systemd/system/playagqr.service

playagqr.serviceを次のような内容で作成する:

[Unit]
Description = Play AGQR radio

[Service]
ExecStart=/opt/playagqr.sh
Restart=always
Type=simple
User=pi
Group=audio

[Install]
WantedBy=multi-user.target

自動起動を設定する:

sudo systemctl enable playagqr

これで、電源を繋いでしばらく(1分20秒ぐらい)するとAGQRの音が再生されるようになる。結構時間かかる……Raspberry Pi OS Liteの方がよかったかも?