ビープ、ビープ、私は羊です

新しい翻訳記事では、さまざまなプラットフォーム間でビーパーを作成する方法について説明します。


オーディオI / Oは、プログラミングをしている多くのミュージシャンや音楽に夢中になっているプログラマーを怖がらせるトリッキーなトピックです。これを理解してみましょう!この記事では、最新の各OS(デスクトップバージョン)でサウンドがどのように機能するかについて説明します。



今日のケースでは、例として単純なビーパーを使用して検討します。あなたのPCボックスの中に不快なブーンという音を出す迷惑なことを覚えていますか?今ではただの思い出になりました!すべてのOSで同様のサウンドを再現するライブラリを作成することをお勧めします。



最終結果は、このリンクから入手できます



ウィンドウズ



Windowsは幸運です<utilapiset.h>にはすでにビープ音(周波数、持続時間)関数 があり ます。使用できます。 この機能には、 非常に長く複雑な歴史があります。 8245プログラマブルタイマーを使用してハードウェアビーパーを介してオーディオ信号を再生するために導入されました。ビーパーなしでリリースされるコンピューターが増えるにつれて、この機能は時間の経過とともに廃止されました。ただし、Windows 7では、サウンドカードAPIを使用してオーディオ信号を再生するように書き直されました。 ただし、この機能の明らかな単純さは、すべてのWindowsサウンドAPIの複雑さを隠します。MMEは1991年にリリースされました







..。優れたサポートがあるため、デフォルトでオーディオに使用されます。



MMEは再生待ち時間が長いことが知られており、ほとんどのオーディオアプリケーションにはおそらく適していません。また、2007年にWASAPIがリリースされました 特に排他モード(アプリケーションの実行中にユーザーがSpotifyやその他のアプリケーションを聞くことができないモード)で使用すると、待ち時間が短くなります。WASAPIはオーディオアプリケーションに適していますが、DirectXと対話するためのWASAPIラッパーであるDirectSoundに注意してください



不明な場合は、WASAPIを使用してください。



LINUX



オーディオは、LinuxAPIが他のプラットフォームと同じくらいクールな数少ない分野の1つです。まず第一に、コア自体の一部であるALSAについて言わなければなりません。



ALSAはハードウェアと直接通信します。アプリケーションをサウンドのみで動作させたい場合、ALSAは複雑さとパフォーマンスの間の適切な妥協点になる可能性があります。 Raspberry Piのシンセサイザーまたはサンプラーを構築している場合は、ALSAが適しています。



さらに、ALSAの上に構築されたオーディオ抽象化レイヤーであるPulseAudioがあります。さまざまなアプリケーションからのオーディオをルーティングし、オーディオストリームをミキシングして、重要なアプリケーションが遅延の問題に悩まされないようにします。 PulseAudioは、ALSAでは不可能な多くの機能(インターネット経由でのオーディオのルーティングなど)を提供しますが、ほとんどの音楽アプリケーションはそれを使用しません。



多くの人がJACKオーディオ接続キットを使用しています ..。 JACKはプロのミュージシャンのために作られました。リアルタイムの再生を処理しますが、PulseAudioは、YouTubeビデオの再生時に多少の遅延が発生する可能性のあるカジュアルユーザー向けに作成されました。 JACKは最小限の遅延でオーディオアプリケーションを接続しますが、それでもALSA上で実行されることに注意してください。したがって、アプリケーションが実行される唯一のオーディオアプリケーションになる場合(たとえば、古いRaspberry Piからドラムマシンを構築する場合)、ALSAは非常に重要です。使いやすく、パフォーマンスも向上します。



ALSAを使用してビーパー機能を作成することは、実際にはそれほど難しくありません。デフォルトのオーディオデバイスを開き、十分にサポートされているサンプルレートとサンプル形式を使用するように構成して、データの書き込みを開始する必要があります。以前の記事で説明したようにオーディオデータは鋸歯状の波にすることができます



int beep(int freq, int ms) {
  static void *pcm = NULL;
  if (pcm == NULL) {
    if (snd_pcm_open(&pcm, "default", 0, 0)) {
      return -1;
    }
    snd_pcm_set_params(pcm, 1, 3, 1, 8000, 1, 20000);
  }
  unsigned char buf[2400];
  long frames;
  long phase;
  for (int i = 0; i < ms / 50; i++) {
    snd_pcm_prepare(pcm);
    for (int j = 0; j < sizeof(buf); j++) {
      buf[j] = freq > 0 ? (255 * j * freq / 8000) : 0;
    }
    int r = snd_pcm_writei(pcm, buf, sizeof(buf));
    if (r < 0) {
      snd_pcm_recover(pcm, r, 0);
    }
  }
  return 0;
}
      
      





ここでは、同期APIを使用し、エラーをチェックせずに、関数を短くシンプルに保ちます。同期ブロッキングI / Oは、本格的なオーディオアプリケーションにはおそらく最適なオプションではありません。ありがたいことに、ALSAにはさまざまな転送方法と動作モードがあります: linkしかし、私たちの簡単な実験では、これで十分です。疑わしい場合は、ALSAを使用してください。他のオーディオアプリケーションと対話する必要がある場合は、JACKを使用してください。



マックOS



MacOSの場合、すべてが非常に単純ですが、基本的なものではありません。



MacOSには、デスクトップとiOSのオーディオ機能用のCoreAudioフレームワーク があります。CoreAudio自体は、レイテンシとパフォーマンスを最適化するためにOSと緊密に統合された低レベルのAPIです。CoreAudioを使用してオーディオを再生するには、AudioUnit(オーディオプラグイン)を作成する必要があります。AudioUnit APIは少し長いですが、理解しやすいです。新しいAudioUnitを作成する方法は次のとおりです。



AudioComponent output;
AudioUnit unit;
AudioComponentDescription descr;
AURenderCallbackStruct cb;
AudioStreamBasicDescription stream;

descr.componentType = kAudioUnitType_Output,
descr.componentSubType = kAudioUnitSubType_DefaultOutput,
descr.componentManufacturer = kAudioUnitManufacturer_Apple,

// Actual sound will be generated asynchronously in the callback tone_cb
cb.inputProc = tone_cb;

stream.mFormatID = kAudioFormatLinearPCM;
stream.mFormatFlags = 0;
stream.mSampleRate = 8000;
stream.mBitsPerChannel = 8;
stream.mChannelsPerFrame = 1;
stream.mFramesPerPacket = 1;
stream.mBytesPerFrame = 1;
stream.mBytesPerPacket = 1;

output = AudioComponentFindNext(NULL, &descr);
AudioComponentInstanceNew(output, &unit);
AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback,
										 kAudioUnitScope_Input, 0, &cb, sizeof(cb));
AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat,
										 kAudioUnitScope_Input, 0, &stream, sizeof(stream));
AudioUnitInitialize(unit);
AudioOutputUnitStart(unit);
      
      





このコードは、新しいAudioUnitを作成して開始するだけで、実際のサウンド生成はコールバックで非同期に行われます。



static OSStatus tone_cb(void *inRefCon,
                        AudioUnitRenderActionFlags *ioActionFlags,
                        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
                        UInt32 inNumberFrames, AudioBufferList *ioData) {
  unsigned int frame;
  unsigned char *buf = ioData->mBuffers[0].mData;
  unsigned long i = 0;
  for (i = 0; i < inNumberFrames; i++) {
    buf[i] = beep_freq > 0 ? (255 * theta * beep_freq / 8000) : 0;
    theta++;
    counter--;
  }
  return 0;
}
      
      





このコールバックは、ALSAで行ったのと同じ方法でオーディオを生成しますが、CoreAudioがオーディオバッファがほとんど空であり、新しいオーディオサンプルで満たす必要があると判断した場合に非同期で呼び出されます。



サウンド生成へのこの非同期アプローチは非常に一般的であり、ほとんどすべての最新のオーディオライブラリがそれをサポートしています。音楽アプリケーションを作成する場合は、非同期再生を念頭に置いて設計する必要があります。



疑わしい場合は、CoreAudioを使用してください。



複雑に聞こえますよね?



音楽アプリケーションを構築している場合は、WASAPI、ALSA、およびCoreAudioのオーディオバックエンドを実装することで同じパスをたどることができます。実際、それほど難しいことではありません。ビーパーの完全なソースコードを見ることができます 。これは、3つのプラットフォームすべてで約100行のコードです。



ただし、次のような優れたクロスプラットフォームライブラリがいくつかあります。



  • RtAudio + RtMidi(非常に使いやすく、1つの.cppおよび.hファイル);
  • PortAudio + PortMidi(Cで記述され、少し大きい)には、さまざまなバックエンドがあります。
  • SoundIOは、Zigの作成者による素晴らしい小さなライブラリです。


クロスプラットフォームオーディオアプリケーションにJUCEを使用することを好む人もいますが、それには制限があります。



上記のすべては困難な作業のように思えるかもしれませんが、多くの実装があり、それらのほとんどは優れています。だから、挑戦し続けてください!



この記事を楽しんでいただけたでしょうか。GithubTwitterでニュースやプロジェクトをフォローしたりRSS経由で購読したり できます



All Articles