bool Player_AD::parseCommand() { uint command = _musicData[_curOffset++]; if (command == 0xFF) { // META EVENT // Get the command number. command = _musicData[_curOffset++]; if (command == 47) { // End of track if (_loopFlag) { // In case the track is looping jump to the start. _curOffset = _musicLoopStart; _nextEventTimer = 0; } else { // Otherwise completely stop playback. stopMusic(); } return true; } else if (command == 88) { // This is proposedly a debug information insertion. The CMS // player code handles this differently, but is still using // the same resources... _curOffset += 5; } else if (command == 81) { // Change tempo. This is used exclusively in Loom. const uint timing = _musicData[_curOffset + 2] | (_musicData[_curOffset + 1] << 8); _musicTicks = 0x73000 / timing; command = _musicData[_curOffset++]; _curOffset += command; } else { // In case an unknown meta event occurs just skip over the // data by using the length supplied. command = _musicData[_curOffset++]; _curOffset += command; } } else { if (command >= 0x90) { // NOTE ON // Extract the channel number and save it in command. command -= 0x90; const uint instrOffset = _instrumentOffset[command]; if (instrOffset) { if (_musicData[instrOffset + 13] != 0) { setupRhythm(_musicData[instrOffset + 13], instrOffset); } else { // Priority 256 makes sure we always prefer music // channels over SFX channels. int channel = allocateHWChannel(256); if (channel != -1) { setupChannel(channel, _musicData + instrOffset); _voiceChannels[channel].lastEvent = command + 0x90; _voiceChannels[channel].frequency = _musicData[_curOffset]; setupFrequency(channel, _musicData[_curOffset]); } } } } else { // NOTE OFF const uint note = _musicData[_curOffset]; command += 0x10; // Find the output channel which plays the note. uint channel = 0xFF; for (int i = 0; i < ARRAYSIZE(_voiceChannels); ++i) { if (_voiceChannels[i].frequency == note && _voiceChannels[i].lastEvent == command) { channel = i; break; } } if (channel != 0xFF) { // In case a output channel playing the note was found, // stop it. noteOff(channel); } else { // In case there is no such note this will disable the // rhythm instrument played on the channel. command -= 0x90; const uint instrOffset = _instrumentOffset[command]; if (instrOffset && _musicData[instrOffset + 13] != 0) { const uint rhythmInstr = _musicData[instrOffset + 13]; if (rhythmInstr < 6) { _mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF; writeReg(0xBD, _mdvdrState); } } } } _curOffset += 2; } return false; }
void MusicPlayer::callback() { if (!_isPlaying) return; _musicTimer += _musicTicks; if (_musicTimer < _timerLimit) return; _musicTimer -= _timerLimit; --_nextEventTimer; if (_nextEventTimer) return; while (true) { uint8_t command = _file.at(_curOffset++); if (command == 0xFF) { command = _file.at(_curOffset++); if (command == 47) { // End of track _isPlaying = false; return; } else if (command == 88) { // This is proposedly a debug information insertion. The CMS // player code handles this differently, but is still using // the same resources... _curOffset += 5; } else if (command == 81) { uint16_t timing = _file.at(_curOffset + 2) | (_file.at(_curOffset + 1) << 8); _musicTicks = 0x73000 / timing; command = _file.at(_curOffset++); _curOffset += command; } else { command = _file.at(_curOffset++); _curOffset += command; } } else { if (command >= 0x90) { command -= 0x90; const uint16_t instrOffset = _instrumentOffset[command]; if (instrOffset) { if (_file.at(instrOffset + 13) != 0) { setupRhythm(_file[instrOffset + 13], instrOffset); } else { uint8_t channel = findFreeChannel(); if (channel != 0xFF) { noteOff(channel); setupChannel(channel, instrOffset); _channelLastEvent[channel] = command + 0x90; _channelFrequency[channel] = _file.at(_curOffset); setupFrequency(channel, _file[_curOffset]); } } } } else { const uint8_t note = _file.at(_curOffset); command += 0x10; uint8_t channel = 0xFF; for (uint8_t i = 0; i < _voiceChannels; ++i) { if (_channelFrequency[i] == note && _channelLastEvent[i] == command) { channel = i; break; } } if (channel != 0xFF) { noteOff(channel); } else { command -= 0x90; const uint16_t instrOffset = _instrumentOffset[command]; if (instrOffset && _file.at(instrOffset + 13) != 0) { const uint8_t rhythmInstr = _file[instrOffset + 13]; //if (rhythmInstr >= 6) // throw std::range_error("rhythmInstr >= 6"); if (rhythmInstr < 6) { _mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF; writeReg(0xBD, _mdvdrState); } } } } _curOffset += 2; } if (_file.at(_curOffset) != 0) break; ++_curOffset; } _nextEventTimer = _file.at(_curOffset++); if (_nextEventTimer & 0x80) { _nextEventTimer -= 0x80; _nextEventTimer <<= 7; _nextEventTimer |= _file.at(_curOffset++); } _nextEventTimer >>= _isLoom ? 2 : 1; if (!_nextEventTimer) _nextEventTimer = 1; }