diff options
author | Alexey Yakovenko <wakeroid@gmail.com> | 2010-01-09 18:15:36 +0100 |
---|---|---|
committer | Alexey Yakovenko <wakeroid@gmail.com> | 2010-01-09 18:15:36 +0100 |
commit | e12d8cdf0cc670f0f2302eab15fa9811fb5507e1 (patch) | |
tree | 66d8f655c6fadd801f6223c86c73377dec1aa64b /plugins/adplug/adplug | |
parent | 782ab60f2e459d795d7fe0c4fcd3a17f32606d54 (diff) |
merged adplug-2.2
Diffstat (limited to 'plugins/adplug/adplug')
43 files changed, 1959 insertions, 151 deletions
diff --git a/plugins/adplug/adplug/a2m.cpp b/plugins/adplug/adplug/a2m.cpp index 3fd68d98..f506275d 100644 --- a/plugins/adplug/adplug/a2m.cpp +++ b/plugins/adplug/adplug/a2m.cpp @@ -29,7 +29,7 @@ * Following commands are ignored: Gxy, Hxy, Kxy - &xy */ -#include <string.h> +#include <cstring> #include "a2m.h" const unsigned int Ca2mLoader::MAXFREQ = 2000, diff --git a/plugins/adplug/adplug/adl.cpp b/plugins/adplug/adplug/adl.cpp index 40896fdb..ae8c4e99 100644 --- a/plugins/adplug/adplug/adl.cpp +++ b/plugins/adplug/adplug/adl.cpp @@ -44,14 +44,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * $URL: https://svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/engines/kyra/sound_adlib.cpp $ - * $Id: adl.cpp,v 1.9 2006/08/16 00:20:45 dynamite Exp $ + * $Id: adl.cpp,v 1.11 2008/02/11 20:18:27 dynamite Exp $ * */ +#include <cstring> #include <inttypes.h> #include <stdarg.h> #include <assert.h> -#include <string.h> #include "adl.h" #include "debug.h" @@ -2378,16 +2378,22 @@ bool CadlPlayer::load(const std::string &filename, const CFileProvider &fp) // _soundFileLoaded = file; - for(int i = 0; i < 200; i++) - if(_trackEntries[i] != 0xff) + // find last subsong + for(int i = 199; i >= 0; i--) + if(_trackEntries[i] != 0xff) { numsubsongs = i + 1; + break; + } fp.close(f); + cursubsong = 2; + rewind(); return true; } void CadlPlayer::rewind(int subsong) { + if(subsong == -1) subsong = cursubsong; opl->init(); opl->write(1,32); playSoundEffect(subsong); diff --git a/plugins/adplug/adplug/adl.h b/plugins/adplug/adplug/adl.h index b0030c43..203692b0 100644 --- a/plugins/adplug/adplug/adl.h +++ b/plugins/adplug/adplug/adl.h @@ -1,6 +1,20 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * adl.h - ADL player adaption by Simon Peter <dn.tlp@gmx.net> */ @@ -24,7 +38,7 @@ class CadlPlayer: public CPlayer bool load(const std::string &filename, const CFileProvider &fp); bool update(); - void rewind(int subsong); + void rewind(int subsong = -1); // refresh rate is fixed at 72Hz float getrefresh() @@ -33,6 +47,7 @@ class CadlPlayer: public CPlayer } unsigned int getsubsongs(); + unsigned int getsubsong() { return cursubsong; } std::string gettype() { return std::string("Westwood ADL"); } private: diff --git a/plugins/adplug/adplug/adplug.cpp b/plugins/adplug/adplug/adplug.cpp index 32fea695..d02bb047 100644 --- a/plugins/adplug/adplug/adplug.cpp +++ b/plugins/adplug/adplug/adplug.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -19,9 +19,9 @@ * adplug.cpp - CAdPlug utility class, by Simon Peter <dn.tlp@gmx.net> */ +#include <cstring> #include <string> #include <binfile.h> -#include <string.h> #include "adplug.h" #include "debug.h" @@ -35,6 +35,7 @@ #include "sng.h" #include "adtrack.h" #include "bam.h" +#include "cmf.h" #include "d00.h" #include "dfm.h" #include "hsp.h" @@ -62,9 +63,11 @@ #include "rol.h" #include "xsm.h" #include "dro.h" +#include "dro2.h" #include "msc.h" #include "rix.h" #include "adl.h" +#include "jbm.h" /***** CAdPlug *****/ @@ -77,12 +80,13 @@ const CPlayerDesc CAdPlug::allplayers[] = { CPlayerDesc(CadtrackLoader::factory, "Adlib Tracker", ".sng\0"), CPlayerDesc(CamdLoader::factory, "AMUSIC", ".amd\0"), CPlayerDesc(CbamPlayer::factory, "Bob's Adlib Music", ".bam\0"), + CPlayerDesc(CcmfPlayer::factory, "Creative Music File", ".cmf\0"), CPlayerDesc(Cd00Player::factory, "Packed EdLib", ".d00\0"), CPlayerDesc(CdfmLoader::factory, "Digital-FM", ".dfm\0"), CPlayerDesc(ChspLoader::factory, "HSC Packed", ".hsp\0"), CPlayerDesc(CksmPlayer::factory, "Ken Silverman Music", ".ksm\0"), CPlayerDesc(CmadLoader::factory, "Mlat Adlib Tracker", ".mad\0"), - CPlayerDesc(CmidPlayer::factory, "MIDI", ".mid\0.cmf\0.sci\0.laa\0"), + CPlayerDesc(CmidPlayer::factory, "MIDI", ".mid\0.sci\0.laa\0"), CPlayerDesc(CmkjPlayer::factory, "MKJamz", ".mkj\0"), CPlayerDesc(CcffLoader::factory, "Boomtracker", ".cff\0"), CPlayerDesc(CdmoLoader::factory, "TwinTeam", ".dmo\0"), @@ -103,10 +107,12 @@ const CPlayerDesc CAdPlug::allplayers[] = { CPlayerDesc(Cu6mPlayer::factory, "Ultima 6 Music", ".m\0"), CPlayerDesc(CrolPlayer::factory, "Adlib Visual Composer", ".rol\0"), CPlayerDesc(CxsmPlayer::factory, "eXtra Simple Music", ".xsm\0"), - CPlayerDesc(CdroPlayer::factory, "DOSBox Raw OPL", ".dro\0"), + CPlayerDesc(CdroPlayer::factory, "DOSBox Raw OPL v0.1", ".dro\0"), + CPlayerDesc(Cdro2Player::factory, "DOSBox Raw OPL v2.0", ".dro\0"), CPlayerDesc(CmscPlayer::factory, "Adlib MSC Player", ".msc\0"), CPlayerDesc(CrixPlayer::factory, "Softstar RIX OPL Music", ".rix\0"), CPlayerDesc(CadlPlayer::factory, "Westwood ADL", ".adl\0"), + CPlayerDesc(CjbmPlayer::factory, "JBM Adlib Music", ".jbm\0"), CPlayerDesc() }; @@ -124,39 +130,41 @@ const CPlayers &CAdPlug::init_players(const CPlayerDesc pd[]) const CPlayers CAdPlug::players = CAdPlug::init_players(CAdPlug::allplayers); CAdPlugDatabase *CAdPlug::database = 0; -CPlayer *CAdPlug::factory(const char *fn, Copl *opl, const CPlayers &pl, +CPlayer *CAdPlug::factory(const std::string &fn, Copl *opl, const CPlayers &pl, const CFileProvider &fp) { CPlayer *p; CPlayers::const_iterator i; unsigned int j; - AdPlug_LogWrite("*** CAdPlug::factory(\"%s\",opl,fp) ***\n", fn); + AdPlug_LogWrite("*** CAdPlug::factory(\"%s\",opl,fp) ***\n", fn.c_str()); // Try a direct hit by file extension for(i = pl.begin(); i != pl.end(); i++) for(j = 0; (*i)->get_extension(j); j++) if(fp.extension(fn, (*i)->get_extension(j))) { AdPlug_LogWrite("Trying direct hit: %s\n", (*i)->filetype.c_str()); - if((p = (*i)->factory(opl))) + if((p = (*i)->factory(opl))) { if(p->load(fn, fp)) { AdPlug_LogWrite("got it!\n"); AdPlug_LogWrite("--- CAdPlug::factory ---\n"); return p; } else delete p; + } } // Try all players, one by one for(i = pl.begin(); i != pl.end(); i++) { AdPlug_LogWrite("Trying: %s\n", (*i)->filetype.c_str()); - if((p = (*i)->factory(opl))) + if((p = (*i)->factory(opl))) { if(p->load(fn, fp)) { AdPlug_LogWrite("got it!\n"); AdPlug_LogWrite("--- CAdPlug::factory ---\n"); return p; } else delete p; + } } // Unknown file diff --git a/plugins/adplug/adplug/adplug.h b/plugins/adplug/adplug/adplug.h index 54ee042a..e5aec896 100644 --- a/plugins/adplug/adplug/adplug.h +++ b/plugins/adplug/adplug/adplug.h @@ -37,7 +37,7 @@ class CAdPlug public: static const CPlayers players; - static CPlayer *factory(const char *fn, Copl *opl, + static CPlayer *factory(const std::string &fn, Copl *opl, const CPlayers &pl = players, const CFileProvider &fp = CProvider_Filesystem()); diff --git a/plugins/adplug/adplug/bmf.cpp b/plugins/adplug/adplug/bmf.cpp index df403c86..469fde43 100644 --- a/plugins/adplug/adplug/bmf.cpp +++ b/plugins/adplug/adplug/bmf.cpp @@ -40,7 +40,7 @@ comment : inaccurate replaying, because constant outport; in original player it can be 380 or 382. */ -#include <string.h> +#include <cstring> #include "bmf.h" #include "debug.h" diff --git a/plugins/adplug/adplug/cff.cpp b/plugins/adplug/adplug/cff.cpp index 37197fd9..68a11692 100644 --- a/plugins/adplug/adplug/cff.cpp +++ b/plugins/adplug/adplug/cff.cpp @@ -1,6 +1,6 @@ /* AdPlug - Replayer for many OPL2/OPL3 audio file formats. - Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + Copyright (C) 1999 - 2008 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 @@ -24,6 +24,7 @@ slides use previous effect data instead of current. */ +#include <cstring> #include <stdlib.h> #include <string.h> diff --git a/plugins/adplug/adplug/cmf.cpp b/plugins/adplug/adplug/cmf.cpp new file mode 100644 index 00000000..cd3d8006 --- /dev/null +++ b/plugins/adplug/adplug/cmf.cpp @@ -0,0 +1,776 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * cmf.cpp - CMF player by Adam Nielsen <malvineous@shikadi.net> + * Subset of CMF reader in MOPL code (Malvineous' OPL player), no seeking etc. + */ + +#include <stdint.h> // for uintxx_t +#include <cassert> +#include <math.h> // for pow() etc. +#include <string.h> // for memset +#include "debug.h" +#include "cmf.h" + +// ------------------------------ +// OPTIONS +// ------------------------------ + +// The official Creative Labs CMF player seems to ignore the note velocity +// (playing every note at the same volume), but you can uncomment this to +// allow the note velocity to affect the volume (as presumably the composer +// originally intended.) +// +//#define USE_VELOCITY +// +// The Xargon demo song is a good example of a song that uses note velocity. + +// OPL register offsets +#define BASE_CHAR_MULT 0x20 +#define BASE_SCAL_LEVL 0x40 +#define BASE_ATCK_DCAY 0x60 +#define BASE_SUST_RLSE 0x80 +#define BASE_FNUM_L 0xA0 +#define BASE_KEYON_FREQ 0xB0 +#define BASE_RHYTHM 0xBD +#define BASE_WAVE 0xE0 +#define BASE_FEED_CONN 0xC0 + +#define OPLBIT_KEYON 0x20 // Bit in BASE_KEYON_FREQ register for turning a note on + +// Supplied with a channel, return the offset from a base OPL register for the +// Modulator cell (e.g. channel 4's modulator is at offset 0x09. Since 0x60 is +// the attack/decay function, register 0x69 will thus set the attack/decay for +// channel 4's modulator.) (channels go from 0 to 8 inclusive) +#define OPLOFFSET(channel) (((channel) / 3) * 8 + ((channel) % 3)) + +// These 16 instruments are repeated to fill up the 128 available slots. A CMF +// file can override none/some/all of the 128 slots with custom instruments, +// so any that aren't overridden are still available for use with these default +// patches. The Word Rescue CMFs are good examples of songs that rely on these +// default patches. +uint8_t cDefaultPatches[] = +"\x01\x11\x4F\x00\xF1\xD2\x53\x74\x00\x00\x06" +"\x07\x12\x4F\x00\xF2\xF2\x60\x72\x00\x00\x08" +"\x31\xA1\x1C\x80\x51\x54\x03\x67\x00\x00\x0E" +"\x31\xA1\x1C\x80\x41\x92\x0B\x3B\x00\x00\x0E" +"\x31\x16\x87\x80\xA1\x7D\x11\x43\x00\x00\x08" +"\x30\xB1\xC8\x80\xD5\x61\x19\x1B\x00\x00\x0C" +"\xF1\x21\x01\x00\x97\xF1\x17\x18\x00\x00\x08" +"\x32\x16\x87\x80\xA1\x7D\x10\x33\x00\x00\x08" +"\x01\x12\x4F\x00\x71\x52\x53\x7C\x00\x00\x0A" +"\x02\x03\x8D\x00\xD7\xF5\x37\x18\x00\x00\x04" +"\x21\x21\xD1\x00\xA3\xA4\x46\x25\x00\x00\x0A" +"\x22\x22\x0F\x00\xF6\xF6\x95\x36\x00\x00\x0A" +"\xE1\xE1\x00\x00\x44\x54\x24\x34\x02\x02\x07" +"\xA5\xB1\xD2\x80\x81\xF1\x03\x05\x00\x00\x02" +"\x71\x22\xC5\x00\x6E\x8B\x17\x0E\x00\x00\x02" +"\x32\x21\x16\x80\x73\x75\x24\x57\x00\x00\x0E"; + + +CPlayer *CcmfPlayer::factory(Copl *newopl) +{ + return new CcmfPlayer(newopl); +} + +CcmfPlayer::CcmfPlayer(Copl *newopl) : + CPlayer(newopl), + data(NULL), + pInstruments(NULL), + bPercussive(false), + iTranspose(0), + iPrevCommand(0) +{ + assert(OPLOFFSET(1-1) == 0x00); + assert(OPLOFFSET(5-1) == 0x09); + assert(OPLOFFSET(9-1) == 0x12); +} + +CcmfPlayer::~CcmfPlayer() +{ + if (this->data) delete[] data; +} + +bool CcmfPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + + char cSig[4]; + f->readString(cSig, 4); + if ( + (cSig[0] != 'C') || + (cSig[1] != 'T') || + (cSig[2] != 'M') || + (cSig[3] != 'F') + ) { + // Not a CMF file + fp.close(f); + return false; + } + uint16_t iVer = f->readInt(2); + if ((iVer != 0x0101) && (iVer != 0x0100)) { + AdPlug_LogWrite("CMF file is not v1.0 or v1.1 (reports %d.%d)\n", iVer >> 8 , iVer & 0xFF); + fp.close(f); + return false; + } + + this->cmfHeader.iInstrumentBlockOffset = f->readInt(2); + this->cmfHeader.iMusicOffset = f->readInt(2); + this->cmfHeader.iTicksPerQuarterNote = f->readInt(2); + this->cmfHeader.iTicksPerSecond = f->readInt(2); + this->cmfHeader.iTagOffsetTitle = f->readInt(2); + this->cmfHeader.iTagOffsetComposer = f->readInt(2); + this->cmfHeader.iTagOffsetRemarks = f->readInt(2); + f->readString((char *)this->cmfHeader.iChannelsInUse, 16); + this->cmfHeader.iNumInstruments = f->readInt(2); + this->cmfHeader.iTempo = f->readInt(2); + + // Load the instruments + + f->seek(this->cmfHeader.iInstrumentBlockOffset); + this->pInstruments = new SBI[128]; // Always 128 available for use + + for (int i = 0; i < this->cmfHeader.iNumInstruments; i++) { + this->pInstruments[i].op[0].iCharMult = f->readInt(1); + this->pInstruments[i].op[1].iCharMult = f->readInt(1); + this->pInstruments[i].op[0].iScalingOutput = f->readInt(1); + this->pInstruments[i].op[1].iScalingOutput = f->readInt(1); + this->pInstruments[i].op[0].iAttackDecay = f->readInt(1); + this->pInstruments[i].op[1].iAttackDecay = f->readInt(1); + this->pInstruments[i].op[0].iSustainRelease = f->readInt(1); + this->pInstruments[i].op[1].iSustainRelease = f->readInt(1); + this->pInstruments[i].op[0].iWaveSel = f->readInt(1); + this->pInstruments[i].op[1].iWaveSel = f->readInt(1); + this->pInstruments[i].iConnection = f->readInt(1); + f->seek(5, binio::Add); // skip over the padding bytes + } + + // Set the rest of the instruments to the CMF defaults + for (int i = this->cmfHeader.iNumInstruments; i < 128; i++) { + this->pInstruments[i].op[0].iCharMult = cDefaultPatches[(i % 16) * 11 + 0]; + this->pInstruments[i].op[1].iCharMult = cDefaultPatches[(i % 16) * 11 + 1]; + this->pInstruments[i].op[0].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 2]; + this->pInstruments[i].op[1].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 3]; + this->pInstruments[i].op[0].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 4]; + this->pInstruments[i].op[1].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 5]; + this->pInstruments[i].op[0].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 6]; + this->pInstruments[i].op[1].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 7]; + this->pInstruments[i].op[0].iWaveSel = cDefaultPatches[(i % 16) * 11 + 8]; + this->pInstruments[i].op[1].iWaveSel = cDefaultPatches[(i % 16) * 11 + 9]; + this->pInstruments[i].iConnection = cDefaultPatches[(i % 16) * 11 + 10]; + } + + if (this->cmfHeader.iTagOffsetTitle) { + f->seek(this->cmfHeader.iTagOffsetTitle); + this->strTitle = f->readString('\0'); + } + if (this->cmfHeader.iTagOffsetComposer) { + f->seek(this->cmfHeader.iTagOffsetComposer); + this->strComposer = f->readString('\0'); + } + if (this->cmfHeader.iTagOffsetRemarks) { + f->seek(this->cmfHeader.iTagOffsetRemarks); + this->strRemarks = f->readString('\0'); + } + + // Load the MIDI data into memory + f->seek(this->cmfHeader.iMusicOffset); + this->iSongLen = fp.filesize(f) - this->cmfHeader.iMusicOffset; + this->data = new unsigned char[this->iSongLen]; + f->readString((char *)data, this->iSongLen); + + fp.close(f); + rewind(0); + + return true; +} + +bool CcmfPlayer::update() +{ + // This has to be here and not in getrefresh() for some reason. + this->iDelayRemaining = 0; + + // Read in the next event + while (!this->iDelayRemaining) { + uint8_t iCommand = this->data[this->iPlayPointer++]; + if ((iCommand & 0x80) == 0) { + // Running status, use previous command + this->iPlayPointer--; + iCommand = this->iPrevCommand; + } else { + this->iPrevCommand = iCommand; + } + uint8_t iChannel = iCommand & 0x0F; + switch (iCommand & 0xF0) { + case 0x80: { // Note off (two data bytes) + uint8_t iNote = this->data[this->iPlayPointer++]; + uint8_t iVelocity = this->data[this->iPlayPointer++]; // release velocity + this->cmfNoteOff(iChannel, iNote, iVelocity); + break; + } + case 0x90: { // Note on (two data bytes) + uint8_t iNote = this->data[this->iPlayPointer++]; + uint8_t iVelocity = this->data[this->iPlayPointer++]; // attack velocity + if (iVelocity) { + this->cmfNoteOn(iChannel, iNote, iVelocity); + } else { + // This is a note-off instead (velocity == 0) + this->cmfNoteOff(iChannel, iNote, iVelocity); // 64 is the MIDI default note-off velocity + break; + } + break; + } + case 0xA0: { // Polyphonic key pressure (two data bytes) + uint8_t iNote = this->data[this->iPlayPointer++]; + uint8_t iPressure = this->data[this->iPlayPointer++]; + AdPlug_LogWrite("CMF: Key pressure not yet implemented! (wanted ch%d/note %d set to %d)\n", iChannel, iNote, iPressure); + break; + } + case 0xB0: { // Controller (two data bytes) + uint8_t iController = this->data[this->iPlayPointer++]; + uint8_t iValue = this->data[this->iPlayPointer++]; + this->MIDIcontroller(iChannel, iController, iValue); + break; + } + case 0xC0: { // Instrument change (one data byte) + uint8_t iNewInstrument = this->data[this->iPlayPointer++]; + this->chMIDI[iChannel].iPatch = iNewInstrument; + AdPlug_LogWrite("CMF: Remembering MIDI channel %d now uses patch %d\n", iChannel, iNewInstrument); + break; + } + case 0xD0: { // Channel pressure (one data byte) + uint8_t iPressure = this->data[this->iPlayPointer++]; + AdPlug_LogWrite("CMF: Channel pressure not yet implemented! (wanted ch%d set to %d)\n", iChannel, iPressure); + break; + } + case 0xE0: { // Pitch bend (two data bytes) + uint8_t iLSB = this->data[this->iPlayPointer++]; + uint8_t iMSB = this->data[this->iPlayPointer++]; + uint16_t iValue = (iMSB << 7) | iLSB; + // 8192 is middle/off, 0 is -2 semitones, 16384 is +2 semitones + this->chMIDI[iChannel].iPitchbend = iValue; + AdPlug_LogWrite("CMF: Channel %d pitchbent to %d (%+.2f)\n", iChannel + 1, iValue, (float)(iValue - 8192) / 8192); + break; + } + case 0xF0: // System message (arbitrary data bytes) + switch (iCommand) { + case 0xF0: { // Sysex + uint8_t iNextByte; + AdPlug_LogWrite("Sysex message: "); + do { + iNextByte = this->data[this->iPlayPointer++]; + AdPlug_LogWrite("%02X", iNextByte); + } while ((iNextByte & 0x80) == 0); + AdPlug_LogWrite("\n"); + // This will have read in the terminating EOX (0xF7) message too + break; + } + case 0xF1: // MIDI Time Code Quarter Frame + this->data[this->iPlayPointer++]; // message data (ignored) + break; + case 0xF2: // Song position pointer + this->data[this->iPlayPointer++]; // message data (ignored) + this->data[this->iPlayPointer++]; + break; + case 0xF3: // Song select + this->data[this->iPlayPointer++]; // message data (ignored) + AdPlug_LogWrite("CMF: MIDI Song Select is not implemented.\n"); + break; + case 0xF6: // Tune request + break; + case 0xF7: // End of System Exclusive (EOX) - should never be read, should be absorbed by Sysex handling code + break; + + // These messages are "real time", meaning they can be sent between + // the bytes of other messages - but we're lazy and don't handle these + // here (hopefully they're not necessary in a MIDI file, and even less + // likely to occur in a CMF.) + case 0xF8: // Timing clock (sent 24 times per quarter note, only when playing) + case 0xFA: // Start + case 0xFB: // Continue + case 0xFE: // Active sensing (sent every 300ms or MIDI connection assumed lost) + break; + case 0xFC: // Stop + AdPlug_LogWrite("CMF: Received Real Time Stop message (0xFC)\n"); + this->bSongEnd = true; + this->iPlayPointer = 0; // for repeat in endless-play mode + break; + case 0xFF: { // System reset, used as meta-events in a MIDI file + uint8_t iEvent = this->data[this->iPlayPointer++]; + switch (iEvent) { + case 0x2F: // end of track + AdPlug_LogWrite("CMF: End-of-track, stopping playback\n"); + this->bSongEnd = true; + this->iPlayPointer = 0; // for repeat in endless-play mode + break; + default: + AdPlug_LogWrite("CMF: Unknown MIDI meta-event 0xFF 0x%02X\n", iEvent); + break; + } + break; + } + default: + AdPlug_LogWrite("CMF: Unknown MIDI system command 0x%02X\n", iCommand); + break; + } + break; + default: + AdPlug_LogWrite("CMF: Unknown MIDI command 0x%02X\n", iCommand); + break; + } + + if (this->iPlayPointer >= this->iSongLen) { + this->bSongEnd = true; + this->iPlayPointer = 0; // for repeat in endless-play mode + } + + // Read in the number of ticks until the next event + this->iDelayRemaining = this->readMIDINumber(); + } + + return !this->bSongEnd; +} + +void CcmfPlayer::rewind(int subsong) +{ + this->opl->init(); + + // Initialise + + // Enable use of WaveSel register on OPL3 (even though we're only an OPL2!) + // Apparently this enables nine-channel mode? + this->writeOPL(0x01, 0x20); + + // Really make sure CSM+SEL are off (again, Creative's player...) + this->writeOPL(0x08, 0x00); + + // This freq setting is required for the hihat to sound correct at the start + // of funky.cmf, even though it's for an unrelated channel. + // If it's here however, it makes the hihat in Word Rescue's theme.cmf + // sound really bad. + // TODO: How do we figure out whether we need it or not??? + this->writeOPL(BASE_FNUM_L + 8, 514 & 0xFF); + this->writeOPL(BASE_KEYON_FREQ + 8, (1 << 2) | (514 >> 8)); + + // default freqs? + this->writeOPL(BASE_FNUM_L + 7, 509 & 0xFF); + this->writeOPL(BASE_KEYON_FREQ + 7, (2 << 2) | (509 >> 8)); + this->writeOPL(BASE_FNUM_L + 6, 432 & 0xFF); + this->writeOPL(BASE_KEYON_FREQ + 6, (2 << 2) | (432 >> 8)); + + // Amplify AM + VIB depth. Creative's CMF player does this, and there + // doesn't seem to be any way to stop it from doing so - except for the + // non-standard controller 0x63 I added :-) + this->writeOPL(0xBD, 0xC0); + + this->bSongEnd = false; + this->iPlayPointer = 0; + this->iPrevCommand = 0; // just in case + + // Read in the number of ticks until the first event + this->iDelayRemaining = this->readMIDINumber(); + + // Reset song state. This used to be in the constructor, but the XMMS2 + // plugin sets the song length before starting playback. AdPlug plays the + // song in its entirety (with no synth) to determine the song length, which + // results in the state variables below matching the end of the song. When + // the real OPL synth is activated for playback, it no longer matches the + // state variables and the instruments are not set correctly! + for (int i = 0; i < 9; i++) { + this->chOPL[i].iNoteStart = 0; // no note playing atm + this->chOPL[i].iMIDINote = -1; + this->chOPL[i].iMIDIChannel = -1; + this->chOPL[i].iMIDIPatch = -1; + + this->chMIDI[i].iPatch = -2; + this->chMIDI[i].iPitchbend = 8192; + } + for (int i = 9; i < 16; i++) { + this->chMIDI[i].iPatch = -2; + this->chMIDI[i].iPitchbend = 8192; + } + + memset(this->iCurrentRegs, 0, 256); + + return; +} + +// Return value: 1 == 1 second, 2 == 0.5 seconds +float CcmfPlayer::getrefresh() +{ + if (this->iDelayRemaining) { + return (float)this->cmfHeader.iTicksPerSecond / (float)this->iDelayRemaining; + } else { + // Delay-remaining is zero (e.g. start of song) so use a tiny delay + return this->cmfHeader.iTicksPerSecond; // wait for one tick + } +} + +std::string CcmfPlayer::gettitle() +{ + return this->strTitle; +} +std::string CcmfPlayer::getauthor() +{ + return this->strComposer; +} +std::string CcmfPlayer::getdesc() +{ + return this->strRemarks; +} + + +// +// PROTECTED +// + +// Read a variable-length integer from MIDI data +uint32_t CcmfPlayer::readMIDINumber() +{ + uint32_t iValue = 0; + for (int i = 0; i < 4; i++) { + uint8_t iNext = this->data[this->iPlayPointer++]; + iValue <<= 7; + iValue |= (iNext & 0x7F); // ignore the MSB + if ((iNext & 0x80) == 0) break; // last byte has the MSB unset + } + return iValue; +} + +// iChannel: OPL channel (0-8) +// iOperator: 0 == Modulator, 1 == Carrier +// Source - source operator to read from instrument definition +// Dest - destination operator on OPL chip +// iInstrument: Index into this->pInstruments array of CMF instruments +void CcmfPlayer::writeInstrumentSettings(uint8_t iChannel, uint8_t iOperatorSource, uint8_t iOperatorDest, uint8_t iInstrument) +{ + assert(iChannel <= 8); + + uint8_t iOPLOffset = OPLOFFSET(iChannel); + if (iOperatorDest) iOPLOffset += 3; // Carrier if iOperator == 1 (else Modulator) + + this->writeOPL(BASE_CHAR_MULT + iOPLOffset, this->pInstruments[iInstrument].op[iOperatorSource].iCharMult); + this->writeOPL(BASE_SCAL_LEVL + iOPLOffset, this->pInstruments[iInstrument].op[iOperatorSource].iScalingOutput); + this->writeOPL(BASE_ATCK_DCAY + iOPLOffset, this->pInstruments[iInstrument].op[iOperatorSource].iAttackDecay); + this->writeOPL(BASE_SUST_RLSE + iOPLOffset, this->pInstruments[iInstrument].op[iOperatorSource].iSustainRelease); + this->writeOPL(BASE_WAVE + iOPLOffset, this->pInstruments[iInstrument].op[iOperatorSource].iWaveSel); + + // TODO: Check to see whether we should only be loading this for one or both operators + this->writeOPL(BASE_FEED_CONN + iChannel, this->pInstruments[iInstrument].iConnection); + return; +} + +// Write a byte to the OPL "chip" and update the current record of register states +void CcmfPlayer::writeOPL(uint8_t iRegister, uint8_t iValue) +{ + this->opl->write(iRegister, iValue); + this->iCurrentRegs[iRegister] = iValue; + return; +} + +void CcmfPlayer::cmfNoteOn(uint8_t iChannel, uint8_t iNote, uint8_t iVelocity) +{ + uint8_t iBlock = iNote / 12; + if (iBlock > 1) iBlock--; // keep in the same range as the Creative player + //if (iBlock > 7) iBlock = 7; // don't want to go out of range + + double d = pow(2, ( + (double)iNote + ( + (this->chMIDI[iChannel].iPitchbend - 8192) / 8192.0 + ) + ( + this->iTranspose / 128 + ) - 9) / 12.0 - (iBlock - 20)) + * 440.0 / 32.0 / 50000.0; + uint16_t iOPLFNum = (uint16_t)(d+0.5); + if (iOPLFNum > 1023) AdPlug_LogWrite("CMF: This note is out of range! (send this song to malvineous@shikadi.net!)\n"); + + // See if we're playing a rhythm mode percussive instrument + if ((iChannel > 10) && (this->bPercussive)) { + uint8_t iPercChannel = this->getPercChannel(iChannel); + + // Will have to set every time (easier) than figuring out whether the mod + // or car needs to be changed. + //if (this->chOPL[iPercChannel].iMIDIPatch != this->chMIDI[iChannel].iPatch) { + this->MIDIchangeInstrument(iPercChannel, iChannel, this->chMIDI[iChannel].iPatch); + //} + + /* Velocity calculations - TODO: Work out the proper formula + + iVelocity -> iLevel (values generated by Creative's player) + 7f -> 00 + 7c -> 00 + + 7b -> 09 + 73 -> 0a + 6b -> 0b + 63 -> 0c + 5b -> 0d + 53 -> 0e + 4b -> 0f + 43 -> 10 + 3b -> 11 + 33 -> 13 + 2b -> 15 + 23 -> 19 + 1b -> 1b + 13 -> 1d + 0b -> 1f + 03 -> 21 + + 02 -> 21 + 00 -> N/A (note off) + */ + // Approximate formula, need to figure out more accurate one (my maths isn't so good...) + int iLevel = 0x25 - sqrt(iVelocity * 16/*6*/);//(127 - iVelocity) * 0x20 / 127; + if (iVelocity > 0x7b) iLevel = 0; // full volume + if (iLevel < 0) iLevel = 0; + if (iLevel > 0x3F) iLevel = 0x3F; + //if (iVelocity < 0x40) iLevel = 0x10; + + int iOPLOffset = BASE_SCAL_LEVL + OPLOFFSET(iPercChannel); + //if ((iChannel == 11) || (iChannel == 12) || (iChannel == 14)) { + if (iChannel == 11) iOPLOffset += 3; // only do bassdrum carrier for volume control + //iOPLOffset += 3; // carrier + this->writeOPL(iOPLOffset, (this->iCurrentRegs[iOPLOffset] & ~0x3F) | iLevel);//(iVelocity * 0x3F / 127)); + //} + // Bass drum (ch11) uses both operators + //if (iChannel == 11) this->writeOPL(iOPLOffset + 3, (this->iCurrentRegs[iOPLOffset + 3] & ~0x3F) | iLevel); + +/* #ifdef USE_VELOCITY // Official CMF player seems to ignore velocity levels + uint16_t iLevel = 0x2F - (iVelocity * 0x2F / 127); // 0x2F should be 0x3F but it's too quiet then + AdPlug_LogWrite("%02X + vel %d (lev %02X) == %02X\n", this->iCurrentRegs[iOPLOffset], iVelocity, iLevel, (this->iCurrentRegs[iOPLOffset] & ~0x3F) | iLevel); + //this->writeOPL(iOPLOffset, (this->iCurrentRegs[iOPLOffset] & ~0x3F) | (0x3F - (iVelocity >> 1)));//(iVelocity * 0x3F / 127)); + this->writeOPL(iOPLOffset, (this->iCurrentRegs[iOPLOffset] & ~0x3F) | iLevel);//(iVelocity * 0x3F / 127)); + #endif*/ + + // Apparently you can't set the frequency for the cymbal or hihat? + // Vinyl requires you don't set it, Kiloblaster requires you do! + this->writeOPL(BASE_FNUM_L + iPercChannel, iOPLFNum & 0xFF); + this->writeOPL(BASE_KEYON_FREQ + iPercChannel, (iBlock << 2) | ((iOPLFNum >> 8) & 0x03)); + + uint8_t iBit = 1 << (15 - iChannel); + + // Turn the perc instrument off if it's already playing (OPL can't do + // polyphonic notes w/ percussion) + if (this->iCurrentRegs[BASE_RHYTHM] & iBit) this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] & ~iBit); + + // I wonder whether we need to delay or anything here? + + // Turn the note on + //if (iChannel == 15) { + this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] | iBit); + //AdPlug_LogWrite("CMF: Note %d on MIDI channel %d (mapped to OPL channel %d-1) - vel %02X, fnum %d/%d\n", iNote, iChannel, iPercChannel+1, iVelocity, iOPLFNum, iBlock); + //} + + this->chOPL[iPercChannel].iNoteStart = ++this->iNoteCount; + this->chOPL[iPercChannel].iMIDIChannel = iChannel; + this->chOPL[iPercChannel].iMIDINote = iNote; + + } else { // Non rhythm-mode or a normal instrument channel + + // Figure out which OPL channel to play this note on + int iOPLChannel = -1; + int iNumChannels = this->bPercussive ? 6 : 9; + for (int i = iNumChannels - 1; i >= 0; i--) { + // If there's no note playing on this OPL channel, use that + if (this->chOPL[i].iNoteStart == 0) { + iOPLChannel = i; + // See if this channel is already set to the instrument we want. + if (this->chOPL[i].iMIDIPatch == this->chMIDI[iChannel].iPatch) { + // It is, so stop searching + break; + } // else keep searching just in case there's a better match + } + } + if (iOPLChannel == -1) { + // All channels were in use, find the one with the longest note + iOPLChannel = 0; + int iEarliest = this->chOPL[0].iNoteStart; + for (int i = 1; i < iNumChannels; i++) { + if (this->chOPL[i].iNoteStart < iEarliest) { + // Found a channel with a note being played for longer + iOPLChannel = i; + iEarliest = this->chOPL[i].iNoteStart; + } + } + AdPlug_LogWrite("CMF: Too many polyphonic notes, cutting note on channel %d\n", iOPLChannel); + } + + // Run through all the channels with negative notestart values - these + // channels have had notes recently stop - and increment the counter + // to slowly move the channel closer to being reused for a future note. + //for (int i = 0; i < iNumChannels; i++) { + // if (this->chOPL[i].iNoteStart < 0) this->chOPL[i].iNoteStart++; + //} + + // Now the new note should be played on iOPLChannel, but see if the instrument + // is right first. + if (this->chOPL[iOPLChannel].iMIDIPatch != this->chMIDI[iChannel].iPatch) { + this->MIDIchangeInstrument(iOPLChannel, iChannel, this->chMIDI[iChannel].iPatch); + } + + this->chOPL[iOPLChannel].iNoteStart = ++this->iNoteCount; + this->chOPL[iOPLChannel].iMIDIChannel = iChannel; + this->chOPL[iOPLChannel].iMIDINote = iNote; + + #ifdef USE_VELOCITY // Official CMF player seems to ignore velocity levels + // Adjust the channel volume to match the note velocity + uint8_t iOPLOffset = BASE_SCAL_LEVL + OPLOFFSET(iChannel) + 3; // +3 == Carrier + uint16_t iLevel = 0x2F - (iVelocity * 0x2F / 127); // 0x2F should be 0x3F but it's too quiet then + this->writeOPL(iOPLOffset, (this->iCurrentRegs[iOPLOffset] & ~0x3F) | iLevel); + #endif + + // Set the frequency and play the note + this->writeOPL(BASE_FNUM_L + iOPLChannel, iOPLFNum & 0xFF); + this->writeOPL(BASE_KEYON_FREQ + iOPLChannel, OPLBIT_KEYON | (iBlock << 2) | ((iOPLFNum & 0x300) >> 8)); + } + return; +} + +void CcmfPlayer::cmfNoteOff(uint8_t iChannel, uint8_t iNote, uint8_t iVelocity) +{ + if ((iChannel > 10) && (this->bPercussive)) { + int iOPLChannel = this->getPercChannel(iChannel); + if (this->chOPL[iOPLChannel].iMIDINote != iNote) return; // there's a different note playing now + this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] & ~(1 << (15 - iChannel))); + this->chOPL[iOPLChannel].iNoteStart = 0; // channel free + } else { // Non rhythm-mode or a normal instrument channel + int iOPLChannel = -1; + int iNumChannels = this->bPercussive ? 6 : 9; + for (int i = 0; i < iNumChannels; i++) { + if ( + (this->chOPL[i].iMIDIChannel == iChannel) && + (this->chOPL[i].iMIDINote == iNote) && + (this->chOPL[i].iNoteStart != 0) + ) { + // Found the note, switch it off + this->chOPL[i].iNoteStart = 0; + iOPLChannel = i; + break; + } + } + if (iOPLChannel == -1) return; + + this->writeOPL(BASE_KEYON_FREQ + iOPLChannel, this->iCurrentRegs[BASE_KEYON_FREQ + iOPLChannel] & ~OPLBIT_KEYON); + } + return; +} + +uint8_t CcmfPlayer::getPercChannel(uint8_t iChannel) +{ + switch (iChannel) { + case 11: return 7-1; // Bass drum + case 12: return 8-1; // Snare drum + case 13: return 9-1; // Tom tom + case 14: return 9-1; // Top cymbal + case 15: return 8-1; // Hihat + } + AdPlug_LogWrite("CMF ERR: Tried to get the percussion channel from MIDI channel %d - this shouldn't happen!\n", iChannel); + return 0; +} + + +void CcmfPlayer::MIDIchangeInstrument(uint8_t iOPLChannel, uint8_t iMIDIChannel, uint8_t iNewInstrument) +{ + if ((iMIDIChannel > 10) && (this->bPercussive)) { + switch (iMIDIChannel) { + case 11: // Bass drum (operator 13+16 == channel 7 modulator+carrier) + this->writeInstrumentSettings(7-1, 0, 0, iNewInstrument); + this->writeInstrumentSettings(7-1, 1, 1, iNewInstrument); + break; + case 12: // Snare drum (operator 17 == channel 8 carrier) + //case 15: + this->writeInstrumentSettings(8-1, 0, 1, iNewInstrument); + + // + //this->writeInstrumentSettings(8-1, 0, 0, iNewInstrument); + break; + case 13: // Tom tom (operator 15 == channel 9 modulator) + //case 14: + this->writeInstrumentSettings(9-1, 0, 0, iNewInstrument); + + // + //this->writeInstrumentSettings(9-1, 0, 1, iNewInstrument); + break; + case 14: // Top cymbal (operator 18 == channel 9 carrier) + this->writeInstrumentSettings(9-1, 0, 1, iNewInstrument); + break; + case 15: // Hi-hat (operator 14 == channel 8 modulator) + this->writeInstrumentSettings(8-1, 0, 0, iNewInstrument); + break; + default: + AdPlug_LogWrite("CMF: Invalid MIDI channel %d (not melodic and not percussive!)\n", iMIDIChannel + 1); + break; + } + this->chOPL[iOPLChannel].iMIDIPatch = iNewInstrument; + } else { + // Standard nine OPL channels + this->writeInstrumentSettings(iOPLChannel, 0, 0, iNewInstrument); + this->writeInstrumentSettings(iOPLChannel, 1, 1, iNewInstrument); + this->chOPL[iOPLChannel].iMIDIPatch = iNewInstrument; + } + return; +} + +void CcmfPlayer::MIDIcontroller(uint8_t iChannel, uint8_t iController, uint8_t iValue) +{ + switch (iController) { + case 0x63: + // Custom extension to allow CMF files to switch the AM+VIB depth on and + // off (officially both are on, and there's no way to switch them off.) + // Controller values: + // 0 == AM+VIB off + // 1 == VIB on + // 2 == AM on + // 3 == AM+VIB on + if (iValue) { + this->writeOPL(BASE_RHYTHM, (this->iCurrentRegs[BASE_RHYTHM] & ~0xC0) | (iValue << 6)); // switch AM+VIB extension on + } else { + this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] & ~0xC0); // switch AM+VIB extension off + } + AdPlug_LogWrite("CMF: AM+VIB depth change - AM %s, VIB %s\n", + (this->iCurrentRegs[BASE_RHYTHM] & 0x80) ? "on" : "off", + (this->iCurrentRegs[BASE_RHYTHM] & 0x40) ? "on" : "off"); + break; + case 0x66: + AdPlug_LogWrite("CMF: Song set marker to 0x%02X\n", iValue); + break; + case 0x67: + this->bPercussive = (iValue != 0); + if (this->bPercussive) { + this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] | 0x20); // switch rhythm-mode on + } else { + this->writeOPL(BASE_RHYTHM, this->iCurrentRegs[BASE_RHYTHM] & ~0x20); // switch rhythm-mode off + } + AdPlug_LogWrite("CMF: Percussive/rhythm mode %s\n", this->bPercussive ? "enabled" : "disabled"); + break; + case 0x68: + // TODO: Shouldn't this just affect the one channel, not the whole song? -- have pitchbends for that + this->iTranspose = iValue; + AdPlug_LogWrite("CMF: Transposing all notes up by %d * 1/128ths of a semitone.\n", iValue); + break; + case 0x69: + this->iTranspose = -iValue; + AdPlug_LogWrite("CMF: Transposing all notes down by %d * 1/128ths of a semitone.\n", iValue); + break; + default: + AdPlug_LogWrite("CMF: Unsupported MIDI controller 0x%02X, ignoring.\n", iController); + break; + } + return; +} diff --git a/plugins/adplug/adplug/cmf.h b/plugins/adplug/adplug/cmf.h new file mode 100644 index 00000000..e4347426 --- /dev/null +++ b/plugins/adplug/adplug/cmf.h @@ -0,0 +1,112 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * cmf.h - CMF player by Adam Nielsen <malvineous@shikadi.net> + */ + +#include <stdint.h> // for uintxx_t +#include "player.h" + +typedef struct { + uint16_t iInstrumentBlockOffset; + uint16_t iMusicOffset; + uint16_t iTicksPerQuarterNote; + uint16_t iTicksPerSecond; + uint16_t iTagOffsetTitle; + uint16_t iTagOffsetComposer; + uint16_t iTagOffsetRemarks; + uint8_t iChannelsInUse[16]; + uint16_t iNumInstruments; + uint16_t iTempo; +} CMFHEADER; + +typedef struct { + uint8_t iCharMult; + uint8_t iScalingOutput; + uint8_t iAttackDecay; + uint8_t iSustainRelease; + uint8_t iWaveSel; +} OPERATOR; + +typedef struct { + OPERATOR op[2]; // 0 == modulator, 1 == carrier + uint8_t iConnection; +} SBI; + +typedef struct { + int iPatch; // MIDI patch for this channel + int iPitchbend; // Current pitchbend amount for this channel +} MIDICHANNEL; + +typedef struct { + int iNoteStart; // When the note started playing (longest notes get cut first, 0 == channel free) + int iMIDINote; // MIDI note number currently being played on this OPL channel + int iMIDIChannel; // Source MIDI channel where this note came from + int iMIDIPatch; // Current MIDI patch set on this OPL channel +} OPLCHANNEL; + +class CcmfPlayer: public CPlayer +{ + private: + uint8_t *data; // song data (CMF music block) + int iPlayPointer; // Current location of playback pointer + int iSongLen; // Max value for iPlayPointer + CMFHEADER cmfHeader; + SBI *pInstruments; + bool bPercussive; // are rhythm-mode instruments enabled? + uint8_t iCurrentRegs[256]; // Current values in the OPL chip + int iTranspose; // Transpose amount for entire song (between -128 and +128) + uint8_t iPrevCommand; // Previous command (used for repeated MIDI commands, as the seek and playback code need to share this) + + int iNoteCount; // Used to count how long notes have been playing for + MIDICHANNEL chMIDI[16]; + OPLCHANNEL chOPL[9]; + + // Additions for AdPlug's design + int iDelayRemaining; + bool bSongEnd; + std::string strTitle, strComposer, strRemarks; + + public: + static CPlayer *factory(Copl *newopl); + + CcmfPlayer(Copl *newopl); + ~CcmfPlayer(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { return std::string("Creative Music File (CMF)"); }; + std::string gettitle(); + std::string getauthor(); + std::string getdesc(); + + protected: + uint32_t readMIDINumber(); + void writeInstrumentSettings(uint8_t iChannel, uint8_t iOperatorSource, uint8_t iOperatorDest, uint8_t iInstrument); + void writeOPL(uint8_t iRegister, uint8_t iValue); + void cmfNoteOn(uint8_t iChannel, uint8_t iNote, uint8_t iVelocity); + void cmfNoteOff(uint8_t iChannel, uint8_t iNote, uint8_t iVelocity); + uint8_t getPercChannel(uint8_t iChannel); + void MIDIchangeInstrument(uint8_t iOPLChannel, uint8_t iMIDIChannel, uint8_t iNewInstrument); + void MIDIcontroller(uint8_t iChannel, uint8_t iController, uint8_t iValue); + +}; diff --git a/plugins/adplug/adplug/d00.cpp b/plugins/adplug/adplug/d00.cpp index 7aa042d1..0644b84b 100644 --- a/plugins/adplug/adplug/d00.cpp +++ b/plugins/adplug/adplug/d00.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -177,7 +177,7 @@ bool Cd00Player::update() setvolume(c); } - if(channel[c].levpuls != 0xff) // Levelpuls + if(channel[c].levpuls != 0xff) { // Levelpuls if(channel[c].frameskip) channel[c].frameskip--; else { @@ -193,6 +193,7 @@ bool Cd00Player::update() channel[c].modvol += levpuls[channel[c].levpuls].voladd; channel[c].modvol &= 63; setvolume(c); } + } } // song handling @@ -413,6 +414,8 @@ void Cd00Player::rewind(int subsong) } *tpoin; int i; + if(subsong == -1) subsong = cursubsong; + if(version > 1) { // do nothing if subsong > number of subsongs if(subsong >= header->subsongs) return; @@ -442,6 +445,7 @@ void Cd00Player::rewind(int subsong) } songend = 0; opl->init(); opl->write(1,32); // reset OPL chip + cursubsong = subsong; } std::string Cd00Player::gettype() diff --git a/plugins/adplug/adplug/d00.h b/plugins/adplug/adplug/d00.h index 50b0de3c..d0bf4b24 100644 --- a/plugins/adplug/adplug/d00.h +++ b/plugins/adplug/adplug/d00.h @@ -91,7 +91,7 @@ class Cd00Player: public CPlayer unsigned char duration,ptr; } *levpuls; - unsigned char songend,version; + unsigned char songend,version,cursubsong; char *datainfo; unsigned short *seqptr; d00header *header; diff --git a/plugins/adplug/adplug/dro.cpp b/plugins/adplug/adplug/dro.cpp index eb97c0f8..8342fe89 100644 --- a/plugins/adplug/adplug/dro.cpp +++ b/plugins/adplug/adplug/dro.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2007 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 @@ -23,8 +23,8 @@ * NOTES: 3-oct-04: the DRO format is not yet finalized. beware. */ +#include <cstring> #include <stdio.h> -#include <string.h> #include "dro.h" @@ -59,9 +59,9 @@ bool CdroPlayer::load(const std::string &filename, const CFileProvider &fp) // load section mstotal = f->readInt(4); // Total milliseconds in file length = f->readInt(4); // Total data bytes in file - f->ignore(1); // Type of opl data this can contain - ignored + f->ignore(4); // Type of opl data this can contain - ignored data = new unsigned char [length]; - for (i=0;i<length;i++) + for (i=0;i<length;i++) data[i]=f->readInt(1); fp.close(f); rewind(0); diff --git a/plugins/adplug/adplug/dro.h b/plugins/adplug/adplug/dro.h index 0f59e7b1..995da6cc 100644 --- a/plugins/adplug/adplug/dro.h +++ b/plugins/adplug/adplug/dro.h @@ -40,7 +40,7 @@ class CdroPlayer: public CPlayer std::string gettype() { - return std::string("DOSBox Raw OPL"); + return std::string("DOSBox Raw OPL v0.1"); } protected: diff --git a/plugins/adplug/adplug/dro2.cpp b/plugins/adplug/adplug/dro2.cpp new file mode 100644 index 00000000..e11196e7 --- /dev/null +++ b/plugins/adplug/adplug/dro2.cpp @@ -0,0 +1,142 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dro2.cpp - DOSBox Raw OPL v2.0 player by Adam Nielsen <malvineous@shikadi.net> + */ + +#include <cstring> +#include <stdio.h> + +#include "dro2.h" + +CPlayer *Cdro2Player::factory(Copl *newopl) +{ + return new Cdro2Player(newopl); +} + +Cdro2Player::Cdro2Player(Copl *newopl) : + CPlayer(newopl), + piConvTable(NULL), + data(0) +{ +} + +Cdro2Player::~Cdro2Player() +{ + if (this->data) delete[] this->data; + if (this->piConvTable) delete[] this->piConvTable; +} + +bool Cdro2Player::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); + if (!f) return false; + + char id[8]; + f->readString(id, 8); + if (strncmp(id, "DBRAWOPL", 8)) { + fp.close(f); + return false; + } + int version = f->readInt(4); + if (version != 0x2) { + fp.close(f); + return false; + } + + this->iLength = f->readInt(4) * 2; // stored in file as number of byte pairs + f->ignore(4); // Length in milliseconds + f->ignore(1); /// OPL type (0 == OPL2, 1 == Dual OPL2, 2 == OPL3) + int iFormat = f->readInt(1); + if (iFormat != 0) { + fp.close(f); + return false; + } + int iCompression = f->readInt(1); + if (iCompression != 0) { + fp.close(f); + return false; + } + this->iCmdDelayS = f->readInt(1); + this->iCmdDelayL = f->readInt(1); + this->iConvTableLen = f->readInt(1); + + this->piConvTable = new uint8_t[this->iConvTableLen]; + f->readString((char *)this->piConvTable, this->iConvTableLen); + + this->data = new uint8_t[this->iLength]; + f->readString((char *)this->data, this->iLength); + + fp.close(f); + rewind(0); + + return true; +} + +bool Cdro2Player::update() +{ + while (this->iPos < this->iLength) { + int iIndex = this->data[this->iPos++]; + int iValue = this->data[this->iPos++]; + + // Short delay + if (iIndex == this->iCmdDelayS) { + this->iDelay = iValue + 1; + return true; + + // Long delay + } else if (iIndex == this->iCmdDelayL) { + this->iDelay = (iValue + 1) << 8; + return true; + + // Normal write + } else { + if (iIndex & 0x80) { + // High bit means use second chip in dual-OPL2 config + this->opl->setchip(1); + iIndex &= 0x7F; + } else { + this->opl->setchip(0); + } + if (iIndex > this->iConvTableLen) { + printf("DRO2: Error - index beyond end of codemap table! Corrupted .dro?\n"); + return false; // EOF + } + int iReg = this->piConvTable[iIndex]; + this->opl->write(iReg, iValue); + } + + } + + // This won't result in endless-play using Adplay, but IMHO that code belongs + // in Adplay itself, not here. + return this->iPos < this->iLength; +} + +void Cdro2Player::rewind(int subsong) +{ + this->iDelay = 0; + this->iPos = 0; + opl->init(); +} + +float Cdro2Player::getrefresh() +{ + if (this->iDelay > 0) return 1000.0 / this->iDelay; + else return 1000.0; +} diff --git a/plugins/adplug/adplug/dro2.h b/plugins/adplug/adplug/dro2.h new file mode 100644 index 00000000..fdd2f06d --- /dev/null +++ b/plugins/adplug/adplug/dro2.h @@ -0,0 +1,60 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dro2.h - DOSBox Raw OPL v2.0 player by Adam Nielsen <malvineous@shikadi.net> + */ + +#include <stdint.h> // for uintxx_t +#include "player.h" + +class Cdro2Player: public CPlayer +{ + protected: + uint8_t iCmdDelayS, iCmdDelayL; + int iConvTableLen; + uint8_t *piConvTable; + + uint8_t *data; + int iLength; + int iPos; + int iDelay; + + + public: + static CPlayer *factory(Copl *newopl); + + Cdro2Player(Copl *newopl); + ~Cdro2Player(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { + return std::string("DOSBox Raw OPL v2.0"); + } + + protected: + //unsigned char *data; + //unsigned long pos,length; + //unsigned long msdone,mstotal; + //unsigned short delay; + //unsigned char index, opl3_mode; +}; diff --git a/plugins/adplug/adplug/dtm.cpp b/plugins/adplug/adplug/dtm.cpp index 3d022ae8..91ea837c 100644 --- a/plugins/adplug/adplug/dtm.cpp +++ b/plugins/adplug/adplug/dtm.cpp @@ -22,7 +22,7 @@ NOTE: Panning (Ex) effect is ignored. */ -#include <string.h> +#include <cstring> #include "dtm.h" /* -------- Public Methods -------------------------------- */ diff --git a/plugins/adplug/adplug/fmc.cpp b/plugins/adplug/adplug/fmc.cpp index c6839a61..75cd99a7 100644 --- a/plugins/adplug/adplug/fmc.cpp +++ b/plugins/adplug/adplug/fmc.cpp @@ -19,7 +19,7 @@ fmc.cpp - FMC Loader by Riven the Mage <riven@ok.ru> */ -#include <string.h> +#include <cstring> #include "fmc.h" /* -------- Public Methods -------------------------------- */ @@ -115,9 +115,9 @@ bool CfmcLoader::load(const std::string &filename, const CFileProvider &fp) tracks[t][k].param2 = event.byte2 & 0x0F; // fix effects - if (tracks[t][k].command == 0x0E) // 0x0E (14): Retrig + if (tracks[t][k].command == 0x0E) // 0x0E (14): Retrig tracks[t][k].param1 = 3; - if (tracks[t][k].command == 0x1A) // 0x1A (26): Volume Slide + if (tracks[t][k].command == 0x1A) { // 0x1A (26): Volume Slide if (tracks[t][k].param1 > tracks[t][k].param2) { tracks[t][k].param1 -= tracks[t][k].param2; @@ -128,6 +128,7 @@ bool CfmcLoader::load(const std::string &filename, const CFileProvider &fp) tracks[t][k].param2 -= tracks[t][k].param1; tracks[t][k].param1 = 0; } + } } t++; diff --git a/plugins/adplug/adplug/fprovide.h b/plugins/adplug/adplug/fprovide.h index 2f1a0b62..572f28ac 100644 --- a/plugins/adplug/adplug/fprovide.h +++ b/plugins/adplug/adplug/fprovide.h @@ -32,7 +32,7 @@ public: { } - virtual binistream *open(std::string filename) const = 0; + virtual binistream *open(std::string) const = 0; virtual void close(binistream *) const = 0; static bool extension(const std::string &filename, diff --git a/plugins/adplug/adplug/hsc.h b/plugins/adplug/adplug/hsc.h index d09b8906..4a9e2d27 100644 --- a/plugins/adplug/adplug/hsc.h +++ b/plugins/adplug/adplug/hsc.h @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2002 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2007 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 @@ -31,7 +31,7 @@ class ChscPlayer: public CPlayer ChscPlayer(Copl *newopl): CPlayer(newopl), mtkmode(0) {} - bool load(const std::string &fn, const CFileProvider &fp); + bool load(const std::string &filename, const CFileProvider &fp); bool update(); void rewind(int subsong); float getrefresh() { return 18.2f; }; // refresh rate is fixed at 18.2Hz @@ -48,16 +48,16 @@ class ChscPlayer: public CPlayer protected: struct hscnote { unsigned char note, effect; - }; // note type in HSC pattern + }; // note type in HSC pattern struct hscchan { unsigned char inst; // current instrument signed char slide; // used for manual slide-effects unsigned short freq; // actual replaying frequency - }; // HSC channel data + }; // HSC channel data - hscchan channel[9]; // player channel-info - unsigned char instr[128][12]; // instrument data + hscchan channel[9]; // player channel-info + unsigned char instr[128][12]; // instrument data unsigned char song[0x80]; // song-arrangement (MPU-401 Trakker enhanced) hscnote patterns[50][64*9]; // pattern data unsigned char pattpos,songpos, // various bytes & flags diff --git a/plugins/adplug/adplug/imf.cpp b/plugins/adplug/adplug/imf.cpp index 37af7aca..c525ac8b 100644 --- a/plugins/adplug/adplug/imf.cpp +++ b/plugins/adplug/adplug/imf.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -105,7 +105,7 @@ bool CimfPlayer::load(const std::string &filename, const CFileProvider &fp) } // read footer, if any - if(fsize && (fsize < flsize - 2 - mfsize)) + if(fsize && (fsize < flsize - 2 - mfsize)) { if(f->readInt(1) == 0x1a) { // Adam Nielsen's footer format track_name = f->readString(); @@ -119,6 +119,7 @@ bool CimfPlayer::load(const std::string &filename, const CFileProvider &fp) f->readString(footer, footerlen); footer[footerlen] = '\0'; // Make ASCIIZ string } + } rate = getrate(filename, fp, f); fp.close(f); diff --git a/plugins/adplug/adplug/jbm.cpp b/plugins/adplug/adplug/jbm.cpp new file mode 100644 index 00000000..0990001b --- /dev/null +++ b/plugins/adplug/adplug/jbm.cpp @@ -0,0 +1,293 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Johannes Bjerregaard's JBM Adlib Music Format player for AdPlug + * Written by Dennis Lindroos <lindroos@nls.fi>, February-March 2007 + * - Designed and coded from scratch (only frequency-table taken from MUSIC.BIN) + * - The percussion mode is buggy (?) but i'm not good enough to find them + * and honestly i think the melodic-mode tunes are much better ;) + * + * This version doesn't use the binstr.h functions (coded with custom func.) + * This is my first attempt on writing a musicplayer for AdPlug, and i'm not + * coding C++ very often.. + * + * Released under the terms of the GNU General Public License. + */ + +#include "jbm.h" + +static const unsigned short notetable[96] = { + 0x0158, 0x016d, 0x0183, 0x019a, 0x01b2, 0x01cc, 0x01e7, 0x0204, + 0x0223, 0x0244, 0x0266, 0x028b, 0x0558, 0x056d, 0x0583, 0x059a, + 0x05b2, 0x05cc, 0x05e7, 0x0604, 0x0623, 0x0644, 0x0666, 0x068b, + 0x0958, 0x096d, 0x0983, 0x099a, 0x09b2, 0x09cc, 0x09e7, 0x0a04, + 0x0a23, 0x0a44, 0x0a66, 0x0a8b, 0x0d58, 0x0d6d, 0x0d83, 0x0d9a, + 0x0db2, 0x0dcc, 0x0de7, 0x0e04, 0x0e23, 0x0e44, 0x0e66, 0x0e8b, + 0x1158, 0x116d, 0x1183, 0x119a, 0x11b2, 0x11cc, 0x11e7, 0x1204, + 0x1223, 0x1244, 0x1266, 0x128b, 0x1558, 0x156d, 0x1583, 0x159a, + 0x15b2, 0x15cc, 0x15e7, 0x1604, 0x1623, 0x1644, 0x1666, 0x168b, + 0x1958, 0x196d, 0x1983, 0x199a, 0x19b2, 0x19cc, 0x19e7, 0x1a04, + 0x1a23, 0x1a44, 0x1a66, 0x1a8b, 0x1d58, 0x1d6d, 0x1d83, 0x1d9a, + 0x1db2, 0x1dcc, 0x1de7, 0x1e04, 0x1e23, 0x1e44, 0x1e66, 0x1e8b +}; + +static const unsigned char percmx_tab[4] = { 0x14, 0x12, 0x15, 0x11 }; +static const unsigned char perchn_tab[5] = { 6, 7, 8, 8, 7 }; +static unsigned char percmaskoff[5] = { 0xef, 0xf7, 0xfb, 0xfd, 0xfe }; +static unsigned char percmaskon[5] = { 0x10, 0x08, 0x04, 0x02, 0x01 }; + +static inline unsigned short GET_WORD(unsigned char *b, int x) +{ + return ((unsigned short)(b[x+1] << 8) | b[x]); +} + +/*** public methods *************************************/ + +CPlayer *CjbmPlayer::factory(Copl *newopl) +{ + return new CjbmPlayer(newopl); +} + +bool CjbmPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + int filelen = fp.filesize(f); + int i; + + if (!filelen || !fp.extension(filename, ".jbm")) goto loaderr; + + // Allocate memory buffer m[] and read entire file into it + + m = new unsigned char[filelen]; + if (f->readString((char *)m, filelen) != filelen) goto loaderr; + + fp.close(f); + + // The known .jbm files always seem to start with the number 0x0002 + + if (GET_WORD(m, 0) != 0x0002) + return false; + + // Song tempo + + i = GET_WORD(m, 2); + timer = 1193810.0 / (i ? i : 0xffff); + + seqtable = GET_WORD(m, 4); + instable = GET_WORD(m, 6); + + // The flags word has atleast 1 bit, the Adlib's rhythm mode, but + // currently we don't support that :( + + flags = GET_WORD(m, 8); + + // Instrument datas are directly addressed with m[] + + inscount = (filelen - instable) >> 4; + + // Voice' and sequence pointers + + seqcount = 0xffff; + for (i = 0; i < 11; i++) { + voice[i].trkpos = voice[i].trkstart = GET_WORD(m, 10 + (i<<1)); + if (voice[i].trkpos && voice[i].trkpos < seqcount) + seqcount = voice[i].trkpos; + } + seqcount = (seqcount - seqtable) >> 1; + sequences = new unsigned short[seqcount]; + for (i = 0; i < seqcount; i++) + sequences[i] = GET_WORD(m, seqtable + (i<<1)); + + rewind(0); + return true; + loaderr: + fp.close(f); + return false; +} + +bool CjbmPlayer::update() +{ + short c, spos, frq; + + for (c = 0; c < 11; c++) { + if (!voice[c].trkpos) // Unused channel + continue; + + if (--voice[c].delay) + continue; + + // Turn current note/percussion off + + if (voice[c].note&0x7f) + opl_noteonoff(c, &voice[c], 0); + + // Process events until we have a note + + spos = voice[c].seqpos; + while(!voice[c].delay) { + switch(m[spos]) { + case 0xFD: // Set Instrument + voice[c].instr = m[spos+1]; + set_opl_instrument(c, &voice[c]); + spos+=2; + break; + case 0xFF: // End of Sequence + voice[c].seqno = m[++voice[c].trkpos]; + if (voice[c].seqno == 0xff) { + voice[c].trkpos = voice[c].trkstart; + voice[c].seqno = m[voice[c].trkpos]; + //voicemask &= 0x7ff-(1<<c); + voicemask &= ~(1<<c); + } + spos = voice[c].seqpos = sequences[voice[c].seqno]; + break; + default: // Note Event + if ((m[spos] & 127) > 95) + return 0; + + voice[c].note = m[spos]; + voice[c].vol = m[spos+1]; + voice[c].delay = + (m[spos+2] + (m[spos+3]<<8)) + 1; + + frq = notetable[voice[c].note&127]; + voice[c].frq[0] = (unsigned char)frq; + voice[c].frq[1] = frq >> 8; + spos+=4; + } + } + voice[c].seqpos = spos; + + // Write new volume to the carrier operator, or percussion + + if (flags&1 && c > 6) + opl->write(0x40 + percmx_tab[c-7], voice[c].vol ^ 0x3f); + else + opl->write(0x43 + op_table[c], voice[c].vol ^ 0x3f); + + // Write new frequencies and Gate bit + + opl_noteonoff(c, &voice[c], !(voice[c].note & 0x80)); + } + return (voicemask); +} + +void CjbmPlayer::rewind(int subsong) +{ + int c; + + voicemask = 0; + + for (c = 0; c < 11; c++) { + voice[c].trkpos = voice[c].trkstart; + + if (!voice[c].trkpos) continue; + + voicemask |= (1<<c); + + voice[c].seqno = m[voice[c].trkpos]; + voice[c].seqpos = sequences[voice[c].seqno]; + + voice[c].note = 0; + voice[c].delay = 1; + } + + opl->init(); + opl->write(0x01, 32); + + // Set rhythm mode if flags bit #0 is set + // AM and Vibrato are full depths (taken from DosBox RAW output) + bdreg = 0xC0 | (flags&1)<<5; + + opl->write(0xbd, bdreg); + +#if 0 + if (flags&1) { + voice[7].frq[0] = 0x58; voice[7].frq[1] = 0x09; // XXX + voice[8].frq[0] = 0x04; voice[8].frq[1] = 0x0a; // XXX + opl_noteonoff(7, &voice[7], 0); + opl_noteonoff(8, &voice[8], 0); + } +#endif + + return; +} + +/*** private methods ************************************/ + +void CjbmPlayer::opl_noteonoff(int channel, JBMVoice *v, bool state) +{ + if (flags&1 && channel > 5) { + // Percussion + opl->write(0xa0 + perchn_tab[channel-6], voice[channel].frq[0]); + opl->write(0xb0 + perchn_tab[channel-6], voice[channel].frq[1]); + opl->write(0xbd, + state ? bdreg | percmaskon[channel-6] : + bdreg & percmaskoff[channel-6]); + } else { + // Melodic mode or Rhythm mode melodic channels + opl->write(0xa0 + channel, voice[channel].frq[0]); + opl->write(0xb0 + channel, + state ? voice[channel].frq[1] | 0x20 : + voice[channel].frq[1] & 0x1f); + } + return; +} + + +void CjbmPlayer::set_opl_instrument(int channel, JBMVoice *v) +{ + short i = instable + (v->instr << 4); + + // Sanity check on instr number - or we'll be reading outside m[] ! + + if (v->instr >= inscount) + return; + + // For rhythm mode, multiplexed drums. I don't care about waveforms! + if ((flags&1) & (channel > 6)) { + opl->write(0x20 + percmx_tab[channel-7], m[i+0]); + opl->write(0x40 + percmx_tab[channel-7], m[i+1] ^ 0x3f); + opl->write(0x60 + percmx_tab[channel-7], m[i+2]); + opl->write(0x80 + percmx_tab[channel-7], m[i+3]); + + opl->write(0xc0 + perchn_tab[channel-6], m[i+8]&15); + return; + } + + // AM/VIB/EG/KSR/FRQMUL, KSL/OUTPUT, ADSR for 1st operator + opl->write(0x20 + op_table[channel], m[i+0]); + opl->write(0x40 + op_table[channel], m[i+1] ^ 0x3f); + opl->write(0x60 + op_table[channel], m[i+2]); + opl->write(0x80 + op_table[channel], m[i+3]); + + // AM/VIB/EG/KSR/FRQMUL, KSL/OUTPUT, ADSR for 2nd operator + opl->write(0x23 + op_table[channel], m[i+4]); + opl->write(0x43 + op_table[channel], m[i+5] ^ 0x3f); + opl->write(0x63 + op_table[channel], m[i+6]); + opl->write(0x83 + op_table[channel], m[i+7]); + + // WAVEFORM for operators + opl->write(0xe0 + op_table[channel], (m[i+8]>>4)&3); + opl->write(0xe3 + op_table[channel], (m[i+8]>>6)&3); + + // FEEDBACK/FM mode + opl->write(0xc0 + channel, m[i+8]&15); + + return; +} diff --git a/plugins/adplug/adplug/jbm.h b/plugins/adplug/adplug/jbm.h new file mode 100644 index 00000000..3f95b03f --- /dev/null +++ b/plugins/adplug/adplug/jbm.h @@ -0,0 +1,80 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * jbm.h - JBM Player by Dennis Lindroos <lindroos@nls.fi> + */ + +#ifndef H_ADPLUG_JBMPLAYER +#define H_ADPLUG_JBMPLAYER + +#include "player.h" + +class CjbmPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CjbmPlayer(Copl *newopl) : CPlayer(newopl), m(0) + { } + ~CjbmPlayer() + { if(m != NULL) delete [] m; } + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + + float getrefresh() + { return timer; } + + std::string gettype() + { + return std::string(flags&1 ? "JBM Adlib Music [rhythm mode]" : + "JBM Adlib Music"); + } + std::string getauthor() + { return std::string("Johannes Bjerregaard"); } + + protected: + + unsigned char *m; + float timer; + unsigned short flags, voicemask; + unsigned short seqtable, seqcount; + unsigned short instable, inscount; + unsigned short *sequences; + unsigned char bdreg; + + typedef struct { + unsigned short trkpos, trkstart, seqpos; + unsigned char seqno, note; + short vol; + short delay; + short instr; + unsigned char frq[2]; + unsigned char ivol, dummy; + } JBMVoice; + + JBMVoice voice[11]; + + private: + //void calc_opl_frequency(JBMVoice *); + void set_opl_instrument(int, JBMVoice *); + void opl_noteonoff(int, JBMVoice *, bool); +}; + +#endif diff --git a/plugins/adplug/adplug/lds.h b/plugins/adplug/adplug/lds.h index ec61ad29..95f59606 100644 --- a/plugins/adplug/adplug/lds.h +++ b/plugins/adplug/adplug/lds.h @@ -29,7 +29,7 @@ class CldsPlayer: public CPlayer CldsPlayer(Copl *newopl); virtual ~CldsPlayer(); - bool load(const std::string &fn, const CFileProvider &fp); + bool load(const std::string &filename, const CFileProvider &fp); virtual bool update(); virtual void rewind(int subsong = -1); float getrefresh() { return 70.0f; } diff --git a/plugins/adplug/adplug/mad.cpp b/plugins/adplug/adplug/mad.cpp index d5a60c43..b8cb527d 100644 --- a/plugins/adplug/adplug/mad.cpp +++ b/plugins/adplug/adplug/mad.cpp @@ -19,7 +19,7 @@ mad.cpp - MAD loader by Riven the Mage <riven@ok.ru> */ -#include <string.h> +#include <cstring> #include "mad.h" /* -------- Public Methods -------------------------------- */ diff --git a/plugins/adplug/adplug/mid.cpp b/plugins/adplug/adplug/mid.cpp index e3dd88c5..03a2d84a 100644 --- a/plugins/adplug/adplug/mid.cpp +++ b/plugins/adplug/adplug/mid.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -62,6 +62,11 @@ * 10/15/2005: Changes by Simon Peter * Added rhythm mode support for CMF format. * + * 09/13/2008: Changes by Adam Nielsen (malvineous@shikadi.net) + * Fixed a couple of CMF rhythm mode bugs + * Disabled note velocity for CMF files + * Added support for nonstandard CMF AM+VIB controller (for VGFM CMFs) + * * Other acknowledgements: * Allegro - for the midi instruments and the midi volume table * SCUMM Revisited - for getting the .LAA / .MIDs out of those @@ -106,9 +111,6 @@ void CmidPlayer::midiprintf(const char *format, ...) // AdLib standard operator table const unsigned char CmidPlayer::adlib_opadd[] = {0x00 ,0x01 ,0x02 ,0x08 ,0x09 ,0x0A ,0x10 ,0x11 ,0x12}; -// dunno -const int CmidPlayer::ops[] = {0x20,0x20,0x40,0x40,0x60,0x60,0x80,0x80,0xe0,0xe0,0xc0}; - // map CMF drum channels 12 - 15 to corresponding AdLib drum operators // bass drum (channel 11) not mapped, cause it's handled like a normal instrument const int CmidPlayer::map_chan[] = { 0x14, 0x12, 0x15, 0x11 }; @@ -300,12 +302,13 @@ bool CmidPlayer::load(const std::string &filename, const CFileProvider &fp) if (s[1]=='T' && s[2]=='M' && s[3]=='F') good=FILE_CMF; break; case 0x84: - if (s[1]==0x00 && load_sierra_ins(filename, fp)) - if (s[2]==0xf0) - good=FILE_ADVSIERRA; - else - good=FILE_SIERRA; - break; + if (s[1]==0x00 && load_sierra_ins(filename, fp)) { + if (s[2]==0xf0) + good=FILE_ADVSIERRA; + else + good=FILE_SIERRA; + } + break; default: if (s[4]=='A' && s[5]=='D') good=FILE_OLDLUCAS; break; @@ -346,30 +349,24 @@ void CmidPlayer::midi_fm_instrument(int voice, unsigned char *inst) midi_write_adlib(0x20+adlib_opadd[voice],inst[0]); midi_write_adlib(0x23+adlib_opadd[voice],inst[1]); - if ((adlib_style&LUCAS_STYLE)!=0) - { + if (adlib_style & LUCAS_STYLE) { midi_write_adlib(0x43+adlib_opadd[voice],0x3f); if ((inst[10] & 1)==0) midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); - else - midi_write_adlib(0x40+adlib_opadd[voice],0x3f); - } else - { - if ((adlib_style&SIERRA_STYLE)!=0) - { - midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + midi_write_adlib(0x40+adlib_opadd[voice],0x3f); + + } else if ((adlib_style & SIERRA_STYLE) || (adlib_style & CMF_STYLE)) { + midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + midi_write_adlib(0x43+adlib_opadd[voice],inst[3]); + + } else { + midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + if ((inst[10] & 1)==0) midi_write_adlib(0x43+adlib_opadd[voice],inst[3]); - } - else - { - midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); - if ((inst[10] & 1)==0) - midi_write_adlib(0x43+adlib_opadd[voice],inst[3]); - else - midi_write_adlib(0x43+adlib_opadd[voice],0); - } - } + else + midi_write_adlib(0x43+adlib_opadd[voice],0); + } midi_write_adlib(0x60+adlib_opadd[voice],inst[4]); midi_write_adlib(0x63+adlib_opadd[voice],inst[5]); @@ -390,7 +387,8 @@ void CmidPlayer::midi_fm_percussion(int ch, unsigned char *inst) midi_write_adlib(0x60 + opadd, inst[4]); midi_write_adlib(0x80 + opadd, inst[6]); midi_write_adlib(0xe0 + opadd, inst[8]); - midi_write_adlib(0xc0 + opadd, inst[10]); + if (opadd < 0x13) // only output this for the modulator, not the carrier, as it affects the entire channel + midi_write_adlib(0xc0 + percussion_map[ch - 11], inst[10]); } void CmidPlayer::midi_fm_volume(int voice, int volume) @@ -429,7 +427,7 @@ void CmidPlayer::midi_fm_playnote(int voice, int note, int volume) midi_fm_volume(voice,volume); midi_write_adlib(0xa0+voice,(unsigned char)(freq&0xff)); - c=((freq&0x300) >> 8)+(oct<<2) + (adlib_mode == ADLIB_MELODIC || voice < 6 ? (1<<5) : 0); + c=((freq&0x300) >> 8)+((oct&7)<<2) + (adlib_mode == ADLIB_MELODIC || voice < 6 ? (1<<5) : 0); midi_write_adlib(0xb0+voice,(unsigned char)c); } @@ -541,58 +539,62 @@ bool CmidPlayer::update() } else on = percussion_map[c - 11]; - if (vel!=0 && ch[c].inum>=0 && ch[c].inum<128) - { - if (adlib_mode == ADLIB_MELODIC || c < 12) + if (vel!=0 && ch[c].inum>=0 && ch[c].inum<128) { + if (adlib_mode == ADLIB_MELODIC || c < 12) // 11 == bass drum, handled like a normal instrument, on == channel 6 thanks to percussion_map[] above midi_fm_instrument(on,ch[c].ins); else midi_fm_percussion(c, ch[c].ins); - if ((adlib_style&MIDI_STYLE)!=0) - { + if (adlib_style & MIDI_STYLE) { nv=((ch[c].vol*vel)/128); - if ((adlib_style&LUCAS_STYLE)!=0) - nv*=2; + if ((adlib_style&LUCAS_STYLE)!=0) nv*=2; if (nv>127) nv=127; nv=my_midi_fm_vol_table[nv]; if ((adlib_style&LUCAS_STYLE)!=0) nv=(int)((float)sqrt((float)nv)*11); - } - else - { + } else if (adlib_style & CMF_STYLE) { + // CMF doesn't support note velocity (even though some files have them!) + nv = 127; + } else { nv=vel; - } + } - midi_fm_playnote(on,note+ch[c].nshift,nv*2); + midi_fm_playnote(on,note+ch[c].nshift,nv*2); // sets freq in rhythm mode chp[on][0]=c; chp[on][1]=note; chp[on][2]=0; if(adlib_mode == ADLIB_RYTHM && c >= 11) { + // Still need to turn off the perc instrument before playing it again, + // as not all songs send a noteoff. midi_write_adlib(0xbd, adlib_data[0xbd] & ~(0x10 >> (c - 11))); + // Play the perc instrument midi_write_adlib(0xbd, adlib_data[0xbd] | (0x10 >> (c - 11))); } - } - else - { - if (vel==0) //same code as end note - { - for (i=0; i<9; i++) - if (chp[i][0]==c && chp[i][1]==note) - { - // midi_fm_volume(i,0); // really end the note - midi_fm_endnote(i); - chp[i][0]=-1; + } else { + if (vel==0) { //same code as end note + if (adlib_mode == ADLIB_RYTHM && c >= 11) { + // Turn off the percussion instrument + midi_write_adlib(0xbd, adlib_data[0xbd] & ~(0x10 >> (c - 11))); + //midi_fm_endnote(percussion_map[c]); + chp[percussion_map[c - 11]][0]=-1; + } else { + for (i=0; i<9; i++) { + if (chp[i][0]==c && chp[i][1]==note) { + // midi_fm_volume(i,0); // really end the note + midi_fm_endnote(i); + chp[i][0]=-1; } + } } - else - { // i forget what this is for. + } else { + // i forget what this is for. chp[on][0]=-1; chp[on][2]=0; - } } - midiprintf(" [%d:%d:%d:%d]\n",c,ch[c].inum,note,vel); + } + midiprintf(" [%d:%d:%d:%d]\n",c,ch[c].inum,note,vel); } else midiprintf ("off"); @@ -616,8 +618,25 @@ midi_fm_playnote(i,note+cnote[c],my_midi_fm_vol_table[(cvols[c]*vel)/128]*2); ch[c].vol=vel; midiprintf("vol"); break; + case 0x63: + if (adlib_style & CMF_STYLE) { + // Custom extension to allow CMF files to switch the + // AM+VIB depth on and off (officially this is on, + // and there's no way to switch it off.) Controller + // values: + // 0 == AM+VIB off + // 1 == VIB on + // 2 == AM on + // 3 == AM+VIB on + midi_write_adlib(0xbd, (adlib_data[0xbd] & ~0xC0) | (vel << 6)); + midiprintf(" AM+VIB depth change - AM %s, VIB %s\n", + (adlib_data[0xbd] & 0x80) ? "on" : "off", + (adlib_data[0xbd] & 0x40) ? "on" : "off" + ); + } + break; case 0x67: - midiprintf ("\n\nhere:%d\n\n",vel); + midiprintf("Rhythm mode: %d\n", vel); if ((adlib_style&CMF_STYLE)!=0) { adlib_mode=vel; if(adlib_mode == ADLIB_RYTHM) @@ -811,11 +830,12 @@ fwait=1.0f/(((float)iwait/(float)deltas)*((float)msqtr/(float)1000000)); midiprintf ("\n"); for (i=0; i<16; i++) - if (track[i].on) - if (track[i].pos < track[i].tend) - midiprintf ("<%d>",track[i].iwait); - else - midiprintf("stop"); + if (track[i].on) { + if (track[i].pos < track[i].tend) + midiprintf ("<%d>",track[i].iwait); + else + midiprintf("stop"); + } /* if (ret==0 && type==FILE_ADVSIERRA) diff --git a/plugins/adplug/adplug/mid.h b/plugins/adplug/adplug/mid.h index d28f7252..5dfe9e76 100644 --- a/plugins/adplug/adplug/mid.h +++ b/plugins/adplug/adplug/mid.h @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 diff --git a/plugins/adplug/adplug/mkj.cpp b/plugins/adplug/adplug/mkj.cpp index 32d7e27c..38c1c082 100644 --- a/plugins/adplug/adplug/mkj.cpp +++ b/plugins/adplug/adplug/mkj.cpp @@ -19,8 +19,8 @@ * mkj.cpp - MKJamz Player, by Simon Peter <dn.tlp@gmx.net> */ +#include <cstring> #include <assert.h> -#include <string.h> #include "mkj.h" #include "debug.h" diff --git a/plugins/adplug/adplug/msc.cpp b/plugins/adplug/adplug/msc.cpp index 3702adcf..2a10c5ce 100644 --- a/plugins/adplug/adplug/msc.cpp +++ b/plugins/adplug/adplug/msc.cpp @@ -19,8 +19,8 @@ * msc.c - MSC Player by Lubomir Bulej (pallas@kadan.cz) */ +#include <cstring> #include <stdio.h> -#include <string.h> #include "msc.h" #include "debug.h" diff --git a/plugins/adplug/adplug/mtk.cpp b/plugins/adplug/adplug/mtk.cpp index 410e00e0..6bafc853 100644 --- a/plugins/adplug/adplug/mtk.cpp +++ b/plugins/adplug/adplug/mtk.cpp @@ -19,7 +19,7 @@ * mtk.cpp - MPU-401 Trakker Loader by Simon Peter (dn.tlp@gmx.net) */ -#include <string.h> +#include <cstring> #include "mtk.h" /*** public methods **************************************/ diff --git a/plugins/adplug/adplug/player.h b/plugins/adplug/adplug/player.h index 6b99c34b..8c474daa 100644 --- a/plugins/adplug/adplug/player.h +++ b/plugins/adplug/adplug/player.h @@ -37,7 +37,7 @@ public: /***** Operational methods *****/ void seek(unsigned long ms); - virtual bool load(const std::string &fn, // loads file + virtual bool load(const std::string &filename, // loads file const CFileProvider &fp = CProvider_Filesystem()) = 0; virtual bool update() = 0; // executes replay code for 1 tick virtual void rewind(int subsong = -1) = 0; // rewinds to specified subsong @@ -48,29 +48,31 @@ public: virtual std::string gettype() = 0; // returns file type virtual std::string gettitle() // returns song title - { return std::string(); } + { return std::string(); } virtual std::string getauthor() // returns song author name - { return std::string(); } + { return std::string(); } virtual std::string getdesc() // returns song description - { return std::string(); } + { return std::string(); } virtual unsigned int getpatterns() // returns number of patterns - { return 0; } + { return 0; } virtual unsigned int getpattern() // returns currently playing pattern - { return 0; } + { return 0; } virtual unsigned int getorders() // returns size of orderlist - { return 0; } + { return 0; } virtual unsigned int getorder() // returns currently playing song position - { return 0; } + { return 0; } virtual unsigned int getrow() // returns currently playing row - { return 0; } + { return 0; } virtual unsigned int getspeed() // returns current song speed - { return 0; } + { return 0; } virtual unsigned int getsubsongs() // returns number of subsongs - { return 1; } + { return 1; } + virtual unsigned int getsubsong() // returns current subsong + { return 0; } virtual unsigned int getinstruments() // returns number of instruments - { return 0; } + { return 0; } virtual std::string getinstrument(unsigned int n) // returns n-th instrument name - { return std::string(); } + { return std::string(); } protected: Copl *opl; // our OPL chip diff --git a/plugins/adplug/adplug/protrack.cpp b/plugins/adplug/adplug/protrack.cpp index 86716ef8..ebb1570b 100644 --- a/plugins/adplug/adplug/protrack.cpp +++ b/plugins/adplug/adplug/protrack.cpp @@ -25,7 +25,7 @@ * Protracker-like format, this is most certainly the player you want to use. */ -#include <string.h> +#include <cstring> #include "protrack.h" #include "debug.h" @@ -72,7 +72,7 @@ bool CmodPlayer::update() for(chan = 0; chan < nchans; chan++) { oplchan = set_opl_chip(chan); - if(arplist && arpcmd && inst[channel[chan].inst].arpstart) // special arpeggio + if(arplist && arpcmd && inst[channel[chan].inst].arpstart) { // special arpeggio if(channel[chan].arpspdcnt) channel[chan].arpspdcnt--; else @@ -107,6 +107,7 @@ bool CmodPlayer::update() channel[chan].arppos++; channel[chan].arpspdcnt = inst[channel[chan].inst].arpspeed - 1; } + } info1 = channel[chan].info1; info2 = channel[chan].info2; @@ -631,8 +632,8 @@ void CmodPlayer::setvolume_alt(unsigned char chan) unsigned char ivol2 = inst[channel[chan].inst].data[9] & 63; unsigned char ivol1 = inst[channel[chan].inst].data[10] & 63; - opl->write(0x40 + op_table[oplchan], (((63 - channel[chan].vol2 & 63) + ivol2) >> 1) + (inst[channel[chan].inst].data[9] & 192)); - opl->write(0x43 + op_table[oplchan], (((63 - channel[chan].vol1 & 63) + ivol1) >> 1) + (inst[channel[chan].inst].data[10] & 192)); + opl->write(0x40 + op_table[oplchan], (((63 - (channel[chan].vol2 & 63)) + ivol2) >> 1) + (inst[channel[chan].inst].data[9] & 192)); + opl->write(0x43 + op_table[oplchan], (((63 - (channel[chan].vol1 & 63)) + ivol1) >> 1) + (inst[channel[chan].inst].data[10] & 192)); } void CmodPlayer::setfreq(unsigned char chan) @@ -679,13 +680,14 @@ void CmodPlayer::playnote(unsigned char chan) void CmodPlayer::setnote(unsigned char chan, int note) { - if(note > 96) + if(note > 96) { if(note == 127) { // key off channel[chan].key = 0; setfreq(chan); return; } else note = 96; + } if(note < 13) channel[chan].freq = notetable[note - 1]; @@ -701,23 +703,25 @@ void CmodPlayer::setnote(unsigned char chan, int note) void CmodPlayer::slide_down(unsigned char chan, int amount) { channel[chan].freq -= amount; - if(channel[chan].freq <= 342) + if(channel[chan].freq <= 342) { if(channel[chan].oct) { channel[chan].oct--; channel[chan].freq <<= 1; } else channel[chan].freq = 342; + } } void CmodPlayer::slide_up(unsigned char chan, int amount) { channel[chan].freq += amount; - if(channel[chan].freq >= 686) + if(channel[chan].freq >= 686) { if(channel[chan].oct < 7) { channel[chan].oct++; channel[chan].freq >>= 1; } else channel[chan].freq = 686; + } } void CmodPlayer::tone_portamento(unsigned char chan, unsigned char info) @@ -799,11 +803,12 @@ void CmodPlayer::vol_up_alt(unsigned char chan, int amount) channel[chan].vol1 += amount; else channel[chan].vol1 = 63; - if(inst[channel[chan].inst].data[0] & 1) + if(inst[channel[chan].inst].data[0] & 1) { if(channel[chan].vol2 + amount < 63) channel[chan].vol2 += amount; else channel[chan].vol2 = 63; + } } void CmodPlayer::vol_down_alt(unsigned char chan, int amount) @@ -812,9 +817,10 @@ void CmodPlayer::vol_down_alt(unsigned char chan, int amount) channel[chan].vol1 -= amount; else channel[chan].vol1 = 0; - if(inst[channel[chan].inst].data[0] & 1) + if(inst[channel[chan].inst].data[0] & 1) { if(channel[chan].vol2 - amount > 0) channel[chan].vol2 -= amount; else channel[chan].vol2 = 0; + } } diff --git a/plugins/adplug/adplug/rad.cpp b/plugins/adplug/adplug/rad.cpp index 0178a6c3..7996d9ae 100644 --- a/plugins/adplug/adplug/rad.cpp +++ b/plugins/adplug/adplug/rad.cpp @@ -22,7 +22,7 @@ * some volumes are dropped out */ -#include <string.h> +#include <cstring> #include "rad.h" CPlayer *CradLoader::factory(Copl *newopl) diff --git a/plugins/adplug/adplug/rat.cpp b/plugins/adplug/adplug/rat.cpp index b20af250..34f97cb6 100644 --- a/plugins/adplug/adplug/rat.cpp +++ b/plugins/adplug/adplug/rat.cpp @@ -29,7 +29,7 @@ comment : there are bug in original replayer's adlib_init(): wrong frequency registers. */ -#include <string.h> +#include <cstring> #include "rat.h" #include "debug.h" diff --git a/plugins/adplug/adplug/raw.cpp b/plugins/adplug/adplug/raw.cpp index 1672a1d0..0ca3f36b 100644 --- a/plugins/adplug/adplug/raw.cpp +++ b/plugins/adplug/adplug/raw.cpp @@ -19,7 +19,7 @@ * raw.c - RAW Player by Simon Peter <dn.tlp@gmx.net> */ -#include <string.h> +#include <cstring> #include "raw.h" /*** public methods *************************************/ diff --git a/plugins/adplug/adplug/rix.cpp b/plugins/adplug/adplug/rix.cpp index 02ebc8d5..5cd69bd1 100644 --- a/plugins/adplug/adplug/rix.cpp +++ b/plugins/adplug/adplug/rix.cpp @@ -20,7 +20,7 @@ * BSPAL <BSPAL.ys168.com> */ -#include <string.h> +#include <cstring> #include "rix.h" #include "debug.h" diff --git a/plugins/adplug/adplug/rol.cpp b/plugins/adplug/adplug/rol.cpp index 7507d79b..a2a88fe8 100644 --- a/plugins/adplug/adplug/rol.cpp +++ b/plugins/adplug/adplug/rol.cpp @@ -20,6 +20,7 @@ * * Visit: http://tenacity.hispeed.com/aomit/oplx/ */ +#include <cstring> #include <algorithm> #include "rol.h" @@ -665,7 +666,7 @@ int CrolPlayer::load_rol_instrument( binistream *f, SBnkHeader const &header, st else { // set up default instrument data here - memset( &usedIns.instrument, 0, kSizeofDataRecord ); + memset( &usedIns.instrument, 0, sizeof(SRolInstrument) ); } ins_list.push_back( usedIns ); diff --git a/plugins/adplug/adplug/rol.h b/plugins/adplug/adplug/rol.h index fdb9fabb..82337adf 100644 --- a/plugins/adplug/adplug/rol.h +++ b/plugins/adplug/adplug/rol.h @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -25,7 +25,7 @@ #include <vector> #include <string> -#include <string.h> +#include <strings.h> #include "player.h" diff --git a/plugins/adplug/adplug/s3m.cpp b/plugins/adplug/adplug/s3m.cpp index ddc2a3b9..ff0baa35 100644 --- a/plugins/adplug/adplug/s3m.cpp +++ b/plugins/adplug/adplug/s3m.cpp @@ -22,7 +22,7 @@ * Extra Fine Slides (EEx, FEx) & Fine Vibrato (Uxy) are inaccurate */ -#include <string.h> +#include <cstring> #include "s3m.h" const char Cs3mPlayer::chnresolv[] = // S3M -> adlib channel conversion @@ -164,16 +164,18 @@ bool Cs3mPlayer::update() vibrato(realchan,channel[realchan].dualinfo); else // dual command: G00 and Dxy tone_portamento(realchan,channel[realchan].dualinfo); - case 4: if(info <= 0x0f) // volume slide down + case 4: if(info <= 0x0f) { // volume slide down if(channel[realchan].vol - info >= 0) channel[realchan].vol -= info; else channel[realchan].vol = 0; - if((info & 0x0f) == 0) // volume slide up + } + if((info & 0x0f) == 0) { // volume slide up if(channel[realchan].vol + (info >> 4) <= 63) channel[realchan].vol += info >> 4; else channel[realchan].vol = 63; + } setvolume(realchan); break; case 5: if(info == 0xf0 || info <= 0xe0) { // slide down @@ -247,7 +249,7 @@ bool Cs3mPlayer::update() if(realchan != -1) { // channel playable? // set channel values donote = 0; - if(pattern[pattnr][row][chan].note < 14) + if(pattern[pattnr][row][chan].note < 14) { // tone portamento if(pattern[pattnr][row][chan].command == 7 || pattern[pattnr][row][chan].command == 12) { channel[realchan].nextfreq = notetable[pattern[pattnr][row][chan].note]; @@ -259,6 +261,7 @@ bool Cs3mPlayer::update() channel[realchan].key = 1; donote = 1; } + } if(pattern[pattnr][row][chan].note == 14) { // key off (is 14 here, cause note is only first 4 bits) channel[realchan].key = 0; setfreq(realchan); @@ -284,11 +287,12 @@ bool Cs3mPlayer::update() if(pattern[pattnr][row][chan].command != 7) donote = 1; } - if(pattern[pattnr][row][chan].volume != 255) + if(pattern[pattnr][row][chan].volume != 255) { if(pattern[pattnr][row][chan].volume < 64) // set volume channel[realchan].vol = pattern[pattnr][row][chan].volume; else channel[realchan].vol = 63; + } channel[realchan].fx = pattern[pattnr][row][chan].command; // set command if(pattern[pattnr][row][chan].info) // set infobyte channel[realchan].info = pattern[pattnr][row][chan].info; @@ -315,16 +319,18 @@ bool Cs3mPlayer::update() case 1: speed = info; break; // set speed case 2: if(info <= ord) songend = 1; ord = info; crow = 0; pattbreak = 1; break; // jump to order case 3: if(!pattbreak) { crow = info; ord++; pattbreak = 1; } break; // pattern break - case 4: if(info > 0xf0) // fine volume down + case 4: if(info > 0xf0) { // fine volume down if(channel[realchan].vol - (info & 0x0f) >= 0) channel[realchan].vol -= info & 0x0f; else channel[realchan].vol = 0; - if((info & 0x0f) == 0x0f && info >= 0x1f) // fine volume up + } + if((info & 0x0f) == 0x0f && info >= 0x1f) { // fine volume up if(channel[realchan].vol + ((info & 0xf0) >> 4) <= 63) channel[realchan].vol += (info & 0xf0) >> 4; else channel[realchan].vol = 63; + } setvolume(realchan); break; case 5: if(info > 0xf0) { // fine slide down @@ -353,7 +359,7 @@ bool Cs3mPlayer::update() case 10: channel[realchan].trigger = 0; break; // arpeggio (set trigger) case 19: if(info == 0xb0) // set loop start loopstart = row; - if(info > 0xb0 && info <= 0xbf) // pattern loop + if(info > 0xb0 && info <= 0xbf) { // pattern loop if(!loopcnt) { loopcnt = info & 0x0f; crow = loopstart; @@ -363,6 +369,7 @@ bool Cs3mPlayer::update() crow = loopstart; pattbreak = 1; } + } if((info & 0xf0) == 0xe0) // patterndelay del = speed * (info & 0x0f) - 1; break; @@ -450,7 +457,7 @@ void Cs3mPlayer::setfreq(unsigned char chan) { opl->write(0xa0 + chan, channel[chan].freq & 255); if(channel[chan].key) - opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2) | 32); + opl->write(0xb0 + chan, (((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2)) | 32); else opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2)); } diff --git a/plugins/adplug/adplug/sa2.cpp b/plugins/adplug/adplug/sa2.cpp index 2e1248bc..8d81e9c1 100644 --- a/plugins/adplug/adplug/sa2.cpp +++ b/plugins/adplug/adplug/sa2.cpp @@ -1,6 +1,6 @@ /* * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * Copyright (C) 1999 - 2008 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 @@ -20,8 +20,9 @@ * SAdT Loader by Mamiya <mamiya@users.sourceforge.net> */ -#include <string.h> +#include <cstring> #include <stdio.h> +#include <string.h> #include "sa2.h" #include "debug.h" diff --git a/plugins/adplug/adplug/sng.cpp b/plugins/adplug/adplug/sng.cpp index bf0fa699..5e8b6af4 100644 --- a/plugins/adplug/adplug/sng.cpp +++ b/plugins/adplug/adplug/sng.cpp @@ -19,7 +19,7 @@ * sng.cpp - SNG Player by Simon Peter <dn.tlp@gmx.net> */ -#include <string.h> +#include <cstring> #include "sng.h" CPlayer *CsngPlayer::factory(Copl *newopl) diff --git a/plugins/adplug/adplug/surroundopl.cpp b/plugins/adplug/adplug/surroundopl.cpp new file mode 100644 index 00000000..14e67e21 --- /dev/null +++ b/plugins/adplug/adplug/surroundopl.cpp @@ -0,0 +1,203 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * surroundopl.cpp - Wrapper class to provide a surround/harmonic effect + * for another OPL emulator, by Adam Nielsen <malvineous@shikadi.net> + * + * Stereo harmonic algorithm by Adam Nielsen <malvineous@shikadi.net> + * Please give credit if you use this algorithm elsewhere :-) + */ + +#include <math.h> // for pow() +#include "surroundopl.h" +#include "debug.h" + +CSurroundopl::CSurroundopl(Copl *a, Copl *b, bool use16bit) + : use16bit(use16bit), + bufsize(4096), + a(a), b(b) +{ + currType = TYPE_OPL2; + this->lbuf = new short[this->bufsize]; + this->rbuf = new short[this->bufsize]; +}; + +CSurroundopl::~CSurroundopl() +{ + delete[] this->rbuf; + delete[] this->lbuf; + delete a; + delete b; +} + +void CSurroundopl::update(short *buf, int samples) +{ + if (samples * 2 > this->bufsize) { + // Need to realloc the buffer + delete[] this->rbuf; + delete[] this->lbuf; + this->bufsize = samples * 2; + this->lbuf = new short[this->bufsize]; + this->rbuf = new short[this->bufsize]; + } + + a->update(this->lbuf, samples); + b->update(this->rbuf, samples); + + // Copy the two mono OPL buffers into the stereo buffer + for (int i = 0; i < samples; i++) { + if (this->use16bit) { + buf[i * 2] = this->lbuf[i]; + buf[i * 2 + 1] = this->rbuf[i]; + } else { + ((char *)buf)[i * 2] = ((char *)this->lbuf)[i]; + ((char *)buf)[i * 2 + 1] = ((char *)this->rbuf)[i]; + } + } + +} + +// template methods +void CSurroundopl::write(int reg, int val) +{ + a->write(reg, val); + + // Transpose the other channel to produce the harmonic effect + + int iChannel = -1; + int iRegister = reg; // temp + int iValue = val; // temp + if ((iRegister >> 4 == 0xA) || (iRegister >> 4 == 0xB)) iChannel = iRegister & 0x0F; + + // Remember the FM state, so that the harmonic effect can access + // previously assigned register values. + /*if (((iRegister >> 4 == 0xB) && (iValue & 0x20) && !(this->iFMReg[iRegister] & 0x20)) || + (iRegister == 0xBD) && ( + ((iValue & 0x01) && !(this->iFMReg[0xBD] & 0x01)) + )) { + this->iFMReg[iRegister] = iValue; + }*/ + this->iFMReg[iRegister] = iValue; + + if ((iChannel >= 0)) {// && (i == 1)) { + uint8_t iBlock = (this->iFMReg[0xB0 + iChannel] >> 2) & 0x07; + uint16_t iFNum = ((this->iFMReg[0xB0 + iChannel] & 0x03) << 8) | this->iFMReg[0xA0 + iChannel]; + //double dbOriginalFreq = 50000.0 * (double)iFNum * pow(2, iBlock - 20); + double dbOriginalFreq = 49716.0 * (double)iFNum * pow(2, iBlock - 20); + + uint8_t iNewBlock = iBlock; + uint16_t iNewFNum; + + // Adjust the frequency and calculate the new FNum + //double dbNewFNum = (dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20)); + //#define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20))) + #define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (49716.0 * pow(2, iNewBlock - 20))) + double dbNewFNum = calcFNum(); + + // Make sure it's in range for the OPL chip + if (dbNewFNum > 1023 - NEWBLOCK_LIMIT) { + // It's too high, so move up one block (octave) and recalculate + + if (iNewBlock > 6) { + // Uh oh, we're already at the highest octave! + AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block 8+ after being transposed (new FNum is %d)\n", + iFNum, iBlock, (int)dbNewFNum); + // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't + // sound *too* bad (hopefully it will just miss out on the nice harmonic.) + iNewBlock = iBlock; + iNewFNum = iFNum; + } else { + iNewBlock++; + iNewFNum = (uint16_t)calcFNum(); + } + } else if (dbNewFNum < 0 + NEWBLOCK_LIMIT) { + // It's too low, so move down one block (octave) and recalculate + + if (iNewBlock == 0) { + // Uh oh, we're already at the lowest octave! + AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block -1 after being transposed (new FNum is %d)!\n", + iFNum, iBlock, (int)dbNewFNum); + // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't + // sound *too* bad (hopefully it will just miss out on the nice harmonic.) + iNewBlock = iBlock; + iNewFNum = iFNum; + } else { + iNewBlock--; + iNewFNum = (uint16_t)calcFNum(); + } + } else { + // Original calculation is within range, use that + iNewFNum = (uint16_t)dbNewFNum; + } + + // Sanity check + if (iNewFNum > 1023) { + // Uh oh, the new FNum is still out of range! (This shouldn't happen) + AdPlug_LogWrite("OPL ERR: Original note (FNum %d/B#%d is still out of range after change to FNum %d/B#%d!\n", + iFNum, iBlock, iNewFNum, iNewBlock); + // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't + // sound *too* bad (hopefully it will just miss out on the nice harmonic.) + iNewBlock = iBlock; + iNewFNum = iFNum; + } + + if ((iRegister >= 0xB0) && (iRegister <= 0xB8)) { + + // Overwrite the supplied value with the new F-Number and Block. + iValue = (iValue & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03); + + this->iCurrentTweakedBlock[iChannel] = iNewBlock; // save it so we don't have to update register 0xB0 later on + this->iCurrentFNum[iChannel] = iNewFNum; + + if (this->iTweakedFMReg[0xA0 + iChannel] != (iNewFNum & 0xFF)) { + // Need to write out low bits + uint8_t iAdditionalReg = 0xA0 + iChannel; + uint8_t iAdditionalValue = iNewFNum & 0xFF; + b->write(iAdditionalReg, iAdditionalValue); + this->iTweakedFMReg[iAdditionalReg] = iAdditionalValue; + } + } else if ((iRegister >= 0xA0) && (iRegister <= 0xA8)) { + + // Overwrite the supplied value with the new F-Number. + iValue = iNewFNum & 0xFF; + + // See if we need to update the block number, which is stored in a different register + uint8_t iNewB0Value = (this->iFMReg[0xB0 + iChannel] & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03); + if ( + (iNewB0Value & 0x20) && // but only update if there's a note currently playing (otherwise we can just wait + (this->iTweakedFMReg[0xB0 + iChannel] != iNewB0Value) // until the next noteon and update it then) + ) { + AdPlug_LogWrite("OPL INFO: CH%d - FNum %d/B#%d -> FNum %d/B#%d == keyon register update!\n", + iChannel, iFNum, iBlock, iNewFNum, iNewBlock); + // The note is already playing, so we need to adjust the upper bits too + uint8_t iAdditionalReg = 0xB0 + iChannel; + b->write(iAdditionalReg, iNewB0Value); + this->iTweakedFMReg[iAdditionalReg] = iNewB0Value; + } // else the note is not playing, the upper bits will be set when the note is next played + + } // if (register 0xB0 or 0xA0) + + } // if (a register we're interested in) + + // Now write to the original register with a possibly modified value + b->write(iRegister, iValue); + this->iTweakedFMReg[iRegister] = iValue; + +}; + +void CSurroundopl::init() {}; diff --git a/plugins/adplug/adplug/surroundopl.h b/plugins/adplug/adplug/surroundopl.h new file mode 100644 index 00000000..d302a9e8 --- /dev/null +++ b/plugins/adplug/adplug/surroundopl.h @@ -0,0 +1,69 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * surroundopl.h - Wrapper class to provide a surround/harmonic effect + * for another OPL emulator, by Adam Nielsen <malvineous@shikadi.net> + * + * Stereo harmonic algorithm by Adam Nielsen <malvineous@shikadi.net> + * Please give credit if you use this algorithm elsewhere :-) + */ + +#ifndef H_ADPLUG_SURROUNDOPL +#define H_ADPLUG_SURROUNDOPL + +#include <stdint.h> // for uintxx_t +#include "opl.h" + +// The right-channel is increased in frequency by itself divided by this amount. +// The right value should not noticeably change the pitch, but it should provide +// a nice stereo harmonic effect. +#define FREQ_OFFSET 128.0//96.0 + +// Number of FNums away from the upper/lower limit before switching to the next +// block (octave.) By rights it should be zero, but for some reason this seems +// to cut it to close and the transposed OPL doesn't hit the right note all the +// time. Setting it higher means it will switch blocks sooner and that seems +// to help. Don't set it too high or it'll get stuck in an infinite loop if +// one block is too high and the adjacent block is too low ;-) +#define NEWBLOCK_LIMIT 32 + +class CSurroundopl: public Copl +{ + private: + bool use16bit; + short bufsize; + short *lbuf, *rbuf; + Copl *a, *b; + uint8_t iFMReg[256]; + uint8_t iTweakedFMReg[256]; + uint8_t iCurrentTweakedBlock[9]; // Current value of the Block in the tweaked OPL chip + uint8_t iCurrentFNum[9]; // Current value of the FNum in the tweaked OPL chip + + public: + + CSurroundopl(Copl *a, Copl *b, bool use16bit); + ~CSurroundopl(); + + void update(short *buf, int samples); + void write(int reg, int val); + + void init(); + +}; + +#endif diff --git a/plugins/adplug/adplug/xsm.h b/plugins/adplug/adplug/xsm.h index 15129606..0e844afc 100644 --- a/plugins/adplug/adplug/xsm.h +++ b/plugins/adplug/adplug/xsm.h @@ -29,7 +29,7 @@ public: CxsmPlayer(Copl *newopl); ~CxsmPlayer(); - bool load(const std::string &fn, const CFileProvider &fp); + bool load(const std::string &filename, const CFileProvider &fp); bool update(); void rewind(int subsong); float getrefresh(); |