336 lines · 7.1 KB · Text
RawDownload
#+TITLE: Bitnoise* Introduction=bitnoise= is a 1-bit noise generator, based on theone found on the [[https://wiki.nesdev.com/w/index.php/APU_Noise][NES APU]].It features a 15-bit linear feedback shift register(abbreviated as =LFSR=), mode toggle switch, and arate parameter for speed control.* Tangled FilesBitnoise tangles to two files: =bitnoise.c= and=bitnoise.h=. #+NAME: bitnoise.c#+BEGIN_SRC c :tangle bitnoise.c/* tangled from sndkit. do not edit by hand */#include <stdint.h>#include <math.h>#define SK_BITNOISE_PRIV#include "bitnoise.h"<<macros>><<funcs>>#+END_SRC If =SK_BITNOISE_PRIV= is enabled, itexposes the struct in the header. Otherwise, it isleft as an opaque struct. #+NAME: bitnoise.h#+BEGIN_SRC c :tangle bitnoise.h/* tangled from sndkit. do not edit by hand */#ifndef SK_BITNOISE_H#define SK_BITNOISE_H #ifndef SKFLT#define SKFLT float#endif <<typedefs>><<funcdefs>> #ifdef SK_BITNOISE_PRIV<<structs>>#endif #endif#+END_SRC* StructsAll parameters are contained in a struct called=sk_bitnoise=. #+NAME: typedefs#+BEGIN_SRC ctypedef struct sk_bitnoise sk_bitnoise;#+END_SRC #+NAME: structs#+BEGIN_SRC cstruct sk_bitnoise {    <<sk_bitnoise>>};#+END_SRC* InitBitnoise is initialized with =sk_bitnoise_init=. Thesampling rate must be supplied here. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_bitnoise_init(sk_bitnoise *bn, int sr);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_bitnoise_init(sk_bitnoise *bn, int sr){    <<init>>}#+END_SRC* Variables, Constants, and State** Phasor Constants and StateA fixed-point phasor is used to manage clocking andfrequency control, similar to the one found in @!(ref"rline")!@. Defined constants =SK_BITNOISE_PHSMAX= and=SK_BITNOISE_PHSMSK= are used to wrap. #+NAME: macros#+BEGIN_SRC c#define SK_BITNOISE_PHSMAX 0x1000000L#define SK_BITNOISE_PHSMSK 0x0FFFFFFL#+END_SRC To calculate the increment amount, a calculated constantcalled =maxlens= is used, which is the maximum phase lengthin units of seconds. When multiplied by the rate paramter,this provides a value (in units of cycles) that tells thephasor how much to increment. #+NAME: sk_bitnoise#+BEGIN_SRC cSKFLT maxlens;#+END_SRC #+NAME: init#+BEGIN_SRC cbn->maxlens = SK_BITNOISE_PHSMAX / (SKFLT) sr;#+END_SRC The phasor position itself is stored in a long integercalled =phs=, and is set to be 0. #+NAME: sk_bitnoise#+BEGIN_SRC cuint32_t phs;#+END_SRC #+NAME: init#+BEGIN_SRC cbn->phs = 0;#+END_SRC** Linear Feedback Shift Register StateStored in a 16-bit unsigned integer called =lfsr=. #+NAME: sk_bitnoise#+BEGIN_SRC cuint16_t lfsr;#+END_SRC According to the APU specs, this is initialized to be 1. #+NAME: init#+BEGIN_SRC cbn->lfsr = 1;#+END_SRC** Bit PositionThe current bit position in the register is kept track of inan integer called =pos=. #+NAME: sk_bitnoise#+BEGIN_SRC cint pos;#+END_SRC #+NAME: init#+BEGIN_SRC cbn->pos = 0;#+END_SRC** Saved ValueThe last computed sample is stored in a variable called=saved=. #+NAME: sk_bitnoise#+BEGIN_SRC cSKFLT saved;#+END_SRC #+NAME: init#+BEGIN_SRC cbn->saved = 0;#+END_SRC* Parameters** RateThe =rate= parameter changes the speed at which the noisegenerator updates, similar to how a sample-and-hold works.This is supplied in units of Hz. Set the rate parameter with =sk_bitnoise_rate=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_bitnoise_rate(sk_bitnoise *bn, SKFLT rate);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_bitnoise_rate(sk_bitnoise *bn, SKFLT rate){    bn->rate = rate;}#+END_SRC #+NAME: sk_bitnoise#+BEGIN_SRC cSKFLT rate;#+END_SRC #+NAME: init#+BEGIN_SRC csk_bitnoise_rate(bn, 1000);#+END_SRC** ModeThe =mode= parameter is a toggle value which changes thebehavior of LFSR. It is either 1 or 0. When 0, the LFSRshould be set up to produce a sequence that is 32767 stepslong. When 1, it should produce 31 or 91 steps, depending onthe state of the shift register. Set the mode parameter with =sk_bitnoise_mode=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_bitnoise_mode(sk_bitnoise *bn, int mode);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_bitnoise_mode(sk_bitnoise *bn, int mode){    bn->m = mode;}#+END_SRC #+NAME: sk_bitnoise#+BEGIN_SRC cint m;#+END_SRC #+NAME: init#+BEGIN_SRC csk_bitnoise_mode(bn, 0);#+END_SRC* ComputeA single sample is initialized with =sk_bitnoise_tick=. #+NAME: funcdefs#+BEGIN_SRC cSKFLT sk_bitnoise_tick(sk_bitnoise *bn);#+END_SRC #+NAME: funcs#+BEGIN_SRC cSKFLT sk_bitnoise_tick(sk_bitnoise *bn){    SKFLT out;     out = 0;    <<update_phasor>>    <<update_LFSR>>    <<write_output>>    return out;}#+END_SRC To begin, the fixed phasor is updated. It is incremented byan amount determined by multiplying the constant =maxlens=with the =rate= parameter. #+NAME: update_phasor#+BEGIN_SRC cbn->phs += floor(bn->rate * bn->maxlens);#+END_SRC When the phasor reaches the or goes beyond the upper limit,it needs to wrap around again. Also, the state of the shiftregister may need to be updated. Wrap around of the phasor is done by AND-ing it with thephase mask =SK_BITNOISE_PHSMSK=. The shift register will need to get updated if it bitposition reaches the end (it exceeds 14). According to the [[https://wiki.nesdev.com/w/index.php/APU_Noise][NES dev wiki]],the LFSR is computed in the following way: Compute feedback as the exclusive OR of bit 0 andone other bit. Depending on the mode flag, thisbit is either bit 1 (mode OFF) or bit 6 (mode ON). The register is right-shifted by 1. The calculated feedback bit is set to be bit 14(the leftmost bit) of the new register. #+NAME: calculate_LFSR#+BEGIN_SRC cx = bn->lfsr;f = (x & 1) ^ ((x >> (bn->m ? 6:1)) & 1);x >>= 1;x |= f << 14;bn->lfsr = x;#+END_SRC The actual noise output is done by extracting the currentbit from the shift register, and then scaling that stateto be in range -1,1. The bitwise operations below work together to "pop" thecurrent bit out of the register. First, the registeris right-shifted so that the desired bit is in the lowestbit position. ANDing with 1 then isolates that last bit. #+NAME: extract_bit#+BEGIN_SRC cy = (bn->lfsr >> bn->pos) & 1;#+END_SRC The value is scaled to be in between -1 and 1. Becauseit is binary, one could be tempted to use a ternary valuelike =y ? 1.0 : -1.0=. However, according to[[https://godbold.org][godbolt], =y * 2 - 1= takes about 3instructions to do in x86_64 (=mov=, =mul=, =sub=), and=y ? 1.0 : -1.0= takes instructions (=cmp=, =je=, =mov=,=jmp=, =mov=). (I'm pretty sure the one with lessinstructions is more efficient). This computed output is cached for later use in =saved=. #+NAME: scale_and_store#+BEGIN_SRC cbn->saved = y * 2 - 1;#+END_SRC #+NAME: update_LFSR#+BEGIN_SRC cif (bn->phs >= SK_BITNOISE_PHSMAX) {    SKFLT y;    bn->phs &= SK_BITNOISE_PHSMSK;    if (bn->pos > 14) {        uint16_t f;        uint16_t x;        bn->pos = 0;        <<calculate_LFSR>>    }     <<extract_bit>>    <<scale_and_store>>    bn->pos++;}#+END_SRC The cached value =saved= is what is returned in the output. #+NAME: write_output#+BEGIN_SRC cout = bn->saved;#+END_SRC

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t