静音化
焦点を高速に動かしたり, AM 変調をかけたりすると可聴音ノイズが発生する. AUTD3 にはこれを抑制する Silencer という機能がある. Silencer は, 振幅 / 位相データの急峻な変化を抑える, つまり, 位相 / 振幅変化を補間することで可聴音ノイズを抑制する.
Silencerを設定するにはSetSilencerを使用する.
なお, Silencer はデフォルトで有効になっている.
無効化するにはSetSilencer::disable()を送信する.
Silencerの効果を実感するには以下のコードを実行してみるとよい.
let foci: Vec<ControlPoints> = (0..20) .map(|i| { let theta = 2.0 * PI * i as f32 / 20.0; let p = center + Vector3::new(radius * theta.cos(), radius * theta.sin(), 0.0); ControlPoints::new([ControlPoint::from(p)], Intensity::MAX) }) .collect();client.send(SetSilencer::disable()).await?;client.send(FociStm::new( SamplingConfig::from_freq(50.0 * Hz * foci.len() as f32)?, &foci, FociStmOption::default(),)).await?;foci = []for i in range(20): theta = 2.0 * np.pi * i / 20.0 p = center + np.array([radius * np.cos(theta), radius * np.sin(theta), 0.0]) foci.append(ControlPoints([ControlPoint(p, Phase.ZERO)], Intensity.MAX))await client.send(SetSilencer.disable())await client.send( FociStm( SamplingConfig(50.0 * Hz * len(foci)), foci, FociStmOption(), ))var foci = new ControlPoints[20];for (var i = 0; i < 20; i++){ var theta = 2.0f * MathF.PI * i / 20.0f; var p = center + new Vector3(radius * MathF.Cos(theta), radius * MathF.Sin(theta), 0.0f); foci[i] = new ControlPoints(new[] { (ControlPoint)p }, Intensity.Max);}await client.SendAsync(SetSilencer.Disable());await client.SendAsync(new FociStm(new SamplingConfig(50.0f * Hz * foci.Length), foci, new FociStmOption()));Silencer によってノイズは抑制されるものの, ユーザーが指定していない位相や振幅が出力される, あるいは, ユーザーが指定した位相や振幅が出力されない可能性がある.
前者は本質的に避けられないが, 後者は避けられる場合もある.
具体的には, FociStm / PatternStm や Modulation など, 位相 / 振幅が変化する間隔がわかっている場合, Silencer による補間がその間隔よりも早く終われば後者の問題は起こらない.
デフォルトでは, 上記のような「ユーザーが指定した位相や振幅が出力されない問題」を回避するように設定されている.
Silencer は振幅に対して 0.25 ms, 位相に対して 1 ms の時間をかけて補間を行う様に設定されており, これを超えるサンプリングレート (例えば40 kHz / 25 μs) に対してはエラーを返す.
つまり, FociStm / PatternStm のサンプリング周期は 1 ms 以上, Modulation のサンプリング周期は 0.25 ms 以上である必要がある (Modulationは位相には作用しない).
したがって, 例えば以下のコードはサンプリング周期が 1/50 Hz/40 = 0.5 ms となるので実行時エラーとなる.
let foci: Vec<ControlPoints> = (0..40) .map(|i| { let theta = 2.0 * PI * i as f32 / 40.0; let p = center + Vector3::new(radius * theta.cos(), radius * theta.sin(), 0.0); ControlPoints::new([ControlPoint::from(p)], Intensity::MAX) }) .collect();client.send(SetSilencer::default()).await?;client.send(FociStm::new( SamplingConfig::from_freq(50.0 * Hz * foci.len() as f32)?, &foci, FociStmOption::default(),)).await?;foci = []for i in range(40): theta = 2.0 * np.pi * i / 40.0 p = center + np.array([radius * np.cos(theta), radius * np.sin(theta), 0.0]) foci.append(ControlPoints([ControlPoint(p, Phase.ZERO)], Intensity.MAX))await client.send(SetSilencer())await client.send( FociStm( SamplingConfig(50.0 * Hz * len(foci)), foci, FociStmOption(), ))var foci = new ControlPoints[40];for (var i = 0; i < 40; i++){ var theta = 2.0f * MathF.PI * i / 40.0f; var p = center + new Vector3(radius * MathF.Cos(theta), radius * MathF.Sin(theta), 0.0f); foci[i] = new ControlPoints(new[] { (ControlPoint)p }, Intensity.Max);}await client.SendAsync(new SetSilencer());await client.SendAsync(new FociStm(new SamplingConfig(50.0f * Hz * foci.Length), foci, new FociStmOption()));このエラーを回避する方法は以下の通りである.
- サンプリングレートを下げる.
- 点数を 20 とすれば, サンプリング周期が 1/50 Hz/20 = 1 ms となるので, エラーは発生しない.
Silencerの補間周期を短くする.- デフォルトの補間は振幅に対して 0.25 ms, 位相に対して 1 ms で行う設定だが, これは変更できる. 例えば 0.5 ms に設定すればエラーにならなくなる.
- ただし, 短くするとその分ノイズが大きくなるので注意. なお, 補間完了時間を 25 µs に設定するのは Silencer の無効化と等価である.
let foci: Vec<ControlPoints> = (0..40) .map(|i| { let theta = 2.0 * PI * i as f32 / 40.0; let p = center + Vector3::new(radius * theta.cos(), radius * theta.sin(), 0.0); ControlPoints::new([ControlPoint::from(p)], Intensity::MAX) }) .collect();client.send(SetSilencer::new(FixedCompletionTime { intensity: Duration::from_micros(500), phase: Duration::from_micros(500), strict_mode: true,})).await?;let config = SamplingConfig::from_freq(50.0 * Hz * foci.len() as f32)?;client.send(FociStm::new(config, &foci, FociStmOption::default())).await?;foci = []for i in range(40): theta = 2.0 * np.pi * i / 40.0 p = center + np.array([radius * np.cos(theta), radius * np.sin(theta), 0.0]) foci.append(ControlPoints([ControlPoint(p, Phase.ZERO)], Intensity.MAX))await client.send( SetSilencer( FixedCompletionTime( intensity=Duration.from_micros(500), phase=Duration.from_micros(500), strict_mode=True, ) ))config = SamplingConfig(50.0 * Hz * len(foci))await client.send(FociStm(config, foci, FociStmOption()))var foci = new ControlPoints[40];for (var i = 0; i < 40; i++){ var theta = 2.0f * MathF.PI * i / 40.0f; var p = center + new Vector3(radius * MathF.Cos(theta), radius * MathF.Sin(theta), 0.0f); foci[i] = new ControlPoints(new[] { (ControlPoint)p }, Intensity.Max);}await client.SendAsync(new SetSilencer(new FixedCompletionTime{ Intensity = TimeSpan.FromMicroseconds(500), Phase = TimeSpan.FromMicroseconds(500), StrictMode = true,}));await client.SendAsync(new FociStm(new SamplingConfig(50.0f * Hz * foci.Length), foci, new FociStmOption()));FixedUpdateRateを使用する.- これを使用する場合, ユーザーが指定した位相や振幅が出力されない (補間が完了する前に次のデータに移行してしまう) 可能性があるので注意.
FixedCompletionTimeのstrict_modeをfalseに設定する.- これは単にエラーを無視するだけなので, 使用は推奨されない.