静音化

焦点を高速に動かしたり, AM変調をかけたりすると可聴音ノイズが発生する. AUTD3にはこれを抑制するSilencerという機能がある. Silencerは, 振幅/位相データの急峻な変化を抑える, つまり, 位相/振幅変化を補間することで可聴音ノイズを抑制する.

Silencerはデフォルトで有効になっており, これを無効化するには以下の様にSilencer::disable()を送信すれば良い.

NOTE: 以下のコードを実行すると大きな騒音が発生するので, 実行する際は注意すること.

use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], Nop::new())?;
autd.send(Silencer::disable())?;

let center = autd.center() + Vector3::new(0.0, 0.0, 150. * mm);
let point_num = 20;
let radius = 30.0 * mm;
autd.send(FociSTM {
    foci: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            center + p
        })
        .collect::<Vec<_>>(),
    config: 50.0 * Hz,
})?;
Ok(())
}
#include <ranges>
#include <vector>
#include "autd3.hpp"
#include "autd3/link/nop.hpp"
using namespace std::ranges::views;
int main() {
auto autd = autd3::Controller::open({autd3::AUTD3{.pos =
autd3::Point3::origin(), .rot = autd3::Quaternion::Identity(),}},
autd3::link::Nop{});
autd.send(autd3::Silencer::disable());

const autd3::Point3 center = autd.center() + autd3::Vector3(0., 0., 150.);
const auto points_num = 20;
const auto radius = 30.0f;
std::vector<autd3::Point3> foci;
std::ranges::copy(iota(0) | take(points_num) | transform([&](auto i) {
                    const auto theta = 2.0f * autd3::pi *
                                       static_cast<float>(i) /
                                       static_cast<float>(points_num);
                    autd3::Point3 p =
                        center + radius * autd3::Vector3(std::cos(theta),
                                                         std::sin(theta), 0);
                    return p;
                  }),
                  std::back_inserter(foci));
autd.send(autd3::FociSTM(foci, 50.0f * autd3::Hz));
return 0;
}
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
using var autd = Controller.Open([new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity)], new Nop());
autd.Send(Silencer.Disable());

var center = autd.Center() + new Vector3(0.0f, 0.0f, 150.0f);
const int pointNum = 20;
const float radius = 30.0f;
new FociSTM(
    foci: Enumerable.Range(0, pointNum).Select(i =>
    {
        var theta = 2.0f * MathF.PI * i / pointNum;
        return center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0);
    }),
    config: 50.0f * Hz
);
import numpy as np
from pyautd3 import AUTD3, Controller, Hz, FociSTM, Silencer
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0])], Nop())
autd.send(Silencer.disable())

center = autd.center() + np.array([0.0, 0.0, 150.0])
point_num = 20
radius = 30.0
autd.send(
    FociSTM(
        foci=(
            center + radius * np.array([np.cos(theta), np.sin(theta), 0])
            for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
        ),
        config=50.0 * Hz,
    )
)

Silencer::disable()を送信する部分をコメントアウトして, ノイズの差を確認してみると良いだろう.

詳細はSilencerを参照されたいが, Silencerによってノイズは抑制されるものの, Silencerによって, ユーザが指定していない位相や振幅を出力する, あるいは, ユーザが指定した位相や振幅が出力されない可能性がある. 前者は本質的に避けられないが, 後者は避けられる場合もある. 具体的には, FociSTM/GainSTMModulationなど, 位相/振幅が変化する間隔がわかっている場合, Silencerによる位相/振幅補間がその間隔よりも短くあれば後者の問題は起こらない.

デフォルトでは, 上記のようなユーザが指定した位相や振幅が出力されない問題を回避するように設定されている. Silencerは振幅に対して, 位相に対しての時間をかけて補間を行い, これを超えるサンプリングレートに対してはエラーを返す. つまり, FociSTM/GainSTMのサンプリング周期は以上, Modulationのサンプリング周期は以上である必要がある.

したがって, 例えば以下のコードは, サンプリング周期がとなるので実行時エラーとなる.

use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], Nop::new())?;
autd.send(Silencer::default())?;

let center = autd.center() + Vector3::new(0.0, 0.0, 150. * mm);
let point_num = 40;
let radius = 30.0 * mm;
autd.send(FociSTM {
    foci: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            center + p
        })
        .collect::<Vec<_>>(),
    config: 50.0 * Hz,
})?;
Ok(())
}
#include <ranges>
#include <vector>
#include "autd3.hpp"
#include "autd3/link/nop.hpp"
using namespace std::ranges::views;
int main() {
auto autd = autd3::Controller::open({autd3::AUTD3{.pos =
autd3::Point3::origin(), .rot = autd3::Quaternion::Identity(),}},
autd3::link::Nop{});
autd.send(autd3::Silencer());

const autd3::Point3 center = autd.center() + autd3::Vector3(0., 0., 150.);
const auto points_num = 40;
const auto radius = 30.0f;
std::vector<autd3::Point3> foci;
std::ranges::copy(iota(0) | take(points_num) | transform([&](auto i) {
                    const auto theta = 2.0f * autd3::pi *
                                       static_cast<float>(i) /
                                       static_cast<float>(points_num);
                    autd3::Point3 p =
                        center + radius * autd3::Vector3(std::cos(theta),
                                                         std::sin(theta), 0);
                    return p;
                  }),
                  std::back_inserter(foci));
autd.send(autd3::FociSTM(foci, 50.0f * autd3::Hz));
return 0;
}
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
using var autd = Controller.Open([new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity)], new Nop());
autd.Send(new Silencer());

var center = autd.Center() + new Vector3(0.0f, 0.0f, 150.0f);
const int pointNum = 40;
const float radius = 30.0f;
new FociSTM(
    foci: Enumerable.Range(0, pointNum).Select(i =>
    {
        var theta = 2.0f * MathF.PI * i / pointNum;
        return center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0);
    }),
    config: 50.0f * Hz
);
import numpy as np
from pyautd3 import AUTD3, Controller, Hz, FociSTM, Silencer
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0])], Nop())
autd.send(Silencer())

center = autd.center() + np.array([0.0, 0.0, 150.0])
point_num = 40
radius = 30.0
autd.send(
    FociSTM(
        foci=(
            center + radius * np.array([np.cos(theta), np.sin(theta), 0])
            for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
        ),
        config=50.0 * Hz,
    )
)

このエラーを回避する方法は以下の通りである.

  1. サンプリングレートを下げる.
    • 最初の例のように, point_num = 20とすれば, サンプリング周期がとなるので, エラーは発生しない.
  2. Silencerの補間周期を短くする.
    • Silencerのデフォルトの補間間隔は振幅に対して, 位相に対してであるが, これは変更できる. 例えば, 以下の例だとに設定すればエラーにならなくなる.
    • ただし, 補間間隔を短くするとその分ノイズが大きくなるので注意.
    • なお, Silencer::disable()は補間間隔をに設定するのと等価である.
use autd3::prelude::*;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], Nop::new())?;
autd.send(Silencer {
    config: FixedCompletionTime {
        intensity: Duration::from_micros(500),
        phase: Duration::from_micros(500),
        strict: true,
    },
})?;

let center = autd.center() + Vector3::new(0.0, 0.0, 150. * mm);
let point_num = 40;
let radius = 30.0 * mm;
autd.send(FociSTM {
    foci: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            center + p
        })
        .collect::<Vec<_>>(),
    config: 50.0 * Hz,
})?;
Ok(())
}
#include <chrono>
#include <ranges>
#include <vector>
#include "autd3.hpp"
#include "autd3/link/nop.hpp"
using namespace std::ranges::views;
int main() {
auto autd = autd3::Controller::open({autd3::AUTD3{.pos =
autd3::Point3::origin(), .rot = autd3::Quaternion::Identity(),}},
autd3::link::Nop{});
autd.send(autd3::Silencer{
    autd3::FixedCompletionTime{.intensity = std::chrono::microseconds(500),
                               .phase = std::chrono::microseconds(500),
                               .strict = true}});

const autd3::Point3 center = autd.center() + autd3::Vector3(0., 0., 150.);
const auto points_num = 40;
const auto radius = 30.0f;
std::vector<autd3::Point3> foci;
std::ranges::copy(iota(0) | take(points_num) | transform([&](auto i) {
                    const auto theta = 2.0f * autd3::pi *
                                       static_cast<float>(i) /
                                       static_cast<float>(points_num);
                    autd3::Point3 p =
                        center + radius * autd3::Vector3(std::cos(theta),
                                                         std::sin(theta), 0);
                    return p;
                  }),
                  std::back_inserter(foci));
autd.send(autd3::FociSTM(foci, 50.0f * autd3::Hz));
return 0;
}
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
using var autd = Controller.Open([new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity)], new Nop());
autd.Send(new Silencer(
    config: new FixedCompletionTime
    {
        Intensity = Duration.FromMicros(500),
        Phase = Duration.FromMicros(500),
        Strict = true
    }
));

var center = autd.Center() + new Vector3(0.0f, 0.0f, 150.0f);
const int pointNum = 40;
const float radius = 30.0f;
new FociSTM(
    foci: Enumerable.Range(0, pointNum).Select(i =>
    {
        var theta = 2.0f * MathF.PI * i / pointNum;
        return center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0);
    }),
    config: 50.0f * Hz
);
import numpy as np
from pyautd3 import AUTD3, Controller, Hz, FociSTM, Silencer, FixedCompletionTime, Duration
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0])], Nop())
autd.send(
    Silencer(
        config=FixedCompletionTime(
            intensity=Duration.from_micros(500),
            phase=Duration.from_micros(500),
            strict=True,
        ),
    )
)

center = autd.center() + np.array([0.0, 0.0, 150.0])
point_num = 40
radius = 30.0
autd.send(
    FociSTM(
        foci=(
            center + radius * np.array([np.cos(theta), np.sin(theta), 0])
            for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
        ),
        config=50.0 * Hz,
    )
)
  1. FixedUpdateRateを使用する.
    • これを使用する場合, ユーザが指定した位相や振幅が出力されない (補間が完了する前に次のデータに移行してしまう) 可能性があるので注意.
  2. FixedCompletionTime::strictfalseに設定する.
    • これは単にエラーを無視するだけなので, 使用は推奨されない.