LoginSignup
5
2

More than 3 years have passed since last update.

SOULによるオーディオプログラミング例

Last updated at Posted at 2020-12-23

SOUL

オーディオプログラミング言語 Advent Calendar 2020

概要

https://soul.dev/
https://github.com/soul-lang/SOUL

既存環境の課題である、性能、移植性、開発容易性の改善を目的として開発されたオーディオ記述言語。Webプレイグラウンドも用意されている( https://soul.dev/lab/ )。

開発はJUCEで有名なROLI社。ROLIに買収される以前からほぼひとりでJUCEを開発していたJules Storerがメインコントリビューター。初出はADC2018カンファレンスのJules Storer自身による基調講演( https://www.youtube.com/watch?t=910&v=-GhleKNaPdk )。

ISCベースの独自ライセンス。大規模な商用利用は別途ライセンス契約が必要になる場合がある。
https://github.com/soul-lang/SOUL/blob/master/LICENSE.md

実装例

サイン波生成

SOULのプログラムはprocessor宣言の中に書きます。void run()がメイン処理です。

位相が2πを超えないようにするaddModulo2Pi()関数など、オーディオ処理の頻出処理用関数が用意されています。

processor SineWave
{
    output stream float out;

    float gain = 0.3f;
    float theta = 0.f;
    float delta = float (440 * twoPi * processor.period);

    void run()
    {
        loop
        {
            out << sin(theta) * gain;
            theta = addModulo2Pi(theta, delta);
            advance();
        }
    }
}

SOULはソースコードである.soulファイルの他に、マニフェストファイル.soulpatchを用意する必要があります。JSON形式のテキストファイルにアプリケーション情報を記述して拡張子.soulpatchで保存します。

{
    "soulPatchV1":
    {
        "ID":               "net.aikelab.sine",
        "version":          "1.0",
        "name":             "SineWave",
        "description":      "sine wave example",
        "category":         "synth",
        "manufacturer":     "aikelab.net",
        "isInstrument":     false,

        "source":           "sine.soul"
    }
}

コマンドラインから実行します。第2引数で指定するのはsoulpatchファイルです。

> soul play sine.soulpatch

シンプルなプログラムの場合soulpatchファイルがなくても--nopatchオプションをつければsoulファイルを指定して実行することができます。

> soul play sine.soul --nopatch

Webプレイグラウンド( https://soul.dev/lab/ )でも実行可能です。

SOULではGUIを追加するのも簡単です。ソースコードにeventとして入力範囲指定とコールバックを追加するだけでGUIの定義になります。


processor SineWave
{
    output stream float out;

    input event
    {
        float Gain [[ min:0, max:1, init:0.3, step:0.01 ]];
    }

    event Gain(float f) { gain = f; }

    float gain;
    float theta = 0;
    float delta = float (440 * twoPi * processor.period);

    void run()
    {
        loop
        {
            out << sin(theta) * gain;
            theta = addModulo2Pi(theta, delta);
            advance();
        }
    }
}

実行すると下の画面が表示されGUIから音を操作できるようになります。

sine.png

サイン波のオシレータやゲイン処理はライブラリとしても用意されています。それらを使ったプログラムは以下のようになります。graph宣言は、複数のprocessorを部品として扱ってオーディオグラフを構築します。

graph SineWave
{
    output stream float out;

    let osc = soul::oscillators::Sine(float, 440);
    let gain = soul::gain::FixedGain(float, 0.3f);

    connection
    {
        osc -> gain -> out;
    }
}

Delayエフェクト

SOULはリングバッファ用の機能がいくつか用意されています。たとえばwrap<bufferSize>とした場合、bufferSizeを超えてアクセスしようとすると実際には0~bufferSize-1の範囲にしてアクセスされます。

wavファイルのファイル名は.soulpatchの方に書いて、ソースコードではリソース名(今回はvoice)を指定します。

readLinearInterpolatedは、サンプリングデータの再生速度を調整するための補間関数です。
今回はアプリとwavファイルのサンプルレートが異なるときの調整に使っています。

processor Delay
{
    output stream float out;

    input event
    {
        float DelayTime [[ min:0, max:1000, init:400, step:1 ]];
        float Feedback  [[ min:0, max:1, init:0.5, step:0.01 ]];
        float WetLevel  [[ min:0, max:1, init:0.5, step:0.01 ]];
    }

    event DelayTime(float delayMs)
    {
        let delaySamples = max(1, int(processor.frequency * (delayMs / 1000.0f)));
        readPos = wrap<bufferSize>(writePos - delaySamples);
    }

    event Feedback(float f) { feedback = f; }
    event WetLevel(float f) { wetLevel = f; }

    float feedback;
    float wetLevel;

    let bufferSize = 100000;
    float[bufferSize] buffer;
    wrap<bufferSize> readPos, writePos;

    external soul::audio_samples::Mono voice;

    void run()
    {
        float64 playPos;
        float64 addPos = voice.sampleRate / processor.frequency;

        loop
        {
            let dry = voice.frames.readLinearInterpolated(playPos);
            playPos += addPos;

            if (playPos >= voice.frames.size)
                addPos = 0;

            buffer[writePos] = dry + buffer[readPos] * feedback;
            out << dry + buffer[readPos] * wetLevel;

            ++readPos;
            ++writePos;

            advance();
        }
    }
}
{
    "soulPatchV1":
    {
        "ID":               "net.aikelab.delay",
        "version":          "1.0",
        "name":             "Delay",
        "description":      "sample playback and delay fx",
        "category":         "Fx",
        "manufacturer":     "aikelab.net",
        "isInstrument":     false,

        "source":           "delay.soul",
        "externals":        { "Delay::voice": "../voice.wav" }
    }
}

実行画面は以下のようになります。

delay.png

感想

Jules Storerが設計しただけあって、コンパクトでモダンでセンスの良い言語という印象です。音の抽象化に独自性があるというより徹底的に現実指向で、現時点におけるオーディオ処理の要件や課題をよく理解していることが伝わってきます。信号処理に便利な構文や機能が多数用意されていて、サンプル単位の処理も比較的シンプルに書くことができます。オーディオエンジンやプラグインの組み込みスクリプトとしても使いやすそうです。

このところ勢いのあるROLIということもあり、今後は最重要のオーディオプログラミング言語になると思います。

オーディオプログラミング言語 Advent Calendar 2020

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2