/* * Adplug - Replayer for many OPL2/OPL3 audio file formats. * Copyright (C) 1999 - 2009 Simon Peter, , 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 * Subset of CMF reader in MOPL code (Malvineous' OPL player), no seeking etc. */ #include // for uintxx_t #include #include // for pow() etc. #include // 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); if (iVer == 0x0100) { this->cmfHeader.iNumInstruments = f->readInt(1); this->cmfHeader.iTempo = 0; } else { // 0x0101 this->cmfHeader.iNumInstruments = f->readInt(2); this->cmfHeader.iTempo = f->readInt(2); } // Load the instruments f->seek(this->cmfHeader.iInstrumentBlockOffset); this->pInstruments = new SBI[ (this->cmfHeader.iNumInstruments < 128) ? 128 : this->cmfHeader.iNumInstruments ]; // Always at least 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); // Disable OPL3 mode (can be left enabled by a previous non-CMF song) this->writeOPL(0x05, 0x00); // 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; }