; Copyright Ad Lib Inc., 1990 ; This file is part of the Ad Lib Programmer's Manual product and is ; subject to copyright laws. As such, you may make copies of this file ; only for the purpose of having backup copies. This file may not be ; redistributed in any form whatsoever. ; If you find yourself in possession of this file without having received ; it directly from Ad Lib Inc., then you are in violation of copyright ; laws, which is a form of theft. ; SETFREQ.ASM ; ; Adlib Inc, 2-mar-90 ; ; The routine of this module is ; int SetFreq (voice, note, pitch, keyOn) ; and is used to send frequency information to the sound board, according ; to the note, pitch bend and pitch bend range. ; ; Normally called by module ADLIB.C .MODEL LARGE ; @codesize is predefined by MASM: 0 = small, compact; 1 = med, large, huge IF @codesize CPSIZE EQU 4 ELSE CPSIZE EQU 2 ENDIF ; ====================== UNINITALIZED DATA SEGMENT ========================= .DATA? EXTRN _pitchRange:WORD ; pitch bend range (global) ; ======================= INITALIZED DATA SEGMENT ========================== .DATA ; TABLE: "fNumTbl" (below) ; This table contains the 10 bit f-numbers that are used to set the ; frequency in the registers 0xA0 and 0xB0. The table contains f-numbers ; for the 12 half-tones in an octave, with a resolution of 16 steps between ; each half-tone. There are then 12 * 16 = 192 entries in the table. ; The following C code is the code originally used to generate the table. ; If the f-number is negative, 1 must be added to the block octave ; information. ; Each value is computed using the formula: ; fN = (2**20 x Fmus) / (3.58e6 / 72) ; fN is shifted right until is below 1024 (10 bits) ;#define CLOCK 3579545L /* 14.31818e6 /4 */ ;#define F_INTERN (CLOCK /72) /* internal chip freq */ ;#define NB_NOTES 96 ;#define OCTAVE 12 ;#define NB_TABLE_DEMI_TON OCTAVE ;#define NB_STEP_PITCH 16 /* 16 pas d'un ton a l'autre */ ;#define LOG_NB_STEP_PITCH 4 /* LOG2( NB_STEP_PITCH) */ ;#define TABLE_SIZE (NB_STEP_PITCH * NB_TABLE_DEMI_TON) ;#define LOG_PITCH 8 /* LOG2( TABLE_SIZE) */ ;#define FREQ_DO (double)261.6256 /* reference, was 260.44 Hz ... */ ; ;#define xexpy( x, y) (exp( y * log( x))) /* return x**y */ ; ;GenTable() ; { ; double freq; ; double range; ; long fNum, fN10; ; int block; ; int i, value; ; ; range = 2.0; ; for( i = 0; i < TABLE_SIZE; i++) { ; freq = xexpy( range, (double)i / TABLE_SIZE); ; freq *= FREQ_DO; ; fNum = ((long)((long)1 << 20) * freq) /F_INTERN; ; for( block = 0, fN10 = fNum; fN10 >= 1024; fN10 >>= 1, block++) ; ; ; /* block range from 3 to 4 */ ; fN10 = (fNum + (1 << block -1)) >> block; /* round to 0.5 */ ; block = 3 - block; ; value = fN10 | (block << 10); ; printf( "%05xH, ", value); ; if( i % 10 == 9) ; printf( "\n"); ; } ; printf( "\n\n"); ; } fNumTbl dw 02b2H dw 02b4H, 02b7H, 02b9H, 02bcH, 02beH, 02c1H, 02c3H, 02c6H, 02c9H dw 02cbH, 02ceH, 02d0H, 02d3H, 02d6H, 02d8H, 02dbH, 02ddH, 02e0H, 02e3H dw 02e5H, 02e8H, 02ebH, 02edH, 02f0H, 02f3H, 02f6H, 02f8H, 02fbH, 02feH dw 0301H, 0303H, 0306H, 0309H, 030cH, 030fH, 0311H, 0314H, 0317H, 031aH dw 031dH, 0320H, 0323H, 0326H, 0329H, 032bH, 032eH, 0331H, 0334H, 0337H dw 033aH, 033dH, 0340H, 0343H, 0346H, 0349H, 034cH, 034fH, 0352H, 0356H dw 0359H, 035cH, 035fH, 0362H, 0365H, 0368H, 036bH, 036fH, 0372H, 0375H dw 0378H, 037bH, 037fH, 0382H, 0385H, 0388H, 038cH, 038fH, 0392H, 0395H dw 0399H, 039cH, 039fH, 03a3H, 03a6H, 03a9H, 03adH, 03b0H, 03b4H, 03b7H dw 03bbH, 03beH, 03c1H, 03c5H, 03c8H, 03ccH, 03cfH, 03d3H, 03d7H, 03daH dw 03deH, 03e1H, 03e5H, 03e8H, 03ecH, 03f0H, 03f3H, 03f7H, 03fbH, 03feH dw 0fe01H, 0fe03H, 0fe05H, 0fe07H, 0fe08H, 0fe0aH, 0fe0cH, 0fe0eH, 0fe10H, 0fe12H dw 0fe14H, 0fe16H, 0fe18H, 0fe1aH, 0fe1cH, 0fe1eH, 0fe20H, 0fe21H, 0fe23H, 0fe25H dw 0fe27H, 0fe29H, 0fe2bH, 0fe2dH, 0fe2fH, 0fe31H, 0fe34H, 0fe36H, 0fe38H, 0fe3aH dw 0fe3cH, 0fe3eH, 0fe40H, 0fe42H, 0fe44H, 0fe46H, 0fe48H, 0fe4aH, 0fe4cH, 0fe4fH dw 0fe51H, 0fe53H, 0fe55H, 0fe57H, 0fe59H, 0fe5cH, 0fe5eH, 0fe60H, 0fe62H, 0fe64H dw 0fe67H, 0fe69H, 0fe6bH, 0fe6dH, 0fe6fH, 0fe72H, 0fe74H, 0fe76H, 0fe79H, 0fe7bH dw 0fe7dH, 0fe7fH, 0fe82H, 0fe84H, 0fe86H, 0fe89H, 0fe8bH, 0fe8dH, 0fe90H, 0fe92H dw 0fe95H, 0fe97H, 0fe99H, 0fe9cH, 0fe9eH, 0fea1H, 0fea3H, 0fea5H, 0fea8H, 0feaaH dw 0feadH, 0feafH ; Integer division by 12 table (96 entries). It is used to find the octave ; for a given note value in the range [0, 95]. noteDIV12 db 0 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 db 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 db 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 db 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7 db 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ; Integer modulo 12 table (96 entries). It is used to find the half-tone ; value of a note ([0, 95]) within an octave. noteMOD12 db 0 db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4 db 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 db 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2 db 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 db 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0 db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ;========================== CODE SEGMENT ============================ .CODE EXTRN _SndOutput:PROC ; SndOutput (addr, data); outputs 'data' at ; register 'addr' ;#define NB_NOTES 96 /* 8 octave of 12 notes */ ;#define OCTAVE 12 /* half-tone by octave */ ;#define NB_TABLE_DEMI_TON OCTAVE ;#define NB_STEP_PITCH 16 /* 16 steps between two half-tones */ ;#define LOG_NB_STEP_PITCH 4 /* LOG2( NB_STEP_PITCH) */ ;#define TABLE_SIZE (NB_STEP_PITCH * NB_TABLE_DEMI_TON) ;#define LOG_PITCH 8 /* LOG2( TABLE_SIZE) */ nb_notes equ 96 octave equ 12 nb_table_demi_ton equ octave nb_step_pitch equ 16 log_nb_step_pitch equ 4 table_size equ (nb_step_pitch * nb_table_demi_ton) log_pitch equ 8 KEYON_BLOCK_FNUM equ 0B0H FNUM_LOW equ 0A0H ;------------------------------------------------------------------------- ; Set the frequency of voice 'voice' to note number 'note', shifting ; the note by 'pitch/0x2000' of 'pitchRange' (global, 1-12). The key-on bit ; is set to the passed 'keyOn' value. ; Returns the value written to register 0xB0+voice. ;int SetFreq( voice, note, pitch, keyOn) ; int voice; /* [0 - 8] */ ; unsigned note; /* [0, 95] */ ; int pitch; /* 14 bits, [0, 3fff] */ ; int keyOn; /* key on bit-feild == 0 or 0x20 */ ; { ; int tblValue, tNote; ; unsigned t1, t2; ; unsigned tableOff; ; unsigned fNLow, fNHigh; ; int block; ; int signP; public _SetFreq _SetFreq proc SF_F struc sf_local dw (?) ; old bp db CPSIZE DUP (?) ; return addr voice dw (?) note dw (?) pitch dw (?) keyOn dw (?) SF_F ends push bp mov bp, sp IF sf_local NE 0 sub sp, sf_local ENDIF ; /* Convert unsigned pitch bend to [-0x2000, +0x2000] signed. */ ; signP = (int)pitch - 0x2000; mov ax, [bp].pitch sub ax, 2000H je after_mul ; if 0, by-pass multiplication... ; /* Keep the 8 most significant bits of the pitch bend. */ ; t2 = signP >> (13 -LOG_PITCH); sar ax, 1 sar ax, 1 sar ax, 1 sar ax, 1 sar ax, 1 ; /* Multiply the modified pitch bend by the pitch bend range to obtain the ; signed note number offset in 1/256's (8 bit fix point). */ ; t2 *= pBRange; IF 0 imul DGROUP: _pitchRange ; t2 ELSE ; code to avoid multiplication: will be faster on 8086 machines... mov cx, ax mov bx, DGROUP: _pitchRange shl bx, 1 neg bx add bx, offset CS: add_table+2 jmp bx EVEN add ax, cx ; x 12 add ax, cx ; x 11 add ax, cx ; x 10 add ax, cx ; x 9 add ax, cx ; x 8 add ax, cx ; x 7 add ax, cx ; x 6 add ax, cx ; x 5 add ax, cx ; x 4 add ax, cx ; x 3 add ax, cx ; x 2 add_table: ; x 1 ENDIF ; /* convert note number in 8 bit fix point and add preceding computed ; note offset: */ ; t1 = note << LOG_PITCH; ; tNote = (t1 + t2); after_mul: add ah, byte ptr [bp].note ; /* round note number to 1/16 (by adding 1/32): */ ; tNote += (1 << LOG_PITCH - LOG_NB_STEP_PITCH -1); add ax, (1 SHL (log_pitch - log_nb_step_pitch - 1)) ; /* ; Convert 8 bit fix point to 4 bit fix point (1/16). ; 'tNote' is then the resulting note number after adjusting for pitch bend, ; expressed in 1/16 half-tones. ; */ ; tNote >>= LOG_PITCH - LOG_NB_STEP_PITCH; sar ax, 1 sar ax, 1 sar ax, 1 sar ax, 1 ; /* Make sure that the note is in the range [0, 95]. */ ; if( tNote < 0) jge l3 ; tNote = 0; xor ax, ax jmp l4 ; /* Compare with 96 in 4 bit fix point. */ ; if( tNote >= NB_NOTES << LOG_NB_STEP_PITCH) l3: cmp ax, (nb_notes SHL log_nb_step_pitch)-1 jl l4 ; tNote = (NB_NOTES << LOG_NB_STEP_PITCH) -1; mov ax, (nb_notes SHL log_nb_step_pitch)-1 ; /* Get half-tone value within 1 octave by using integer part ; of note number MODULO 12. */ ; tableOff = noteMOD12[ (tNote >> LOG_NB_STEP_PITCH)]; l4: mov di, ax shr di, 1 shr di, 1 shr di, 1 shr di, 1 mov dx, di ; tNote >> log_nb_step_pitch mov bl, noteMOD12[di] xor bh, bh mov di, bx ; /* Convert half-tone number to offset in table 'tableOff', in order ; to point to start of data for this half-tone. ; Since each half-tone consisting of 16 entries of 2 bytes, we multiply by ; 32. */ ; tableOff <<= LOG_NB_STEP_PITCH +1; shl di, 1 shl di, 1 shl di, 1 shl di, 1 shl di, 1 ; /* Add to this offset the fractional part (low order 4 bits) ; of note number multiplied by 2 to obtain the exact offset in 'tableOff' ; for the note number. */ ; tableOff += (tNote << 1) & (NB_STEP_PITCH *2 -1); shl ax, 1 and ax, (nb_step_pitch *2 -1) add di, ax ; /* Get frequency information pointed to... */ ; tblValue =* (int *)((char *)fNumTbl +tableOff); mov ax, fNumTbl[di] ; /* Get octave number using integer part of note number and DIV 12 table. */ ; block = noteDIV12 [(tNote >> LOG_NB_STEP_PITCH)] -1; mov di, dx mov bl, noteDIV12[di] dec bl ; /* If high bit of frequency information is set, this means that we must ; add 1 to octave number (frequency information has been divided by 2). */ ; block += (tblValue < 0) ? 1 : 0; or ax, ax jge l5 inc bl ; if (block < 0) { l5: or bl, bl jge l6 ; block++; inc bl ; tblValue >>= 1; sar ax, 1 l6: ;/* Send low bits of frequency information to sound board. */ ; SndOutput (0xA0 +voice, tblValue & 255); push bx ; save block push ax ; save tblValue xor ah, ah push ax mov al, FNUM_LOW add al, byte ptr [bp].voice push ax call _SndOutput add sp, 4 pop ax ; restore tblValue pop bx ; restore block ; /* Send 'keyOn' bit, octave number and 2 high bits of frequency information ; to sound board. */ ; SndOutput (0xB0 +voice, keyOn + (block << 2) + (tblValue >> 8) & 3); mov al, ah and al, 3 shl bl, 1 shl bl, 1 add al, bl add al, byte ptr [bp].keyOn xor ah, ah push ax ; save return value push ax mov ax, KEYON_BLOCK_FNUM add ax, [ bp].voice push ax call _SndOutput add sp, 4 pop ax ; retore return value ; } IF sf_local NE 0 add sp, sf_local ENDIF pop bp ret _SetFreq endp end