From d4855ff81929b9d8faa854e428122271bbd0b219 Mon Sep 17 00:00:00 2001 From: Sven Hesse Date: Thu, 18 Jun 2009 13:27:35 +0000 Subject: Splitting an ADLPlayer and MDYPlayer from the AdLib class svn-id: r41633 --- engines/gob/sound/adlib.cpp | 769 +++++++++++++++++++++++--------------------- 1 file changed, 406 insertions(+), 363 deletions(-) (limited to 'engines/gob/sound/adlib.cpp') diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp index b55e9fb0c8..a888b6ee49 100644 --- a/engines/gob/sound/adlib.cpp +++ b/engines/gob/sound/adlib.cpp @@ -39,21 +39,34 @@ const unsigned char AdLib::_volRegNums[] = { }; AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) { + init(); +} + +AdLib::~AdLib() { + Common::StackLock slock(_mutex); + + _mixer->stopHandle(_handle); + OPLDestroy(_opl); + if (_data && _freeData) + delete[] _data; +} + +void AdLib::init() { _index = -1; _data = 0; _playPos = 0; _dataSize = 0; _rate = _mixer->getOutputRate(); + _opl = makeAdlibOPL(_rate); _first = true; _ended = false; _playing = false; - _needFree = false; - _mdySong = false; + + _freeData = false; - _soundMode = 0; _repCount = -1; _samplesTillPoll = 0; @@ -65,15 +78,6 @@ AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) { this, -1, 255, 0, false, true); } -AdLib::~AdLib() { - Common::StackLock slock(_mutex); - - _mixer->stopHandle(_handle); - OPLDestroy(_opl); - if (_data && _needFree) - delete[] _data; -} - int AdLib::readBuffer(int16 *buffer, const int numSamples) { Common::StackLock slock(_mutex); int samples; @@ -109,25 +113,17 @@ int AdLib::readBuffer(int16 *buffer, const int numSamples) { if (_ended) { _first = true; _ended = false; - if (_mdySong) - _playPos = _data; - else - _playPos = _data + 3 + (_data[1] + 1) * 0x38; + + rewind(); _samplesTillPoll = 0; if (_repCount == -1) { reset(); - if (_mdySong) - setVoicesTbr(); - else - setVoices(); + setVoices(); } else if (_repCount > 0) { _repCount--; reset(); - if (_mdySong) - setVoicesTbr(); - else - setVoices(); + setVoices(); } else _playing = false; @@ -182,126 +178,8 @@ void AdLib::reset() { // Authorize the control of the waveformes writeOPL(0x01, 0x20); - -// _soundMode 1 : Percussive mode. - if (_soundMode == 1) { - writeOPL(0xA6, 0); - writeOPL(0xB6, 0); - writeOPL(0xA7, 0); - writeOPL(0xB7, 0); - writeOPL(0xA8, 0); - writeOPL(0xB8, 0); - -// TODO set the correct frequency for the last 4 percussive voices - - } -} - -void AdLib::setVoices() { - // Definitions of the 9 instruments - for (int i = 0; i < 9; i++) - setVoice(i, i, true); -} - -void AdLib::setVoice(byte voice, byte instr, bool set) { - int i; - int j; - uint16 strct[27]; - byte channel; - byte *dataPtr; - - // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 - // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 - for (i = 0; i < 2; i++) { - dataPtr = _data + 3 + instr * 0x38 + i * 0x1A; - for (j = 0; j < 27; j++) { - strct[j] = READ_LE_UINT16(dataPtr); - dataPtr += 2; - } - channel = _operators[voice] + i * 3; - writeOPL(0xBD, 0x00); - writeOPL(0x08, 0x00); - writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); - if (!i) - writeOPL(0xC0 | voice, - ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); - writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); - writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); - writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | - ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | - ((strct[11] & 1) << 4) | (strct[1] & 0xF)); - if (!i) - writeOPL(0xE0 | channel, (strct[26] & 3)); - else - writeOPL(0xE0 | channel, (strct[14] & 3)); - if (i && set) - writeOPL(0x40 | channel, 0); - } -} - -void AdLib::setVoicesTbr() { - int i; - byte *timbrePtr; - - timbrePtr = _timbres; - debugC(6, kDebugSound, "TBR version: %X.%X", timbrePtr[0], timbrePtr[1]); - timbrePtr += 2; - - _tbrCount = READ_LE_UINT16(timbrePtr); - debugC(6, kDebugSound, "Timbres counter: %d", _tbrCount); - timbrePtr += 2; - _tbrStart = READ_LE_UINT16(timbrePtr); - - timbrePtr += 2; - for (i = 0; i < _tbrCount ; i++) - { - setVoiceTbr (i, i, true); - } -} - -void AdLib::setVoiceTbr (byte voice, byte instr, bool set) { - int i; - int j; - uint16 strct[27]; - byte channel; - byte *timbrePtr; - char timbreName[10]; - - timbreName[9] = '\0'; - for (j = 0; j < 9; j++) - timbreName[j] = _timbres[6 + j + (instr * 9)]; - debugC(6, kDebugSound, "Loading timbre %s", timbreName); - - // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 - // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 - for (i = 0; i < 2; i++) { - timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A; - for (j = 0; j < 27; j++) { - strct[j] = READ_LE_UINT16(timbrePtr); - timbrePtr += 2; - } - channel = _operators[voice] + i * 3; - writeOPL(0xBD, 0x00); - writeOPL(0x08, 0x00); - writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); - if (!i) - writeOPL(0xC0 | voice, - ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); - writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); - writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); - writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | - ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | - ((strct[11] & 1) << 4) | (strct[1] & 0xF)); - if (!i) - writeOPL(0xE0 | channel, (strct[26] & 3)); - else { - writeOPL(0xE0 | channel, (strct[14] & 3)); - writeOPL(0x40 | channel, 0); - } - } } - void AdLib::setKey(byte voice, byte note, bool on, bool spec) { short freq = 0; short octa = 0; @@ -374,206 +252,56 @@ void AdLib::setVolume(byte voice, byte volume) { } void AdLib::pollMusic() { - unsigned char instr; - byte channel; - byte note; - byte volume; - uint16 tempo; - - uint8 i, tempoMult, tempoFrac, ctrlByte1, ctrlByte2, timbre; - if ((_playPos > (_data + _dataSize)) && (_dataSize != 0xFFFFFFFF)) { _ended = true; return; } - if (_mdySong) - { - if (_first) { - for (i = 0; i < 11; i ++) - setVolume(i, 0); + interpret(); +} -// TODO : Set pitch range +void AdLib::unload() { + _playing = false; + _index = -1; - _tempo = _basicTempo; - _wait = *(_playPos++); - _first = false; - } - do { - instr = *_playPos; -// printf("instr 0x%X\n", instr); - switch(instr) { - case 0xF8: - _wait = *(_playPos++); - break; - case 0xFC: - _ended = true; - _samplesTillPoll = 0; - return; - case 0xF0: - _playPos++; - ctrlByte1 = *(_playPos++); - ctrlByte2 = *(_playPos++); - if (ctrlByte1 != 0x7F || ctrlByte2 != 0) { - _playPos -= 2; - while (*(_playPos++) != 0xF7); - } else { - tempoMult = *(_playPos++); - tempoFrac = *(_playPos++); - _tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7); - _playPos++; - } - _wait = *(_playPos++); - break; - default: - if (instr >= 0x80) { - _playPos++; - } - channel = (int)(instr & 0x0f); - - switch(instr & 0xf0) { - case 0x90: - note = *(_playPos++); - volume = *(_playPos++); - _pollNotes[channel] = note; - setVolume(channel, volume); - setKey(channel, note, true, false); - break; - case 0x80: - _playPos += 2; - note = _pollNotes[channel]; - setKey(channel, note, false, false); - break; - case 0xA0: - setVolume(channel, *(_playPos++)); - break; - case 0xC0: - timbre = *(_playPos++); - setVoiceTbr(channel, timbre, false); - break; - case 0xE0: - warning("Pitch bend not yet implemented\n"); - - note = *(_playPos)++; - note += (unsigned)(*(_playPos++)) << 7; - - setKey(channel, note, _notOn[channel], true); - - break; - case 0xB0: - _playPos += 2; - break; - case 0xD0: - _playPos++; - break; - default: - warning("Bad MIDI instr byte: 0%X", instr); - while ((*_playPos) < 0x80) - _playPos++; - if (*_playPos != 0xF8) - _playPos--; - break; - } //switch instr & 0xF0 - _wait = *(_playPos++); - break; - } //switch instr - } while (_wait == 0); + if (_data && _freeData) + delete[] _data; - if (_wait == 0xF8) { - _wait = 0xF0; - if (*_playPos != 0xF8) - _wait += *(_playPos++); - } -// _playPos++; - _samplesTillPoll = _wait * (_rate / 1000); - } else { - // First tempo, we'll ignore it... - if (_first) { - tempo = *(_playPos++); - // Tempo on 2 bytes - if (tempo & 0x80) - tempo = ((tempo & 3) << 8) | *(_playPos++); - } - _first = false; - - // Instruction - instr = *(_playPos++); - channel = instr & 0x0F; - - switch (instr & 0xF0) { - // Note on + Volume - case 0x00: - note = *(_playPos++); - _pollNotes[channel] = note; - setVolume(channel, *(_playPos++)); - setKey(channel, note, true, false); - break; - // Note on - case 0x90: - note = *(_playPos++); - _pollNotes[channel] = note; - setKey(channel, note, true, false); - break; - // Last note off - case 0x80: - note = _pollNotes[channel]; - setKey(channel, note, false, false); - break; - // Frequency on/off - case 0xA0: - note = *(_playPos++); - setKey(channel, note, _notOn[channel], true); - break; - // Volume - case 0xB0: - volume = *(_playPos++); - setVolume(channel, volume); - break; - // Program change - case 0xC0: - setVoice(channel, *(_playPos++), false); - break; - // Special - case 0xF0: - switch (instr & 0x0F) { - case 0xF: // End instruction - _ended = true; - _samplesTillPoll = 0; - return; - default: - warning("Unknown special command in ADL, stopping playback: %X", - instr & 0x0F); - _repCount = 0; - _ended = true; - break; - } - break; - default: - warning("Unknown command in ADL, stopping playback: %X", - instr & 0xF0); - _repCount = 0; - _ended = true; - break; - } - - // Temporization - tempo = *(_playPos++); - // End tempo - if (tempo == 0xFF) { - _ended = true; - return; - } - // Tempo on 2 bytes - if (tempo & 0x80) - tempo = ((tempo & 3) << 8) | *(_playPos++); - if (!tempo) - tempo ++; - - _samplesTillPoll = tempo * (_rate / 1000); - } + _freeData = false; +} + +bool AdLib::isPlaying() const { + return _playing; +} + +bool AdLib::getRepeating() const { + return _repCount != 0; +} + +void AdLib::setRepeating(int32 repCount) { + _repCount = repCount; +} + +int AdLib::getIndex() const { + return _index; +} + +void AdLib::startPlay() { + if (_data) _playing = true; +} + +void AdLib::stopPlay() { + Common::StackLock slock(_mutex); + _playing = false; +} + +ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer) { +} + +ADLPlayer::~ADLPlayer() { } -bool AdLib::load(const char *fileName) { +bool ADLPlayer::load(const char *fileName) { Common::File song; unload(); @@ -581,9 +309,7 @@ bool AdLib::load(const char *fileName) { if (!song.isOpen()) return false; - _soundMode = 0; - - _needFree = true; + _freeData = true; _dataSize = song.size(); _data = new byte[_dataSize]; song.read(_data, _dataSize); @@ -596,12 +322,10 @@ bool AdLib::load(const char *fileName) { return true; } -bool AdLib::load(byte *data, uint32 size, int index) { +bool ADLPlayer::load(byte *data, uint32 size, int index) { unload(); _repCount = 0; - _soundMode = 0; - _dataSize = size; _data = data; _index = index; @@ -613,7 +337,168 @@ bool AdLib::load(byte *data, uint32 size, int index) { return true; } -bool AdLib::loadMdy(const char *fileName) { +void ADLPlayer::unload() { + AdLib::unload(); +} + +void ADLPlayer::interpret() { + unsigned char instr; + byte channel; + byte note; + byte volume; + uint16 tempo; + + // First tempo, we'll ignore it... + if (_first) { + tempo = *(_playPos++); + // Tempo on 2 bytes + if (tempo & 0x80) + tempo = ((tempo & 3) << 8) | *(_playPos++); + } + _first = false; + + // Instruction + instr = *(_playPos++); + channel = instr & 0x0F; + + switch (instr & 0xF0) { + // Note on + Volume + case 0x00: + note = *(_playPos++); + _pollNotes[channel] = note; + setVolume(channel, *(_playPos++)); + setKey(channel, note, true, false); + break; + // Note on + case 0x90: + note = *(_playPos++); + _pollNotes[channel] = note; + setKey(channel, note, true, false); + break; + // Last note off + case 0x80: + note = _pollNotes[channel]; + setKey(channel, note, false, false); + break; + // Frequency on/off + case 0xA0: + note = *(_playPos++); + setKey(channel, note, _notOn[channel], true); + break; + // Volume + case 0xB0: + volume = *(_playPos++); + setVolume(channel, volume); + break; + // Program change + case 0xC0: + setVoice(channel, *(_playPos++), false); + break; + // Special + case 0xF0: + switch (instr & 0x0F) { + case 0xF: // End instruction + _ended = true; + _samplesTillPoll = 0; + return; + default: + warning("Unknown special command in ADL, stopping playback: %X", + instr & 0x0F); + _repCount = 0; + _ended = true; + break; + } + break; + default: + warning("Unknown command in ADL, stopping playback: %X", + instr & 0xF0); + _repCount = 0; + _ended = true; + break; + } + + // Temporization + tempo = *(_playPos++); + // End tempo + if (tempo == 0xFF) { + _ended = true; + return; + } + // Tempo on 2 bytes + if (tempo & 0x80) + tempo = ((tempo & 3) << 8) | *(_playPos++); + if (!tempo) + tempo ++; + + _samplesTillPoll = tempo * (_rate / 1000); +} + +void ADLPlayer::reset() { + AdLib::reset(); +} + +void ADLPlayer::rewind() { + _playPos = _data + 3 + (_data[1] + 1) * 0x38; +} + +void ADLPlayer::setVoices() { + // Definitions of the 9 instruments + for (int i = 0; i < 9; i++) + setVoice(i, i, true); +} + +void ADLPlayer::setVoice(byte voice, byte instr, bool set) { + uint16 strct[27]; + byte channel; + byte *dataPtr; + + // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 + // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 + for (int i = 0; i < 2; i++) { + dataPtr = _data + 3 + instr * 0x38 + i * 0x1A; + for (int j = 0; j < 27; j++) { + strct[j] = READ_LE_UINT16(dataPtr); + dataPtr += 2; + } + channel = _operators[voice] + i * 3; + writeOPL(0xBD, 0x00); + writeOPL(0x08, 0x00); + writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); + if (!i) + writeOPL(0xC0 | voice, + ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); + writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); + writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); + writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | + ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | + ((strct[11] & 1) << 4) | (strct[1] & 0xF)); + if (!i) + writeOPL(0xE0 | channel, (strct[26] & 3)); + else + writeOPL(0xE0 | channel, (strct[14] & 3)); + if (i && set) + writeOPL(0x40 | channel, 0); + } +} + + +MDYPlayer::MDYPlayer(Audio::Mixer &mixer) : AdLib(mixer) { + init(); +} + +MDYPlayer::~MDYPlayer() { +} + +void MDYPlayer::init() { + _soundMode = 0; + + _timbres = 0; + _tbrCount = 0; + _tbrStart = 0; + _timbresSize = 0; +} + +bool MDYPlayer::loadMDY(const char *fileName) { Common::File song; byte mdyHeader[70]; @@ -622,8 +507,7 @@ bool AdLib::loadMdy(const char *fileName) { if (!song.isOpen()) return false; - _needFree = true; - _mdySong = true; + _freeData = true; song.read(mdyHeader, 70); @@ -652,7 +536,7 @@ bool AdLib::loadMdy(const char *fileName) { return true; } -bool AdLib::loadTbr(const char *fileName) { +bool MDYPlayer::loadTBR(const char *fileName) { Common::File timbres; unload(); @@ -666,44 +550,203 @@ bool AdLib::loadTbr(const char *fileName) { timbres.close(); reset(); - setVoicesTbr(); + setVoices(); return true; } -void AdLib::unload() { - _playing = false; - _index = -1; +void MDYPlayer::unload() { + AdLib::unload(); - if (_data && _needFree) - delete[] _data; + delete[] _timbres; - _needFree = false; + _timbres = 0; + _timbresSize = 0; } -bool AdLib::isPlaying() const { - return _playing; -} +void MDYPlayer::interpret() { + unsigned char instr; + byte channel; + byte note; + byte volume; + uint8 tempoMult, tempoFrac; + uint8 ctrlByte1, ctrlByte2; + uint8 timbre; -bool AdLib::getRepeating() const { - return _repCount != 0; + if (_first) { + for (int i = 0; i < 11; i ++) + setVolume(i, 0); + +// TODO : Set pitch range + + _tempo = _basicTempo; + _wait = *(_playPos++); + _first = false; + } + do { + instr = *_playPos; +// printf("instr 0x%X\n", instr); + switch(instr) { + case 0xF8: + _wait = *(_playPos++); + break; + case 0xFC: + _ended = true; + _samplesTillPoll = 0; + return; + case 0xF0: + _playPos++; + ctrlByte1 = *(_playPos++); + ctrlByte2 = *(_playPos++); + if (ctrlByte1 != 0x7F || ctrlByte2 != 0) { + _playPos -= 2; + while (*(_playPos++) != 0xF7); + } else { + tempoMult = *(_playPos++); + tempoFrac = *(_playPos++); + _tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7); + _playPos++; + } + _wait = *(_playPos++); + break; + default: + if (instr >= 0x80) { + _playPos++; + } + channel = (int)(instr & 0x0f); + + switch(instr & 0xf0) { + case 0x90: + note = *(_playPos++); + volume = *(_playPos++); + _pollNotes[channel] = note; + setVolume(channel, volume); + setKey(channel, note, true, false); + break; + case 0x80: + _playPos += 2; + note = _pollNotes[channel]; + setKey(channel, note, false, false); + break; + case 0xA0: + setVolume(channel, *(_playPos++)); + break; + case 0xC0: + timbre = *(_playPos++); + setVoice(channel, timbre, false); + break; + case 0xE0: + warning("Pitch bend not yet implemented\n"); + + note = *(_playPos)++; + note += (unsigned)(*(_playPos++)) << 7; + + setKey(channel, note, _notOn[channel], true); + + break; + case 0xB0: + _playPos += 2; + break; + case 0xD0: + _playPos++; + break; + default: + warning("Bad MIDI instr byte: 0%X", instr); + while ((*_playPos) < 0x80) + _playPos++; + if (*_playPos != 0xF8) + _playPos--; + break; + } //switch instr & 0xF0 + _wait = *(_playPos++); + break; + } //switch instr + } while (_wait == 0); + + if (_wait == 0xF8) { + _wait = 0xF0; + if (*_playPos != 0xF8) + _wait += *(_playPos++); + } +// _playPos++; + _samplesTillPoll = _wait * (_rate / 1000); } -void AdLib::setRepeating(int32 repCount) { - _repCount = repCount; +void MDYPlayer::reset() { + AdLib::reset(); + +// _soundMode 1 : Percussive mode. + if (_soundMode == 1) { + writeOPL(0xA6, 0); + writeOPL(0xB6, 0); + writeOPL(0xA7, 0); + writeOPL(0xB7, 0); + writeOPL(0xA8, 0); + writeOPL(0xB8, 0); + +// TODO set the correct frequency for the last 4 percussive voices + } } -int AdLib::getIndex() const { - return _index; +void MDYPlayer::rewind() { + _playPos = _data; } -void AdLib::startPlay() { - if (_data) _playing = true; +void MDYPlayer::setVoices() { + byte *timbrePtr; + + timbrePtr = _timbres; + debugC(6, kDebugSound, "TBR version: %X.%X", timbrePtr[0], timbrePtr[1]); + timbrePtr += 2; + + _tbrCount = READ_LE_UINT16(timbrePtr); + debugC(6, kDebugSound, "Timbres counter: %d", _tbrCount); + timbrePtr += 2; + _tbrStart = READ_LE_UINT16(timbrePtr); + + timbrePtr += 2; + for (int i = 0; i < _tbrCount ; i++) + setVoice(i, i, true); } -void AdLib::stopPlay() { - Common::StackLock slock(_mutex); - _playing = false; +void MDYPlayer::setVoice(byte voice, byte instr, bool set) { + uint16 strct[27]; + byte channel; + byte *timbrePtr; + char timbreName[10]; + + timbreName[9] = '\0'; + for (int j = 0; j < 9; j++) + timbreName[j] = _timbres[6 + j + (instr * 9)]; + debugC(6, kDebugSound, "Loading timbre %s", timbreName); + + // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 + // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 + for (int i = 0; i < 2; i++) { + timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A; + for (int j = 0; j < 27; j++) { + strct[j] = READ_LE_UINT16(timbrePtr); + timbrePtr += 2; + } + channel = _operators[voice] + i * 3; + writeOPL(0xBD, 0x00); + writeOPL(0x08, 0x00); + writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); + if (!i) + writeOPL(0xC0 | voice, + ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); + writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); + writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); + writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | + ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | + ((strct[11] & 1) << 4) | (strct[1] & 0xF)); + if (!i) + writeOPL(0xE0 | channel, (strct[26] & 3)); + else { + writeOPL(0xE0 | channel, (strct[14] & 3)); + writeOPL(0x40 | channel, 0); + } + } } } // End of namespace Gob -- cgit v1.2.3