octahedron

LemとSKKとCommon Lispでたたかうプログラマのブログ

tanasinnをぼくの手の上に

tanasinnをぼくの手の上に

 この記事ではGoを使ってコマンドラインプログラムを書いたよーという話をします。

tanasinnコマンドがほしい

 ところでみなさん、tanasinnを知っていますか? tanasinnというのはインターネットミームのひとつです。2ちゃんねる掲示板に投稿されたのが初めてで、どこか見ていると不安になってくるようなシュールなアスキーアート群についた名称です。ニコニコ大百科の当該記事によれば、以下のようなものです。

  • 退廃的で人にどこ∵∴∵な(○)不安を感じさせる雰囲気。
  • 侵蝕に対する人類意識の根源的な恐怖感・畏∵(●)∴∵徴。
  • インターネット内に存在する別世界・別精神で∵り、全世界の根幹を成し操作している。我々はtanasinnに常に見られている。tanasinn(●)決して怖くな∵∴         (●)
  • Don't think. Feel and you'll be tanasinn.

 この不安になる感じがとてもよいですね。

 ぼくもtanasinnになりたい!!!!!

 そこで手軽にtanasinnになれるコマンドをGoの勉強がてら作成してみました。

tanasinnコマンド

 まずはtanasinnコマンドをどのように作るか考えてみます。
 利用ケースとしてはSlackなどで発言するときなどが考えられますが、後々なんにでも使えるようなデザインにしておきたいです。そこでtanasinnコマンドはシェルコマンドのフィルタプログラムのように実装することにしました。引数でファイルを受け取るかあるいはパイプされた文字列の文字をランダムにtanasinn化して吐き出すプログラムとして実装します。tanasinn化度合いを引数で指定できるといいかもしれません。

いかにtanasinnするか

 さて、文字列をtanasinn化する方法を考えるのですが、文章におけるtanasinnっぽさを突き詰めると隠れマルコフモデルだとかなんだとか、ちょっとよく知らない概念あるいは宇宙の真理に手を出さなければならなくなりそうです。ここでは簡単に、入力文字列中の文字がランダムに所与のtanasinn文字列に置き換えることでtanasinn化します。つまりPythonっぽい疑似コードで書けばこうです:

for 文字 in 入力文字列:
    n = 0.0から1.0乱数を生成

    if nが閾値を超えていたら:
        tanasinn文字列をランダムにピックアップして出力
        
    else:
        文字を出力

 ここでまず上記コードのfor文の中身を行う補助関数を用意します。

func tanasinnize(th float64, r rune) string {
    s := []string{
        "∴",
        "∵",
        "∴∵",
        ")",
        "(・)",
        "(・",
        "∴・",
        "∴",
        "・・",
        "・:",
        "..",
    }
    if th < rand.Float64() {
        return fmt.Sprintf("%s", s[int(rand.Uint32()) % len(s)])
    } else {
        return fmt.Sprintf("%c", r)
    }
}

 tanasinn化文字列は関数内の定数として保持しています。生成した乱数がもし引数で与えられた閾値を超えていたらtanasinn化文字列を出力し、そうでなければ入力された文字を返すといういたってシンプルなものです。

 あとはこれを文字列内の文字についてのループで呼び出してやり、標準入力まわりを整えてやれば完成です。

 できたものがこちらになります。

github.com

 動かすとこのようになります。

$ echo "Don't think. Feel and you'll be tanasinn." | ./tanasinn
(・on't th..nk. ・:eel ∵nd y∴u'l∴ be ta∴∴sinn.

 これで気軽にtanasinn化できるようになりました。

だんだんtan(・sinn∴なっ・・い∴∵..・:

 しかしあまりにシンプルすぎてちょっと面白くないです。具体的には「あぁ……消えて…い…k……」みたいなことがしたいです。なのでtanasinn閾値を引数で弄れるようにし、さらに閾値区間指定できるようにします。

 コマンドライン引数-tに数値(float64)を指定するとそれが閾値として採用されます。また、閾値1.0,0.6,0.3,0.1のようにカンマ区切りで渡すと、入力文字列を区間数で等分した領域の閾値を前から指定できます。たとえば閾値1,0のとき、区間数は2なので前50%が閾値1、後ろ50%が閾値0としてtanasinn化されます。1.0,0.6,0.3,0.1のときは最初の25%が1.0、次の25%が0.6、……という感じです。

 この文字列をパースして閾値のスライスを取り出す処理をparseThreasholdsという補助関数にしました。ループや分岐、スライスの扱い、エラーハンドリング等の基本的なGoの要素が登場し、入門に最適な感じです。

func parseThreasholds(th string) ([]float64, error) {
    var ths []float64

    if strings.Contains(th, ",") {
        list := strings.Split(th, ",")
        for i := 0; i < len(list); i++ {

            f, err := strconv.ParseFloat(list[i], 32)
            if err != nil {
                log.Fatal(err)
                return ths, &parseError{s: th, reason: "not float in the list"}
            }

            ths = append(ths, f)
        }

    } else {
        f, err := strconv.ParseFloat(th, 32)
        if err != nil {
            log.Fatal(err)
            return []float64{}, &parseError{s: th, reason: "not float"}
        }
        ths = []float64{f}
    }

    return ths, nil
}

 こいつを引数のパース処理に組込み、標準入力の内容をいったんメモリに全て読み込むように変更し(区間指定するには全体の流さが必要だからです)、あとは文字についてのループを以下のように変更します。

 for n := 0; n < len(text); n++ {
        p := float64(n) / float64(len(text))
        i := int(p * float64(len(option.th)))
        r := text[n]
        if r == '\n' {
            fmt.Printf("\n")
        } else {
            fmt.Printf("%s", tanasinnize(option.th[i], r))
        }
    }
}

 pは入力文字列中の今の位置の割合です。それをoption.thという閾値スライスの長さを掛けて小数点以下を切り捨てることで、閾値スライスの中での位置を計算しています。

 ということで実装したものがこちらのリポジトリです。

github.com

使ってみるとこのような感じです。

# 閾値0.2
$ echo "だんだんtanasinnになっていく……" | ./tanasinn -t .2
)∴∵・・..・:an∴・・:・:∴・∴・:∴・()∴・・・・・…∴・

# 閾値0.7
$ echo "だんだんtanasinnになっていく……" | ./tanasinn -t .7
∴・∴だんtanas..・・nに)()ていく……

# だんだんtanasinn化する例
$ echo "だんだんtanasinnになっていく……" | ./tanasinn -t 1,0.6,.4,.2
だんだんta∴(()()nnにな・:)..く..…

おわりに

 この記事ではさくっと作ったtanasinn化コマンドの紹介と中身の解説をしました。やってることは単純ですがこれで気軽にtanasinnごっこができるようになりました。

悲しみの記

今回はとっても愚痴記事です。読まないことをお勧めします。

ぼくがやりたいことはいくつかあって(一般にはたくさんと呼ばれるらしい)、それを実現するために動きたいと常日頃考えている。先日はやりたいことのひとつである「リアルタイム操作可能なシンセサイザープログラム」のミニマムかつ最初の成果物であるKoto https://github.com/t-sin/koto とかリリースした。

この先にあるのはRez InfiniteやDEPTHといった音楽ゲームの音楽演奏部分の基礎理解と私的モジュール化だ。

やりたいことの他にはグラフィクスプログラミングやプレイヤーの体験を設計するという意味でのゲーム実装、プログラミング言語処理系への理解の強化などある(よく誤解されるが「言語処理系」といったとき「形式言語を処理するシステム」の意味で用いているので注意)。もともとチューリングマシンかっけーからプログラミングに入ってきたので、低レイヤーとても興味があるのだ。

ところで、人生の最も多くの時間を割かざるを得ない活動がある。労働である。ヒトの仕事を機械が完全に奪ってくれていない現在において、日銭あるいは趣味のための資金を捻出するのに労働は欠かせない。

しかし労働は大抵の部分において苦である。

電車通勤を強いられたりオフィス移転により通勤距離の変更(遠くなったり乗り換えめんどくさくなったりするかもしれない)。私的にやりたいことが山ほどある人間にとっては1日8時間以上を当然の顔して掠め取られるだけでも大打撃だ。内容だって苦しみが多く、まずもってやりたいこと・伸ばしたいことと業務が一致することは多くない。会社の方針、理解されない提案、理解しがたい提案、開発陣と経営陣の連携のなさ、マイナー言語好きのつらみ、孤独感。要するに虚無である。

では環境を変え、なんならやりたいことに近い分野の仕事を探せばよいのでは。と転職活動もやってみた。

しかし、事業内容に興味を持てないのである。世の中を技術で変えることは大事かもしれないが、ぼくにとっての気持ちよさはかけらもない。ぼくにとっての気持ちよさとは、やりたいことを要約して、気持ち良いものをつくれることにあるからだ。そして、そのような仕事がないか探してみた。応募もしてみた(たしかにゲーム系請負の小さな会社だった)。しかし、ぼくにフィットする会社がみつからないのである。あるいは待遇がとても悪く、ビビったりした。こちらがまだ勉強不足である面は看過しがたい。しかし労働に身を縛られているため勉強にさける時間は全く多くはない。詰んでいる。

また、やりたいことに重なるようなことがネットで世の中で既にやられていくのを見た時の無力感といったらないのだ。

やりたいことがたくさんある故、ふとこのように自問することがある。 「このままではなにもわからない人間でただ終わるのではないか」

ぼくはそのように終わるのは嫌だが、何かを成し遂げられるという希望が定期的に潰えては怯えている。

ぼくは、どうなってしまうんだ?

C言語製の自由なDAW Zrythmをビルドしてみる

あらまし

 最近GitHubを眺めていたらオープンソースDAW (Degital Audio Workstation; つまり作曲ソフト)を発見しました。
 そんなDAWの名はZrythm。

www.zrythm.org

 そのDAWは、

  • ほとんどC言語で書かれており、
  • 開発を開始したのが2018年7月からのようであり(2年目くらい)、
  • AGPLでライセンスされており、
  • 何故か作者が日本語ローカライズをしており、
  • なぜか名前がZrhythmではなくZrythmであり、
  • WindowsGNU/Linuxで動く

というものでした。新興のDAWでありながら公式のスクリーンショットとかを見るかぎりよくできていそうで、1年ちょっとでこのレベルのものが生えてくるのねとヴィックリ。

 この記事ではそんなZrythmをビルドして動かしてみました。

ビルド環境

 Zrythmではビルドツールとしてninja、Ninjaのビルドファイル生成ツール(つまりCMake的なツール)としてMesonを利用しています。Ninjaはmakeよりも小さくて速いビルドツールとして開発されているもので、かつNinjaのビルドファイルは自動生成される想定で書かれているため別途Mesonが必要という感じらしいです。ちなみにちょっと前にFUSEのコードをみたらMesonとNinjaを使っていたので、最近のC界隈のトレンドのようです。

 Ninjaはコードをクローンしてきて./configure.py --bootstrapで、MesonはPython製なのでpython3 -m venv venv ; . venv/bin/activate; pip install mesonとかでセットアップしてください。

 手元のビルド環境を晒しておくとこんなかんじです。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
$ uname -a
Linux hogehoge 4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:24:28 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ meson --version
0.53.1
$ $HOME/tmp/ninja/ninja --version
1.10.0

依存ライブラリのインストール

 まずは依存ライブラリを入れていきます。以下のライブラリのヘッダが必要なので入れていってください。

  • libsndfile (音声ファイルの読み書きライブラリ)
  • librubberband (音声データのリピッチ・ストレッチライブラリ)
  • libsamplerate (音声データのサンプリングレート変換ライブラリ)
  • libyaml (YAMLライブラリ)
  • libfftw3 (高速フーリエ変換ライブラリ)
  • libgtk-3 (GTK3)

 それにしてもsndfileは知ってたんですが(cl-sndfileとかある)、リピッチとかサンプリングレート変換とかFFTもライブラリがあるんですね。

Zrythmのビルド

 依存ライブラリを入れおわったら、Zrythm本体のコードをもってきます。GitHubに公式でミラーされているのでこちらを持ってきましょう。公式サイトに書いてあるGitリポジトリはなんだかモジュールごとにリポジトリが切ってありサクッとビルドしたい方にはオススメしません。

github.com

 Mesonが使える状態の元、このようにするとビルドできます(ninjaをシステムにインストールしたくなかったため、直接パスごと指定してます)。

$ git clone https://github.com/zrythm/zrythm
$ cd zrythm

# mesonを叩くときにはninjaにパスが通っている必要がある
$ PATH=$PATH:$HOME/tmp/ninja/ meson build
$ PATH=$PATH:$HOME/tmp/ninja/ ninja -C build

# インストール
$ sudo $HOME/tmp/ninja/ninja -C build install

 これでzrythmが/usr/local/binあたりに置かれ、起動できるようになります。

使用感

 さて、使ってみるタイムです。
 ちなみにデフォルトのビルド設定ではサウンドドライバがJackのみでしたが起動時に再生中の音楽が止まるのがいやだったのでPortAudioオプションを有効にしてビルドしています。

f:id:t-sin:20200201015841p:plain
Zrythm起動時の画面

 …なんですが。

 まず、サウンドドライバ設定でPortAudio選べないのね! しかたがないのでjack_control startでJackを起動しました。すると、上のスクショにも出てますがXRUN occuredとでたまま固まってしまいました。どうもJackを起動した状態で繋ぐと死ぬっぽい。バージョン合ってない系かしら(いやしかしjack_lspでポート一覧にはでてくる)…。

 コードはかなり素直で読みやすい(あとCでOOPっぽさのあるコードになっている)ので、ちょっと調べてみるといろいろ楽しそうですねえ。

Hysteresis --- 値の歴史を遺す

 この記事はNextremer Advent Calendar 2019の25日目の記事です。ハッピーニューイヤー!(開き直り)

Hysteresis

 2019年の暮れに、ひとつCommon Lispのライブラリをつくりました。これは、このライブラリのもつ値設定マクロsetfを通してシンボルに値を設定するとそのシンボルの値の履歴が残るようになる、というものです。

github.com

動機

 ゲーム(cl-sdl2)とかジェネラティブアート(sketch)とかをCommon Lispでやっていると、Common Lisp単体では画面への描画ができないためCFFIを通して描画スレッドにコールバック関数を渡してやることがよくあります。REPLからコールバック関数を変更できたりして便利なのですが、lambda式なんかを渡していると、エラー時に差し替えようがなくてけっきょくCL処理系のスレッドごと落としてロードしなおすことになります。あまりREPL駆動のよさみを受けていない。

 この問題の解決方法のひとつとして、名前 (Common Lispのbindings)を通じて関数を渡すようにすればいいなあーと2019年12月ごろにおもいつきました。そのついでに、エラーが起こった瞬間に値を巻き戻す的な操作があると便利かもしれない、と思ったため実装しました。

 想定利用ケースとして以下のようなものを想定しています:

  • おもむろにシンボルの束縛していた過去の値にもどしたい
  • エラー時、自動で1個前の値にもどしたい

概要

 Hysteresisではシンボルに履歴というものを導入します。この履歴つきシンボルをhistorized symbolsとこのライブラリでは呼ぶことにしました(historizedって「歴史に名を残す」という意味らしいですがライブラリの内容的に洒落っぽくていいなと思いました)。まあ履歴つきシンボルとかおおげさな胡椒ですが、専用の構造体にリングバッファがついていて、リングバッファの最大長以内であれば値が保持されるシンボルがある、というていどのものです。

使いかたの基本

値の設定と取得はhysteresis:set-valuehysteresis:get-valueを用います。シンボルをキーとして内部のハッシュテーブルに保持された値履歴に書き込んだり取得したりします。

CL-USER> (hysteresis:set-value 'foo 42)
42
#(HYSTERESIS:HISTORIZED-SYMBOL ...)
CL-USER> (hysteresis:get-value 'foo)
42

でもこれは内部API的であまりユーザに利用されることはあまり想定していません。hysteresis:hsetqというsetqもどきを通じて、シンボルマクロを定義するのが便利です。利用感はこんなかんじです。

CL-USER> (hysteresis:hsetq hoge 42)
42
CL-USER> (setf hoge 42)
42
CL-USER> hoge
42

歴史を戻したり現在に戻ったりする

 履歴つきシンボルは、もちろん過去の値に遡ってシンボルの値を戻すことができます。まずは値を設定しておきます。

;; 履歴に値を設定する (きっとミュージックプレイヤーの現在の曲とかで利用している?)
CL-USER> (hysteresis:setq song "Walking In The Sun")
"Walking In The Sun"
CL-USER> (hysteresis:setq song "My Hero")
"My Hero"
CL-USER> (hysteresis:setq song "Half The Man")
"Half The Man"

このとき、現在の値は"Half The Man"です。これを唐突に2つ前の値"Walking In The Sun"に戻したいと思いました。こんなときhysteresis:revertを用いると、全ての履歴つきシンボルの値を2つもどすことができます。

CL-USER> song
"Half The Man"
CL-USER> (hysteresis:revert 2)
NIL
CL-USER> song
"Walking In The Sun"

わーお。でも、やっぱり最新の値に全てもどしたい場合もありますよね(たとえば過去の値もヤバかった的な)。そんなときはhystresis:presentを用いると、最新の(現在の)値に戻ります。

CL-USER> (hysteresis:present)
NIL
CL-USER> @song
"Half The Man"

値ったら最強ね(9)。

履歴つき関数

さて、冒頭ではゲームとかでコールバック関数がうんぬんという話がでてきました。もちろんこのライブラリでは関数も履歴つきにすることができます。hysteresis:hdefunの登場です。

CL-USER> (hysteresis:hdefun foo (x)
           x)
#<COMPILED-LEXICAL-CLOSURE #x302000DB1C8F>
CL-USER> (foo 42)
42
CL-USER> (foo 'hoge)
HOGE
CL-USER> (foo "wow")
"wow"

そしてもちろん履歴を辿りたい場合がある(はず)です。そんなときにはhysteresis:revertを用いることができます。

CL-USER> (hysteresis:hdefun foo (x)
           (list x x))
#<COMPILED-LEXICAL-CLOSURE #x302000E7309F>
CL-USER> (hysteresis:hdefun foo (x)
           (format nil "+~a+" x))
#<COMPILED-LEXICAL-CLOSURE #x302000ED2D6F>
CL-USER> (foo 'hoge)
"+HOGE+"
CL-USER> (hysteresis:revert 1)
NIL
CL-USER> (foo 'hoge)
(HOGE HOGE)

 こんな感じで、履歴を遡ったり現在側にもどってきたりできます。機能としてはとても単純なので、このような感じです。

 改良できそうな部分は多々あれど、これは使えるライブラリなのかしら…?? …うーん、なぞ。

今後の展望

  • hysteresis:hunintern
    • 通常のcl:uninternすると、内部ハッシュテーブルから値が消えないため
  • テストを書きたい
    • リングバッファが手書きなので、まじでみっちりと!!
  • REPL操作を意識した情報取得や一覧表示?
    • 履歴の内容確認
    • 履歴つきシンボル一覧
  • 実用する(ゲームとかで?)

まとめ

シンボルに履歴を持たせるライブラリをつくりました。しかし、このアイデアに需要があるのか、ほんとうに問題(動機)の解になっているのか、などが謎なのでこれから実用してみて判断を下す段階のものだと思います。

CORBIT/BOOPS: プロトタイプベースオブジェクト指向を実現する拡張ライブラリ

 ひょんなことから見つけた*1のでちょっと遊んでみました。

CORBITとBOOPSについて

 CORBIT (CommonORBIT) はCommon Lisp用のプロトタイプベースのオブジェクト指向ライブラリです。

Package: lang/lisp/oop/non_clos/corbit/

 どうも、Orbitというプロトタイプベースのオブジェクト指向のシステムがあり*2、それをCommon Lisp向けに移植・再実装したものがCORBITであるようです。また、BOOPS (Beginner's Object-Oriented Programming System) はふるまいや仕組みが初学者にもわかりやすいようCORBITのコア要素を抽出し、小さくしたものです。

 CLOSと異なる点は、CLOSがクラスベースのオブジェクトシステムであるのに対し、CORBIT/BOOPSはプロトタイプベースのオブジェクトシステムであるところです。クラスベースのオブジェクトシステムでは各オブジェクトがクラスに属し、多態性が必要な際はクラス間の関係に従ってふるまいを決定します。この「クラス」は、実行時には変更できない言語(JavaとかC++とか)と実行時に変更できる言語(SmalltalkCommon Lisp)がありますが、いずれせよある時点ではなにか「クラス」に属している点では共通しています。一方プロトタイプベースのオブジェクトシステムでは、あるオブジェクトが多態的にふるまう際に「オブジェクトの属するクラス」ではなく、関係する別のオブジェクトに処理を移譲する点が異なります。 (え、これ、あってるのかしら…。書いてみたら理解が雑だった…。)

 現在のCLOSになるまでにいくつかのオブジェクト指向実装があったというのは聞いていましたが、CLOSっぽいものだけでなく、JavaScriptのようなプロトタイプベースのオブジェクト指向システムも存在したんですね。

 ちなみにCommon Lispに詳しくない人のために説明すると、CORBITはCommon LispANSI仕様に含まれているCLOS (Common Lisp Object System)とは別物です。CORBITはCLOSを用いずライブラリとして実現されています。

BOOPS使ってみる

 BOOPSは1ファイルにパッケージが1つ定義されているだけのとても小さな実装です。使うにはお手元のCommon Lisp処理系でファイルをロードするだけで済みます。BOOPSパッケージ内で作業すると若干楽です。

CL-USER> (in-package :boops)
#<Package "BOOPS">

 BOOPSではオブジェクトはシンボルで表現し、アスペクト (aspect; 側面? CLOSでいうスロット、Javaとかだとメンバ/フィールド)をリストで表現します。アスペクトには:value:functionの種別があり、:functionだとオブジェクトが引数に渡されてきます。

 まずはとりあえず、哺乳類をいくつか定義して鳴かせてみます。

;; 哺乳類を定義 (鳴くコマンドつき)
BOOPS> (defobject marmal object
           (toot :function (o) (format t "toot!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
MARMAL

;; わんこを定義 (鳴くコマンド上書き)
BOOPS> (defobject doggo marmal
         (toot :function (o)
               (format t "bow wow!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
DOGGO

;; 鳴く
BOOPS> (message 'marmal 'toot)
toot!
NIL
BOOPS> (message 'doggo 'toot)
bow wow!
NIL

 継承できた!

 次に、アドホックにメソッドを追加してみます。これには匿名オブジェクト(というかgensym)を作ります。このように。

;; 匿名オブジェクトとしてジョンを定義しアスペクトを追加を追加
BOOPS> (let ((john (a doggo (introduce :function (o)
                                       (format t "I'm John! ")
                                       (message o 'toot)))))
         (message john 'introduce))
I'm John! bow wow!
NIL

 静的なクラスに依存しないので、このようなこともできます(ただしCLOSは静的ではないので同じようなことできそう)。

BOOPS> (defobject human marmal
         (toot :function (o)
               (format t "hello!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
HUMAN
BOOPS> (let ((tom (a human (introduce :function (o)
                                      (format t "I'm Tom! ")
                                      (message o 'toot)))))
         (message tom 'introduce))
I'm Tom! hello!
NIL

 これでアドホックなインターフェースっぽいサムシングが実現できました。

中身について

 だいぶシンプルです。BOOPSではオブジェクトはシンボルだと言いました。BOOPSの内部的には、オブジェクトのアスペクトは、シンボルのpslitスロットにplistの形で入っています。

;; BOOPSで定義されているオブジェクトのアスペクト
BOOPS> (symbol-plist 'object)
(ASPECTS ((SET-VALUE :FUNCTION . #<Anonymous Function #x302000C8FF7F>) (SHOW :FUNCTION . #<Anonymous Function #x302000C900DF>)) ISA NIL)

;; わんこのアスペクト
BOOPS> (symbol-plist 'doggo)
(ASPECTS ((TOOT :FUNCTION . #<Anonymous Function #x302000C9835F>)) ISA MARMAL)
BOOPS>

 匿名オブジェクトもgensymであるただのシンボルなのでplistスロットがあり、おなじようにアスペクトが入っています。

BOOPS> (let ((tom (a human (introduce :function (o)
                                      (format t "I'm Tom! ")
                                      (message o 'toot)))))
         (symbol-plist tom))
(ASPECTS ((INTRODUCE :FUNCTION . #<Anonymous Function #x302000E3B3AF>)) ISA HUMAN)

 おしまい。

*1:Common Lisp Musicについて調べていたら"Compact LISP Machine"なる論文を見つけ、論文に出てくるTexas InstrumentsExplorerというLISPマシンを調べていたらCMUアーカイブに流れ着き、そこを眺めていたらあった。

*2:論文情報はこちら(読めぬ): https://ai.vub.ac.be/publications/236 。著者の方はGuy. L. SteeleさんではなくファーストネームがSteelsさん。

Linux版OneShotの終盤でファイルが開けない(ネタバレ注意)

 この記事は以下の記事の続きです。

octahedron.hatenablog.jp

 上の記事の内容も利用しているので、適宜参照ください。

 ストーリー終盤のギミックについての不具合対処記事なのでネタバレを含みます。ご注意を!!

続きを読む

OneShot (ゲーム)をUbuntu 18.04で動かす

あらまし

 先日、チャンスは一度きりなゲームOneShotGNU/Linux版がリリースされました。ふだんUbuntuのLTS版を利用しているので、いままでWindows 10に切り替えなければ遊べなかったOneShotをいつもの環境で起動できるのはとてもうれしいです。

steamcommunity.com

 しかし要求してくるライブラリのバージョンがとても新しく、LTS版では起動できませんでした。この記事ではそれをなんとかしてみます。

発生現象

 Steam GNU/Linux版でOneShotをダウンロード後に起動しようとすると起動中っぽい動きを一瞬したあとそのままなにも起こりません。これはプログラムの起動に失敗して無言で落ちているためであり、ターミナルで直に起動しようとしてみると以下のような出力が得られます。

# OneShotがインストールされたディレクトリに移動
$ cd ~/.local/share/Steam/steamapps/common/OneShot
# 起動 (失敗)
$ ./oneshot
./oneshot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /home/grey/.local/share/Steam/steamapps/common/OneShot/libgio-2.0.so.0)
./oneshot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /home/grey/.local/share/Steam/steamapps/common/OneShot/libglib-2.0.so.0)
./oneshot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /home/grey/.local/share/Steam/steamapps/common/OneShot/libsystemd.so.0)

 これは、システムにインストールされたglibcライブラリのバージョンが古いと言っています。システムの情報とglibcのバージョンを確認してみると、以下のようでした。

# 環境情報の確認
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS"
grey@timberwolf:~$ /lib/x86_64-linux-gnu/libc
libc-2.27.so  libc.so.6

# glibcのバージョン確認
$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

 おしい! 1つだけ足りない! それにしてもglibclsといった各コマンド自体も依存している動的ライブラリですので、ディストリビューションが提供するアップデートなしには容易に差し替えられません。ちなみに「OneShot用のglibcコンパイルしてゲーム本体のディレクトリに置けばいいじゃん」とやってみたところダメでした。差し替えたglibcを向くようにLD_PRELOAD環境変数で指定しても.so中のオブジェクト等の位置が異なるのかセグフォになります。むずかしいですね。

対処方法

 このスレッドにも書かれているのですが、glibcを差し替えて解決するのではなく新しいglibcに依存しているライブラリを使わないようにするのが楽かつスマートです。

steamcommunity.com

 つまり、OneShotディレクトリにある新しいglibcに依存する.soは削除もしくはリネームしてしまい、システムにapt等で対応するライブラリを入れましょう。

$ cd ~/.local/share/Steam/steamapps/common/OneShot
$ rm libgio-2.0.so.0 libglib-2.0.so.0 libsystemd.so.0

 これでglibcのエラーはでなくなります。

そのあと

 ぼくの環境では、この後Error initializing SDL: Could not initialize UDEVというエラーがでてまだ起動できませんでした。udevとはユーザ空間でデバイスドライバを動的にロードしたりするライブラリっぽいのですが、きっとシステムのものではないudevを用いているせいかもしれない(libudev.so.1が実際ある)のでこれも消してしまいましょう。

 そうするとエラーの内容がCould not initialize Steamworks APIに変わります。きっとStreamが起動していて認証されてないと動かないのでしょうね。そこで、今まで./oneshotを起動していたのですが同じディレクトリにある./steamshimを起動してみましょう。Steamへの認証っぽいログがでたあと…。

f:id:t-sin:20190502000343p:plain
OneShot起動成功!

 やったー!!

 あとは遊びましょう。