;;; An example for the lazy. ;;; this line is a comment ; this too ;;; To avoid confusion, it should be pointed out that the dsp programs are also ;;; referred to as "patches" or "effects". ;; first we need to give our program a name, this will be used by the loader to ;; identify it. Commands such a these are called a "directive", they don't ;; map to an emu10k1 instruction, but instead are used by the assembler and/or loader: name "fir_example" ; note, everything after a semicolon is a comment. ;; we include the "emu_constants.asm" file, it contains constants and macros. ;; Like most other languages, an include basically results in another file ;; being "pasted" at the point of inclusion. include "emu_constants.asm" ;; The above shows that unlike C, semi colons are not nessecary to terminate a line. ;; Next we'll define some variables. "coef" is an array of read-only constants, the "con" ;; (short for constants) is another directive. These constants will be stored in the ;; emu's memory, the read-only-ness allows for reuse of the memory values by other loaded ;; programs. This increases memory usage efficiency. The array will be called "coef", "coef" is ;; known as a label, labels are always placed at the beginning of the line. coef con 0.038684406 0.058115275 0.113007075 0.194116501 0.287525429 0.377072924 0.447195555 0.485671998 0.485783252 0.447503000 0.377505237 0.287987288 0.194517783 0.113292922 0.058289230 0.038818213 ;; note: lines can be up to 256 characters long. ;; the next line defines an assembly time constant, this is the assembly ;; equivalent of C's #define statement. ;; in assembly, this is called an "equate". n equ 15 ; filter order ;;; The loader currently only handle mono dsp programs. The programs ;;; are loaded onto "lines", and should always have one input and one output. ;;; input-output this pair is defined using the "io" directive: in io ;; "in" is a our io line, if we read from it we'll get the input value, ;; if we write to it, we'll put the output value. To make the code a bit more readable, ;; we'll create an equate of "in" and call it "out". out equ in ;; The program needs user control, for this we define special memory locations ;; as "control" regiters. The name of the control seen by the users will be the label placed at ;; the beginning of the line. the format is: ;; ;;label control initial-value, min-value, max-value bass control 0,0,#1 ;; The next line declares an array of static (or shorthand sta) memory locations with initial ;; values: delay sta 0,0,0,0,0 ,0,0,0,0,0 ,0,0,0,0,0 ,0 ;; temporary locations are declared using dyn (short for dynamic). ;; dynamic registers are reused by other dsp programs to increase ;; register usage. tmp dyn ;;ok, now that we've declared everything we need, it's time to tackle the actual code: ;; the 16 instructions instructions always 4 operands ;; the first operand is the destination register, the last three ;; are source registers. macints delay,in,C_0,C_0 ;; the next instruction is a dummy instruction used to clear the accumulator ;; the C_0 was declared in "emu_constants.asm", it's a hardware register containing the ;; value 0. macs C_0,C_0,C_0,C_0 ;; An assembly time for statement is available, the format: ;; for variable = start : finnish for i = n : 1 ;; operands can be calculated at assembly time as is shown below: macmv delay+i,delay+i-1,delay+i,coef+i endfor ;; end of for loop macs tmp,ACCUM,delay,coef macs1 out,in,tmp,bass ;; an "end" directive tells the assmbler that we have reached the end of the program, ;; anything after is ingnored end
Fir and IIR filters are a staple of dsp programming. In this section I'll show how the emu10k1 can be used to accomplish such filtering.
FIR filters are the simplest filter to implement. Saturation is easy to avoid and a variety of frequency and phase responses can be created. The FIR filter's weakness is that they generally require more coefficients and more multiplications to have the same frequency response as an IIR filter.
If you've never worked with FIR filters, I recommend the following reading:
DSPGuru's FIR FAQ
Figure 1 below shows a FIR filter with N=3, the z^-1 elements are unit delays, the series of them tied together is called a delay-line. The "a0", "a1", etc. are multipications, when two lines join, it is a summation.
FIG 1, FIR filter
An fir filter has the following z-domain transfer function
H(z) = a0 + a1*Z^-1 + a2*Z^-2 + ...
Implementation
To implement a FIR filter we can either use tram, or the macmv instruction. If we use the macmv instruction an fir filter will have to following form (this example has n=4):
;;5th-order FIR filter include "emu_contanst.asm" coef con 0.52478 , 0.12367 , 0.12365, 0.14734, 0.76346 ;;our "a" coefficients dly sta 0,0,0,0,0 ;;we need "n" static storage spaces in IO out equ in macs C_0,C_0,C_0,C_0 ;; clear the accumulator macmv dly+5 , dly+4, dly+5, coef +5 ;; we start by macing the last element, ;; and move the previous delay into the current ;; The Result doesn't get written to "dly+5"till ;; after the mac is preformed. macmv dly+4 , dly+3 , dly+4 , coef+4 macmv dly+3 , dly+2 , dly+3 ,coef+3 macmv dly+2 , dly+1 , dly+2 ,coef+2 macmv dly+1 , dly , dly+1 ,coef+1 macmv dly, in , dly, coef ;; on the last one we move the input into the delay-line move out,ACCUM ;; Moves the accumulated value to the output
On normal processor, the above may be implemented with a "for" loop. Since the emu10k1 does not have a loop instruction (in order to maintain real-timeness), each delay element must take 1 instruction. To code faster, we can use the assembly-time-for statement in as10k1, in which case the above example becomes:
;;5th-order FIR filter using assembly-time for statement include "emu_contanst.asm" coef con 0.52478 , 0.12367 , 0.12365, 0.14734, 0.76346 ;;our "a" coefficients dly sta 0,0,0,0,0 ;;we need "n" static storage spaces in IO out equ in macs C_0,C_0,C_0,C_0 ;; clear the accumulator for i=5:1 macmv dly+i , dly+i-1, dly+i, coef +i endfor macmv dly, in , dly, coef ;; We move the input into the delay-line move out,ACCUM ;; Moves the accumulated value to the output
Alternatively one can use tram for the delay-line. I would only recommend to do such a thing in the event that you have alot of coefficients equal to 0. An example of a tram-based FIR filter is shown below:
;;6-th order FIR filter using tram ;;In this example, the 2 middle coefficients are 0, thus no need to calculate them. include "emu_contanst.asm" coef con 0.12454, 0.52478 , 0.12367 , -0.12367, -0.52478 ;our "a" coefficients dly delay 7 write twrite dly,0 rd1 tread dly,1 rd2 tread dly,2 ;;the 2 middle taps are gone ;; rd5 tread dly,5 rd6 tread dly,6 in IO out equ in tmp dyn move write,in macs tmp,C_0,in,coef for i=0:3 macw tmp , tmp, rd1 +i, coef +i+1 ;;we use macw since it's ok if ;;intermediate results wraparound endfor move out,tmp ;; Moves the accumulated value to the output
Things to remember:
Octave has some prebuilt functions which will spit out filter coefficients based on arguments (filter order, cut-off frequency, etc) you give it. It's a good time saver.
IIR filters are much more complicated to implement then FIR filter. This is primarily due to the fact that the emu10k1 is a fixed-point beast, and saturation and round-off are it's natural predators :-). None the less, the advantages of IIR filters usually warrant going to great length to implement. And well, nothing beats the personal satisfaction of have pull of such an impressive feat. The figure below shows two common methods of implementing a 2nd-order IIR filter.
Fig 2, IIR System
The IIR system's transfer function is given by:
Y(z) b0 + b1*z^-1 + b2*z^-2 + ... H(z) = ---- = ------------------------------ X(z) 1 + a1*z^-1 + a2*z^-2 + ...
Direct Form II The point which is of most concern in the system is W(z), the transfer function from the input to W(z) is given by:
W(z) 1 ---- = ------------------------------ X(z) 1 + a1*z^-1 + a2*z^-2 + ...
It is important to check that this point is bounded within [-1,1]. The simplest way is to calculate it's impulse response, take it's absolute value, then summ it up. Or:
--- \_ imp( W(z) ) / ---- --- X(z)
[august 11, 2001] oops, I forgot to finish this part :-(