6:エフェクト p5.sound.js サウンド

フィルタ

一般的にフィルタとは、ろ紙を使ったろ過機を言いますが、「特定の周波数範囲を通過させたり阻止したりする装置や回路。ろ波器」という意味もあります(「フィルターとは何? Weblio辞書」)。これは、今の場合で言うと、音が鳴る前の周波数のある範囲をカットして、残った結果を鳴らすということです。

主なフィルタには次のものがあります。

フィルタ 説明 周波数のイメージ
ローパスフィルタ 周波数の低い成分を通す(ローをパスする)フィルタ。高い成分をカットする
ハイパスフィルタ ローパスフィルタの逆
バンドパスフィルタ ローパスフィルタとハイパスフィルタを組み合わせたもの

次のサウンドはSynth1で作った元の音です。大砲かカミナリの音のように聞こえます。

これにローパスフィルタをかけた音です。高い音がカットされ、遠くで何かが爆発したような、こもった感じの音になっています。

ハイパスフィルタをかけた音です。低い音がカットされ、かなり近い場所でカミナリが鳴ったような音に聞こえます。

フィルタにはカットオフ周波数とレゾナンスというパラメータがあります。

カットオフ周波数

これは、周波数のどの部分を境にしてカットするかを決める数値です。下図はCakewalk by BandLabに組み込むことのできるスペクトルアナライザ「Voxengo SPAN」で、上記のローパスフィルタ適用前の音と適用後のスペクトル周波数を表示し、その結果を重ねたものです。

レゾナンス

レゾナンスは、カットオフ周波数近辺の周波数を強調します。

次の音を、

Synth1で強いレゾナンスをかけると、次のように変化します。

次のコードは3つのフィルタをテストする例です。画面のクリックで再生がスタートし、使用するフィルタが画面に描画されます。下部のスライダを操作すると、フィルタに適用するカットオフ周波数が変更できます。

// フィルタテスト
let filter;
// 周波数の初期値は440Hz
let freq = 440;
let fft, sound;

let isUserStarted = false;
let isSoundStarted = false;
let slider;

let duration;

function preload() {
    sound = loadSound('assets/before_Lowpassed.mp3');
}

// フィルタタイプを設定
const filterType = 'lowpass';
//const filterType = 'highpass';
//const filterType = 'bandpass'

function setup() {
    createCanvas(400, 300);
    fill(255, 40, 255);
    textSize(20);
    noStroke();
    // フィルタを作成
    filter = new p5.Filter(filterType);
    // FFTを作成
    fft = new p5.FFT();

    // 周波数を変更するスライダ
    slider = createSlider(10, 880, freq, 10);
    slider.position(10, 300);
    slider.style('width', '400px');

    duration = sound.duration();
}

function draw() {
    background(30);
    // スライダの値を調べ、フィルタの周波数に設定
    freq = slider.value();
    filter.freq(freq);
    // フィルタ処理したスペクトルを描画
    const spectrum = fft.analyze();
    const len = spectrum.length
    for (let i = 0; i < len; i++) {
        let x = map(i, 0, len, 0, width);
        let h = -height + map(spectrum[i], 0, 255, height, 0);
        rect(x, height, width / len, h);
    }
    // フィルタのタイプと現在の周波数と残り時間を左上に描画
    const restTime = floor(duration - sound.currentTime());
    text(filterType + ' ' + freq + ' Hz ' + restTime + 'sec', 10, 50);

    if (isSoundStarted && !sound.isPlaying()) {
        noLoop();
        print('終了');
    }
}

function touchStarted() {
    // 最初のタッチ(クリック)時のみ実行
    if (!isUserStarted) {
        userStartAudio().then(() => {
            // ノイズの接続を解除して、フィルタに接続し、スタート
            sound.disconnect();
            sound.connect(filter);
            sound.play();
            isSoundStarted = true;
            filter.freq(freq);
        });
        isUserStarted = true;
    }
}

ローパスフィルタ

p5.LowPassフィルタをp5.SoundFileに適用し、FFTを使ってサウンドを視覚化します。mouseXをフィルタのカットオフ周波数に、mouseYをフィルタのレゾナンスにマッピングします。

let soundFile;
let fft;

let filter, filterFreq, filterRes;

let isUserStarted = false;
let duration;

function preload() {
    soundFormats('mp3', 'ogg');
    soundFile = loadSound('assets/forevermore');
}

function setup() {
    const canvas = createCanvas(710, 256);
    // マウスプレスで再生/一時停止を切り替える
    canvas.mousePressed(togglePlay);

    textSize(20);
    duration = soundFile.duration();
    // LowPassオブジェクト(フィルタ)
    filter = new p5.LowPass();

    // FFTオブジェクト(スペクトル解析用)
    fft = new p5.FFT();
}

function draw() {
    background(30);
    // soundFileが再生中なら、
    if (soundFile.isPlaying()) {
        // mouseXを、人間が聞くことのできる最低周波数(10Hz)から最高周波数(22050Hz)までの
        // カットオフ周波数にマッピングする
        filterFreq = map(mouseX, 0, width, 10, 22050);

        // mouseYを、カットオフ周波数近辺でのレゾナンス(ボリュームブースト、音量増加)にマッピングする
        filterRes = map(mouseY, 0, height, 15, 5);

        // フィルタパラメータを設定
        filter.set(filterFreq, filterRes);

        // FFTスペクトル解析ですべての値を描画する
        // x = 最低周波数(10Hz)から最高周波数(22050Hz)
        // h = その周波数でのエネルギー(振幅/音量)
        let spectrum = fft.analyze();
        noStroke();
        fill(255, 40, 255);
        for (let i = 0; i < spectrum.length; i++) {
            let x = map(i, 0, spectrum.length, 0, width);
            let h = -height + map(spectrum[i], 0, 255, height, 0);
            rect(x, height, width / spectrum.length, h);
        }
        const restTime = floor(duration - soundFile.currentTime());
        fill(255, 255, 255);
        text('宇多田ヒカル - forevermore/cover by MiyuTakeuchi ' + restTime + 'sec', 10, 25);
    }
}

function touchStarted() {
    // 最初のタッチ(クリック)時のみ実行
    if (!isUserStarted) {
        userStartAudio().then(() => {
            // フィルタリングしたサウンドだけが聞こえるように、
            // soundFileのマスター出力からの接続を解除してから、
            soundFile.disconnect();
            // soundFileをフィルタに接続する
            soundFile.connect(filter);
        });
        isUserStarted = true;
    }
}

// マウスプレスで再生/一時停止を切り替える
function togglePlay() {
    if (!soundFile.isPlaying()) {
        // soundFileを再生
        soundFile.play();
    }
    else {
        // soundFileの再生を一時停止
        soundFile.pause();
    }
}

画面のクリックで曲の再生と一時停止が切り替わります。曲は1分40秒ほどあります。

上記で(無断で)使っている曲はYoutubeで公開されている下記「宇多田ヒカル – forevermore/cover by MiyuTakeuchi」の一部です。

解説

フィルタの組み込みで少し分かりづらいのが、p5.SoundFileオブジェクトのdisconnect()とconnect(filter)の呼び出しです。

// フィルタリングしたサウンドだけが聞こえるように、
// soundFileのマスター出力からの接続を解除してから、
soundFile.disconnect();
// soundFileをフィルタに接続する
soundFile.connect(filter);

p5.SoundFileオブジェクトもp5.LowPassオブジェクトも作成時、自動的にスピーカーに接続します。ローパスのフィルタ効果を再生音に反映させるには、p5.SoundFileオブジェクトの出力先をスピーカーからフィルタに変える必要があります。そのためにここでは、p5.SoundFileオブジェクトのdiscconect()を呼び出してスピーカーへの接続を解除して、その後connect(filter)を呼び出して接続先をフィルタに設定しています。

リファレンスメモ

p5.Effect

説明

Effectはp5のオーディオエフェクトの基本クラス。このモジュールは、現在とその後のエフェクトに有用なノードとメソッドを処理する。このクラスは、p5.Distortionp5.Compressorp5.Delayp5.Filterp5.Reverbによって拡張される。

メソッド

connect()
出力をp5.jsサウンド、Web Audio Nodeに送信するか、信号を使ってAudioParamを制御する
disconnect()
すべての出力の接続を解除する

p5.Filter

説明

p5.Filterは、Web Audio Biquad Filterを使って、入力ソースの周波数特性をフィルタ処理する。サブクラスには次のクラスがある。

* p5.LowPass: カットオフ周波数より低い周波数を通過させ、カットオフ周波数より高い周波数を減衰させる。
* p5.HighPass: ローパスフィルタの逆。
* p5.BandPass: 特定範囲の周波数を通過させ、その周波数範囲の上下の周波数を減衰させる。
.res()メソッドは、バンドパスの帯域か、ローパス/ハイパスのカットオフ周波数のレゾナンスを制御する。

このクラスはp5.Effectを拡張する。amp()、chain()、drywet()、connect()、disconnect()メソッドが使用できる。

シンタックス

new p5.Filter([type])

パラメータ

type 文字列: ‘lowpass’ (デフォルト), ‘highpass’, ‘bandpass’ (オプション)

メソッド

set()
フィルタの周波数とレゾナンスを設定する。

シンタックス

set([freq], [res], [timeFromNow])

パラメータ

freq 数値: 周波数(Hz単位)、10から22050まで(オプション)
res 数値: レゾナンス(Q)、0.001から1000まで (オプション)
timeFromNow 数値: この出来事を今から何秒後に起こすか(オプション)

p5.SoundFileオブジェクトのメソッド—

connect()

説明

p5soundオブジェクトの出力を、別のp5.soundオブジェクトの入力に接続する。たとえば、p5.SoundFileをFFTやEffectに接続できます。パラメータが与えられない場合は、マスター出力に接続する。ほとんどのp5soundオブジェクトは、作成されたときに、マスター出力に接続する。

シンタックス

connect([object])

パラメータ

object オブジェクト: 入力を受け取るオーディオオブジェクト(オプション)

disconnect()

説明

このp5soundオブジェクトの出力の接続を解除する。.

ディレイ

ディレイは、1度鳴らした音を、音量を少し下げ遅らせてまた鳴らすことを繰り返すことで、その音に飾りを付ける効果です。

縦軸に音量、横軸に時間を取ったディレイ効果は次のグラフで表すことができます。図のdelayTimeは次の音が鳴るまでの時間(遅延時間)で、feedbackは音量を小さくする比率です。0.5の場合には前の音量の半分に減ります。

次の2つのコントロールは、上がディレイなしのもので、下がAudacityの[エフェクト]->[ディレイ]でディレイ効果を追加したものです。

[ヤッホー ディレイ無:元素材 ポケットサウンド/効果音素材]

[ヤッホー ディレイ有]

下図はAudacityの[ディレイ]ダイアログボックスです。[繰り返しごとのディレイレベル(dB)]でfeedbackに相当する値を、[ディレイ時間]でdelayTimeを設定します。

*[繰り返しごとのディレイレベル(dB)]では-30~1(dB)が指定できます。-6を指定するとfeedback=0.5を、-12を指定するとfeedback=0.25を指定するのと同じことになります。

ディレイ

マウスクリックでp5.Delayが処理したSoundFileを聞くことができます。mouseXでp5.Delayフィルタの周波数を制御し、mouseYでp5.Delayの遅延時間とレゾナンスを制御します。そしてその結果の音量をp5.Amplitudeオブジェクトを使って視覚化します。

let soundFile, analyzer, delay;
let isUserStarted = false;

function preload() {
    soundFormats('ogg', 'mp3');
    soundFile = loadSound('assets/beatbox.mp3');
}

function setup() {
    createCanvas(710, 400);
    // ディレイ音のみ聞けるようにする
    soundFile.disconnect();
    // soundFileの再生が終わったらsoundEnded()関数を呼び出す
    soundFile.onended(soundEnded);

    // p5.Delayオブジェクト
    delay = new p5.Delay();
    // soundFileに対するディレイを設定する
    delay.process(soundFile, 0.12, 0.7, 2300);
    // ステレオ効果
    delay.setType('pingPong');
    // 音量アナライザー
    analyzer = new p5.Amplitude();
}

function draw() {
    background(0);
    if (isUserStarted) {
        // p5.Amplitudeアナライザーから音量測定値を得る
        let level = analyzer.getLevel();

        // levelを使って緑の矩形を描く
        let levelHeight = map(level, 0, 0.1, 0, height);
        fill(100, 250, 100);
        rect(0, height, width, -levelHeight);

        let filterFreq = map(mouseX, 0, width, 60, 15000);
        filterFreq = constrain(filterFreq, 60, 15000);
        let filterRes = map(mouseY, 0, height, 3, 0.01);
        filterRes = constrain(filterRes, 0.01, 3);
        // ディレイのローパスフィルタの周波数を設定する
        delay.filter(filterFreq, filterRes);
        let delTime = map(mouseY, 0, width, 0.2, 0.01);
        delTime = constrain(delTime, 0.01, 0.2);
        // ディレイの遅延時間を設定する
        delay.delayTime(delTime);
    }
}

function soundEnded() {
    print('ended');
    isUserStarted = false;
}

function touchStarted() {
    // 最初のタッチ(クリック)時のみ実行
    if (!isUserStarted) {
        userStartAudio().then(() => {
            soundFile.play();
        });
        isUserStarted = true;
    }
}
ディレイの有無を比較する例

ディレイ効果の有る無しを比べる例を作成してみました。p5.Noiseのノイズのタイプについては「4:サウンドの作成と加工(エンベロープ) p5.sound.js サウンド」で述べています。

let env, noise, delay, fft;
let isUserStarted = false;

function setup() {
    createCanvas(400, 300);
    fill(255);
    noStroke();
    textSize(20);
    textAlign(CENTER);

    // ブラウンノイズ
    noise = new p5.Noise('brown');
    // 振幅を0にする(無音状態)
    noise.amp(0);
    //  p5.Delayオブジェクトを作成
    delay = new p5.Delay();
    delay.setType('pingPong');
    // noiseを再生するエンベロープを作成
    env = new p5.Envelope(.01, 0.2, .2, .1);
    // FFTを作成
    fft = new p5.FFT();
}

function draw() {
    background(0);
    if (!isUserStarted) {
        text('click to start', width / 2, height / 2);
    }
    else {
        fill(255, 40, 255);
        drawSpectrum();
    }
}

function touchStarted() {
    // 最初のタッチ(クリック)時のみ実行
    if (!isUserStarted) {
        userStartAudio().then(() => {
            // ノイズをスタート
            noise.start();
            // ボタンを作成して表示
            makeButton();
        });
        isUserStarted = true;
    }
}

// 2つのボタンを作成
function makeButton() {
    // ディレイ効果を設定するボタン
    const delayButton = setButton('DELAY', {
        x: 300,
        y: 30
    });
    delayButton.mousePressed(() => {
        // delayの出力をp5.soundに送る
        delay.connect();
        // noiseにディレイを追加
        delay.process(noise, .12, .7, 2300);
        // noiseをエンベロープで再生
        env.play(noise);
    });

    // ディレイ効果のないnoiseを再生
    const noDelayButton = setButton('NO DELAY', {
        x: 160,
        y: 30
    });
    noDelayButton.mousePressed(() => {
        // 出力の接続を解除
        delay.disconnect();
        // noiseをエンベロープで再生
        env.play(noise);
    });
}

function setButton(label, pos) {
    const button = createButton(label);
    button.size(100, 30);
    button.position(pos.x, pos.y);
    return button;
}

// FFT.analyze()による周波数解析を描画
function drawSpectrum() {
    let spectrum = fft.analyze();
    for (let i = 0; i < spectrum.length; i++) {
        let x = map(i, 0, spectrum.length, 0, width);
        let h = -height + map(spectrum[i], 0, 255, height, 0);
        rect(x, height, width / spectrum.length, h);
    }
}

画面をクリックすると[DELAY]と[NO DELAY]ボタンが現れます。それぞれをクリックすると、ディレイ効果の有無が耳で比較できます。

リファレンスメモ

p5.Delay

説明

ディレイはエコー(残響)効果。既存のサウンドソース(音源)を処理し、そのサウンドの遅延版を出力する。p5.Delayは、delayTimeとfeedback、filterそしてtypeに応じて、さまざまなエフェクトを生み出すことができる。下のサンプルでは、0.5のfeedback(デフォルト値)が、繰り返しごとに50%音量が小さくなるループディレイを生み出す。フィルタが高周波数をカットするので、突き刺すような音源の音とは違って聞こえる。

このクラスはp5.Effectを拡張するので、そのamp()chain()drywet()connect()disconnect()が使用できる。

メソッド

process()

説明

遅延パラメータのセットにしたがってオーディオ信号に遅延を追加する。

シンタックス

process(Signal, [delayTime], [feedback], [lowPass])

パラメータ

Signal オブジェクト: オーディオを出力するオブジェクト
delayTime 数値: 遅延/残響の時間(秒単位)。一部のブラウザでは1秒に制限される。(オプション)
feedback 数値: 毎回音量が小さくなるループで遅延をそれ自体に送り返す(オプション)
lowPass 数値: カットオフ周波数。lowPassより低い周波数のみが遅延に含まれる(オプション)

setType()

説明

ディレイのプリセットタイプを選択する。’pingPong’は信号を左チャンネルから右チャンネルに跳ね返し、ステレオ効果を生み出す。これ以外のパラメータを与えるとデフォルトの遅延設定に戻る。

シンタックス

setType(type)

パラメータ

type 文字列|数値: ‘pingPong’ (1) または ‘default’ (0)

filter()

説明

ディレイ用のローパスフィルタ周波数を設定する。ローパスフィルタは、このフィルタ周波数より高い周波数をカットする。

シンタックス

filter(cutoffFreq, res)

パラメータ

cutoffFreq 数値|オブジェクト: ローパスフィルタは、このフィルタ周波数より高い周波数をカットする。
res 数値|オブジェクト: フィルタによる周波数カットオフのレゾナンスか、またはこのパラメータの変調に使用できるオブジェクト。高い数値(15など)はレゾナンスを生み出し、低い数値(0.2など)は傾斜を生み出す。

delayTime()

説明

遅延(エコー)時間を秒単位で設定する。通常この値は0.0~1.0の間の浮動小数点数。

シンタックス

delayTime(delayTime)

パラメータ

delayTime 数値: 遅延の時間(秒単位)

feedback()

説明

フィードバックは、Delayがループ内で入力を通して信号を返すときに発生する。フィードバック量はループのたびに送信する信号の量を決める。1.0より大きなフィードバックは、ループするたびに出力全体を増加させ、無限のフィードバックループを生み出すので、望ましくない。デフォルト値は0.5。

シンタックス

feedback(feedback)

パラメータ

feedback 数値|オブジェクト: 0.0〜1.0、またはこのパラメーターの変調に使用できるOscillatorなどのオブジェクト

戻り

数値: フィードバック値

リバーブ

リバーブは、音に奥行きと知覚できる空間を与えます。ここではp5.SoundFileオブジェクトにリバーブ効果を与えています。

let sound, reverb, fft;

function preload() {
    soundFormats('mp3', 'ogg');
    soundFile = loadSound('assets/yahho');
    // デフォルトの接続を解除する。
    // これにより、リバーブ処理を通したサウンドだけ聞くことができる
    soundFile.disconnect();
}

function setup() {
    createCanvas(400, 300);
    // p5.Reverbオブジェクト
    reverb = new p5.Reverb();
    // soundFileに対するリバーブを設定する
    // 時間を6秒、減衰率を0.2%に設定
    reverb.process(soundFile, 6, 0.2);
    // 音量を上げる
    reverb.amp(4);
    // FFTを作成
    fft = new p5.FFT();
}

function draw() {
    background(200);
    drawSpectrum();
}

// FFT.analyze()による周波数解析を描画
function drawSpectrum() {
    let spectrum = fft.analyze();
    for (let i = 0; i < spectrum.length; i++) {
        let x = map(i, 0, spectrum.length, 0, width);
        let h = -height + map(spectrum[i], 0, 255, height, 0);
        rect(x, height, width / spectrum.length, h);
    }
}
function mousePressed() {
    soundFile.play();
}

画面のクリックでスタートします。

解説

ディレイが音を飾り付けるエフェクトであるのに対し、リバーブは、室内で音が壁などに当たったときに発生する残響を付けるエフェクトです。

音源(スピーカーや楽器など)が鳴ると、最短距離でメインの音が直接聞く人に届きます(直接音)。またこれとは別に室内の壁などで当たってから届く音もあります(初期反射)。さらにこの後、壁などでの反射で減衰を繰り返しながら届く音もあります(後部残響)。(参考:「リバーブとディレイ、エコーの違い【今さら聞けない用語シリーズ】」)

これを表そうとするのがリバーブです。Audacityの[リバーブ]ダイアログボックスには、リバーブの特徴をよく示す[ルームサイズ]があります。Audacityはこの値が小さければ狭い空間(部屋)での残響を、大きければ広い空間(ホール)での残響をシミュレーションします。

リファレンスメモ

p5.Reverb

説明

リバーブは、減衰する多くのエコーを通して音に深みを追加し、その音が物理的な空間で発生しているという知覚を生み出す。p5.Reverbには、時間(リバーブの持続時間)と減衰率(音がエコーごとに減衰する比率)に関するパラメータがあり、これらは.set()か.process()メソッドで設定できる。p5.Convolverはp5.Reverbを拡張し、畳み込み(コンボリューション)によって実際の物理空間の音が再現できる。

このクラスはp5.Effectを拡張する。amp()chain()drywet()connect()そしてdisconnect()が使用できる。

メソッド

process()

説明

ソースをリバーブに接続し、リバーブパラメータを割り当てる。

シンタックス

process(src, [seconds], [decayRate], [reverse])

パラメータ

src オブジェクト: p5.sound / サウンド出力を備えたWeb Audioオブジェクト
seconds 数値: リバーブの継続時間、秒単位。最小:0,最大:10。デフォルトは3(オプション)
decayRate 数値: 各エコーでの減衰のパーセント。最小:0,最大:100。デフォルトは2(オプション)
reverse ブール値: リバーブを後方に再生するか前方に再生するか(オプション)

set()

説明

リバーブ設定を設定する。.process()に似ているが、新しい入力の割り当ては行わない。

シンタックス

set([seconds], [decayRate], [reverse])

パラメータ

seconds 数値: リバーブの継続時間、秒単位。最小:0,最大:10。デフォルトは3(オプション)
decayRate 数値: 各エコーでの減衰のパーセント。最小:0,最大:100。デフォルトは2(オプション)
reverse ブール値: リバーブを後方に再生するか前方に再生するか(オプション)

amp()

説明

リバーブエフェクトの出力レベルを設定する。

シンタックス

amp(volume, [rampTime], [timeFromNow])

パラメータ

volume 数値: 0から1.0の振幅
rampTime 数値: rampTimeだけつづくフェードを作成する(オプション)
timeFromNow 数値: 今から何秒後にこの出来事を起こすか(オプション)

コンボリューションリバーブ

コンボリューションリバーブは、別名サンプリングリバーブとも呼ばれ、コンサートホールなど、実際の空間で採取したデータ(インパルスレスポンス、IR)を使って、残響を再現します。そのときに用いられるのがコンボリューション(畳み込み)演算です(参考:「g200kg > 偏ったDTM用語辞典 > Convolution Reverb コンボリューションリバーブ」)。

これはIRデータ(パルス、短い音)があれば、その場所に行かなくてもその場所での音の響きが再現できるということです。もちろん適切なIRデータの採取には相応の機材やノウハウが要りますが、実は適当なサウンドファイルでもコンボリューションリバーブに利用できます。

コンボリューションサウンド処理以外にも、画像処理画像認識にも利用されます。

下のサウンドはCakewalk by BandLabのドラムキットで作成した音です。

[元のドラム音] 録音スタジオの中ですぐそこで鳴っている感じ

Cakewalk by BandLabにはコンボリューションリバーブ用のプラグイン「MConvolutionEZ」を組み込むことができます。MConvolutionEZを使うと、環境を選択してそこでで鳴っているような音が作成できます。

次の2つはその例です。

[コンボリューションリバーブ 小ルーム版] 室内の少し離れたところで鳴っている感じ

[コンボリューションリバーブ 大ホール版] 大きなホールの遠くで鳴っている感じ

コンボリューションリバーブ

p5.Convolverは、コンボリューション(畳み込み)を使って、実際の空間の音が再現できます。コンボリューションはインパルスレスポンス(部屋の残響音)を受け取り、それを使ってその空間での音を再現します。

基本的な例

上記リンクのサンプルはかなり分かりづらいので、基本的なものを以下に作成しました。

let sound, cVerb, fft, canvas;
let isUserStarted = false;

const IRPath = 'Batcave.wav';

function preload() {
    // (1) preload()内で、IRデータを持つp5.Convolverオブジェクトを作成
    cVerb = createConvolver('assets/' + IRPath);
    // WebAudio ConvolverNode
    print(cVerb.convolverNode);
    print(cVerb.impulses);
    // (2) preload()内で、コンボリューション処理対象のサウンドを読み込む
    sound = loadSound('assets/drum_blues.mp3');
}

function setup() {
    canvas = createCanvas(710, 400);
    fill(0, 255, 40);
    noStroke();
    textSize(20);

    // (3) p5.SoundFileオブジェクトは作成時、自動的にスピーカーに接続するので、setup()内でそれを解除する
    sound.disconnect();

    // (4) p5.SoundFileオブジェクトの接続を解除した後、soundをコンボリューションリバーブ処理する
    // => soundの中身を書き換えるイメージ
    cVerb.process(sound);
    fft = new p5.FFT();
}

function draw() {
    background(30);
    if (isUserStarted) {
        drawSpectrum();
        // 使用中のIR名を左上に描画
        text(cVerb.impulses[0].name, 10, 20);
    }
}

function touchStarted() {
    // 最初のタッチ(クリック)時のみ実行
    if (!isUserStarted) {
        userStartAudio().then(() => {
            // soundを再生
            sound.play();
            // キャンバスのマウスイベント関数はここで設定
            canvas.mousePressed(togglePlay);
        });
        isUserStarted = true;
    }
}

// マウスプレスで再生/一時停止を切り替える
function togglePlay() {
    if (!sound.isPlaying()) {
        sound.play();
    }
    else {
        sound.pause();
    }
}

function drawSpectrum() {
    let spectrum = fft.analyze();
    for (let i = 0; i < spectrum.length; i++) {
        let x = map(i, 0, spectrum.length, 0, width);
        let h = -height + map(spectrum[i], 0, 255, height, 0);
        rect(x, height, width / spectrum.length, h);
    }
}

画面のクリックでサウンドが再生されます。

解説

コンボリューションリバーブをかける、処理対象のサウンドはこれです。

インパルスレスポンスには「ummonai/ewap」から得たBatcave.wavを使っています(「バットマン」が撮影されたバットケーブで採取した音だそうです)。

上記コードのコメントにも書いていますが、コンボリューションリバーブではサウンドファイルを読み込むので、それが確実に終わってから次に進むことがポイントです。そのためにはpreload()関数で必要な読み込みを終わらせておくのが一番です。

preload()関数で読み込みを行わなない場合、あるタイミングで読み込みが終わっていても、p5.Convolverオブジェクト自体の内部的な設定が終わっていない可能性があるので、たとえばdraw()関数でp5.Convolverオブジェクトのimpulses配列にアクセスするとエラーになります。

下図はサウンドファイルのプリロードから再生までの流れをまとめた図です。

IRを選択して適用する

複数のIRデータを使って、コンボリューションリバーブを行いたいときには、p5.ConvolverオブジェクトのaddImpulse()とtoggleImpulse()メソッドが利用できます。

次の例では、HTMLの<select>要素を使ってIRデータを選択し、コンボリューションリバーブを適用します。IRデータには「Impulse Responses」ページのものをお借りしています。

// コンボリューションリバーブ

let sound, cVerb, fft, canvas;
let isUserStarted = false;

const IRArray = ['Washing_machine.wav', 'POT1.wav', 'dog1a.mp3']
let IRSelector;
const selectedIndex = 0;
let selectedItem = IRArray[selectedIndex];

function preload() {
    // 複数のIRデータを使用する場合
    // preload()内で、createConvolver(path)で、
    // まず1つのIRデータを持つp5.Convolverオブジェクトを作成。
    // その後、addImpulse(path)で、1つずつ追加
    // forループを使ってIRを追加しても、うまく機能しない
    cVerb = createConvolver('assets/' + IRArray[0]);
    cVerb.addImpulse('assets/' + IRArray[1]);
    cVerb.addImpulse('assets/' + IRArray[2]);
    // ただしimpulses配列に追加される順番は必ずしも、addImpulse()で追加した順番に一致しない(不定)
    print(cVerb.impulses);
    print(cVerb);

    sound = loadSound('assets/drum_blues.mp3');
}

function setup() {
    canvas = createCanvas(710, 400);
    fill(0, 255, 40);
    noStroke();
    textSize(20);
    sound.disconnect();
    cVerb.process(sound);
    fft = new p5.FFT();
    // IR選択リスト
    IRSelector = createSelect();
    IRSelector.position(540, 30);
    for (i = 0; i < IRArray.length; i++) {
        IRSelector.option(IRArray[i]);
    }
    IRSelector.elt.selectedIndex = selectedIndex;
    IRSelector.changed(selectChanged);
}

function draw() {
    background(30);
    if (isUserStarted) {
        drawSpectrum();
        // 使用中のIR名を左上に描画
        text(selectedItem, 10, 20);
    }
}

function selectChanged(e) {
    selectedItem = IRSelector.value();
    cVerb.toggleImpulse(selectedItem);
}

function touchStarted() {
    if (!isUserStarted) {
        userStartAudio().then(() => {
            // soundを再生
            cVerb.toggleImpulse(selectedItem);
            sound.play();
            canvas.mousePressed(togglePlay);

        });
        isUserStarted = true;
    }
}

function togglePlay() {
    if (!sound.isPlaying()) {
        sound.play();
    }
    else {
        sound.pause();
    }
}

function drawSpectrum() {
    let spectrum = fft.analyze();
    for (let i = 0; i < spectrum.length; i++) {
        let x = map(i, 0, spectrum.length, 0, width);
        let h = -height + map(spectrum[i], 0, 255, height, 0);
        rect(x, height, width / spectrum.length, h);
    }
}

画面のクリックでサウンドがスタートします。右上のリスト選択でIRが変更できます。

IRデータのWashing_machine.wavは、洗濯機の中にマイクを置いて採取されたファイルで、POT1.wavは花瓶の中の残響データです。dog1a.mp3はただの犬の鳴き声ですが、これを適用すると、ドラムの音に犬の声が混じって聞こえます。


注意:
上記コードのコメントにも書いていますが、複数のIRデータの読み込みには、

  1. createConvolver(path)で、1つのIRデータを持つp5.Convolverオブジェクトを作成する
  2. addImpulse(path)で、1つずつ追加する

という手順を取るのが確実のようです。preload()関数内ではforループを使って一気にIRデータが追加できそうに思えますが、ファイルの読み込みには必ず時間がかかるためか、うまくいきません。

またp5.Convolverオブジェクトのimpulses配列に収まる順番は不定です。addImpulse()で追加した順に収まるように思えますが、そうではありません。順番は読み込むたびに変わることもあります。


リファレンスメモ

p5.Convolver

説明

p5.Convolverはp5.Reverbを拡張する。コンボリューション(畳み込み)と呼ばれる処理を通して、現実の物理空間の音をエミュレートできる。

コンボリューションは、オーディオ入力に”インパルスレスポンス”を乗算して、時間経過のともなう音の分散をシミュレーションする。インパルスレスポンスは、指定したオーディオファイルから生成される。また、残響空間で風船を破裂させ、そのエコーを録音するのも生成する方法の1つ。コンボリューションはサウンドの実験にも使用できる。

p5.Convolverをインスタンス化するには、createConvolution(path)関数を使う。pathにはインパルスレスポンスに使用するオーディオファイルを指定する。

p5.Effectを拡張する。

シンタックス

new p5.Convolver(path, [callback], [errorCallback])

パラメータ

path 文字列: サウンドファイルへのパス
callback 関数: 読み込みが成功したときに呼び出す関数(オプション)
errorCallback 関数: 読み込みが失敗したときに呼び出す関数。この関数は、エラーかうまくいかなかったことに関する情報を持つXMLHttpRequestオブジェクトを受け取る。

フィールド

convolverNode:
p5.Convolverは内部的に、Web Audio Convolver Nodeを使用する。
impulses:
複数のインパルスファイルを.addImpulse()メソッドを使って読み込んでいる場合には、インパルスファイルはこのimpulses配列にオブジェクトとして保持される。ファイルの切り替えには.toggleImpulse(id)メソッドを使用する。

メソッド

createConvolver()

説明

p5.Convolverを作成する。インパルスレスポンスの生成に使用するサウンドファイルへのパスを受け取る。

シンタックス

createConvolver(path, [callback], [errorCallback])

パラメータ

path 文字列: サウンドファイルへのパス
callback 関数: 読み込みが成功したときに呼び出す関数。コールバック関数には引数として、オブジェクトが渡される(オプション)
errorCallback 関数: 読み込みが成功しなかったときに呼び出す関数。コールバック関数には引数として、カスタムエラーが渡される(オプション)

戻り

p5.Convolver:

process()
ソースをリバーブに接続し、リバーブパラメータを割り当てる。

シンタックス

process(src)

パラメータ

src オブジェクト: p5.sound / サウンド出力を備えたWeb Audioオブジェクト

addImpulse()

説明

新しいインパルスレスポンスを読み込み、p5.Convolverに割り当てる。インパルスは.impulses配列に追加される。それまでのインパルスには、.toggleImpulse(id)メソッドでアクセスできる。

シンタックス

addImpulse(path, callback, errorCallback)

パラメータ

path String: 文字列: サウンドファイルへのパス
callback 関数: 関数(オプション)
errorCallback 関数: 関数(オプション)

toggleImpulse()

説明

複数のインパルスを.addImpulse()を使ってp5.Convolverに追加した場合には、このメソッドで.impulses配列内の項目を切り替えることができる。パラメータには、使用したいインパルスを識別する元のファイル名(文字列)か.impulses配列での位置(数値)を指定する。

.impulses配列内のオブジェクトには直接アクセスできる。各オブジェクトは、.audioBuffer(type: Web Audio AudioBuffer))と、元のファイル名に対応する文字列の.nameという2つの属性を持っている。

シンタックス

toggleImpulse(id)

パラメータ

id 文字列|数値: インパルスを元のファイル名(文字列)で識別するか、または.impulses配列内の位置で識別する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA