Dieser Artikel wurde maschinell übersetzt.

Der DirectX-Faktor

Eine Einführung in Audio Processing Objects

Charles Petzold

Charles PetzoldDie XAudio2-Komponente von DirectX ist viel mehr als nur eine Möglichkeit, Klänge und Musik in einer Windows-8-Anwendung wiederzugeben. Ich bin gekommen, um es eher als eine vielseitige Baukasten der Klangverarbeitung anzeigen.Durch den Einsatz von mehreren IXAudio2SourceVoice und IXAudio2SubmixVoice Instanzen können Programmierer Klänge in separaten Leitungen für individuelle Verarbeitung aufgeteilt und dann kombinieren sie in der letzten IXAudio2MasteringVoice zusammenführen.

Wie ich in der letzten Ausgabe dieser Kolumne gezeigt (msdn.microsoft.com/magazine/dn198248), XAudio2 ermöglicht audio Standardfiltern Quell- und Submix Stimmen zugewiesen werden.Diese Filter dämpfen Frequenzbereiche und folglich die harmonischen Inhalt und Klangfarbe der Töne.

Aber viel leistungsfähiger ist eine verallgemeinerte Funktion den Zugriff auf die eigentlichen audio-Streams durch die Stimmen.Sie können einfach diese audio-Stream zu analysieren oder ändern es.

Diese Einrichtung ist informell bekannt als ein "audio-Effekt." Formal geht es um die Schaffung von einem Audio Processing Objekt (APO), auch bekannt als plattformübergreifende APO oder XAPO, wenn sie mit Xbox 360-Anwendungen als auch unter Windows verwendet werden kann.

XAudio2 enthält zwei vordefinierte APOs für allgemeine Aufgaben.Die XAudio2CreateVolumeMeter-Funktion erstellt eine APO, mit der ein Programm, um dynamisch die Spitzenamplitude einen Audiostream in Abständen an die Anwendung bequem abrufen kann.Die XAudio­2CreateReverb-Funktion erstellt eine APO, für die eine Stimme auf der Grundlage von 23 Laufzeitparameter Echo oder Widerhall gilt — und ich meine "Runtime" in diesem Zusammenhang Parameter, die dynamisch geändert werden können, während die APO aktiv Audio verarbeitet wird.Darüber hinaus stellt eine Bibliothek, die als XAPOFX bekannt, Echo und Hall-Effekte, sowie ein Volume Limiter und einen 4-Band Equalizer.

Ein APO implementiert die IXAPO-Schnittstelle und ein APO mit Laufzeitparameter implementiert die IXAPOParameters-Schnittstelle.Aber ein einfacher Ansatz zum Erstellen Ihrer eigenen APOs umfasst Deriv­Ing aus der CXAPOBase und CXAPOParametersBase-Klassen, die diese Schnittstellen implementieren und viel von den Aufwand zu behandeln.

Ableitung von diese beiden Klassen ist die Strategie, die ich in diesem Artikel verwenden werde.Neben den anderen Header-Dateien und wichtige Bibliotheken, die ich in früheren Artikeln besprochen habe, benötigen Projekte, die APOs implementieren einen Verweis auf die xapobase.h-Headerdatei und der xapobase.lib-Import-Bibliothek.

Mithilfe von Audio-Bearbeitung-Objekten

Lassen Sie vor der Diskussion die Interna einer APO-Klasse mich Ihnen zeigen, wie die Auswirkungen auf XAudio2 Stimmen angewendet.Das SimpleEffectDemo-Projekt im herunterladbaren Code für diesen Artikel können Sie eine Datei aus Ihrer Musikbibliothek in Windows 8 laden und spielen.Es ist ähnlich dem Code, den ich, in früheren Artikeln gezeigt habe: Die Datei wird geladen und mit Media Foundation Classes decodiert und spielte mit XAudio2.SimpleEffectDemo schafft nur zwei XAudio2 Stimmen: eine Quelle-Stimme für die Generierung der Audio- und die erforderlichen mastering-Stimme, die die Audiodatei die Soundhardware Trichter.

SimpleEffectDemo enthält auch zwei nicht-parametrierte CXAPO­Basis Derivate namens OneSecondTremoloEffect (die ein Tremolo oder schwanken der Lautstärke, basierend auf einfachen Amplitudenmodulation gilt) und OneSecondEchoEffect.Abbildung 1 zeigt das Programm läuft mit einer geladenen Musikdatei.Jede der beiden Effekte aktivierten oder deaktivierten durch einen Tastschalter.Der Screenshot zeigt den Effekt "Echo" aktiviert, aber der Tremoloeffekt deaktiviert.

The SimpleEffectDemo Program with Two Audio Effects
Abbildung 1 das SimpleEffectDemo-Programm mit zwei Audio-Effekte

Abtsdorf bei Agnetheln können auf jede Art von XAudio2 Stimme angewendet werden.Bei Quelle Stimmen oder Submix Stimmen Einwirkungszeit die audio-Bearbeitung nach dem eingebauten Filter, dass Sie mit SetFilterParameters, aber vor auf Audio gesendet, andere Stimmen mit SetOutputFilterParameters angewendeten Filter festgelegt.

Ich habe beschlossen, diese beiden Effekte auf die mastering Stimme anwenden.Der Code instanziiert die Auswirkungen und fügt sie der mastering-Stimme ist dargestellt Abbildung 2.Jeder Effekt wird mit einer XAUDIO2_­EFFECT_DESCRIPTOR.Wenn es gibt mehr als einen Effekt (wie hier der Fall ist), Sie verwenden ein Array von Strukturen.Dieser Struktur (oder Array) wird dann durch eine XAUDIO2_EFFECT_CHAIN-Struktur verwiesen, die von allen XAudio2 Stimmen unterstützte SetEffectChain-Methode übergeben wird.Die Reihenfolge der Effekte-Fragen: Der Effekt "Echo" erhalten in diesem Fall einen audio-Stream, der bereits den Tremoloeffekt angewendet.

Abbildung 2 eine Mastering Stimme zwei Audioeffekte zuweisen

// Create tremolo effect
ComPtr<OneSecondTremoloEffect> pTremoloEffect = new OneSecondTremoloEffect();
// Create echo effect
ComPtr<OneSecondEchoEffect> pEchoEffect = new OneSecondEchoEffect();
// Reference those effects with an effect descriptor array
std::array<XAUDIO2_EFFECT_DESCRIPTOR, 2> effectDescriptors;
effectDescriptors[0].pEffect = pTremoloEffect.Get();
effectDescriptors[0].InitialState = tremoloToggle->IsChecked->Value;
effectDescriptors[0].OutputChannels = 2;
effectDescriptors[1].pEffect = pEchoEffect.Get();
effectDescriptors[1].InitialState = echoToggle->IsChecked->Value;
effectDescriptors[1].OutputChannels = 2;
// Reference that array with an effect chain
XAUDIO2_EFFECT_CHAIN effectChain;
effectChain.EffectCount = effectDescriptors.size();
effectChain.pEffectDescriptors = effectDescriptors.data();
hresult = pMasteringVoice->SetEffectChain(&effectChain);
if (FAILED(hresult))
  throw ref new COMException(hresult, "pMasteringVoice->SetEffectChain failure");

Nach dem Aufruf von SetEffectChain sollte die Effekt-Instanzen nicht weiter durch das Programm verwiesen werden. XAudio2 wurde bereits einen Verweis auf diese Instanzen hinzugefügt und das Programm kann seine eigenen Kopien freigeben, oder ComPtr, die für Sie tun kann. Von hier an, die Auswirkungen durch Indizes gekennzeichnet sind – in diesem Fall 0 für den Tremoloeffekt und 1 für den Effekt "Echo". Vielleicht möchten eine Enumeration für diese Konstanten zu verwenden.

Für beide Effekte, ich habe eingestellt, InitialState Bereich der XAUDIO2_­EFFECT_DESCRIPTOR auf einem ToggleButton geprüft Status. Dies regelt, ob die Wirkung zunächst aktiviert oder deaktiviert ist. Die Auswirkungen sind später aktiviert und deaktiviert, indem die Checked und Unchecked für die beiden ToggleButton-Steuerelemente, wie im Abbildung 3.

Abbildung 3 aktivieren und Deaktivieren von Audio-Effekte

void MainPage::OnTremoloToggleChecked(Object^ sender, 
  RoutedEventArgs^ args)
{
  EnableDisableEffect(safe_cast<ToggleButton^>(sender), 0);
}
void MainPage::OnEchoToggleChecked(Object^ sender, 
  RoutedEventArgs^ args)
{
  EnableDisableEffect(safe_cast<ToggleButton^>(sender), 1);
}
void MainPage::EnableDisableEffect(ToggleButton^ toggle, int index)
{
  HRESULT hresult = toggle->IsChecked->Value ?
pMasteringVoice->EnableEffect(index) :
  pMasteringVoice->DisableEffect(index);
  if (FAILED(hresult))
    throw ref new COMException(hresult, "pMasteringVoice->Enable/DisableEffect " +
       index.ToString());
}

Instanziierung und Initialisierung

OneSecondTremoloEffect und OneSecondEchoEffect ableiten von CXAPOBase. Vielleicht ist die erste Verwirrung, die Ihnen begegnen, wenn Sie von dieser Klasse ableiten Umgang mit der CXAPOBase-Konstruktor. Muss diesen Konstruktor einen Zeiger auf eine initialisierte XAPO_REGISTRATION_PROPERTIES-Struktur, aber wie initialisiert diese Struktur? C++ erfordert, dass ein Basisklassenkonstruktor abgeschlossen werden, bevor der Code in der abgeleiteten Klasse ausgeführt wird.

Dies ist ein bisschen wie eine Zwickmühle, die Sie lösen können, indem Sie definieren und Initialisieren der Struktur als eine globale Variable oder ein statisches Feld oder in einer statischen Methode. Ich ziehe den statischen Feld Ansatz in diesem Fall, wie Sie, in der OneSecondTremoloEffect.h-Headerdatei in sehen können Abbildung 4.

Abbildung 4 die OneSecondTremoloEffect.h-Headerdatei

#pragma once
class OneSecondTremoloEffect sealed : public CXAPOBase
{
private:
  static const XAPO_REGISTRATION_PROPERTIES RegistrationProps;
  WAVEFORMATEX waveFormat;
  int tremoloIndex;
public:
  OneSecondTremoloEffect() : CXAPOBase(&RegistrationProps),
                             tremoloIndex(0)
  {
  }
protected:
  virtual HRESULT __stdcall LockForProcess(
    UINT32 inpParamCount,
    const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pInpParams,
    UINT32 outParamCount,
    const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pOutParam) override;
  virtual void __stdcall Process(
    UINT32 inpParameterCount,
    const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
    UINT32 outParamCount,
    XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
    BOOL isEnabled) override;
};
class __declspec(uuid("6FB2EBA3-7DCB-4ADF-9335-686782C49911"))
                       OneSecondTremoloEffect;

Das RegistrationProperties-Feld wird in der Codedatei (kommen in Kürze) initialisiert. Ein Zeiger auf es wird an den CXAPOBase-Konstruktor übergeben. Sehr oft ein CXAPOBase-Derivat wird auch definieren Sie ein Feld vom Typ WAVEFORMATEX (wie diese) oder WAVEFORMATEXTENSIBLE (im allgemeinen) zum Speichern des Wellenform Format des audio-Streams auf der Durchreise der Wirkung.

Beachten Sie auch die __declspec ("Deklarationsbestimmung") am unteren Ende der Datei, die die OneSecondTremoloEffect-Klasse mit einer GUID zuordnet. Für Ihre eigenen Effekte-Klassen aus der GUID erstellen-Option im Menü "Extras" im Visual Studiokönnen Sie eine GUID generieren.

Ein CXAPOBase-Derivat muss die Process-Methode überschreiben und in der Regel überschreibt die LockForProcess-Methode. Die LockForProcess-Methode ermöglicht die APO auszuführenden Initialisierung basierend auf einem bestimmten audio Format, umfasst die Sampling-Rate, die Anzahl der Kanäle und die Sample-Daten-Typ. Die Prozess-Methode führt eigentlich die Analyse oder Änderung der Audiodaten.

Abbildung 5 zeigt diese beiden Methoden als auch die Initialisierung des Feldes RegistrationProperties. Beachten Sie, dass das erste Feld der XAPO_REGISTRATION_PROPERTIES die GUID identifiziert mit der Klasse ist.

Abbildung 5 die OneSecondTremoloEffect.cpp-Datei

#include "pch.h"
#include "OneSecondTremoloEffect.h"
const XAPO_REGISTRATION_PROPERTIES OneSecondTremoloEffect::RegistrationProps =
{
  __uuidof(OneSecondTremoloEffect),
  L"One-Second Tremolo Effect",
  L"Coded by Charles Petzold",
  1,      // Major version number
  0,      // Minor version number
  XAPOBASE_DEFAULT_FLAG | XAPO_FLAG_INPLACE_REQUIRED,
  1,      // Min input buffer count
  1,      // Max input buffer count
  1,      // Min output buffer count
  1       // Max output buffer count
};
HRESULT OneSecondTremoloEffect::LockForProcess(
  UINT32 inpParamCount,
  const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pInpParams,
  UINT32 outParamCount,
  const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pOutParams)
{
  waveFormat = * pInpParams[0].pFormat;
  return CXAPOBase::LockForProcess(inpParamCount, pInpParams,
                                   outParamCount, pOutParams);
}
void OneSecondTremoloEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
  BOOL isEnabled)
{
  XAPO_BUFFER_FLAGS flags = pInpParams[0].BufferFlags;
  int frameCount = pInpParams[0].ValidFrameCount;
  const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParams[0].pBuffer);
  int numChannels = waveFormat.
nChannels;
  switch(flags)
  {
  case XAPO_BUFFER_VALID:
    for (int frame = 0; frame < frameCount; frame++)
    {
      float sin = 1;
      if (isEnabled)
      {
        sin = fabs(DirectX::XMScalarSin(DirectX::XM_PI * tremoloIndex /
                                        waveFormat.
nSamplesPerSec));
        tremoloIndex = (tremoloIndex + 1) % waveFormat.
nSamplesPerSec;
      }
      for (int channel = 0; channel < numChannels; channel++)
      {
        int index = numChannels * frame + channel;
        pDst[index] = sin * pSrc[index];
      }
    }
    break;
  case XAPO_BUFFER_SILENT:
    break;
  }
  pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount;
  pOutParams[0].BufferFlags = pInpParams[0].BufferFlags;
}

Theoretisch können mehrere Eingabepuffern und mehrere Ausgabepuffer APOs beschäftigen. APOs sind jedoch derzeit auf einem Eingabepuffer und einen Ausgabepuffer beschränkt. Diese Einschränkung betrifft die letzten vier Felder der Struktur XAPO_REGISTRATION_PROPERTIES und die Parameter der Methode LockForProcess und Prozess. Für beide Methoden InpParamCount und OutParamCount sind immer gleich 1, und die Zeigerargumente zeigen immer nur eine Instanz der angegebenen Struktur.

Mit der Rate von 100 Aufrufe pro Sekunde die Process-Methode eine APO erhält einen Eingabepuffer des audio-Daten und bereitet einen Ausgabepuffer. Es ist möglich für APOs Formatkonvertierungen durchführen — z. B. die Sampling-Rate zwischen Eingabe und Ausgabe-Puffer oder die Anzahl der Kanäle oder den Datentyp der Proben zu ändern.

Diese Formatkonvertierungen können schwierig sein, so dass Sie in das sechste Feld der XAPO_REGISTRATION_PROPERTIES-Struktur angeben können, welche Konvertierungen du bist nicht bereit, zu implementieren. Die XAPOBASE_DEFAULT_FLAG zeigt, dass Sie keine Konvertierung von Sampling-Rate, die Anzahl der Kanäle, die Beispiel-Bit-Größen oder die Frame-Größe (die Anzahl der Proben in jedem Prozess-Aufruf) durchführen möchten.

Das Format der Audiodaten auf der Durchreise der APO steht aus den Parametern der LockForProcess-Überschreibung in Form einer WAVEFORMATEX-Standardstruktur. LockForProcess wird in der Regel nur einmal aufgerufen. Die meisten APOs die Sampling-Rate und die Anzahl der Kanäle wissen musst, und es empfiehlt sich, Ihre APO für alle möglichen Werte zu verallgemeinern.

Entscheidend ist auch der Datentyp der Proben selbst. In den meisten Fällen bei der Arbeit mit XAudio2 sind Sie den Umgang mit 16-Bit-Ganzzahlen oder 32-Bit-Gleitkommawerte werden Beispiele für die. Intern jedoch XAudio2 bevorzugt mit Gleitkommadaten (der C++-Float-Datentyp), und das ist, was Sie in Ihrem APOs sehen. Wenn Sie möchten, können Sie den Sample-Daten-Typ in der LockForProcess-Methode überprüfen. Allerdings ist es auch meine Erfahrung, dass das wFormatTag-Feld der WAVEFORMATEX-Struktur nicht WAVE_FORMAT_IEEE_FLOAT gleich ist, wie zu erwarten. Stattdessen ist es WAVE_FORMAT_EXTENSIBLE (der Wert 65534), was bedeutet, dass man es wirklich mit einer WAVEFORMATEXTENSIBLE-Struktur zu tun in dem Fall SubFormat-Feld den Datentyp KSDATAFORMAT_SUBTYPE_IEEE_FLOAT aufzeigt.

Wenn die LockForProcess-Methode ein Audioformat, die es nicht schaffen entdeckt, sollte es ein HRESULT, einen Fehler vielleicht E_NOTIMPL darauf "nicht implementiert." zurückgeben

Verarbeitung der Audio-Daten

Die LockForProcess-Methode kann verbringen, was Zeit braucht es für die Initialisierung, aber die Process-Methode wird im Audio-Verarbeitung-Thread ausgeführt und es muss nicht trödeln. Sie werden feststellen, dass für eine Abtastrate von 44.100 Hz, ValidFrameCount Bereich der Puffer Parameter gleich 441, darauf hinweist, dass Prozess 100 Mal pro Sekunde aufgerufen wird mit 10 ms von Audiodaten jedesmal. Für zwei-Kanal-Stereo enthält der Puffer 882 Float-Werte mit den Kanälen verschachtelt: linken Kanal, gefolgt vom rechten Kanal.

Das BufferFlags-Feld ist entweder XAPO_BUFFER_VALID oder XAPO_BUFFER_SILENT. Dieses Kennzeichen können Sie überspringen, Verarbeitung, wenn keine eigentlichen Audiodaten durchschreiten. Darüber hinaus gibt der IsEnabled-Parameter, wenn dieser Effekt durch die EnableEffect und DisableEffect Methoden aktiviert wurde, die Sie bereits gesehen haben.

Wenn der Puffer gültig ist, die OneSecondTremoloEffect APO-Schleifen durch die Rahmen und die Kanäle berechnet einen Index für den Puffer und Übertragungen schweben Schwankungsbereich der Quellpuffer (pSrc) in den Zielpuffer (pDst). Wenn der Effekt deaktiviert ist, wird ein Faktor von 1 auf die Quellwerte angewendet. Wenn es aktiviert ist, wird ein Sinuswert angewendet, berechnet mit der zippy XMScalarSin-Funktion aus der DirectX-Mathematik-Bibliothek.

Am Ende der Process-Methode werden die ValidFrameCount und BufferFlags für die Ausgabe-Parameter-Struktur auf die entsprechenden Werte der Eingabeparameter-Struktur festgelegt.

Obwohl der Code die Input- und Output-Puffer als separate Objekte behandelt, ist dies nicht der Fall. Zählen Sie die Flags sich in der XAPO_REGISTRATION_PROPERTIES-Struktur lassen XAPO_FLAG_INPLACE_SUPPORTED (die in der XAPOBASE_DEFAULT_FLAG enthalten ist) und XAPO_FLAG_INPLACE_REQUIRED. Das Wort "Inplace" bedeutet, dass die Zeiger auf die ein- und Ausgabe-Puffer — genannt pSrc und pDst in meinem Code — eigentlich gleich sind. Es gibt nur einen Puffer für ein- und Ausgabe verwendet. Sie sollten auf jeden Fall von dieser Tatsache bewusst sein, beim Schreiben von Code.

Aber aufpassen: Es ist meine Erfahrung, dass diese Flags entfernt, separaten Puffer tatsächlich vorhanden sind, aber nur den Eingabepuffer gilt für beide ein- und Ausgang.

Vorbei an Beispiele speichern

Der Tremoloeffekt muss lediglich Beispiele zu verändern. Ein Echoeffekt muss die vorhergehenden Beispiele zu speichern, da die Ausgabe für einen ein-Sekunden Echoeffekt der aktuellen Audio zuzüglich Audio von einer Sekunde vor.

Dies bedeutet, dass die OneSecondEchoEffect-Klasse muss weiterhin seinen eigenen Puffer von audio-Daten, die es während der LockForProcess-Methode als eine Std Typ Float und Größen definiert:

delayLength = waveFormat.
nSamplesPerSec;   

int numDelaySamples = waveFormat.
nChannels * 
                      waveFormat.
nSamplesPerSec;delayBuffer.resize(numDelaySamples);

Dieser Vektor DelayBuffer ist ausreichend, um eine Sekunde von Audiodaten zu halten, und es wird behandelt, als drehbare Puffer. Die LockForProcess-Methode initialisiert den Puffer mit 0-Werte und einen Index in diesen Puffer:

delayIndex = 0;

Abbildung 6 zeigt die Process-Methode in OneSecondEchoEffect. Da der Effekt "Echo" fortgesetzt werden muss nachdem die Quellaudiodaten abgeschlossen ist, können Sie nicht mehr überspringen, Verarbeitung, wenn das XAPO_BUFFER_SILENT-Flag gibt an, keine KanΣle. Stattdessen nach die sound-Datei abgeschlossen ist, muss die Ausgabe-Audio weiterhin das Ende des Echo spielen. Die Variable mit dem Namen Source ist daher entweder die Audio-Eingabe oder den Wert 0, abhängig von der Existenz des XAPO_BUFFER_SILENT-Flags. Die Hälfte dieser Quellwert mit halben in der Verzögerung-Puffer gespeicherten Wert kombiniert werden und das Ergebnis wird wieder in der Verzögerung-Puffer gespeichert. Zu jeder Zeit hören Sie halben aktuellen Audio, plus ein Viertel der Audiodaten von einer Sekunde vor plus ein Achtel des Tons von zwei Sekunden vor und so weiter. Passen Sie das Gleichgewicht für unterschiedliche Effekte, einschließlich ein Echo, das lauter mit jeder Wiederholung ruft.

Abbildung 6 die Process-Methode in OneSecondEchoEffect

void OneSecondEchoEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
  BOOL isEnabled)
{
  const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParams[0].pBuffer);
  int frameCount = pInpParams[0].ValidFrameCount;
  int numChannels = waveFormat.
nChannels;
  bool isSourceValid = pInpParams[0].BufferFlags == XAPO_BUFFER_VALID;
  for (int frame = 0; frame < frameCount; frame++)
  {
    for (int channel = 0; channel < numChannels; channel++)
    {
      // Get sample based on XAPO_BUFFER_VALID flag
      int index = numChannels * frame + channel;
      float source = isSourceValid ?
pSrc[index] : 0.0f;
      // Combine sample with contents of delay buffer and save back
      int delayBufferIndex = numChannels * delayIndex + channel;
      float echo = 0.5f * source + 0.5f * delayBuffer[delayBufferIndex];
      delayBuffer[delayBufferIndex] = echo;
      // Transfer to destination buffer
      pDst[index] = isEnabled ?
echo : source;
    }
    delayIndex = (delayIndex + 1) % delayLength;
  }
  pOutParams[0].BufferFlags = XAPO_BUFFER_VALID;
  pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount;
}

Versuchen Sie, wenn Sie die Länge des Puffers Verzögerung auf ein Zehntel einer Sekunde:

delayLength = waveFormat.
nSamplesPerSec / 10;

Jetzt bekommen Sie mehr für einen Hall-Effekt als eine deutliche Echo. Natürlich in eine echte APO sollten programmgesteuerten Kontrolle über diese verschiedenen Parameter (und andere auch), Sie deshalb der echte Echo/Hall APO durch eine XAUDIO2FX_REVERB_PARAMETERS Struktur mit 23 Feldern gesteuert wird.

Ein APO mit Parametern

Die meisten APOs ermöglichen ihr Verhalten mit Runtime-Parameter geändert werden, die programmgesteuert festgelegt werden können. Die SetEffectParameters-Methode ist für die Stimme-Klassen definiert und einer bestimmten APO mit einem Index verweist. Eine parametrierte APO ist ein wenig schwieriger zu implementieren, aber nicht viel.

In der letzten Ausgabe dieser Kolumne demonstriert habe ich, wie mithilfe den integrierten Bandpass-Filter implementiert in die XAudio2 Quell- und Submix Stimmen um einen 26-Band Graphic Equalizer, zu erstellen, in dem jedes Band ein Drittel Oktave des das gesamte Audiospektrum betrifft. Das GraphicEqualizer-Programm effektiv den Sound in 26 Teile für die Anwendung dieser Filter aufgeteilt und dann rekombiniert diese audio-Streams. Diese Technik könnte etwas ineffizient vorgekommen.

Es ist möglich, einen gesamte Grafik-Equalizer-Algorithmus in einen einzigen APO zu implementieren und um die gleiche Wirkung wie das vorherige Programm mit nur einer Quelle Stimme und einer mastering-Stimme zu erhalten. Dies ist, was ich getan habe, in das GraphicEqualizer2-Programm. Das neue Programm sieht gleich aus und klingt genauso wie das frühere Programm, aber intern ist es ganz anders.

Eines der Themen in der Übergabe von Parametern an ein APO ist die Threadsynchronisierung. Die Process-Methode wird in der Audio-Verarbeitung-Thread ausgeführt und Parameter sind wahrscheinlich vom UI-Thread festgelegt wird. Glücklicherweise führt die CXAPOParametersBase-Klasse diese Synchronisierung für Sie.

Als erstes müssen Sie eine Struktur für die Parameter zu definieren. Für den 26-Band-Equalizer-Effekt enthält die Struktur nur ein Feld, das ein Array von 26 Amplitude-Ebenen:

struct OneThirdOctaveEqualizerParameters
{
  std::array<float, 26> Amplitude;};

Im Rahmen des Programms werden die Mitglieder dieses Arrays aus den Dezibel-Werten der Regler berechnet.

Um CXAPOParametersBase zu initialisieren, müssen Sie ein Array von drei der Parameterstrukturen an dem Konstruktor übergeben. CXAPOParametersBase wird dieser Speicherblock verwendet, um die Threadsynchronisierung durchzuführen.

Wir haben wieder festgestellt, das Problem der Übergabe initialisierte Daten an ein Basisklassenkonstruktor aus einer abgeleiteten Klasse. Die Lösung, die ich diesmal wählte war Konstruktor den abgeleiteten Klasse zu definieren, wie geschützt und Instanziieren der Klasse aus eine öffentliche statische Methode mit dem Namen erstellen, die in dargestellt ist Abbildung 7.

Abbildung 7 der statischen Create-Methode für OneThirdOctaveEqualizerEffect

OneThirdOctaveEqualizerEffect * OneThirdOctaveEqualizerEffect::Create()
{
  // Create and initialize three effect parameters
  OneThirdOctaveEqualizerParameters * pParameterBlocks =
    new OneThirdOctaveEqualizerParameters[3];
  for (int i = 0; i < 3; i++)
    for (int band = 0; band < 26; band++)
      pParameterBlocks[i].Amplitude[band] = 1.0f;
  // Create the effect
  return new OneThirdOctaveEqualizerEffect(
    &RegistrationProps,
    (byte *) pParameterBlocks,
    sizeof(OneThirdOctaveEqualizerParameters),
    false);
}

Die digitale Biquad Filter implementiert in XAudio2 (die in diesem APO emuliert werden) betreffen die folgende Formel:

y = (b0·x + b1·x’ + b2·x’’ – a1·y’ – a2·y’’) / a0

In dieser Formel ist x die Eingabe Beispiel, x' ist der früheren input-Beispiel und X'' wird die Probe vorher. Die Ausgabe ist y, y' ist die vorherige Ausgabe und y'' ist die Ausgabe davor.

Ein Equalizer-Effekt somit zwei früheren input-Werte für jeden Kanal speichern muss, und zwei frühere Ausgabe der Werte für jeden Kanal und jedes Band.

Die sechs Konstanten in dieser Formel ist abhängig von den Typ des Filters; der cutoff-Frequenz (oder bei einem Bandpass-Filter, die Center-Frequenz) relativ die Sampling-Rate; und die Filtergüte Q. Für eine ein-Drittel-Oktaven-Grafik-Equalizer hat jeder Filter eine Q entspricht einer Bandbreite von 1 / 3 Oktave oder 4.318. Jeder Band hat einen eindeutigen Satz von Konstanten, die in der LockForProcess-Methode, durch den Code berechnet werden Abbildung 8.

Abbildung 8 die Berechnung der Equalizer-Filter-Konstanten

Q = 4.318f;       // One-third octave
static float frequencies[26] =
{
  20.0f, 25.0f, 31.5f, 40.0f, 50.0f, 63.0f, 80.0f, 100.0f, 125.0f,
  160.0f, 200.0f, 250.0f, 320.0f, 400.0f, 500.0f, 630.0f, 800.0f, 1000.0f,
  1250.0f, 1600.0f, 2000.0f, 2500.0f, 3150.0f, 4000.0f, 5000.0f, 6300.0f
};
for (int band = 0; band < 26; band++)
{
  float frequency = frequencies[band];
  float omega = 2 * 3.14159f * frequency / waveFormat.
nSamplesPerSec;
  float alpha = sin(omega) / (2 * Q);
  a0[band] = 1 + alpha;
  a1[band] = -2 * cos(omega);
  a2[band] = 1 - alpha;
  b0[band] = Q * alpha;       // == sin(omega) / 2;
  b1[band] = 0;
  b2[band] = -Q * alpha;      // == -sin(omega) / 2;
}

Während der Prozess-Methode die APO erhält einen Zeiger auf die aktuelle Parameter-Struktur mit einem Aufruf von CXAPOParametersBase::BeginProcess, in diesem Fall Gießen den Rückgabewert in eine Struktur des Typs zugeteilten­OctaveEqualizerParameters. Am Ende der Process-Methode gibt ein Aufruf von CXAPOParametersBase::EndProcess die Methode halten über die Parameter-Struktur. Die vollständige Prozess-Methode zeigt sich an Abbildung 9.

Abbildung 9 die Process-Methode in OneThirdOctaveEqualizerEffect

void OneThirdOctaveEqualizerEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParam,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParam,
  BOOL isEnabled)
{
  // Get effect parameters
  OneThirdOctaveEqualizerParameters * pEqualizerParams =
    (OneThirdOctaveEqualizerParameters *) CXAPOParametersBase::BeginProcess();
  // Get buffer pointers and other information
  const float * pSrc = static_cast<float *>(pInpParam[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParam[0].pBuffer);
  int frameCount = pInpParam[0].ValidFrameCount;
  int numChannels = waveFormat.
nChannels;
  switch(pInpParam[0].BufferFlags)
  {
  case XAPO_BUFFER_VALID:
    for (int frame = 0; frame < frameCount; frame++)
    {
      for (int channel = 0; channel < numChannels; channel++)
      {
        int index = numChannels * frame + channel;
        // Do very little if filter is disabled
        if (!isEnabled)
        {
          pDst[index] = pSrc[index];
          continue;
        }
        // Get previous inputs
        float x = pSrc[index];
        float xp = pxp[channel];
        float xpp = pxpp[channel];
        // Initialize accumulated value
        float accum = 0;
        for (int band = 0; band < 26; band++)
        {
          int bandIndex = numChannels * band + channel;
          // Get previous outputs
          float yp = pyp[bandIndex];
          float ypp = pypp[bandIndex];
          // Calculate filter output
          float y = (b0[band] * x + b1[band] * xp + b2[band] * xpp
                                  - a1[band] * yp - a2[band] * ypp) / a0[band];
          // Accumulate amplitude-adjusted filter output
          accum += y * pEqualizerParams->Amplitude[band];
          // Save previous output values
          pypp[bandIndex] = yp;
          pyp[bandIndex] = y;
        }
        // Save previous input values
        pxpp[channel] = xp;
        pxp[channel] = x;
        // Save final value adjusted for filter gain
        pDst[index] = accum / Q;
      }
    }
    break;
  case XAPO_BUFFER_SILENT:
    break;
  }
  // Set output parameters
  pOutParam[0].ValidFrameCount = pInpParam[0].ValidFrameCount;
  pOutParam[0].BufferFlags = pInpParam[0].BufferFlags;
  CXAPOParametersBase::EndProcess();
}

Ein Charakteristikum der Programmierung, die ich immer gern haben ist, dass Probleme oft mehrere Lösungen haben. Manchmal ist eine andere Lösung effizienter in irgendeiner Weise, und manchmal nicht. Ersetzen sicherlich 26 IXAudio2SubmixVoice-Instanzen mit einem einzigen APO ist eine radikale Veränderung. Aber wenn Sie, dass diese Änderung in beträchtlichem Ausmaß verbesserte Leistung reflektiert wird denken, Sie irren sich. Der Taskmanager von Windows 8 zeigt, dass die beiden GraphicEqualizer-Programme sind etwa gleichwertig, darauf hindeutet, dass einen Audiostream in 26 Submix Stimmen aufteilen so verrückt gar nicht sind.

Charles Petzold ist ein langjähriger Beitrag zum MSDN Magazine und Autor von "Programming Windows, 6th Edition" (O' Reilly Media, 2012), ein Buch über das Schreiben von Anwendungen für Windows 8. Seiner Website lautet charlespetzold.com.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Duncan McKay (Microsoft) und James McNellis (Microsoft)