Klangerzeugung auf AVR-Mikrocontrollern mithilfe der Wavetable-Methode mit PolyphonieunterstĂŒtzung

AVR-Mikrocontroller sind ziemlich billig und weit verbreitet. Wahrscheinlich beginnt fast jeder eingebettete Entwickler mit ihnen. Und unter Amateuren regiert der Arduino den Ball, dessen Herz normalerweise der ATmega328p ist. Sicherlich fragten sich viele: Wie können Sie sie klingen lassen?

Wenn Sie sich vorhandene Projekte ansehen, gibt es verschiedene Arten von Projekten:

  1. Rechteckimpulsgeneratoren. Generieren Sie mit PWM oder Ruck-Pins in Interrupts. In jedem Fall wird ein sehr charakteristisches QuietschgerÀusch erhalten.
  2. Verwendung externer GerÀte wie eines MP3-Decoders.
  3. Verwenden von PWM zur Ausgabe von 8-Bit-Sound (manchmal 16-Bit) im PCM- oder ADPCM-Format. Da der Speicher in den Mikrocontrollern dafĂŒr eindeutig nicht ausreicht, verwenden sie normalerweise eine SD-Karte.
  4. Verwenden von PWM zur Erzeugung von Sound basierend auf Wave-Tabellen wie MIDI.

Letzterer Typ war fĂŒr mich besonders interessant, weil erfordert fast keine zusĂ€tzliche AusrĂŒstung. Ich prĂ€sentiere meine Option der Community. ZunĂ€chst eine kleine Demo:



Interessiert frage ich nach Katze.

Also die AusrĂŒstung:

  • ATmega8 oder ATmega328. Die Portierung auf andere ATmega ist nicht schwierig. Und sogar auf ATtiny, aber dazu spĂ€ter mehr;
  • Widerstand;
  • Kondensator;
  • Lautsprecher oder Kopfhörer;
  • ErnĂ€hrung;

Wie alles.

Eine einfache RC-Schaltung mit einem Lautsprecher ist an den Ausgang des Mikrocontrollers angeschlossen. Der Ausgang ist ein 8-Bit-Sound mit einer Abtastfrequenz von 31250 Hz. Bei einer Kristallfrequenz von 8 MHz können bis zu 5 KlangkanĂ€le + ein Rauschkanal fĂŒr Percussion erzeugt werden. In diesem Fall wird fast die gesamte Prozessorzeit verwendet, aber nach dem FĂŒllen des Puffers kann der Prozessor zusĂ€tzlich zum Sound mit etwas NĂŒtzlichem beschĂ€ftigt werden:


Dieses Beispiel passt vollstĂ€ndig in den ATmega8-Speicher, 5 KanĂ€le + Rauschen werden mit einer Kristallfrequenz von 8 MHz verarbeitet und es bleibt wenig Zeit fĂŒr Animationen auf dem Display.

In diesem Beispiel wollte ich auch zeigen, dass die Bibliothek nicht nur als normale Musikpostkarte verwendet werden kann, sondern auch, um Sound mit vorhandenen Projekten zu verbinden, beispielsweise fĂŒr Benachrichtigungen. Und selbst wenn Sie nur einen Soundkanal verwenden, können Benachrichtigungen viel interessanter sein als ein einfacher Hochtöner.

Und jetzt die Details ...

Wave-Tabellen oder Wavetables


Die Mathematik ist sehr einfach. Es gibt eine periodische Tonfunktion , zum Beispiel Ton (t) = sin (t * freq / (2 * Pi)) .

Es gibt auch eine Funktion zum Ändern der LautstĂ€rke des Grundtons im Laufe der Zeit, zum Beispiel LautstĂ€rke (t) = e ^ (- t) .

Im einfachsten Fall ist der Klang eines Instruments das Produkt dieser Funktionen Instrument (t) = Ton (t) * LautstÀrke (t) :

Auf dem Diagramm sieht alles ungefĂ€hr so ​​aus:



Als nÀchstes nehmen wir alle Instrumente, die zu einem bestimmten Zeitpunkt klingen, und fassen sie mit einigen LautstÀrkefaktoren (Pseudocode) zusammen:

for (i = 0; i < CHANNELS; i++) { value += channels[i].tone(t) * channels[i].volume(t) * channels[i].volume; } 

Das Volumen muss nur so ausgewĂ€hlt werden, dass kein Überlauf auftritt. Und das ist fast alles.

Der Rauschkanal funktioniert Àhnlich, jedoch anstelle einer Tonfunktion ein Pseudozufallssequenzgenerator.

Percussion ist eine Mischung aus Rauschkanal und Niederfrequenzwelle bei etwa 50-70 Hz.
NatĂŒrlich ist es schwierig, auf diese Weise einen hochwertigen Klang zu erzielen. Aber wir haben nur 8 Kilobyte fĂŒr alles. Hoffe das kann vergeben werden.

Was kann ich aus 8 Bits herausdrĂŒcken?


Anfangs habe ich mich auf ATmega8 konzentriert. Ohne externen Quarz arbeitet es mit einer Frequenz von 8 MHz und verfĂŒgt ĂŒber eine 8-Bit-PWM, die eine Basisabtastfrequenz von 8000000/256 = 31250 Hz ergibt. Ein Timer verwendet PWM zur Tonausgabe und verursacht eine Unterbrechung wĂ€hrend des Überlaufs, um den nĂ€chsten Wert an den PWM-Generator zu ĂŒbertragen. Dementsprechend haben wir 256 Taktzyklen zur Berechnung des Sample-Werts fĂŒr alles, einschließlich Interrupt-Overhead, Aktualisierung der Soundkanalparameter, Verfolgung der Zeit, zu der Sie die nĂ€chste Note spielen mĂŒssen usw.

Zur Optimierung werden wir die folgenden Tricks aktiv anwenden:

  • Da wir einen 8-Bit-Prozessor haben, werden wir versuchen, die Variablen gleich zu machen. Manchmal verwenden wir 16 Bit.
  • Berechnungen werden bedingt in hĂ€ufig und nicht so unterteilt. Die ersten mĂŒssen fĂŒr jede Probe berechnet werden, die zweite - viel seltener alle paar zehn / hundert Proben.
  • Um die Last gleichmĂ€ĂŸig ĂŒber die Zeit zu verteilen, verwenden wir einen Ringpuffer. In der Hauptschleife des Programms fĂŒllen wir den Puffer und subtrahieren ihn im Interrupt. Wenn alles in Ordnung ist, fĂŒllt sich der Puffer schneller als er sich leert und wir haben Zeit fĂŒr etwas anderes.
  • Der Code ist in C mit viel Inline geschrieben. Die Praxis zeigt, dass es so viel schneller geht.
  • Alles, was der PrĂ€prozessor berechnen kann, insbesondere unter Beteiligung der Division, wird vom PrĂ€prozessor erledigt.

Teilen Sie zuerst die Zeit in Intervalle von 4 Millisekunden (ich habe sie Ticks genannt). Bei einer Abtastfrequenz von 31250 Hz erhalten wir 125 Abtastwerte pro Tick. Die Tatsache, dass jede Probe gelesen werden muss, muss bei jeder Probe gezĂ€hlt werden, und der Rest - einmal pro Tick oder weniger. Zum Beispiel ist die LautstĂ€rke des Instruments innerhalb eines Ticks konstant: Instrument (t) = Ton (t) * currentVolume ; und currentVolume selbst werden einmal pro Tick unter BerĂŒcksichtigung der LautstĂ€rke (t) und der ausgewĂ€hlten LautstĂ€rke des Soundkanals neu berechnet.

Eine Tick-Dauer von 4 ms wurde basierend auf einer einfachen 8-Bit-Grenze gewÀhlt: Mit einem 8-Bit-AbtastzÀhler können Sie mit einer Abtastfrequenz von bis zu 64 kHz arbeiten, mit einem 8-Bit-Tick-ZÀhler können wir die Zeit bis zu 1 Sekunde messen.

Etwas Code


Der Kanal selbst wird durch diese Struktur beschrieben:

 typedef struct { // Info about wave const int8_t* waveForm; // Wave table array uint16_t waveSample; // High byte is an index in waveForm array uint16_t waveStep; // Frequency, how waveSample is changed in time // Info about volume envelope const uint8_t* volumeForm; // Array of volume change in time uint8_t volumeFormLength; // Length of volumeForm uint8_t volumeTicksPerSample; // How many ticks should pass before index of volumeForm is changed uint8_t volumeTicksCounter; // Counter for volumeTicksPerSample // Info about volume uint8_t currentVolume; // Precalculated volume for current tick uint8_t instrumentVolume; // Volume of channel } waveChannel; 

Bedingt sind die Daten hier in 3 Teile unterteilt:

  1. Informationen zu Wellenform, Phase, Frequenz.

    WaveForm: Informationen zur Tone (t) -Funktion: Ein Verweis auf ein Array mit einer LĂ€nge von 256 Bytes. Legt den Ton und den Instrumentenklang fest.

    WaveSample: High Byte gibt den aktuellen Index des WaveForm-Arrays an.

    WaveStep: Legt die Frequenz fest, um die WaveSample beim ZÀhlen des nÀchsten Samples erhöht wird.

    Jede Probe wird ungefĂ€hr so ​​betrachtet:

     int8_t tone = channelData.waveForm[channelData.waveSample >> 8]; channelData.waveSample += channelaData.waveStep; return tone * channelData.currentVolume; 

  2. Volumeninformationen. Legt die Funktion zum Ändern der LautstĂ€rke im Laufe der Zeit fest. Da sich die LautstĂ€rke nicht so oft Ă€ndert, können Sie sie einmal pro Tick seltener nachzĂ€hlen. Dies geschieht folgendermaßen:

     if ((channel->volumeTicksCounter--) == 0 && channel->volumeFormLength > 0) { channel->volumeTicksCounter = channel->volumeTicksPerSample; channel->volumeFormLength--; channel->volumeForm++; } channel->currentVolume = channel->volumeForm * channel->instrumentVolume >> 8; 

  3. Legt die LautstÀrke des Kanals und die berechnete aktuelle LautstÀrke fest.

Bitte beachten Sie: Die Wellenform ist acht Bit, die LautstÀrke ist ebenfalls acht Bit und das Ergebnis ist 16 Bit. Mit einem leichten Leistungsverlust können Sie den Sound (fast) 16 Bit erzeugen.

Im Kampf um ProduktivitĂ€t musste ich auf schwarze Magie zurĂŒckgreifen.

Beispiel Nummer 1. So berechnen Sie die LautstÀrke der KanÀle neu:

 if ((tickSampleCounter--) == 0) { //    tickSampleCounter = SAMPLES_PER_TICK – 1; //   - } // volume recalculation should no be done so often for all channels if (tickSampleCounter < CHANNELS_SIZE) { recalculateVolume(channels[tickSampleCounter]); } 

Somit zÀhlen alle KanÀle die LautstÀrke einmal pro Tick, jedoch nicht gleichzeitig.

Beispiel Nummer 2. Das Speichern von Kanalinformationen in einer statischen Struktur ist billiger als in einem Array. Ohne auf Details der Implementierung von wavechannel.h einzugehen, möchte ich sagen, dass diese Datei mehrmals (entsprechend der Anzahl der KanĂ€le) mit unterschiedlichen PrĂ€prozessoranweisungen in den Code eingefĂŒgt wird. Jede EinfĂŒgung erstellt neue globale Variablen und eine neue Kanalberechnungsfunktion, die dann in den Hauptcode integriert wird:

 #if CHANNELS_SIZE >= 1 val += channel0NextSample(); #endif #if CHANNELS_SIZE >= 2 val += channel1NextSample(); #endif 
 

Beispiel Nummer 3. Wenn wir etwas spĂ€ter mit dem Spielen der nĂ€chsten Note beginnen, wird es niemand bemerken. Stellen wir uns die Situation vor: Wir haben den Prozessor mit etwas aufgenommen und wĂ€hrend dieser Zeit war der Puffer fast leer. Dann fangen wir an, es zu fĂŒllen und plötzlich stellt sich heraus, dass eine neue Maßnahme kommt: Wir mĂŒssen die aktuellen Notizen aktualisieren, aus dem Array lesen, was als nĂ€chstes kommt usw. Wenn wir keine Zeit haben, kommt es zu charakteristischen Stottern. Es ist viel besser, den Puffer ein wenig mit alten Daten zu fĂŒllen und erst dann den Status der KanĂ€le zu aktualisieren.

 while ((samplesToWrite) > 4) { //          fillBuffer(SAMPLES_PER_TICK); //     -  updateMusicData(); //    } 

Auf eine gute Weise wĂ€re es notwendig, den Puffer nach der Schleife wieder aufzufĂŒllen, aber da wir fast alles inline haben, ist die CodegrĂ¶ĂŸe merklich aufgeblasen.

Musik


Ein 8-Bit-Tick-ZĂ€hler wird verwendet. Wenn Null erreicht ist, beginnt ein neuer Takt, dem ZĂ€hler wird die Dauer des Takts zugewiesen (in Ticks), etwas spĂ€ter wird das Array von Musikbefehlen ĂŒberprĂŒft.

Die Musikdaten werden in einem Array von Bytes gespeichert. Es ist ungefĂ€hr so ​​geschrieben:

 const uint8_t demoSample[] PROGMEM = { DATA_TEMPO(160), // Set beats per minute DATA_INSTRUMENT(0, 1), // Assign instrument 1 (see setSample) to channel 0 DATA_INSTRUMENT(1, 1), // Assign instrument 1 (see setSample) to channel 1 DATA_VOLUME(0, 128), // Set volume 128 to channel 0 DATA_VOLUME(1, 128), // Set volume 128 to channel 1 DATA_PLAY(0, NOTE_A4, 1), // Play note A4 on channel 0 and wait 1 beat DATA_PLAY(1, NOTE_A3, 1), // Play note A3 on channel 1 and wait 1 beat DATA_WAIT(63), // Wait 63 beats DATA_END() // End of data stream }; 

Alles, was mit DATA_ beginnt, sind PrÀprozessormakros, die die Parameter auf die erforderliche Anzahl von Datenbytes erweitern.

Beispielsweise wird der Befehl DATA_PLAY auf 2 Bytes erweitert, in denen gespeichert sind: die Befehlsmarkierung (1 Bit), die Pause vor dem nĂ€chsten Befehl (3 Bit), die Kanalnummer, auf der die Note gespielt werden soll (4 Bit), Informationen ĂŒber die Note (8 Bit). Die wichtigste EinschrĂ€nkung besteht darin, dass dieser Befehl nicht fĂŒr lange Pausen mit maximal 7 Takten verwendet werden kann. Wenn Sie mehr benötigen, mĂŒssen Sie den Befehl DATA_WAIT verwenden (bis zu 63 Kennzahlen). Leider habe ich nicht gefunden, ob das Makro je nach Makroparameter auf eine andere Anzahl von Bytes des Arrays erweitert werden kann. Und selbst Warnung, ich weiß nicht, wie ich anzeigen soll. Vielleicht sagst du es mir.

Verwenden Sie


Im Demo-Verzeichnis gibt es mehrere Beispiele fĂŒr verschiedene Mikrocontroller. Aber kurz gesagt, hier ist ein StĂŒck aus der Readme-Datei, ich habe wirklich nichts hinzuzufĂŒgen:

 #include "../../microsound/devices/atmega8timer1.h" #include "../../microsound/micromusic.h" // Make some settings #define CHANNELS_SIZE 5 #define SAMPLES_SIZE 16 #define USE_NOISE_CHANNEL initMusic(); // Init music data and sound control sei(); // Enable interrupts, silence sound should be generated setSample(0, instrument1); // Use instrument1 as sample 0 setSample(1, instrument2); // Init all other instruments
 playMusic(mySong); // Start playing music at pointer mySong while (!isMusicStopped) { fillMusicBuffer(); // Fill music buffer in loop // Do some other stuff } 

Wenn Sie neben Musik noch etwas anderes machen möchten, können Sie den Puffer mit BUFFER_SIZE vergrĂ¶ĂŸern. Die PuffergrĂ¶ĂŸe sollte 2 ^ n betragen, aber leider tritt bei einer GrĂ¶ĂŸe von 256 eine Leistungsverschlechterung auf. Bis ich es herausgefunden habe.

Um die ProduktivitÀt zu steigern, können Sie die Frequenz mit externem Quarz erhöhen, die Anzahl der KanÀle verringern und die Abtastfrequenz verringern. Mit dem letzten Trick können Sie eine lineare Interpolation verwenden, die den Abfall der KlangqualitÀt etwas kompensiert.

Eine Verzögerung wird nicht empfohlen, da CPU-Zeit wird verschwendet. Stattdessen ist eine eigene Methode in der Datei microsound / delay.h implementiert, die zusĂ€tzlich zur Pause selbst am FĂŒllen des Puffers beteiligt ist. Diese Methode funktioniert bei kurzen Pausen möglicherweise nicht sehr genau, bei langen Pausen jedoch mehr oder weniger vernĂŒnftig.

Mach deine eigene Musik


Wenn Sie Befehle manuell schreiben, mĂŒssen Sie in der Lage sein, zuzuhören, was passiert. Das Eingießen jeder Änderung in den Mikrocontroller ist nicht bequem, insbesondere wenn es eine Alternative gibt.

Es gibt einen ziemlich lustigen Dienst wavepot.com - einen Online-JavaScript-Editor, in dem Sie von Zeit zu Zeit die Funktion des Tonsignals einstellen mĂŒssen, und dieses Signal wird an die Soundkarte ausgegeben. Das einfachste Beispiel:

 function dsp(t) { return 0.1 * Math.sin(2 * Math.PI * t * 440); } 

Ich habe die Engine auf JavaScript portiert, sie befindet sich in demos / wavepot.js . Der Inhalt der Datei muss in den Editor Wavepot.com eingefĂŒgt werden und Sie können Experimente durchfĂŒhren. Wir schreiben unsere Daten in das SoundData-Array, hören zu, vergessen nicht zu speichern.

Wir sollten auch die Variable simulate8bits erwÀhnen. Sie simuliert laut Namen einen Acht-Bit-Sound. Wenn plötzlich das Schlagzeug summt und in gedÀmpften Instrumenten GerÀusche mit leisem Klang auftreten, dann ist dies eine Verzerrung eines Acht-Bit-Klangs. Sie können versuchen, diese Option zu deaktivieren und auf den Unterschied zu achten. Das Problem ist viel weniger auffÀllig, wenn die Musik keine Stille enthÀlt.

Verbindung


In einer einfachen Version sieht die Schaltung folgendermaßen aus:

 +5V ^ MCU | +-------+ +---+VC | R1 | Pin+---/\/\--+-----> OUT | | | +---+GN | === C1 | +-------+ | | | --- Grnd --- Grnd 

Der Ausgangspin hĂ€ngt vom Mikrocontroller ab. Der Widerstand R1 und der Kondensator C1 mĂŒssen basierend auf der Last, dem VerstĂ€rker (falls vorhanden) usw. ausgewĂ€hlt werden. Ich bin kein Elektronikingenieur und gebe keine Formeln an. Sie lassen sich zusammen mit Online-Taschenrechnern leicht googeln.

Ich habe R1 = 130 Ohm, C1 = 0,33 uF. An den Ausgang schließe ich gewöhnliche chinesische Kopfhörer an.

Was war mit 16-Bit-Sound?


Wie ich oben sagte, erhalten wir beim Multiplizieren von zwei Acht-Bit-Zahlen (Frequenz und LautstÀrke) eine 16-Bit-Zahl. Sie können es nicht auf acht Bits runden, sondern beide Bytes in 2 PWM-KanÀlen ausgeben. Wenn Sie diese beiden KanÀle im VerhÀltnis 1/256 mischen, erhalten Sie 16-Bit-Sound. Der Unterschied zum Acht-Bit ist besonders leicht zu erkennen, wenn Sounds und Drums in Momenten, in denen nur ein Instrument erklingt, sanft verblassen.

16-Bit-Ausgangsverbindung:

 +5V ^ MCU | +-------+ +---+VCC | R1 | PinH+---/\/\--+-----> OUT | | | | | R2 | | PinL+---/\/\--+ +---+GND | | | +-------+ === C1 | | --- Grnd --- Grnd 

Es ist wichtig, die 2 AusgĂ€nge richtig zu mischen: Der R2-Widerstand sollte 256-mal grĂ¶ĂŸer sein als der R1-Widerstand. Je genauer, desto besser. Leider liefern selbst WiderstĂ€nde mit einem Fehler von 1% nicht die erforderliche Genauigkeit. Selbst bei einer nicht sehr genauen Auswahl von WiderstĂ€nden kann die Verzerrung merklich gedĂ€mpft werden.

Leider verschlechtert sich bei Verwendung von 16-Bit-Sound die Leistung und 5 KanĂ€le + Rauschen haben in den zugewiesenen 256 Taktzyklen keine Zeit mehr fĂŒr die Verarbeitung.

Ist es auf Arduino möglich?


Ja, das kannst du. Ich habe nur einen chinesischen Nano-Klon auf ATmega328p, er funktioniert darauf. Höchstwahrscheinlich sollten auch andere Arduine auf dem ATmega328p funktionieren. Der ATmega168 scheint die gleichen Timer-Steuerregister zu haben. Höchstwahrscheinlich werden sie unverĂ€ndert funktionieren. Bei anderen Mikrocontrollern, die Sie ĂŒberprĂŒfen mĂŒssen, mĂŒssen Sie möglicherweise einen Treiber hinzufĂŒgen.

In demos / arduino328p gibt es eine Skizze, aber damit sie in der Arduino IDE normal geöffnet werden kann, mĂŒssen Sie sie in das Stammverzeichnis des Projekts kopieren.

In diesem Beispiel wird 16-Bit-Sound erzeugt und die AusgÀnge D9 und D10 werden verwendet. Zur Vereinfachung können Sie sich auf 8-Bit-Sound beschrÀnken und nur einen D9-Ausgang verwenden.

Da fast alle Arduine mit 16 MHz arbeiten, können Sie auf Wunsch die Anzahl der KanÀle auf 8 erhöhen.

Was ist mit ATtiny?


ATtiny hat keine Hardware-Multiplikation. Die vom Compiler verwendete Softwaremultiplikation ist Ă€ußerst langsam und wird am besten vermieden. Bei Verwendung optimierter Assembler-EinsĂ€tze sinkt die Leistung im Vergleich zu ATmega um das Zweifache. Es scheint, dass es keinen Sinn macht, ATtiny ĂŒberhaupt zu verwenden, aber ...

Einige ATtiny haben einen Frequenzvervielfacher, PLL. Und das bedeutet, dass es bei solchen Mikrocontrollern zwei interessante Funktionen gibt:

  1. Die Frequenz des PWM-Generators betrÀgt 64 MHz, was eine PWM-Periode von 250 kHz ergibt, was viel besser ist als 31 250 Hz bei 8 MHz oder 62500 Hz mit Quarz bei 16 MHz bei jedem ATmega.
  2. Mit dem gleichen Frequenzvervielfacher kann der Kristall ohne Quarz mit 16 MHz takten.

Daher die Schlussfolgerung: Einige ATtiny können verwendet werden, um Ton zu erzeugen. Sie schaffen es, die gleichen 5 Instrumente + Rauschkanal zu verarbeiten, jedoch bei 16 MHz, und sie benötigen keinen externen Quarz.

Der Nachteil ist, dass die Frequenz nicht mehr erhöht werden kann und die Berechnungen fast immer dauern. Um Ressourcen freizugeben, können Sie die Anzahl der KanÀle oder die Abtastrate reduzieren.

Ein weiteres Minus ist die Notwendigkeit, zwei Timer gleichzeitig zu verwenden: einen fĂŒr PWM und einen fĂŒr Unterbrechungen. Hier enden normalerweise die Timer.

Von den mir bekannten PLL-Mikrocontrollern kann ich ATtiny85 / 45/25 (8 Beine), ATtiny861 / 461/261 (20 Beine), ATtiny26 (20 Beine) erwÀhnen.

Was den Speicher betrifft, ist der Unterschied zu ATmega nicht groß. In 8kb passen mehrere Instrumente und Melodien perfekt. In 4kb können Sie 1-2 Instrumente und 1-2 Melodien setzen. Es ist schwer, etwas in 2 Kilobyte zu packen, aber wenn Sie wirklich wollen, dann können Sie. Es ist notwendig, die Methoden zu trennen, einige Funktionen wie die LautstĂ€rkeregelung ĂŒber die KanĂ€le zu deaktivieren, die Abtastfrequenz und die Anzahl der KanĂ€le zu reduzieren. Im Allgemeinen fĂŒr einen Amateur, aber es gibt ein funktionierendes Beispiel fĂŒr ATtiny26.

Die Probleme


Es gibt Probleme. Das grĂ¶ĂŸte Problem ist die Rechengeschwindigkeit. Der Code ist vollstĂ€ndig in C mit kleinen Assembler-MultiplikationseinsĂ€tzen fĂŒr ATtiny geschrieben. Der Compiler wird optimiert und verhĂ€lt sich manchmal seltsam. Mit kleinen Änderungen, die nichts zu beeinflussen scheinen, kann es zu einer spĂŒrbaren Leistungsminderung kommen. DarĂŒber hinaus hilft der Wechsel von -Os zu -O3 nicht immer. Ein solches Beispiel ist die Verwendung eines 256-Byte-Puffers. Besonders unangenehm ist, dass es keine Garantie dafĂŒr gibt, dass in neuen Versionen des Compilers bei demselben Code keine Leistungseinbußen auftreten.

Ein weiteres Problem besteht darin, dass der DĂ€mpfungsmechanismus vor der nĂ€chsten Note ĂŒberhaupt nicht implementiert ist. Das heißt, Wenn auf einem Kanal eine Note durch eine andere ersetzt wird, wird der alte Ton abrupt unterbrochen, manchmal ist ein kleines Klicken zu hören. Ich wĂŒrde gerne einen Weg finden, dies loszuwerden, ohne an Leistung zu verlieren, aber bisher.

Es gibt keine Befehle zum sanften Erhöhen / Verringern der LautstĂ€rke. Dies ist besonders wichtig fĂŒr kurze Benachrichtigungsklingeltöne, bei denen Sie am Ende die LautstĂ€rke schnell dĂ€mpfen mĂŒssen, damit der Ton nicht scharf unterbrochen wird. Ein Teil des Problems besteht darin, eine Reihe von Befehlen mit manueller Einstellung der LautstĂ€rke und einer kurzen Pause zu schreiben.

Der gewĂ€hlte Ansatz ist im Prinzip nicht in der Lage, den Instrumenten einen naturalistischen Klang zu verleihen. FĂŒr einen natĂŒrlicheren Klang mĂŒssen Sie die KlĂ€nge der Instrumente in Attack-Sustain-Release unterteilen, mindestens die ersten beiden Teile verwenden und eine viel lĂ€ngere Dauer als eine Schwingungsperiode haben. Aber dann brauchen die Daten fĂŒr das Tool viel mehr. Es gab die Idee, kĂŒrzere Wellentabellen zu verwenden, beispielsweise in 32 Bytes anstelle von 256, aber ohne Interpolation nimmt die KlangqualitĂ€t dramatisch ab und mit der Interpolation nimmt die Leistung ab. Und weitere 8 Bits Sampling reichen fĂŒr Musik eindeutig nicht aus, können aber umgangen werden.

Die PuffergrĂ¶ĂŸe ist auf 256 Abtastwerte begrenzt. Dies entspricht ungefĂ€hr 8 Millisekunden und dies ist die maximale integrale Zeitspanne, die anderen Aufgaben zugewiesen werden kann. Gleichzeitig wird die AusfĂŒhrung von Aufgaben immer noch regelmĂ€ĂŸig durch Unterbrechungen unterbrochen.

Das Ersetzen der Standardverzögerung funktioniert bei kurzen Pausen nicht sehr genau.

Ich bin sicher, dass dies keine vollstÀndige Liste ist.

Referenzen


Source: https://habr.com/ru/post/de466121/


All Articles