void SoundGen2GS::advanceMidiPlayer() { if (_disableMidi) return; const uint8 *p; uint8 parm1, parm2; static uint8 cmd, chn; if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) { warning("Error playing Apple IIGS MIDI sound resource"); _playing = false; return; } IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound]; _ticks++; _playing = true; p = midiObj->getPtr(); while (true) { // Check for end of MIDI sequence marker (Can also be here before delta-time) if (*p == MIDI_STOP_SEQUENCE) { debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)"); _playing = false; midiObj->rewind(); return; } if (*p == MIDI_TIMER_SYNC) { debugC(3, kDebugLevelSound, "Timer sync"); p++; // Jump over the timer sync byte as it's not needed continue; } // Check for delta time uint8 delta = *p; if (midiObj->_ticks + delta > _ticks) break; midiObj->_ticks += delta; p++; // Check for end of MIDI sequence marker (This time it after reading delta-time) if (*p == MIDI_STOP_SEQUENCE) { debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)"); _playing = false; midiObj->rewind(); return; } // Separate byte into command and channel if it's a command byte. // Otherwise use running status (i.e. previously set command and channel). if (*p & 0x80) { cmd = *p++; chn = cmd & 0x0f; cmd >>= 4; } switch (cmd) { case MIDI_NOTE_OFF: parm1 = *p++; parm2 = *p++; debugC(3, kDebugLevelSound, "channel %X: note off (key = %d, velocity = %d)", chn, parm1, parm2); midiNoteOff(chn, parm1, parm2); break; case MIDI_NOTE_ON: parm1 = *p++; parm2 = *p++; debugC(3, kDebugLevelSound, "channel %X: note on (key = %d, velocity = %d)", chn, parm1, parm2); midiNoteOn(chn, parm1, parm2); break; case MIDI_CONTROLLER: parm1 = *p++; parm2 = *p++; debugC(3, kDebugLevelSound, "channel %X: controller %02X = %02X", chn, parm1, parm2); // The tested Apple IIGS AGI MIDI resources only used // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off). // Controller 0's parameter was in range 94-127, // controller 7's parameter was in range 0-127 and // controller 64's parameter was always 0 (i.e. sustain off). switch (parm1) { case 7: _channels[chn].setVolume(parm2); break; } break; case MIDI_PROGRAM_CHANGE: parm1 = *p++; debugC(3, kDebugLevelSound, "channel %X: program change %02X", chn, parm1); _channels[chn].setInstrument(getInstrument(parm1)); break; case MIDI_PITCH_WHEEL: parm1 = *p++; parm2 = *p++; debugC(3, kDebugLevelSound, "channel %X: pitch wheel (unimplemented)", chn); break; default: debugC(3, kDebugLevelSound, "channel %X: unimplemented command %02X", chn, cmd); break; } }
void SoundGen2GS::playMidiSound() { if (_disabledMidi) return; const uint8 *p; uint8 parm1, parm2; static uint8 cmd, ch; if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) { warning("Error playing Apple IIGS MIDI sound resource"); _playing = false; return; } IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound]; _playing = true; p = midiObj->getPtr(); midiObj->_soundBufTicks++; while (true) { uint8 readByte = *p; // Check for end of MIDI sequence marker (Can also be here before delta-time) if (readByte == MIDI_BYTE_STOP_SEQUENCE) { debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)"); _playing = false; midiObj->rewind(); return; } else if (readByte == MIDI_BYTE_TIMER_SYNC) { debugC(3, kDebugLevelSound, "Timer sync"); p++; // Jump over the timer sync byte as it's not needed continue; } uint8 deltaTime = readByte; if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) { break; } midiObj->_midiTicks += deltaTime; p++; // Jump over the delta-time byte as it was already taken care of // Check for end of MIDI sequence marker (This time it after reading delta-time) if (*p == MIDI_BYTE_STOP_SEQUENCE) { debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)"); _playing = false; midiObj->rewind(); return; } // Separate byte into command and channel if it's a command byte. // Otherwise use running status (i.e. previously set command and channel). if (*p & 0x80) { cmd = *p++; ch = cmd & 0x0f; cmd >>= 4; } switch (cmd) { case MIDI_CMD_NOTE_OFF: parm1 = *p++; parm2 = *p++; midiNoteOff(ch, parm1, parm2); break; case MIDI_CMD_NOTE_ON: parm1 = *p++; parm2 = *p++; midiNoteOn(ch, parm1, parm2); break; case MIDI_CMD_CONTROLLER: parm1 = *p++; parm2 = *p++; midiController(ch, parm1, parm2); break; case MIDI_CMD_PROGRAM_CHANGE: parm1 = *p++; midiProgramChange(ch, parm1); break; case MIDI_CMD_PITCH_WHEEL: parm1 = *p++; parm2 = *p++; uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value midiPitchWheel(wheelPos); break; } }
void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& time, f_cnt_t offset ) { if( Engine::getSong()->isExporting() ) { return; } bool eventHandled = false; switch( event.type() ) { // we don't send MidiNoteOn, MidiNoteOff and MidiKeyPressure // events to instrument as NotePlayHandle will send them on its // own case MidiNoteOn: if( event.velocity() > 0 ) { if( m_notes[event.key()] == NULL ) { NotePlayHandle* nph = NotePlayHandleManager::acquire( this, offset, typeInfo<f_cnt_t>::max() / 2, Note( MidiTime(), MidiTime(), event.key(), event.volume( midiPort()->baseVelocity() ) ), NULL, event.channel(), NotePlayHandle::OriginMidiInput ); m_notes[event.key()] = nph; if( ! Engine::mixer()->addPlayHandle( nph ) ) { m_notes[event.key()] = NULL; } } eventHandled = true; break; } case MidiNoteOff: if( m_notes[event.key()] != NULL ) { // do actual note off and remove internal reference to NotePlayHandle (which itself will // be deleted later automatically) Engine::mixer()->requestChangeInModel(); m_notes[event.key()]->noteOff( offset ); if (isSustainPedalPressed() && m_notes[event.key()]->origin() == m_notes[event.key()]->OriginMidiInput) { m_sustainedNotes << m_notes[event.key()]; } m_notes[event.key()] = NULL; Engine::mixer()->doneChangeInModel(); } eventHandled = true; break; case MidiKeyPressure: if( m_notes[event.key()] != NULL ) { // setVolume() calls processOutEvent() with MidiKeyPressure so the // attached instrument will receive the event as well m_notes[event.key()]->setVolume( event.volume( midiPort()->baseVelocity() ) ); } eventHandled = true; break; case MidiPitchBend: // updatePitch() is connected to m_pitchModel::dataChanged() which will send out // MidiPitchBend events m_pitchModel.setValue( m_pitchModel.minValue() + event.pitchBend() * m_pitchModel.range() / MidiMaxPitchBend ); break; case MidiControlChange: if( event.controllerNumber() == MidiControllerSustain ) { if( event.controllerValue() > MidiMaxControllerValue/2 ) { m_sustainPedalPressed = true; } else if (isSustainPedalPressed()) { for (NotePlayHandle* nph : m_sustainedNotes) { if (nph && nph->isReleased()) { if( nph->origin() == nph->OriginMidiInput) { nph->setLength( MidiTime( static_cast<f_cnt_t>( nph->totalFramesPlayed() / Engine::framesPerTick() ) ) ); midiNoteOff( *nph ); } } } m_sustainedNotes.clear(); m_sustainPedalPressed = false; } } if( event.controllerNumber() == MidiControllerAllSoundOff || event.controllerNumber() == MidiControllerAllNotesOff || event.controllerNumber() == MidiControllerOmniOn || event.controllerNumber() == MidiControllerOmniOff || event.controllerNumber() == MidiControllerMonoOn || event.controllerNumber() == MidiControllerPolyOn ) { silenceAllNotes(); } break; case MidiMetaEvent: // handle special cases such as note panning switch( event.metaEvent() ) { case MidiNotePanning: if( m_notes[event.key()] != NULL ) { eventHandled = true; m_notes[event.key()]->setPanning( event.panning() ); } break; default: qWarning( "InstrumentTrack: unhandled MIDI meta event: %i", event.metaEvent() ); break; } break; default: break; } if( eventHandled == false && instrument()->handleMidiEvent( event, time, offset ) == false ) { qWarning( "InstrumentTrack: unhandled MIDI event %d", event.type() ); } }