Help us understand the problem. What is going on with this article?

Rust でサウンドプログラミング

はじめに

rustlings を終えたので、とりあえず少し具体的な事をしてみようと、サウンドをやってみました。

環境は下記の通りです。

  • Windows 10
  • rustc 1.40.0 (stable-x86_64-pc-windows-gnu)

IDE は CLion を使っているので toolchain は mingw を使っています (CLion の Rust デバッガーが現時点では msvc に対応していない) 。ちなみに JetBrains IDE としては IntelliJ IDEA でも Rust プラグインがあるのでそちらでもコーティングは可能ですが現時点で IntelliJ では Rust デバッガーがありません。

オーディオライブラリを導入する

とりあえず音を出したいので、ライブラリの導入から始めます。

検索をすると "rust-portaudio" がよさそうなので、これを使うことにします。

rust-portaudio は "PortAudio" というクロスプラットフォームかつ OSS のオーディオライブラリの Rust バインディングになります。

とりあえず Cargo.toml に rust-portaudio の Crate を入れる記述をします。

Cargo.toml
[dependencies]
portaudio = "0.7.0"

で適当にコード書いてビルドをしてみるとリンク時にエラーが出ます。

error: linking with `gcc` failed: exit code: 1
  |
  = note: "gcc" "-Wl,--enable-long-section-names" "-fno-use-linker-plugin" "-Wl,--nxcompat" "-nostdlib" "-m64" (略)
  = note: ld: cannot find -lportaudio

PortAudio の本体がないためリンクエラーになりました。よって PortAudio 本体をビルドします。

Git から checkout するかダウンロードサイトでソースコードを落としてきます。 Git は最新 stable の "pa_stable_v190600_20161030" ブランチで。

今回は Rust toolchain が mingw (x64) なので、 PortAudio も mingw (mingw64) でビルドします。 Makefile でビルドしました。

ビルドした PortAudio の成果物のうち、 "libportaudio.dll.a" をリンカーから見えるようにする必要があります (これがないからエラーになっていた) 。最初は "%USERPROFILE%\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib\rustlib\x86_64-pc-windows-gnu\lib" にコピーしてたのですがどうにも納得がいかず、 cargo から参照先を指定できる方法があるはず、ということで調べてみました。

build.rs というソースファイルをプロジェクトのルート (cargo.toml と同じ位置) に配置し、標準出力にオプションを書き出すとそのオプションが反映されます。

build.rs
fn main() {
    println!("cargo:rustc-link-search=native=lib");
}

上記でプロジェクト下の "lib" フォルダーに "libportaudio.dll.a" を配置するとリンクされます。

"libportaudio-2.dll" は Rust プロジェクトでビルドして生成された .exe と同じ位置にコピーします (これも build.rs を駆使すれば配置まで自動で行えそうですが今回は省略) 。

以上で準備完了です。

PortAudio を使ってみる

ストリーミング再生をするオーディオデバイスのライブラリは何を使っても大体考え方は同じで

  • 再生する音声サンプルを取得するコールバックを設定する
  • バッファ内のサンプル消費状況を自分で監視し空いている事を確認したら埋めていく

のどちらかのパターンです。 DirectSound と OpenAL が後者で多くは前者かなあと思います。 PortAudio の前者のパターンです。

ということでサンプルコードを見ながら実装していきます。

let pa = portaudio::PortAudio::new()?;
let mut settings = pa.default_output_stream_settings(2, 48000.0, 48000)?;
settings.flags = portaudio::stream_flags::CLIP_OFF;

PortAudio の初期化と出力設定情報を準備します。

let mut sample_number: u32 = 0;
let callback = move |portaudio::OutputStreamCallbackArgs { buffer, frames, .. }| {
    let mut i = 0;
    for _ in 0..frames {
        let value = (sample_number as f32 * 440.0 * 2.0 * PI / 48000.0).sin();
        buffer[i] = value;
        buffer[i + 1] = value;
        i += 2;
        sample_number += 1;
    }
    portaudio::Continue
};

440Hz のサイン波を生成するコールバックです。

let mut stream = pa.open_non_blocking_stream(settings, callback)?;
stream.start()?;
pa.sleep(3 * 1_000);
stream.stop()?;
stream.close()?;

設定とコールバックを指定して出力ストリームを開き、再生を行います。

割とシンプルに記述することができました。

応用

以前書いた F# の記事内のコードを Rust に移植して同じことをしてみます。 Note 構造体や NoteName 列挙型の定義などは長いので GitHub を参照してください。

コールバックでの波形生成でドミソの和音は下記のようになりました。

let time = Time::samples_to_time(SAMPLE_RATE,sample_number);
let value =
    (Note::new(4, NoteName::C).pitch().square_wave(time) +
     Note::new(4, NoteName::E).pitch().square_wave(time) +
     Note::new(4, NoteName::G).pitch().square_wave(time)).volume(0.3);

パイプライン演算子が使えないので中間型を定義してメソッドチェーンでそれっぽい見通しのよい感じにしてみました。

おわりに

Rust なんもわからん・・・

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした