Dieser Artikel wurde maschinell übersetzt.

Der DirectX-Faktor

Erstellen von Audio-Oszillatoren für Windows 8

Charles Petzold

Charles PetzoldIch habe elektronischer Musikinstrumente als Hobby seit ca. 35 Jahren tun.Ich begann in der späten 1970er Jahre Verdrahtung bis CMOS- und TTL-Chips und viel später ging die Software Route — zuerst mit der Multimedia-Erweiterungen für Windows im Jahre 1991, und seit kurzem mit der NAudio-Bibliothek für Windows Presentation Foundation (WPF) und die MediaStreamSource-Klasse in Silverlight und Windows Phone 7.Gerade im letzten Jahr widmete ich ein paar Raten von meinem Touch & Gehen Sie Spalte, um Anwendungen für Windows Phone, die Klang und Musik zu spielen.

Ich wäre wahrscheinlich zu dieser Zeit abgestumpft, und vielleicht nur ungern noch einen anderen Ton-Generation-API zu erkunden.Aber ich bin nicht, weil ich denke, dass Windows 8 ist wahrscheinlich die beste Windows-Plattform noch für die Herstellung von Musikinstrumenten.Windows 8 kombiniert eine leistungsstarke audio API — die XAudio2-Komponente von DirectX — mit Touchscreens auf handheld-Tabletten.Diese Kombination bietet viel Potenzial und ich bin besonders interessiert an der Erforschung, wie Note kann, als eine subtile und intime Schnittstelle für ein Musikinstrument, das vollständig in Software implementiert ausgenutzt werden.

Oszillatoren, Beispiele und Frequenz

Das Herzstück der Anlage Ton-Generation Musik-Synthesizer sind mehrere Oszillatoren, so genannt, weil sie eine mehr oder weniger regelmäßige oszillierende Wellenform zu einem bestimmten Frequenz und Volumen zu erzeugen.Im Generieren von Sounds für Musik klingen Oszillatoren, die gleichbleibende periodische Wellenformen in der Regel erstellen eher langweilig.Weitere interessante Oszillatoren zu integrieren, Vibrato, Tremolo oder Ändern von Klangfarben, und sie sind nur etwa regelmäßige.

Ein Programm, das Erstellen von Oszillatoren mit XAudio2 möchte beginnt durch Aufrufen der XAudio2Create-Funktion.Dies stellt ein Objekt, das die IXAudio2-Schnittstelle implementiert.Aus diesem Objekt können Sie CreateMasteringVoice nur einmal aufrufen, zum Abrufen einer Instanz von IXAudio2MasteringVoice, die als die wichtigsten audio-Mixer fungiert.Nur ein IXAudio2MasteringVoice ist jederzeit vorhanden.Im Gegensatz dazu im allgemeinen rufen CreateSourceVoice mehrere Male Sie mehrere Instanzen der IXAudio2SourceVoice-Schnittstelle zu erstellen.Jede dieser Instanzen IXAudio2SourceVoice kann als ein unabhängiger Oszillator fungieren.Kombinieren Sie mehrere Oszillatoren für ein Multiphone Instrument, ein Ensemble oder ein volles Orchester.

Ein IXAudio2SourceVoice-Objekt generiert Klang durch Erstellen und Einreichen von Puffern, enthält eine Folge von Zahlen, die eine Wellenform zu beschreiben.Diese Nummern werden oft als Beispiele genannt.Sie sind oft 16 Bit breit (dem Standard für Audio-CD), und sie kommen mit einer Konstanten Rate — gewöhnlich 44.100 Hz (auch der Standard für Audio-CD) oder so ungefähr.Diese Technik hat den ausgefallenen Namen Pulse Code Modulation oder PCM.

Obwohl diese Abfolge von Proben eine sehr komplexe Wellenform beschreiben kann, oft ein Synthesizer erzeugt einen relativ einfachen Stream Proben — am häufigsten eine quadratische Welle, ein Dreieck-Wave oder ein Sägezahn — mit einer Periodizität der Wellenform-Frequenz (als Tonhöhe wahrgenommen) und entspricht einer durchschnittlichen Amplitude, die als Band wahrgenommen wird.

Beispielsweise, wenn die Abtastrate 44.100 Hz ist, und jeder Zyklus von 100 Proben Werte, die schrittweise größer, dann kleinere, dann negative und 0 (null) zurück zu erhalten, ist die Häufigkeit der resultierende Ton 44.100 geteilt durch 100 oder 441 Hz — eine Frequenz in die Nähe der Wahrnehmung in den hörbaren Bereich für den Menschen.(Eine Frequenz von 440 Hz ist der A dreigestrichenen und Feinabstimmung serienmäßig verwendet wird.)

Die IXAudio2SourceVoice-Schnittstelle erbt eine Methode namens SetVolume aus IXAudio2Voice und definiert eine Methode von seinen eigenen benannte SetFrequencyRatio.Ich war durch diese letztere Methode, besonders fasziniert, denn es schien zu bieten eine Möglichkeit, einen Oszillator zu erstellen, der eine bestimmte periodische Wellenform mit einer Variablen Frequenz mit einem Minimum an Aufwand generiert.

Abbildung 1 zeigt den Großteil einer Klasse namens SawtoothOscillator1, die diese Technik implementiert.Obwohl ich vertraute 16-Bit-Ganzzahl-Beispiele verwenden, für die Definition der Wellenform, intern XAudio2 32bit unverankerten Punkt Proben.Bei leistungskritischen Anwendungen möchten Sie wahrscheinlich die Leistungsunterschiede zwischen der Ganzzahl und Gleitkommazahlen zu erkunden.

Abbildung 1 viel der SawtoothOscillator1-Klasse

SawtoothOscillator1::SawtoothOscillator1(IXAudio2* pXAudio2)
{
  // Create a source voice
  WAVEFORMATEX waveFormat;
  waveFormat.wFormatTag = WAVE_FORMAT_PCM;
  waveFormat.nChannels = 1;
  waveFormat.nSamplesPerSec = 44100;
  waveFormat.nAvgBytesPerSec = 44100 * 2;
  waveFormat.nBlockAlign = 2;
  waveFormat.wBitsPerSample = 16;
  waveFormat.cbSize = 0;
  HRESULT hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &waveFormat,
                                           0, XAUDIO2_MAX_FREQ_RATIO);
  if (FAILED(hr))
    throw ref new COMException(hr, "CreateSourceVoice failure");
  // Initialize the waveform buffer
  for (int sample = 0; sample < BUFFER_LENGTH; sample++)
    waveformBuffer[sample] =
      (short)(65535 * sample / BUFFER_LENGTH - 32768);
  // Submit the waveform buffer
  XAUDIO2_BUFFER buffer = {0};
  buffer.AudioBytes = 2 * BUFFER_LENGTH;
  buffer.pAudioData = (byte *)waveformBuffer;
  buffer.Flags = XAUDIO2_END_OF_STREAM;
  buffer.PlayBegin = 0;
  buffer.PlayLength = BUFFER_LENGTH;
  buffer.LoopBegin = 0;
  buffer.LoopLength = BUFFER_LENGTH;
  buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
  hr = pSourceVoice->SubmitSourceBuffer(&buffer);
  if (FAILED(hr))
    throw ref new COMException(hr, "SubmitSourceBuffer failure");
  // Start the voice playing
  pSourceVoice->Start();
}
void SawtoothOscillator1::SetFrequency(float freq)
{
  pSourceVoice->SetFrequencyRatio(freq / BASE_FREQ);
}
void SawtoothOscillator1::SetAmplitude(float amp)
{
  pSourceVoice->SetVolume(amp);
}

In der Header-Datei ist eine Grundfrequenz festgelegt, die sauber in die Abtastrate von 44.100 teilt. Daraus kann eine Puffergröße berechnet werden, ist die Länge eines einzigen Zyklus einer Wellenform der Frequenz:

static const int BASE_FREQ = 441;
static const int BUFFER_LENGTH = (44100 / BASE_FREQ);

Auch im Header ist Datei die Definition dieses Puffers als Feld:

short waveformBuffer[BUFFER_LENGTH];

Nach dem Erstellen des IXAudio2SourceVoice-Objekts, die Sawtooth­Oscillator1-Konstruktor füllt einen Puffer mit einem Zyklus einer Sägezahn Wellenform — eine einfache Wellenform, die von einer Amplitude von -32.768 bis eine Amplitude von 32.767 geht. Dieser Puffer wird die IXAudio2SourceVoice mit Anweisungen mitgeteilt, dass es für immer wiederholt werden sollen.

Ohne weiteren Code ist dies ein Oszillator, der für immer ein Sägezahn 441 Hz spielt. Das ist toll, aber es ist nicht sehr vielseitig. Um SawtoothOscillator1 ein bisschen mehr Flexibilität zu geben, habe ich auch eine SetFrequency-Methode enthalten. Das Argument, dies ist eine Frequenz, die die Klasse verwendet, um SetFrequencyRatio zu rufen. An SetFrequencyRatio übergebene Wert reichen von Float-Werte der XAUDIO2_MIN_FREQ_RATIO (oder 1/1,024.0) bis zu einem Höchstwert, der zuvor als Argument für CreateSourceVoice angegeben. Ich habe XAUDIO2_MAX_FREQ_RATIO (oder 1,024.0) für dieses Argument. Der Bereich des menschlichen Gehörs — etwa 20 Hz bis 20.000 Hz — gut innerhalb der Begrenzungen, die durch diese zwei Konstanten auf der Grundfrequenz von 441 angewendet definiert ist.

Puffer und Rückrufe

Ich muss gestehen, dass ich anfangs etwas skeptisch über die SetFrequencyRatio-Methode war. Digital erhöhen und verringern die Häufigkeit einer Wellenform ist keine triviale Aufgabe. Ich fühlte mich verpflichtet, die Ergebnisse mit einer Wellenform algorithmisch generiert zu vergleichen. Dies ist die treibende Kraft hinter dem OscillatorCompare-Projekt, das unter den herunterladbaren Code für diesen Artikel ist.

Das OscillatorCompare-Projekt umfasst die SawtoothOscillator1-Klasse, die ich bereits, sowie eine SawtoothOscillator2-Klasse beschrieben habe. Diese zweite-Klasse verfügt über eine SetFrequency-Methode, die steuert, wie die Klasse dynamisch Proben generiert, die die Wellenform zu definieren. Diese Wellenform ist kontinuierlich in einem Puffer gebaut und sehen Sie in Echtzeit auf das IXAudio2SourceVoice-Objekt als Antwort auf Rückrufe.

Eine Klasse kann Rückrufe von IXAudio2SourceVoice empfangen, indem Sie die IXAudio2VoiceCallback-Schnittstelle implementieren. Eine Instanz der Klasse, die diese Schnittstelle implementiert wird dann als Argument an die CreateSourceVoice-Methode übergeben. Die SawtoothOscillator2-Klasse implementiert diese Schnittstelle selbst und übergibt an CreateSourceVoice, auch, dass es sein wird nicht der SetFrequencyRatio Gebrauch machen, eine eigene Instanz:

pXAudio2->CreateSourceVoice(&pSourceVoice, &waveFormat,
        XAUDIO2_VOICE_NOPITCH, 1.0f,
        this);

Eine Klasse, die IXAudio2VoiceCallback implementiert können die OnBufferStart-Methode benachrichtigt werden, wenn es Zeit, einen neuen Puffer der Wellenformdaten einzureichen. Im Allgemeinen wenn Sie OnBufferStart verwenden, um Wellenformdaten aktuell zu halten, Sie wollen ein paar Puffer pflegen und wechseln sie. Dies ist wahrscheinlich die beste Lösung, wenn Sie Audiodaten aus einer anderen Quelle, z. B. eine audio-Datei erhalten sind. Das Ziel ist, nicht zulassen, den audio-Prozessor "ausgehungert werden." Halten einen Puffer vor der Verarbeitung verhindert, dass Hunger, aber kann es nicht garantieren.

Aber ich zog es in Richtung einer anderen Methode definiert durch IXAudio2VoiceCallback — OnVoiceProcessingPassStart. Es sei denn, Sie mit sehr kleinen Puffern arbeiten, im allgemeinen OnVoiceProcessingPassStart wird häufiger als OnBufferStart bezeichnet und gibt an, wann ein Stück von audio-Daten ist, verarbeitet werden und wie viele Bytes benötigt werden. In der Dokumentation XAudio2 ist diese Rückrufmethode als die mit die niedrigste Latenz gefördert, die oft sehr wünschenswert für interaktive elektronische Musikinstrumente ist. Sie wollen keine Verzögerung zwischen dem Drücken einer Taste und den Hinweis zu hören!

Die SawtoothOscillator2-Headerdatei definiert zwei Konstanten:

static const int BUFFER_LENGTH = 1024;
static const int WAVEFORM_LENGTH = 8192;

Die erste Konstante ist die Länge des Puffers, Wellenformdaten senden. Hier funktioniert es als zirkulärer Puffer. Aufrufe der Methode OnVoiceProcessingPassStart verlangen eine bestimmte Anzahl von Bytes. Die Methode reagiert indem setzen diese Bytes in den Puffer (ab wo es das letzte Mal aufgehört) und SubmitSourceBuffer nur für dieses aktualisierte Segment des Puffers. Sie möchten diesen Puffer groß genug sein, so dass Programmcode überschreiben ist nicht von den Teil des Puffers noch im Hintergrund gespielt wird.

Es stellt sich heraus, dass für eine Stimme mit einer Abtastrate von 44.100 Hz Aufrufe von OnVoiceProcessingPassStart immer noch 882 Bytes oder 441 16-Bit-Samples anfordern. Mit anderen Worten, ist OnVoiceProcessingPassStart mit einer Konstanten Rate von 100 Mal pro Sekunde oder alle 10 ms bezeichnet. Obwohl nicht dokumentiert, kann diese 10 ms Dauer als ein XAudio2-audio-Verarbeitung "Quanten" behandelt werden, und es ist eine gute Figur im Auge zu behalten. Daher kann nicht der Code, den Sie für diese Methode schreiben trödeln. Vermeiden Sie API-Aufrufe und Runtime Bibliothek aufrufen.

Die zweite Konstante ist die Länge eines einzigen Zyklus der gewünschte Wellenform. Es könnte sein, dass die Größe eines Arrays mit die Proben dieser Wellenform, aber in SawtoothOscillator2, die es nur für Berechnungen verwendet wird.

Die SetFrequency-Methode in SawtoothOscillator2 verwendet, die konstant eine Schrittweite Winkel zu berechnen, die auf die gewünschte Frequenz der Wellenform proportional ist:

angleIncrement = (int)(65536.0
                * WAVEFORM_LENGTH
                * freq / 44100.0);

Obwohl AngleIncrement eine ganze Zahl ist, wird es behandelt, als ob es vor- und Nachkommastellen Wörter umfasst. Dies ist der Wert jedes nachfolgenden Beispiel der Wellenform bestimmt.

Genommen Sie an, dass das Argument für SetFrequency 440 Hz ist. Die AngleIncrement wird als 5.356.535 berechnet. In hexadezimal ist das 0x51BBF7, die als Integer 0x51 behandelt wird (oder 81 dezimal), mit einen Bruchteil der 0xBBF7, 0.734 entspricht. Wenn der komplette Zyklus einer Wellenform 8.192 Bytes ist, und Sie verwenden nur den ganzzahlige Teil und 81 Bytes für jede Probe zu überspringen, ist die resultierende Frequenz ca. 436.05 Hz. (Das ist 44.100 Mal 81 geteilt durch 8.192.) Wenn Sie 82 Byte überspringen, ist die resultierende Frequenz 441.43 Hz. Sie wollen etwas zwischen diesen beiden Frequenzen.

Deshalb ein Bruchteil auch die Berechnung eingeben muss. Das ganze wäre wahrscheinlich einfacher in Gleitkomma und Gleitkomma-möglicherweise sogar schneller auf einigen modernen Prozessoren, aber Abbildung 2 zeigt einen weitere "traditionell" nur Ganzzahl-Ansatz. Beachten Sie, dass nur aktualisierte Abschnitt der zirkulären Puffer mit jedem Aufruf von SubmitSourceBuffer angegeben wird.

Abbildung 2 OnVoiceProcessingPassStart in SawtoothOscillator2

void _stdcall SawtoothOscillator2::OnVoiceProcessingPassStart(UINT32 bytesRequired)
{
  if (bytesRequired == 0)
      return;
  int startIndex = index;
  int endIndex = startIndex + bytesRequired / 2;
  if (endIndex <= BUFFER_LENGTH)
  {
    FillAndSubmit(startIndex, endIndex - startIndex);
  }
  else
  {
    FillAndSubmit(startIndex, BUFFER_LENGTH - startIndex);
    FillAndSubmit(0, endIndex % BUFFER_LENGTH);
  }
  index = (index + bytesRequired / 2) % BUFFER_LENGTH;
}
void SawtoothOscillator2::FillAndSubmit(int startIndex, int count)
{
  for (int i = startIndex; i < startIndex + count; i++)
  {
    pWaveformBuffer[i] = (short)(angle / WAVEFORM_LENGTH - 32768);
    angle = (angle + angleIncrement) % (WAVEFORM_LENGTH * 65536);
  }
  XAUDIO2_BUFFER buffer = {0};
  buffer.AudioBytes = 2 * BUFFER_LENGTH;
  buffer.pAudioData = (byte *)pWaveformBuffer;
  buffer.Flags = 0;
  buffer.PlayBegin = startIndex;
  buffer.PlayLength = count;
  HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
  if (FAILED(hr))
    throw ref new COMException(hr, "SubmitSourceBuffer");
}

SawtoothOscillator1 und SawtoothOscillator2 können im Vergleich-nebeneinander in das OscillatorCompare-Programm sein. MainPage verfügt über zwei Schieberegler ändern Sie die Frequenz und Lautstärke von jeder Oszillator. Das Schieberegler-Steuerelement für die Frequenz generiert nur Integer-Werte, die zwischen 24 und 132. Ich lieh mir diese Werte aus den Codes, die in dem Musical Instrument Digital Interface (MIDI) Norm zur Darstellung von Tonhöhen verwendet. Der Wert von 24 entspricht der C drei Oktaven unterhalb mittleren-C, das heißt C 1 C Oktave 1 in wissenschaftlichen Tonhöhe-Notation und hat eine Frequenz von ca. 32,7 Hz. Der Wert von 132 entspricht C 10, sechs Oktaven über dem mittleren C und einer Frequenz von ca. 16.744 Hz. Ein Tooltip-Konverter auf diese Schieberegler zeigt den aktuellen Wert sowohl in wissenschaftlichen Pitch Notation als auch in der Frequenz entspricht.

Ich konnte keinen Unterschied hören, wie ich mit diesen zwei Oszillatoren experimentierte. Ich brachte auch eine Software-Oszilloskop auf einem anderen Computer, die daraus resultierenden Wellenformen visuell zu prüfen, und ich konnte nicht entweder keinen Unterschied sehen. Dies zeigt mir, dass die SetFrequency­Ratio Methode ist intelligent, die sollte natürlich in einem System so anspruchsvoll wie DirectX erwarten wir implementiert. Ich vermute, dass Interpolationen auf resampled Wellenformdaten, verschieben die Häufigkeit durchgeführt werden. Wenn Sie nervös sind, können Sie festlegen der BASE_FREQ sehr gering — z. B. auf 20 Hz — und die Klasse generiert eine detaillierte Wellenform bestehend aus 2.205 Proben. Sie können auch mit einem hohen Wert experimentieren: 8.820 Hz verursacht z. B. eine Wellenform nur fünf Proben generiert werden! Sicher, dies hat einen etwas anderen Sound, da die interpolierte Wellenform irgendwo zwischen einem Sägezahn und ein Dreieck-Welle liegt, aber die resultierende Wellenform ist noch glatt ohne "Jaggies."

Das soll nicht bedeuten, dass alles bester Ordnung funktioniert. Mit beiden Sägezahn Oszillator erhalten die obersten paar Oktaven ziemlich chaotisch. Die Probenahme der Wellenform tendenziell hoch emittieren Niederfrequenz Obertöne einer Art ich habe gehört, vor und die ich plane, in Zukunft mehr vollständig zu untersuchen.

Halten Sie die Lautstärke niedrig!

Die SetVolume-Methode definiert, die von IXAudio2Voice und von IXAudio2SourceVoice geerbt ist dokumentiert als Gleitkommazahlen Multiplikator, die auf Werte im Bereich von-2 eingestellt werden kann ^ 24 bis 2 ^ 24, was 16.777.216 entspricht.

In der Realität jedoch sollten wahrscheinlich Sie die Lautstärke auf ein IXAudio2SourceVoice-Objekt auf einen Wert zwischen 0 und 1 zu halten. Die 0, die Stille Wert entspricht und 1 entspricht keine Verstärkung oder Dämpfung. Denken Sie daran, dass was auch immer die Quelle der Wellenform im Zusammenhang mit einer IXAudio2SourceVoice — ob es algorithmisch erzeugt wird oder hat seinen Ursprung in einer Audiodatei — es hat wahrscheinlich 16-Bit-Proben, die womöglich in der Nähe die minimale und maximale Werte von 32.768 und 32.767 kommen. Wenn Sie versuchen, diese Wellenformen mit einer Lautstärke, die größer als 1 zu verstärken, die Proben werden mehr als die Breite einer 16-Bit-Ganzzahl und werden an den minimalen und maximalen Werten abgeschnitten werden. Verzerrung und Rauschen führt.

Dies wird kritisch Wenn Sie kombinieren mehrere IXAudio2SourceVoice-Instanzen. Die Wellenformen dieser mehrere Instanzen werden durch hinzugefügte zusammen gemischt. Wenn Sie jede dieser Instanzen haben ein Volumen von 1 zulassen, könnte die Summe der Stimmen sehr gut Proben führen, die die 16-Bit-Ganzzahlen nicht übersteigen. Dies kann passieren, sporadisch — nur intermittierend Verzerrung führt — oder chronisch, was ein echtes Chaos.

Wenn Sie mehrere Instanzen von IXAudio2SourceVoice verwenden, die volle 16-Bit breiter Signalverläufe, setzt eine Sicherheitsmaßnahme die Lautstärke von jeder Oszillator 1 geteilt durch die Anzahl der Stimmen. Das garantiert, dass die Summe nie einen 16-Bit-Wert überschreitet. Eine allgemeine Lautstärke-Einstellung kann auch über die mastering Stimme erfolgen. Sie sollten auch in die XAudio2CreateVolumeMeter-Funktion zu suchen, können Sie ein audio-Verarbeitung-Objekt zu erstellen, die Monitor-Volume für Debugzwecke helfen können.

Unser erstes Musikinstrument

Es ist üblich für Musikinstrumente auf Tabletten, eine Klavier-Stil-Tastatur zu haben, aber ich habe vor kurzem eine Art von Schaltfläche Tastatur gefunden auf Akkordeons wie dem russischen Bajan (die ich bin vertraut mit der Arbeit des russischen Komponisten Sofia Gubaidulina) fasziniert. Da jede Taste eine Taste, anstatt einen langen Hebel ist, viele weitere Schlüssel können gepackt werden in den begrenzten Raum der Tablet-Bildschirm, wie in Abbildung 3.

The ChromaticButtonKeyboard Program
Abbildung 3 das ChromaticButtonKeyboard-Programm

Die unteren zwei Zeilen duplizieren Sie die Tasten auf die obersten beiden Zeilen und werden bereitgestellt, um die Griffweise gemeinsame Akkorde und melodischen Sequenzen zu erleichtern. Andernfalls jede Gruppe von 12 Tasten in den ersten drei Zeilen bieten alle Töne der Oktave, im allgemeinen aufsteigend von links nach rechts. Der gesamte Bereich hier ist vier Oktaven, das ist zweimal was mit einer Klaviertastatur gleicher Größe hätte man.

Eine echte Bayan hat eine weitere Oktave, aber ich könnte nicht es passen, ohne dass die Tasten zu klein. Der Source-Code können Sie Konstanten dieser zusätzlichen Oktave, auszuprobieren oder zu beseitigen eine weitere Oktave und die Tasten noch größer zu machen.

Denn ich kann nicht behaupten, dass dieses Programm klingt wie ein Instrument, das in der realen Welt existiert, ich einfach nannte es ChromaticButton­Tastatur. Die Schlüssel sind Instanzen eines benutzerdefinierten Steuerelements benannten Schlüssel, die von ContentControl abgeleitet, aber führt einige Touch-Verarbeitung zu erhalten eine IsPressed-Eigenschaft und ein IsPressedChanged-Ereignis zu generieren. Der Unterschied zwischen der Note in diesem Steuerelement behandeln und die Note, die Behandlung in einer normalen Schaltfläche (die hat auch eine IsPressed-Eigenschaft) ist bemerkbar, wenn Sie Ihren Finger über die Tastatur zu fegen: Eine standardmäßige Schaltfläche setzt die IsPressed-Eigenschaft auf true, nur, wenn die Finger-Presse auf der Oberfläche der Schaltfläche auftritt, während dieses benutzerdefinierte Steuerelement Schlüssel die Taste hält gedrückt werden, wenn ein Finger in von der Seite fegt.

Das Programm erstellt sechs Instanzen einer SawtoothOscillator-Klasse, die nahezu identisch mit der SawtoothOscillator1-Klasse aus dem früheren Projekt ist. Wenn Sie den Touchscreen dies unterstützt, können Sie sechs gleichzeitige Noten spielen. Es gibt keine Rückrufe und die Oszillatorfrequenz wird durch Aufrufen der SetFrequencyRatio-Methode gesteuert.

Die MainPage.xaml.h-Datei hat zu verfolgen welche Oszillatoren zur Verfügung stehen und welche Oszillatoren spielen, zwei standard-Collection-Objekte als Felder definiert:

std::vector<SawtoothOscillator *> availableOscillators;
std::map<int, SawtoothOscillator *> playingOscillators;

Ursprünglich hatte jedes Key-Objekt seine Tag-Eigenschaft der MIDI-Hinweis-Code eingestellt, die ich bereits erwähnt. Das ist, wie der IsPressedChanged-Ereignishandler bestimmt, welche Taste gedrückt wird, wird und welche Frequenz zu berechnen. MIDI-Code auch als Karte Schlüssel für die PlayingOscillators-Auflistung verwendet wurde. Es funktionierte gut, bis ich eine Notiz von den unteren beiden Reihen, die eine Note spielt bereits gespielt, dupliziert, was einen doppelten Schlüssel und eine Ausnahme geführt. Ich löste leicht dieses Problem durch die Einbeziehung von eines Wert in die Tag-Eigenschaft angibt, die Zeile, in der sich der Schlüssel befindet: Das Tag entspricht jetzt der MIDI-Hinweis-Code plus 1.000 Mal die Zeile Anzahl.

Abbildung 4 zeigt den IsPressedChanged-Ereignishandler für die Schlüssel-Instanzen. Wenn eine Taste gedrückt wird, ist ein Oszillator aus der AvailableOscillators-Auflistung entfernt, eine Frequenz und NULL Volumen und setzen in die PlayingOscillators-Auflistung. Wenn eine Taste losgelassen wird, wird der Oszillator erhält ein NULL Volumen und zog zurück nach AvailableOscillators.

Abbildung 4 der IsPressedChanged-Ereignishandler für die wichtigen Instanzen

void MainPage::OnKeyIsPressedChanged(Object^ sender, bool isPressed)
{
  Key^ key = dynamic_cast<Key^>(sender);
  int keyNum = (int)key->Tag;
  if (isPressed)
  {
    if (availableOscillators.size() > 0)
    {
      SawtoothOscillator* pOscillator = availableOscillators.back();
      availableOscillators.pop_back();
      double freq = 440 * pow(2, (keyNum % 1000 - 69) / 12.0);
      pOscillator->SetFrequency((float)freq);
      pOscillator->SetAmplitude(1.0f / NUM_OSCILLATORS);
      playingOscillators[keyNum] = pOscillator;
    }
  }
  else
  {
    SawtoothOscillator * pOscillator = playingOscillators[keyNum];
    if (pOscillator != nullptr)
    {
      pOscillator->SetAmplitude(0);
      availableOscillators.push_back(pOscillator);
      playingOscillators.erase(keyNum);
    }
  }
}

Das ist ungefähr so einfach wie eine mehrstimmige Instrument sein kann, und natürlich ist es falsch: Klingt sollte nicht ab und zu wie ein Schalter gedreht werden. Das Volumen sollte sich schnell gleiten, aber reibungslos, wenn eine Notiz startet, und fallen zurück, wenn es aufhört. Viele echte Instrumente haben auch eine Änderung der Lautstärke und Klangfarbe der Verlauf der Hinweis. Es gibt noch viel Raum für Verbesserungen.

Aber in Anbetracht der Einfachheit des Codes, es funktioniert überraschend gut und ist sehr empfänglich. Wenn Sie das Programm für den ARM-Prozessor kompilieren, können Sie es bereitstellen, auf die ARM-basierten Microsoft Surface und Spaziergang durch die wiegt des ungefesselte Tabletts in einen Arm während des Spielens auf es mit der anderen Hand, das ich sagen muss, ein bisschen wie ein Nervenkitzel.

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: Tom Mathews und Thomas Petchel