Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <pc.h>
- #include <stdio.h>
- #include <conio.h>
- /*
- 3kr [pre-alpha] -- a tracker for the OPL3 synth chip found on a bunch of SoundBlaster cards from the 90s
- the tracker interface or anything substantial isn't here yet, but you could probably figure out how to work with the chip yourself from this
- haven't tested it on real hardware yet, only DOSBox so far
- build with DJGPP, nothing special
- at this moment in time, it lets you fiddle with adlib registers interactively
- there are obvious bugs, this is massively a work in progress
- */
- /*
- Copyright (c) 2016, null1024 <[email protected]>
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- int opl_base=0x220; /* todo: read this from BLASTER environment variable */
- /* the operator register offsets are right next to each other in this table, so you'd pick which one by doing (channel*2+odd) */
- /* IIRC, odd entries are actually the carrier*/
- int reg_tbl[] = {0x00, 0x03, 0x01, 0x04, 0x02, 0x05, 0x08, 0x0B, 0x09,
- 0x0C, 0x0A, 0x0D, 0x10, 0x13, 0x11, 0x14, 0x12, 0x15};
- /* notes starting from C, increasing chromatically*/
- int note_tbl[] = {343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647};
- /* generic OPL3 write function */
- void writeOPL3(int reg, int data, int bank) {
- /* select given register bank*/
- if (bank=1) { bank=3; } else { bank=1; }
- /* select register, write to register*/
- outp(opl_base, reg);
- inp(opl_base); inp(opl_base); /* reads for delay, but not enough for OPL2 */
- outp(opl_base+bank, (unsigned int)data);
- inp(opl_base); inp(opl_base);
- }
- /* resets the test bits, enables waveform select*/
- void resetTest(void) {
- writeOPL3(0x01,0b00100000,0);
- }
- /* for writing to operator settings (adsr and the lot) */
- /* op is 0 to 35*/
- void writeOp(unsigned int op, int reg, int data) {
- int bank=0;
- resetTest();
- if (op >= 18) { /* bank switching */
- bank=1;
- op-=18;
- }
- writeOPL3(reg+reg_tbl[op], data, bank);
- }
- /* for writing to channel settings (frequency, etc) */
- void writeCh(int ch, int reg, int data) {
- int bank=0;
- resetTest();
- if (ch >= 9) { /* bank switching */
- bank = 1;
- ch -= 9;
- }
- writeOPL3(reg+ch, data, bank);
- }
- /* clear ADSR settings, volume, waveform */
- void clearADSR(void) {
- int ii;
- for (ii=0; ii < 36; ii++) {
- writeOp(ii, 0x20, 0); /* tremolo, vibrato, sustain, etc */
- writeOp(ii, 0x40, 0); /* volume */
- writeOp(ii, 0x60, 0x0F); /* attack, decay */
- writeOp(ii, 0x80, 0xFF); /* sustain, release */
- writeOp(ii, 0xE0, 0); /* waveform */
- }
- }
- /* set things up*/
- void initOPL3(void) {
- int ii;
- writeOPL3(0x05,0b00000001,1); /* enable opl3 mode */
- clearADSR();
- /* set key on for all channels, clear frequency, enable FM */
- for (ii=0; ii < 18; ii++) {
- writeCh(ii, 0xB0, 0b00100000);
- writeCh(ii, 0xC0, 0b00110000);
- }
- }
- void endOPL3(void) {
- int ii;
- clearADSR();
- /* set keyoff for all channels */
- for (ii=0; ii < 18; ii++) {
- writeCh(ii, 0xb0, 0);
- }
- writeOPL3(0x05,0x00,1); /* disable opl3 mode */
- writeOPL3(0x01,0x00,0); /* disable waveform select*/
- }
- /* very basic test note player -- need to replace this with something proper */
- void playTestNote(int note, int channel) {
- unsigned char low_note, high_note, key_on, key_off;
- low_note=(unsigned char)note_tbl[note];
- high_note=((unsigned char)(note_tbl[note]>>8)) & 0b00000011;
- key_on = 0b00101000 | high_note;
- /* key off to start next note */
- writeCh(channel,0xB0,0);
- /* attack, decay */
- writeOp(channel*2 ,0x60,0xF1);
- writeOp(channel*2+1,0x60,0xF1);
- /* sustain, release */
- writeOp(channel*2 ,0x80,0xF0);
- writeOp(channel*2+1,0x80,0xF0);
- /* low bytes of note */
- writeCh(channel,0xA0,low_note);
- /* volume divider, volume */
- writeOp(channel*2 ,0x40,0x00);
- writeOp(channel*2+1,0x40,0x00);
- /* high bytes of note, play it*/
- writeCh(channel,0xB0,key_on);
- }
- /* test program -- probably should split this off and make this into a library */
- int testProg(void) {
- int key;
- initOPL3();
- clrscr();
- cprintf("press 1-9...");
- do {
- key=getch();
- if (key >= 48 && key <= 57) {
- playTestNote(key-48,1);
- }
- if (key == ' ') {
- /* do a keyoff to start a new note? */
- writeCh(0,0xB0,0b00000000);
- /* key on, octave, frequency */
- writeCh(0,0xB0,0b00110110);
- /* attack, decay */
- writeOp(0,0x60,0xF3);
- writeOp(1,0x60,0xF4);
- /* sustain, release */
- writeOp(0,0x80,0xF0);
- writeOp(1,0x80,0xF0);
- /* volume divider, volume */
- writeOp(0,0x40,0x00);
- writeOp(1,0x40,0x00);
- }
- } while (key != 'q');
- endOPL3();
- return 0;
- }
- /* beginning work on keyboard input now that */
- int keyCheck(void) {
- int key, ext_key;
- while(1) {
- key=-1;
- ext_key=-1;
- if (kbhit()) {
- key=getch();
- if (key == 0) {
- ext_key=getch();
- }
- }
- if (key=='q') { break; }
- if (key!=-1) {
- printf("key:%d\n", key);
- }
- if (ext_key!=-1) {
- printf("extended key:%d\n", ext_key);
- }
- }
- }
- /* key structure for readKey */
- /* values are -1 if not there */
- typedef struct {
- int key;
- int ext;
- } Key;
- /* extended key codes*/
- int key_up = 72;
- int key_down = 80;
- int key_left = 75;
- int key_right = 77;
- /* normal key codes */
- int key_esc = 27;
- int key_tab = 9;
- int key_ctrlQ = 17; /* quit */
- int key_ctrlS = 19; /* save */
- int key_ctrlO = 15; /* load */
- int key_F1=59;
- int key_F2=60;
- int key_F3=61;
- int key_F4=62;
- int key_F5=63;
- int key_F6=64;
- /* why isn't a function like this part of conio.h, anyway? */
- Key readKey() {
- Key result;
- result.key=-1;
- result.ext=-1;
- /* check key */
- if (kbhit()) {
- result.key=getch();
- /* check extended keycode */
- if (result.key == 0) {
- result.ext=getch();
- }
- }
- return result;
- }
- /* each FM operator */
- /* 3kr doesn't bother using all of the parameters because I'm lazy */
- typedef struct {
- /* all except volume and wave are 0-15 */
- int volume; /* 0-63, 63 is loudest */
- int attack;
- int decay;
- int wave; /* 0-7 */
- int modulation;
- int vibrato;
- } Operator;
- /* half of a paired instrument*/
- typedef struct {
- Operator op[2];
- int finetune; /* -127 to 128 range */
- int fm; /* on/off switch */
- int delay; /* not sure if I'm gonna implement this... */
- } ChanBank;
- /* the full instrument as will be used in the tracker */
- typedef struct {
- char name[9]; /* 8 character instrument name */
- ChanBank bank[2];
- } Instrument;
- void initChanBank(ChanBank *bank) {
- bank->op[0].volume=40;
- bank->op[0].attack=15;
- bank->op[0].decay=2;
- bank->op[0].wave=0;
- bank->op[0].modulation=0;
- bank->op[0].vibrato=0;
- bank->op[1].volume=63;
- bank->op[1].attack=15;
- bank->op[1].decay=1;
- bank->op[1].wave=0;
- bank->op[1].modulation=0;
- bank->op[1].vibrato=0;
- bank->finetune=0;
- bank->fm=1;
- bank->delay=0;
- }
- /*
- int min(int a, int b) {
- if (a < b) { return a; }
- return b;
- }
- int max(int a, int b) {
- if (a > b) { return a; }
- return b;
- }
- */
- /* at this moment, channel is just 0-18, might probably stay that way */
- /* hardware channels are paired side-by side in 3kr (eg, 0+1, 2+3, 4+5, etc) */
- /* so we get 9 4-op software channels with fancy-ass features */
- /* todo: deal with delay if I ever bother implementing that */
- /* also, add a volume argument, and the rest of the operator stuff (wave) */
- void playChanBank(ChanBank *bank, int channel, int note, int octave) {
- unsigned char atk_dec, low_note, high_note;
- unsigned int final_note;
- /* key-off, force new note*/
- writeCh(channel,0xB0,0x00);
- /* attack, decay */
- atk_dec = (bank->op[0].attack<<4) & 0xF0;
- atk_dec |= (bank->op[0].decay) & 0x0F;
- writeOp(channel*2 ,0x60,atk_dec);
- atk_dec = (bank->op[1].attack<<4) & 0xF0;
- atk_dec |= (bank->op[1].decay) & 0x0F;
- writeOp(channel*2+1,0x60,atk_dec);
- /* ignoring sustain/release because I'm lazy */
- writeOp(channel*2 ,0x80,0xF0);
- writeOp(channel*2+1,0x80,0xF0);
- /* todo: do volume right */
- writeOp(channel*2 ,0x40,63-bank->op[0].volume);
- writeOp(channel*2+1,0x40,63-bank->op[1].volume);
- /* write note data */
- final_note = note_tbl[note]+bank->finetune;
- low_note = final_note; /* just the low 8 bits, should go in fine */
- octave = (octave & 0b00000111)<<2; /* adjust octave for bit field */
- high_note = (final_note>>8) & 0b00000011; /* top 2 bits of frequency */
- high_note |= 0b00100000 | octave; /* key-on, octave */
- /* with these, the note gets played*/
- writeCh(channel, 0xA0, low_note);
- writeCh(channel, 0xB0, high_note);
- }
- void drawTestEditor(ChanBank *test_bank) {
- clrscr();
- cprintf("modulator\n");
- cprintf(" vol: %d\n", test_bank->op[0].volume);
- cprintf(" atk: %d\n", test_bank->op[0].attack);
- cprintf(" dec: %d\n", test_bank->op[0].decay);
- cprintf("carrier\n");
- cprintf(" vol: %d\n", test_bank->op[1].volume);
- cprintf(" atk: %d\n", test_bank->op[1].attack);
- cprintf(" dec: %d\n", test_bank->op[1].decay);
- }
- /* lazy beta instrument editor */
- void testEditor(void) {
- int op=0, pos=1;
- /*
- pos goes from
- volume 1
- attack 2
- decay 3
- */
- ChanBank test_bank;
- Key key;
- initChanBank(&test_bank);
- clrscr();
- initOPL3();
- drawTestEditor(&test_bank);
- gotoxy(1,2);
- cprintf(">");
- while (1) {
- key=readKey();
- if (key.key != -1) {
- drawTestEditor(&test_bank);
- gotoxy(1,pos+op*4+1);
- cprintf(">");
- if (key.key==key_esc) {
- break;
- }
- if (key.key==' ') {
- playChanBank(&test_bank, 0, 0, 5);
- }
- if (key.key==key_tab) {
- if (op==0) { op=1; } else { op=0; }
- }
- /* movement */
- if (key.ext==key_up) {
- pos--;
- if (pos < 1) { pos=1; }
- }
- if (key.ext==key_down) {
- pos++;
- if (pos > 3) { pos=3; }
- }
- /* settings adjust */
- if (key.ext==key_left) {
- if (pos == 1) {
- test_bank.op[op].volume--;
- if (test_bank.op[op].volume < 0) { test_bank.op[op].volume=0; }
- }
- if (pos == 2) {
- test_bank.op[op].attack--;
- if (test_bank.op[op].attack < 0) { test_bank.op[op].attack=0; }
- }
- if (pos == 3) {
- test_bank.op[op].decay--;
- if (test_bank.op[op].decay < 0) { test_bank.op[op].decay=0; }
- }
- }
- if (key.ext==key_right) {
- if (pos == 1) {
- test_bank.op[op].volume++;
- if (test_bank.op[op].volume > 63) { test_bank.op[op].volume=63; }
- }
- if (pos == 2) {
- test_bank.op[op].attack++;
- if (test_bank.op[op].attack > 15) { test_bank.op[op].attack=15; }
- }
- if (pos == 3) {
- test_bank.op[op].decay++;
- if (test_bank.op[op].decay < 15) { test_bank.op[op].decay=15; }
- }
- }
- }
- }
- endOPL3();
- }
- /* instrument editor demo */
- int main(void) {
- testEditor();
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement