Mercurial > audlegacy-plugins
diff src/adplug/core/d00.cxx @ 12:3da1b8942b8b trunk
[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 03:14:20 -0700 |
parents | src/Input/adplug/core/d00.cxx@13389e613d67 |
children | cae46214b8bf |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/adplug/core/d00.cxx Mon Sep 18 03:14:20 2006 -0700 @@ -0,0 +1,538 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * d00.c - D00 Player by Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * Sorry for the goto's, but the code looks so much nicer now. + * I tried it with while loops but it was just a mess. If you + * can come up with a nicer solution, just tell me. + * + * BUGS: + * Hard restart SR is sometimes wrong + */ + +#include <string.h> +#include <stdio.h> +#include <inttypes.h> + +#include "debug.h" +#include "d00.h" + +#define HIBYTE(val) (val >> 8) +#define LOBYTE(val) (val & 0xff) + +static const unsigned short notetable[12] = // D00 note table + {340,363,385,408,432,458,485,514,544,577,611,647}; + +static inline uint16_t LE_WORD(const uint16_t *val) +{ + const uint8_t *b = (const uint8_t *)val; + return (b[1] << 8) + b[0]; +} + +/*** public methods *************************************/ + +CPlayer *Cd00Player::factory(Copl *newopl) +{ + return new Cd00Player(newopl); +} + +bool Cd00Player::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + d00header *checkhead; + d00header1 *ch; + unsigned long filesize; + int i,ver1=0; + char *str; + + // file validation section + checkhead = new d00header; + f->readString((char *)checkhead, sizeof(d00header)); + + // Check for version 2-4 header + if(strncmp(checkhead->id,"JCH\x26\x02\x66",6) || checkhead->type || + !checkhead->subsongs || checkhead->soundcard) { + // Check for version 0 or 1 header (and .d00 file extension) + delete checkhead; + if(!fp.extension(filename, ".d00")) { fp.close(f); return false; } + ch = new d00header1; + f->seek(0); f->readString((char *)ch, sizeof(d00header1)); + if(ch->version > 1 || !ch->subsongs) + { delete ch; fp.close(f); return false; } + delete ch; + ver1 = 1; + } else + delete checkhead; + + AdPlug_LogWrite("Cd00Player::load(f,\"%s\"): %s format D00 file detected!\n", + filename.c_str(), ver1 ? "Old" : "New"); + + // load section + filesize = fp.filesize(f); f->seek(0); + filedata = new char [filesize + 1]; // 1 byte is needed for old-style DataInfo block + f->readString((char *)filedata, filesize); + fp.close(f); + if(!ver1) { // version 2 and above + header = (struct d00header *)filedata; + version = header->version; + datainfo = (char *)filedata + LE_WORD(&header->infoptr); + inst = (struct Sinsts *)((char *)filedata + LE_WORD(&header->instptr)); + seqptr = (unsigned short *)((char *)filedata + LE_WORD(&header->seqptr)); + for(i=31;i>=0;i--) // erase whitespace + if(header->songname[i] == ' ') + header->songname[i] = '\0'; + else + break; + for(i=31;i>=0;i--) + if(header->author[i] == ' ') + header->author[i] = '\0'; + else + break; + } else { // version 1 + header1 = (struct d00header1 *)filedata; + version = header1->version; + datainfo = (char *)filedata + LE_WORD(&header1->infoptr); + inst = (struct Sinsts *)((char *)filedata + LE_WORD(&header1->instptr)); + seqptr = (unsigned short *)((char *)filedata + LE_WORD(&header1->seqptr)); + } + switch(version) { + case 0: + levpuls = 0; + spfx = 0; + header1->speed = 70; // v0 files default to 70Hz + break; + case 1: + levpuls = (struct Slevpuls *)((char *)filedata + LE_WORD(&header1->lpulptr)); + spfx = 0; + break; + case 2: + levpuls = (struct Slevpuls *)((char *)filedata + LE_WORD(&header->spfxptr)); + spfx = 0; + break; + case 3: + spfx = 0; + levpuls = 0; + break; + case 4: + spfx = (struct Sspfx *)((char *)filedata + LE_WORD(&header->spfxptr)); + levpuls = 0; + break; + } + if((str = strstr(datainfo,"\xff\xff"))) + while((*str == '\xff' || *str == ' ') && str >= datainfo) { + *str = '\0'; str--; + } + else // old-style block + memset((char *)filedata+filesize,0,1); + + rewind(0); + return true; +} + +bool Cd00Player::update() +{ + unsigned char c,cnt,trackend=0,fx,note; + unsigned short ord,*patt,buf,fxop,pattpos; + + // effect handling (timer dependant) + for(c=0;c<9;c++) { + channel[c].slideval += channel[c].slide; setfreq(c); // sliding + vibrato(c); // vibrato + + if(channel[c].spfx != 0xffff) { // SpFX + if(channel[c].fxdel) + channel[c].fxdel--; + else { + channel[c].spfx = LE_WORD(&spfx[channel[c].spfx].ptr); + channel[c].fxdel = spfx[channel[c].spfx].duration; + channel[c].inst = LE_WORD(&spfx[channel[c].spfx].instnr) & 0xfff; + if(spfx[channel[c].spfx].modlev != 0xff) + channel[c].modvol = spfx[channel[c].spfx].modlev; + setinst(c); + if(LE_WORD(&spfx[channel[c].spfx].instnr) & 0x8000) // locked frequency + note = spfx[channel[c].spfx].halfnote; + else // unlocked frequency + note = spfx[channel[c].spfx].halfnote + channel[c].note; + channel[c].freq = notetable[note%12] + ((note/12) << 10); + setfreq(c); + } + channel[c].modvol += spfx[channel[c].spfx].modlevadd; channel[c].modvol &= 63; + setvolume(c); + } + + if(channel[c].levpuls != 0xff) // Levelpuls + if(channel[c].frameskip) + channel[c].frameskip--; + else { + channel[c].frameskip = inst[channel[c].inst].timer; + if(channel[c].fxdel) + channel[c].fxdel--; + else { + channel[c].levpuls = levpuls[channel[c].levpuls].ptr - 1; + channel[c].fxdel = levpuls[channel[c].levpuls].duration; + if(levpuls[channel[c].levpuls].level != 0xff) + channel[c].modvol = levpuls[channel[c].levpuls].level; + } + channel[c].modvol += levpuls[channel[c].levpuls].voladd; channel[c].modvol &= 63; + setvolume(c); + } + } + + // song handling + for(c=0;c<9;c++) + if(version < 3 ? channel[c].del : channel[c].del <= 0x7f) { + if(version == 4) // v4: hard restart SR + if(channel[c].del == inst[channel[c].inst].timer) + if(channel[c].nextnote) + opl->write(0x83 + op_table[c], inst[channel[c].inst].sr); + if(version < 3) + channel[c].del--; + else + if(channel[c].speed) + channel[c].del += channel[c].speed; + else { + channel[c].seqend = 1; + continue; + } + } else { + if(channel[c].speed) { + if(version < 3) + channel[c].del = channel[c].speed; + else { + channel[c].del &= 0x7f; + channel[c].del += channel[c].speed; + } + } else { + channel[c].seqend = 1; + continue; + } + if(channel[c].rhcnt) { // process pending REST/HOLD events + channel[c].rhcnt--; + continue; + } + readorder: // process arrangement (orderlist) + ord = LE_WORD(&channel[c].order[channel[c].ordpos]); + switch(ord) { + case 0xfffe: channel[c].seqend = 1; continue; // end of arrangement stream + case 0xffff: // jump to order + channel[c].ordpos = LE_WORD(&channel[c].order[channel[c].ordpos + 1]); + channel[c].seqend = 1; + goto readorder; + default: + if(ord >= 0x9000) { // set speed + channel[c].speed = ord & 0xff; + ord = LE_WORD(&channel[c].order[channel[c].ordpos - 1]); + channel[c].ordpos++; + } else + if(ord >= 0x8000) { // transpose track + channel[c].transpose = ord & 0xff; + if(ord & 0x100) + channel[c].transpose = -channel[c].transpose; + ord = LE_WORD(&channel[c].order[++channel[c].ordpos]); + } + patt = (unsigned short *)((char *)filedata + LE_WORD(&seqptr[ord])); + break; + } + readseq: // process sequence (pattern) + if(!version) // v0: always initialize rhcnt + channel[c].rhcnt = channel[c].irhcnt; + pattpos = LE_WORD(&patt[channel[c].pattpos]); + if(pattpos == 0xffff) { // pattern ended? + channel[c].pattpos = 0; + channel[c].ordpos++; + goto readorder; + } + cnt = HIBYTE(pattpos); + note = LOBYTE(pattpos); + fx = pattpos >> 12; + fxop = pattpos & 0x0fff; + channel[c].pattpos++; pattpos = LE_WORD(&patt[channel[c].pattpos]); + channel[c].nextnote = LOBYTE(pattpos) & 0x7f; + if(version ? cnt < 0x40 : !fx) { // note event + switch(note) { + case 0: // REST event + case 0x80: + if(!note || version) { + channel[c].key = 0; + setfreq(c); + } + // fall through... + case 0x7e: // HOLD event + if(version) + channel[c].rhcnt = cnt; + channel[c].nextnote = 0; + break; + default: // play note + // restart fx + channel[c].slideval = 0; channel[c].slide = 0; channel[c].vibdepth = 0; + + if(version) { // note handling for v1 and above + if(note > 0x80) // locked note (no channel transpose) + note -= 0x80; + else // unlocked note + note += channel[c].transpose; + channel[c].note = note; // remember note for SpFX + + if(channel[c].ispfx != 0xffff && cnt < 0x20) { // reset SpFX + channel[c].spfx = channel[c].ispfx; + if(LE_WORD(&spfx[channel[c].spfx].instnr) & 0x8000) // locked frequency + note = spfx[channel[c].spfx].halfnote; + else // unlocked frequency + note += spfx[channel[c].spfx].halfnote; + channel[c].inst = LE_WORD(&spfx[channel[c].spfx].instnr) & 0xfff; + channel[c].fxdel = spfx[channel[c].spfx].duration; + if(spfx[channel[c].spfx].modlev != 0xff) + channel[c].modvol = spfx[channel[c].spfx].modlev; + else + channel[c].modvol = inst[channel[c].inst].data[7] & 63; + } + + if(channel[c].ilevpuls != 0xff && cnt < 0x20) { // reset LevelPuls + channel[c].levpuls = channel[c].ilevpuls; + channel[c].fxdel = levpuls[channel[c].levpuls].duration; + channel[c].frameskip = inst[channel[c].inst].timer; + if(levpuls[channel[c].levpuls].level != 0xff) + channel[c].modvol = levpuls[channel[c].levpuls].level; + else + channel[c].modvol = inst[channel[c].inst].data[7] & 63; + } + + channel[c].freq = notetable[note%12] + ((note/12) << 10); + if(cnt < 0x20) // normal note + playnote(c); + else { // tienote + setfreq(c); + cnt -= 0x20; // make count proper + } + channel[c].rhcnt = cnt; + } else { // note handling for v0 + if(cnt < 2) // unlocked note + note += channel[c].transpose; + channel[c].note = note; + + channel[c].freq = notetable[note%12] + ((note/12) << 10); + if(cnt == 1) // tienote + setfreq(c); + else // normal note + playnote(c); + } + break; + } + continue; // event is complete + } else { // effect event + switch(fx) { + case 6: // Cut/Stop Voice + buf = channel[c].inst; + channel[c].inst = 0; + playnote(c); + channel[c].inst = buf; + channel[c].rhcnt = fxop; + continue; // no note follows this event + case 7: // Vibrato + channel[c].vibspeed = fxop & 0xff; + channel[c].vibdepth = fxop >> 8; + channel[c].trigger = fxop >> 9; + break; + case 8: // v0: Duration + if(!version) + channel[c].irhcnt = fxop; + break; + case 9: // New Level + channel[c].vol = fxop & 63; + if(channel[c].vol + channel[c].cvol < 63) // apply channel volume + channel[c].vol += channel[c].cvol; + else + channel[c].vol = 63; + setvolume(c); + break; + case 0xb: // v4: Set SpFX + if(version == 4) + channel[c].ispfx = fxop; + break; + case 0xc: // Set Instrument + channel[c].ispfx = 0xffff; + channel[c].spfx = 0xffff; + channel[c].inst = fxop; + channel[c].modvol = inst[fxop].data[7] & 63; + if(version < 3 && version && inst[fxop].tunelev) // Set LevelPuls + channel[c].ilevpuls = inst[fxop].tunelev - 1; + else { + channel[c].ilevpuls = 0xff; + channel[c].levpuls = 0xff; + } + break; + case 0xd: // Slide up + channel[c].slide = fxop; + break; + case 0xe: // Slide down + channel[c].slide = -fxop; + break; + } + goto readseq; // event is incomplete, note follows + } + } + + for(c=0;c<9;c++) + if(channel[c].seqend) + trackend++; + if(trackend == 9) + songend = 1; + + return !songend; +} + +void Cd00Player::rewind(int subsong) +{ + struct Stpoin { + unsigned short ptr[9]; + unsigned char volume[9],dummy[5]; + } *tpoin; + int i; + + if(version > 1) { // do nothing if subsong > number of subsongs + if(subsong >= header->subsongs) + return; + } else + if(subsong >= header1->subsongs) + return; + + memset(channel,0,sizeof(channel)); + if(version > 1) + tpoin = (struct Stpoin *)((char *)filedata + LE_WORD(&header->tpoin)); + else + tpoin = (struct Stpoin *)((char *)filedata + LE_WORD(&header1->tpoin)); + for(i=0;i<9;i++) { + if(LE_WORD(&tpoin[subsong].ptr[i])) { // track enabled + channel[i].speed = LE_WORD((unsigned short *) + ((char *)filedata + LE_WORD(&tpoin[subsong].ptr[i]))); + channel[i].order = (unsigned short *) + ((char *)filedata + LE_WORD(&tpoin[subsong].ptr[i]) + 2); + } else { // track disabled + channel[i].speed = 0; + channel[i].order = 0; + } + channel[i].ispfx = 0xffff; channel[i].spfx = 0xffff; // no SpFX + channel[i].ilevpuls = 0xff; channel[i].levpuls = 0xff; // no LevelPuls + channel[i].cvol = tpoin[subsong].volume[i] & 0x7f; // our player may savely ignore bit 7 + channel[i].vol = channel[i].cvol; // initialize volume + } + songend = 0; + opl->init(); opl->write(1,32); // reset OPL chip +} + +std::string Cd00Player::gettype() +{ + char tmpstr[40]; + + sprintf(tmpstr,"EdLib packed (version %d)",version > 1 ? header->version : header1->version); + return std::string(tmpstr); +} + +float Cd00Player::getrefresh() +{ + if(version > 1) + return header->speed; + else + return header1->speed; +} + +unsigned int Cd00Player::getsubsongs() +{ + if(version <= 1) // return number of subsongs + return header1->subsongs; + else + return header->subsongs; +} + +/*** private methods *************************************/ + +void Cd00Player::setvolume(unsigned char chan) +{ + unsigned char op = op_table[chan]; + unsigned short insnr = channel[chan].inst; + + opl->write(0x43 + op,(int)(63-((63-(inst[insnr].data[2] & 63))/63.0)*(63-channel[chan].vol)) + + (inst[insnr].data[2] & 192)); + if(inst[insnr].data[10] & 1) + opl->write(0x40 + op,(int)(63-((63-channel[chan].modvol)/63.0)*(63-channel[chan].vol)) + + (inst[insnr].data[7] & 192)); + else + opl->write(0x40 + op,channel[chan].modvol + (inst[insnr].data[7] & 192)); +} + +void Cd00Player::setfreq(unsigned char chan) +{ + unsigned short freq = channel[chan].freq; + + if(version == 4) // v4: apply instrument finetune + freq += inst[channel[chan].inst].tunelev; + + freq += channel[chan].slideval; + opl->write(0xa0 + chan, freq & 255); + if(channel[chan].key) + opl->write(0xb0 + chan, ((freq >> 8) & 31) | 32); + else + opl->write(0xb0 + chan, (freq >> 8) & 31); +} + +void Cd00Player::setinst(unsigned char chan) +{ + unsigned char op = op_table[chan]; + unsigned short insnr = channel[chan].inst; + + // set instrument data + opl->write(0x63 + op, inst[insnr].data[0]); + opl->write(0x83 + op, inst[insnr].data[1]); + opl->write(0x23 + op, inst[insnr].data[3]); + opl->write(0xe3 + op, inst[insnr].data[4]); + opl->write(0x60 + op, inst[insnr].data[5]); + opl->write(0x80 + op, inst[insnr].data[6]); + opl->write(0x20 + op, inst[insnr].data[8]); + opl->write(0xe0 + op, inst[insnr].data[9]); + if(version) + opl->write(0xc0 + chan, inst[insnr].data[10]); + else + opl->write(0xc0 + chan, (inst[insnr].data[10] << 1) + (inst[insnr].tunelev & 1)); +} + +void Cd00Player::playnote(unsigned char chan) +{ + // set misc vars & play + opl->write(0xb0 + chan, 0); // stop old note + setinst(chan); + channel[chan].key = 1; + setfreq(chan); + setvolume(chan); +} + +void Cd00Player::vibrato(unsigned char chan) +{ + if(!channel[chan].vibdepth) + return; + + if(channel[chan].trigger) + channel[chan].trigger--; + else { + channel[chan].trigger = channel[chan].vibdepth; + channel[chan].vibspeed = -channel[chan].vibspeed; + } + channel[chan].freq += channel[chan].vibspeed; + setfreq(chan); +}