Snelle tip gebruik de gegevensstructuur van de Ringbuffer om schokkerige waarden te verzachten

Tijdens het ontwikkelen van een spel, vindt u mogelijk waarden die te veel ruis bevatten voor uw behoeften. Het algemene geval is analoge gebruikersinvoer (muis, aanraak- of joystick), maar de ruis kan ook afkomstig zijn van de spelsystemen, zoals fysica of stuurgedrag, waarbij bij benadering oplossingen of niet-opeenvolgende veranderingen leiden tot ruis. In deze zelfstudie leert u een eenvoudige manier om die ruisige waarden te verzachten. De codevoorbeelden zijn in C #, maar ze zijn eenvoudig aan te passen aan een andere taal.


Ringbuffer

De eenvoudigste manier om de variërende waarde te verzachten, is door een aantal van de eerdere steekproeven te nemen en deze te laten gemiddelde. We zullen een constant aantal samples gebruiken, dus een array met een vaste grootte is een natuurlijke en efficiënte keuze om deze op te slaan. Om vervolgens te voorkomen dat deze array wordt verschoven, gebruiken we een truc: de gegevensstructuur van de "ringbuffer".

Laten we beginnen met het definiëren van de gegevens die moeten worden opgeslagen in onze hulpprogramma-klasse:

 publieke klasse SlidingAverage float [] buffer; zwevende som; int lastIndex; public SlidingAverage (int num_samples, float initial_value) buffer = new float [num_samples]; lastIndex = 0; reset (initial_value); 

Hier hebben we onze samples-buffer, de som van de samples en de laatst gebruikte index in de array. De constructor wijst de bufferarray, sets toe lastIndex naar nul en roept de reset () methode:

 public void reset (float waarde) sum = waarde * buffer.Length; voor (int i = 0; i 

Hier vullen we de buffer met de opgegeven beginwaarde en stellen we de som in om deze overeen te laten komen. Deze methode kan worden gebruikt wanneer u de smoothing opnieuw moet starten om geheugeneffecten van eerdere samples te voorkomen.

Nu, de belangrijkste methode: een nieuwe waarde in onze ringbuffer duwen:

 public void pushValue (float value) sum- = buffer [lastIndex]; // trek de oudste steekproef af van de som sum + = waarde; // voeg de nieuwe voorbeeldbuffer toe [lastIndex] = waarde; // sla de nieuwe steekproef op // voer de index door en wikkel deze rond lastIndex + = 1; if (lastIndex> = buffer.Length) lastIndex = 0; 

Hier overschrijven we de oudste sample naar lastIndex met de nieuwe, maar daarvoor passen we de som aan door het oude monster af te trekken en het nieuwe toe te voegen.

Dan gaan we verder lastIndex zodat het verwijst naar de volgende steekproef (die nu de oudste is). Maar als we gewoon doorgaan lastIndex Binnen de kortste keren zullen we zonder array zitten, dus wanneer het uit de array komt, wikkelen we het rond naar nul.

Dat is waarom het is a ring buffer. Het is in essentie hetzelfde als het verplaatsen van de array en het toevoegen van de nieuwe sample, maar veel sneller omdat in plaats van de waarden in het geheugen te kopiëren, we de index omzeilen.

Het enige wat ontbreekt, is het verkrijgen van de afgevlakte waarde:

 public float getSmoothedValue () return sum / buffer.Length; 

Dat is het; we delen gewoon de som door het aantal monsters om het gemiddelde te krijgen. Als we de som niet hebben opgeslagen, moeten we deze hier berekenen op basis van de steekproeven.


resultaten

Laten we de resultaten bekijken:


De zwarte lijn is het oorspronkelijke signaal (sinusgolf met wat ruis), de witte lijn wordt afgevlakt met twee voorbeelden en de rode lijn wordt afgevlakt met vier voorbeelden.

Zoals u ziet, maken zelfs enkele voorbeelden het merkbaar vloeiender, maar hoe meer samples we gebruiken, hoe meer het achterblijft bij het oorspronkelijke signaal. Dat wordt verwacht omdat we alleen monsters uit het verleden gebruiken in de real-time case. Als u een nabewerking uitvoert, kunt u de afgevlakte waarden op tijd verplaatsen om vertraging te voorkomen.


Conclusie

U hebt nu een eenvoudige utiliteitsklasse die kan worden gebruikt om binnenkomende luidruchtige geluiden af ​​te vlakken, of het nu een gebruikersinvoer, een objecttraject of een snelheidsindicator is.

Het kan verder worden verbeterd door het toevoegen van steekproefgewichten (we gebruikten een eenvoudig gemiddelde met constante 1 / N gewicht), maar dat is een enorm onderwerp, digitale signaalverwerking en beter over voor een toekomstige zelfstudie!