Windows環境でHaskellを使ってゲームを作りたい人のための、音声ファイル再生方法

## この記事でわかること

* Windows環境でHaskellを使ってゲームを作るために必要な、音声ファイルの再生方法
* 音声ファイル再生のために試した方法と、できなかった理由

## Haskellでゲームを作りたい！

私は普段Windows環境でC#を使ってプログラミングをしているのですが、近年は関数型言語の考え方が言語仕様や設計に取り入れられることが多くなり、関数型言語に強い興味を持っていました。関数型言語を学ぶなら純粋関数型言語のHaskellを学んでみるのが一番だろう！ということで、去年からHaskellの勉強を始めました。

プログラミング言語を学ぶには、何かを作ってみるのが一番です。私はゲームが大好きなので、今回はHaskellでゲームを作りながら関数型言語の基礎を叩き込むことにしました。
Haskellでゲームなんて作れるの？と思う方もいらっしゃるかもしれませんが、作れます。全然余裕で作れます。Glossという便利なグラフィックライブラリがあるので、簡単なゲームであれば以下の記事を参考にすぐ作れます。

https://qiita.com/lotz/items/eb73e62a64bc208c2dd6

https://qiita.com/lotz/items/9c62a5a0eb0e34a86ac7

実際、私も上記の記事を参考にゲーム作成のチュートリアルを行い、そこから自作ゲームを作り始めました。Haskellでゲームを作りたい方は、まずは上記二つの記事の内容を一通りこなしてみることをオススメします。

### Windows環境での音声再生は茨の道

Glossを使えばグラフィックを簡単に扱えることはわかりましたが、本格的にゲームを作るとなると、どこかで必ず音声再生の必要が出てきます。ゲーム内容にもよりますが、私が作ろうとしているジャンルはRPGなので、BGMのループ再生と、SEを重ねての再生くらいできれば十分なので、それほど難しくはなさそうです。

実際、Linux環境であればHaskellで音声再生することは（おそらく）難しくありません。しかしWindows環境では **C言語ライブラリとの致命的な相性の悪さ** と戦うことになります。

## 試したこと一覧

前置きが長くなってしまいましたが、以下が試したこと一覧です。Haskell環境はGHCup + Stackで構築しています。

|試した方法|結果|
|:--|:--:|
|SDL2-Mixer（Haskellライブラリ）|×|
|ALUT（Haskellライブラリ）|×|
|OpenAL（Haskellライブラリ）|×|
|別言語からHaskellを呼び出す|×|
|別言語で作ったDLLをHaskellから呼び出す|×|
|Godot-Haskellで音声再生をGodotにやらせる|×|
|Docker環境でHaskell開発|△|
|別言語で作った音声再生アプリをプロセス間通信で呼び出す|○|

※○：成功, ×：失敗, △：今回の目的に不適

表を見るとわかるとおり、Haskellのライブラリは軒並み動かなかったため、裏道を探すように他の方法を色々試しました。しかし他の方法も中々うまく行かず、最終的には **別言語で作った音声再生コンソールアプリをプロセス間通信でHaskellから呼び出す** という **力業** で解決しました。

ということで、まずは成功した方法であるプロセス間通信を使った音声再生を紹介し、そのあとに成功に至らなかった各方法の詳細とつまづきポイントを紹介していきます。

## 別言語で作った音声再生アプリをプロセス間通信で呼び出す

Haskellでの音声再生は難しいですが、言語によっては簡単に音声再生を実現できます。また、Haskellでのプロセス間通信は非常に簡単なので、この組み合わせで音声再生に成功しました。

### 別言語で音声ファイル再生アプリを作成する

まずは別言語で音声ファイル再生アプリを作る必要がありますが、これは自分の得意な言語を使ってOKです。例えば私はC#が最も得意なのですが、NAudioというライブラリを使えばめちゃくちゃ簡単に音声再生を実現できます。C#はWindows環境との相性も抜群なので、言語にこだわりがない人はとりあえずC#を選んでおくと良いでしょう。

NAudioの使い方は本題からそれるので省略し、作成した音声再生アプリのコマンドの一部を紹介します。コマンドを指定して都度起動するのではなく、一度起動したらメッセージを送って音声再生を管理し、使い終わったら `Exit` で終了するイメージです。

* `PlayBGM FilePath`
    * FilePathに指定した音声ファイルを読み込んでループ再生します。すでにBGMを再生中の場合は、再生中のBGMを止めて新たにBGMを再生します。
* `PlaySE FilePath`
    * FilePathに指定した音声ファイルを読み込んで、現在再生中の音声に重ねて1度だけ再生します。
* `Exit`
    * 再生中の全ての音声を停止して、アプリを終了します

### プロセス間通信でアプリを呼び出す

作成したアプリをHaskellから呼び出す方法を、サンプルコードとともに順に解説していきます。

#### 子プロセスを起動する

```haskell:haskell
-- 実行中のターミナルのカレントディレクトリを取得
currentDir <- getCurrentDirectory

-- 子プロセスを起動
(Just hin, Just hout, Just herr, ph) <- createProcess (proc (currentDir </> "(作成したアプリの相対パス)") []) {
    std_in = CreatePipe,
    std_out = CreatePipe,
    cwd = Just currentDir  -- 作業ディレクトリを指定
}
```

子プロセスを生成するには `createProcess` 関数を使います。戻り値の `hin` はアプリへのメッセージ送信、`hout` はアプリからのメッセージ受信、`herr` はアプリからの例外を受信するためのハンドルです（今回は `herr` は使いません）。また、`ph` はプロセス全体のハンドラで、プロセスの終了待ちや強制終了などに使います。

起動したいプロセスの指定は `proc` 関数を使います。今回は引数無しで起動していますが、第二引数のリストにコマンドを指定して起動することも可能です。

#### 子プロセスとメッセージをやり取りする

```haskell:haskell
-- 親プロセスから子プロセスへメッセージを送る
hPutStrLn hin "PlayBGM C:/(省略)/BackGroundMusic.wav"
hFlush hin

-- 子プロセスからメッセージを受け取る
response <- hGetLine hout
putStrLn $ "Received from child process: " ++ response
```

子プロセスにメッセージを送信するには `hPutStrLn` 関数を使います。第一引数に `hin` 第二引数に贈りたいメッセージをStringで指定すればOKです。通常だとメッセージはある程度バッファにため込んでから送られるので、すぐに音声再生したい場合は `hFlush hin` でバッファを即座にフラッシュすることを忘れないようにしましょう。

今回作ったアプリは特にメッセージを返しませんが、もし子プロセスから成功・失敗などのレスポンスを受け取りたいときは `hGetLine hout` で受け取ることができます。`herr` を使った例外処理については今回は省略します。

#### 子プロセスの後始末

```haskell:haskell
hPutStrLn hin "Exit"
hFlush hin

-- プロセス終了を待つ
_ <- waitForProcess ph
```

子プロセスを終了するときは、しっかり終了を待機してから親プロセスも終了するようにしましょう。`waitForProsess` 関数に `ph` を渡すとプロセスの終了を待ってもらえます。

#### 出来上がったコード

まとめると、サンプルコードは以下のようになります。
BGMの再生を開始してから1秒待機したあと、0.1秒間隔でSEを2連続で重ねて再生し、さらに1秒待機して終了します。

```haskell:Sample.hs
import System.Process
import System.IO
import System.Directory (getCurrentDirectory)
import System.FilePath ((</>))
import Control.Concurrent (threadDelay)

audio :: IO ()
audio = do
    -- 実行中のターミナルのカレントディレクトリを取得
    currentDir <- getCurrentDirectory

    -- 子プロセスを起動
    (Just hin, Just hout, Just herr, ph) <- createProcess (proc (currentDir </> "(作成したアプリの相対パス)") []) {
        std_in = CreatePipe,
        std_out = CreatePipe,
        cwd = Just currentDir  -- 作業ディレクトリを指定
    }

    -- 親プロセスから子プロセスへメッセージを送る
    hPutStrLn hin "PlayBGM C:/(省略)/BackGroundMusic.wav"
    hFlush hin

    threadDelay 1000000
    
    hPutStrLn hin "PlaySE C:/(省略)/SoundEffect.wav"
    hFlush hin
    threadDelay 100000
    hPutStrLn hin "PlaySE C:/(省略)/SoundEffect.wav"
    hFlush hin

    threadDelay 1000000

    hPutStrLn hin "Exit"
    hFlush hin

    -- プロセス終了を待つ
    _ <- waitForProcess ph

    return ()
```

これで無事にWindows環境のHaskellプログラムで音声再生することに成功しました！
実際には音声再生アプリ側の機能をもう少し増やす必要があるものの、音ゲーのような音楽メインなゲームでなければ何とかなるでしょう。多分。

これでこの記事の目的は8割達成しましたが、もっと凝った音声再生をしたい人もいるかもしれません。ということで、ここからは私が挑戦したほかの音声再生方法と詰まったポイントを解説していきます。

## Haskellライブラリでの音声ファイル再生

Haskellには音声再生用のライブラリがあるので、本来は最初に選択肢として挙がります。
しかしHaskellの音声再生ライブラリはいずれも内部でC言語のライブラリを呼び出しており、このC言語ライブラリとWindows環境との相性が非常に悪いため、軒並み壁にぶつかります。さらに、そもそもHaskellをWindows環境で開発している人が少数派なので、Windows関連の問題に対する解決策が見つけにくく、結局私は解決に至りませんでした。
以下、各ライブラリで試行錯誤した結果を紹介します。

### SDL2-Mixer

Haskellの音声再生ライブラリとして最初の選択肢です。~~ChatGPTが言ってたので間違いありません。~~
実際、後述するDocker（Linux）環境でのHaskell開発では、このSDL2-Mixerが無事にビルドできることを確認しています。

とりあえずWindows環境で試してみましょう。Stackプロジェクトを作り、Package.ymlにsdl2-mixerを追加します。

```diff_yaml:package.yaml
  dependencies:
  - base >= 4.7 && < 5
+ - sdl2-mixer
```

SDL2-Mixerは内部で同名のC言語ライブラリを利用しており、Windows環境にはこのライブラリがデフォルトで存在しないため、このままビルドしてもエラーになります。これを解決するには、以下のコマンドで対象のライブラリをインストールします。

```bash:bash
stack exec -- pacman -S mingw64/mingw-w64-x86_64-pkg-config
stack exec -- pacman -S mingw64/mingw-w64-x86_64-SDL2_mixer
stack build
```

1行目はC言語ライブラリのインストールに必要なpkg-configをインストールするためのコマンドで、2行目はSDL2-mixerをインストールするコマンドです。インストール後ビルドが進むと `__stack_chk_guard` というキーワードを含むエラーメッセージが出る場合があります。いろいろ調べてみると、どうやら最新のSDL2-mixer（の親となるSDLライブラリ）をWindows環境でコンパイルするとエラーを吐く場合があるようです。私はこのエラーに遭遇したのですが、この記事を書くために再現しようとしたらエラーにならなかったので、必ず発生するわけではなくバージョン以外にも何か原因がありそうです。

とりあえず該当のエラーメッセージが出た場合は、少しバージョンを下げるとコンパイルできるらしいことが以下のページに記載されていました。

https://github.com/haskell-game/sdl2/issues/277

上記ページの内容に従って、古いバージョンのSDL2-Mixerをダウンロードし、こちらをインストールしてから再度ビルドしてみます。

```bash:bash
stack exec -- curl -O  https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-SDL2_mixer-2.6.2-1-any.pkg.tar.zst
stack exec -- pacman -U .\mingw-w64-x86_64-SDL2_mixer-2.6.2-1-any.pkg.tar.zst
stack build
```

メッセージが変わり、Cライブラリのコンパイルに成功した旨のメッセージが表示されましたが、また別のエラーメッセージが表示されてしまいました。

```bash:bash
(中略)
sdl2-mixer> Preprocessing executable 'sdl2-mixer-basic-jumbled' for sdl2-mixer-1.2.0.0..
sdl2-mixer> Building executable 'sdl2-mixer-basic-jumbled' for sdl2-mixer-1.2.0.0..
sdl2-mixer> [1 of 2] Compiling Main
sdl2-mixer> [2 of 2] Compiling Paths_sdl2_mixer
sdl2-mixer> [3 of 3] Linking .stack-work\\dist\\22605e11\\build\\sdl2-mixer-basic-jumbled\\sdl2-mixer-basic-jumbled.exe
sdl2-mixer> ld.lld: error: duplicate symbol: main
sdl2-mixer> >>> defined at C:\Users\username\AppData\Local\Temp\ghc16080_0\ghc_4.o
sdl2-mixer> >>> defined at C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crt0_c.c:17
sdl2-mixer> >>>            libmingw32.a(lib64_libmingw32_a-crt0_c.o)
(中略)
sdl2-mixer> clang: error: linker command failed with exit code 1 (use -v to see invocation)
sdl2-mixer> ghc-9.4.7.exe: `clang.exe' failed in phase `Linker'. (Exit code: 1)
```

エラーコードを読むと、どうやら `mingw` と `GitLubRunner`？ がそれぞれC言語の同じ関数を定義しており、シンボルの重複でエラーになっているようです。`mingw` はWindows環境でC言語を扱うための機能なのですが、GitLubRunnerはどこから湧いてきたのかよくわからず、duplicate symbolエラーの解決方法を探しても元となるコードを直せ、というような内容しか出てこず、ここで詰みとなりました。
もし詳しく知ってる方がいらっしゃいましたら、是非情報提供をお願いします。

### ALUT

SDL2-Mixerがダメな場合の第二の候補がALUTです。
これはOpenALという低レベルな音声再生を扱うライブラリを使いやすくしてくれるラッパーとしてのライブラリです（ChatGPT談）。
ただし、ALUTは既に開発や公開が終了しているので、ALUTに必要なDLLやlibファイルを入手するのに骨が折れました。私はWayBackを利用して過去のALUTサイトから必要そうなファイルを拾ってきました。

* https://web.archive.org/web/20130103042819/http://connect.creativelabs.com/openal/Downloads/Forms/AllItems.aspx

実際に必要なファイルがどれなのかは、以下の記事が参考になります。

* http://pocol.blog64.fc2.com/blog-entry-138.html

上記記事に従い、以下の通りALUTとOpenALを配置します。

1. OpenALCoreSDKをクリックしてダウンロード
1. ALUTをクリックし、freeallut_1.1.0-binをクリックしてダウンロード
1. OpenAL11CoreSDK.zipを任意のフォルダに展開
1. OpenAL11CoreSDK.exeが出てくるので実行してインストール
1. freealut-1.1.0-bin.zipを任意のフォルダに展開
    * 中にあるincludeフォルダとlibフォルダを `4.` でインストールしたフォルダ配下に格納する
        * freealut-1.1.0-bin/include/AL/alut.h　→　C:/Program Files/OpenAL1.1 SDK/includeに配置
        * freealut-1.1.0-bin/lib/alut.lib, alut.dll　→　C:/Program Files/OpenAL1.1 SDK/lib/Win64に配置

DLLとlibの配置が終わったら、それぞれのファイルの格納先をStackに教える必要があります。stack.yamlとpackage.yamlに以下の行を追加します。

```diff_yaml:stack.yaml
  # Extra directories used by Stack for building
  # extra-include-dirs: [/path/to/dir]
  # extra-lib-dirs: [/path/to/dir]
+ extra-lib-dirs: [C:\(省略)\OpenAL 1.1 SDK\libs\Win64]
```

```diff_yaml:package.yaml
  dependencies:
  - base >= 4.7 && < 5
+ - ALUT
```

これでStackプロジェクトをビルドすると･･･なんと、ビルドに成功します！やったぜ！！！
以下の記事を参考にとりあえずファイルを読み込んで再生するコードを書いてみます。

https://qiita.com/zaneli@github/items/b939f00d47ee1ab7773c

```haskell:Mixer.hs
import Sound.ALUT hiding (buffer)
import Control.Concurrent (threadDelay)

sample :: IO ()
sample = withProgNameAndArgs runALUTUsingCurrentContext $ \_ _ ->
  do
    let tmpFileName = "./(省略)/BackGroundMusic.wav"
    (Just device) <- openDevice Nothing
    context <- createContext device []
    currentContext $= context
    buffer <- createBuffer $ File tmpFileName
    [source] <- genObjectNames 1
    queueBuffers source [buffer]
    play [source]
    threadDelay (5 * 1000000)
    _ <- closeDevice device
    return ()
```

これで `stack build` して動かしてみます！･･･と思ったら、なんと、またもやC言語まわりのエラーがでてしまいました。

```bash:bash
Building executable 'Sample-exe' for Sample-0.1.0.0..
[3 of 3] Linking .stack-work\\dist\\22605e11\\build\\Sample-exe\\Sample-exe.exe
ld.lld: error: undefined symbol: alutGetError
>>> referenced by libHSSample-0.1.0.0-3x4Wv6IH1Eg2HGUu7l9e2L.a(Mixer.o):(Samplezm0zi1zi0zi0zm3x4Wv6IH1Eg2HGUu7l9e2L_FrontziUtilziSoundziMixer_sample2_info)
>>> referenced by libHSALUT-2.4.0.3-78JeORbPFLSE0BZeHmKGtO.a(Errors.o):(ALUTzm2zi4zi0zi3zm78JeORbPFLSE0BZZeHmKGtO_SoundziALUTziErrors_throwIfNullPtr1_info)
>>> referenced by libHSALUT-2.4.0.3-78JeORbPFLSE0BZeHmKGtO.a(Errors.o):(ALUTzm2zi4zi0zi3zm78JeORbPFLSE0BZZeHmKGtO_SoundziALUTziErrors_zdwthrowIfALfalse_info)
>>> referenced 2 more times

(省略)

ld.lld: error: undefined symbol: alutSleep
>>> referenced by libHSALUT-2.4.0.3-78JeORbPFLSE0BZeHmKGtO.a(Config.o):(ALUTzm2zi4zi0zi3zm78JeORbPFLSE0BZZeHmKGtO_SoundziALUTziConfig_alutzuSleep_info)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ghc-9.4.7.exe: `clang.exe' failed in phase `Linker'. (Exit code: 1)
```

どうやらALUTライブラリ内で指定されたシンボルが見つからないようです。
いろいろ調べた結果、OpenAL 1.1 SDKの中に含まれるC言語のヘッダーファイル（拡張子が.hのファイル）が参照されていないのが原因ではないか？と推測し、いくつかの方法でヘッダーファイルの読み込みを指定してみました。

```diff_yaml:package.yaml
  # Extra directories used by Stack for building
  # extra-include-dirs: [/path/to/dir]
  # extra-lib-dirs: [/path/to/dir]
+ extra-include-dirs: [C:\(省略)\OpenAL 1.1 SDK\include]
  extra-lib-dirs: [C:\(省略)\OpenAL 1.1 SDK\libs\Win64]
```

```diff_yaml:package.yaml
library:
  source-dirs: src
+ include-dirs: include
```

しかしどちらも結果は変わらず、シンボルが見つかることはありませんでした。ここでALUTの利用も断念しました。

### OpenAL

Haskellのライブラリを使って音声再生をする最後の手段がOpenALです。

OpenALはSDL2-MixerやALUTよりも低レベルな音声操作を行うためのライブラリです（ALUTはOpenALを使いやすくラップしたライブラリ）。良く言えば自由度が高く細かい操作もできるのですが、今回やりたいことに対してはオーバースペックですし、音声再生に関する専門知識も必要になります。できれば使用を避けたいところですが、SDL2-MixerもALUTも動かなかったので仕方ありません。

OpenALは（私の検索の仕方が悪い可能性もありますが）Web上の記事が非常に少なく、仕方なくChatGPTを頼りにコードを書いていきました。一応、音声再生デバイスを正しく取得してるっぽいところまでは実現できたのですが、Waveファイルのヘッダーを解析して音声再生用のバッファを生成するあたりがうまく行きませんでした。

この方法に関してはもうちょっと真剣に調べれば再生できる可能性もありそうでしたが、時間をかけた末に結局またC言語との相性問題で動かない、となったときに完全に心が折れるだろうと思うとやる気が出ず、あまり深追いせず諦めることにしました。Haskellライブラリでの音声再生を目指したい方は、まずはOpenALでの音声再生を目指してみると良いかもしれません。

## WindowsとC言語ライブラリの相性問題を回避する

ここまででHaskell･･･というかC言語とWindowsとの相性がすこぶる悪いことがわかったので、これらの組み合わせを回避する方法を模索し始めます。

### 他の言語でDLLを自作して呼び出す

Haskellは他のプログラミング言語で書いたライブラリを呼び出すことができます。これまでも見てきた通り、少なくともC言語で作ったDLLをHaskellから呼び出すことは当然できるので、C#で作ったDLLをHaskellから呼び出すことができないか考えました。

HaskellからC#を呼び出す記事は見つからなかったのですが、C++からC#を呼び出す方法についての記事は見つけることができました。

https://qiita.com/Midoliy/items/58d56e202f104ebf867a

HaskellはCやC++のDLLは呼び出せるので、上記の方法でC++向けのC#製DLLを作ればHaskellから呼び出せるのでは！？と考えました。が、残念ながら呼び出しの成功には至りませんでした。一応エラーメッセージは出るものの「関数が見つかりません」的なエラーが出るだけで、原因の詳細がｻｯﾊﾟﾘわからず。

今回作ったDLLがC++から呼べるかの検証はしてないので、そもそも根本の設定ミスを犯している可能性はあります。ただ、Haskellのためだけに未経験のC++まで勉強する気は起きなかったので、そこまでの調査はせずに断念しました。

ちなみに、実際に調査していた時はいくつかの方法を行ったり来たりしていたのですが、このDLL呼び出しの調査中には同じことをしようとしている人を発見しました。その人がまた別の人から「プロセス間通信を使えば良いのでは？」とアドバイスを受けているページを発見し、これがこの記事唯一の音声再生の成功につながりました。

### 他の言語からHaskellを呼び出す

ちょっと番外編っぽい感じですが、今回の私の最大の目的はHaskellでゲームを作ることではなくHaskellを通して関数型プログラミングについて学ぶことでした。ただし、ゲーム作成以外ではやる気が起きないので、ゲーム作成は必須という条件です。ということは、ゲーム作成の一部だけでもHaskellを使うことができれば関数型の勉強には十分です。特にアニメーションや音声再生などの副作用モリモリの部分は他言語に任せれば開発効率も向上するかもしれません。

ということで考え始めたのが、C#からHaskellを呼ぶ方法です。これについては少しだけ需要があるようで、UnityからHaskellのコードの呼び出しに成功している記事を見つけました。

https://qiita.com/masatoko/items/bba6392dbc9000d29fa6

上記記事の通りに作成を進めるも、またも壁にぶつかります。Haskell製のDLLを使うときはGHCランタイムの初期化と終了を挟む必要があるため `hs_init()` -> `自作の関数` -> `hs_exit()` の順に呼び出す必要があります。上記記事では `hs_init()` の呼び出しを簡略化するために `hs_init_wrapper()` を作成しており、私もこの関数の生成と呼び出しには成功しました。が、なぜか自作関数の生成がどうしてもうまくいかずに挫折しました。

こちらはある程度原因調査していて

* `hs_init_wrapper()` はC#からの呼び出しに成功している（デバッグ実行で確認）
* 自作関数は「該当の関数が存在しない」旨のエラーが出る
* DLLをバイナリエディタで開くと `hs_init_wrapper` は見つかるが自作関数の関数名が見つからないため、DLL生成時点で関数の生成に失敗している

という感じで、そもそもDLLの生成時に自作関数が出力対象になっていなさそうなことまではわかっています。

が、何が原因でどう直せば自作関数が出力対象になるのか皆目わかりませんでした。仮にこれを解決したとしても、上記記事の人も別のところで躓いていて本格的なHaskellの利用には至っていないため、ここで断念しました。

### Godot-Haskellで音声再生をGodotにやらせる

これはさらに番外編。最近人気上昇中のゲームエンジンGodotでHaskellを使えるようにするという素晴らしいGitプロジェクトがありまして、そちらを利用して音声再生をGodotに任せようという作戦です。

が、もちろんこのプロジェクトはWindows環境など考慮されていないので、またまた環境差異との戦いになります。とりあえずGitリポジトリをCloneしてきただけの状態ではもちろんビルドできないので、Windowsに必要そうな設定を一つ一つ潰していきます。最終的にビルドが正常終了するところまではこぎつけたのですが、結局ハリボテの成功にすぎず、Godotを起動してもDLLが読み込めずに動きませんでした。

もともとWindows向けに作られていないということもあり、ここで断念。

### DockerでLinux環境を構築する

ここにきて突如最高のアイディアが閃きます。Windows環境がダメならLinux環境を作ってしまえば良いのです！

最近、本職の方でも開発環境をDockerで構築する流れがあり、そのあたりを調べてくうちに「Haskellの開発環境もDockerで構築すればよいのでは！？」ということに気づきます。私のメインウェポンのC#はVisualStudioが強すぎてDockerが必要になるケースが少ないので、ここに辿り着くのに大変時間がかかりました。

HaskellやStackが公式でDockerイメージを公開しているので、とりあえず動かすだけなら簡単です。Haskell単独は流石にツライので、[Stack公式のDockerイメージ](https://docs.haskellstack.org/en/stable/docker_integration/)を落としてきて早速動かしてみます！

Dockerコンテナのビルドが終了し、適当なStackプロジェクトを作り、Package.yamlにSDL2-Mixerを設定してビルドすると･･･一発でビルドに成功！！！最初からLinux環境で作れば良かったんだ！！！

音声再生で躓いてから早1か月、ついにSDL2-Mixerのビルドに成功して浮かれたのも束の間、すぐに問題に気づきます。

至極当たり前のことなのですが、Linux環境でビルドした実行ファイルは**Linux環境でしか動きません。**

何が悲しくて自作のゲームを動かすためにDockerを立ち上げてLinux環境を作らなければならないのか。開発中はともかく、完成したゲームを遊ぶときくらいは実行ファイルダブルクリックで起動して遊びたいです。

さらに追い打ちをかけたのが、Docker上での音声再生には課題があり、一手間かけないと音声再生できないという事実です。一手間かければ音声再生できそうではあったものの、結局Windows環境単独で動かないゲームに価値を感じることができず、ここで断念となりました。

## 学んだこと

以上、私がWindows環境でHaskellを使って音声再生するために四苦八苦した記録でした。長く苦しい戦いでしたが、実は得たものも少なくないと思っています。

一つ目は、プロセス間通信という武器を得たことです。今後もHaskellで開発を進めていくと、同じようにC言語周りで詰まってしまう可能性がありますが、最悪プロセス間通信で他言語に処理を任せればどうにでもなると思えば、かなり気楽に開発を進められます。製品プログラムならともかく、趣味のプログラムなので極端に中身の作り方にこだわる必要も無いですからね。

もう一つは、ある程度の調査をしたので、もしまたライブラリ利用に挑戦したくなったときに、この記事の途中から調査を再開できるということです。未来の私は今より知識をつけて原因解消できるかもしれないので、そのための布石を打てたというのは悪くない時間だったのではないかと思います。

他にも、この調査を通して苦手なDockerについてちょっと学んだり、DLLの仕組みを学んだり、今までの私では知ることのなかった知識を得られましたし、最終的に目的もギリギリ達成できたので、頑張って良かったです。もし似たような問題で詰まっている方がいらっしゃいましたら、この記事が少しでも助けにもなれば幸いです。

## 最後に

もしWindows環境でのSDL2-Mixerの動かし方に知見を持っている方がいらっしゃいましたら、是非ご連絡をよろしくお願いいたします！！！