| /*---------------------------------------------------------------------------- |
| * |
| * File: |
| * eas_voicemgt.c |
| * |
| * Contents and purpose: |
| * Implements the synthesizer functions. |
| * |
| * Copyright Sonic Network Inc. 2004 |
| |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *---------------------------------------------------------------------------- |
| * Revision Control: |
| * $Revision: 794 $ |
| * $Date: 2007-08-01 00:08:48 -0700 (Wed, 01 Aug 2007) $ |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /* includes */ |
| #include "eas.h" |
| #include "eas_data.h" |
| #include "eas_config.h" |
| #include "eas_report.h" |
| #include "eas_midictrl.h" |
| #include "eas_host.h" |
| #include "eas_synth_protos.h" |
| #include "eas_vm_protos.h" |
| |
| #ifdef DLS_SYNTHESIZER |
| #include "eas_mdls.h" |
| #endif |
| |
| // #define _DEBUG_VM |
| |
| /* some defines for workload */ |
| #define WORKLOAD_AMOUNT_SMALL_INCREMENT 5 |
| #define WORKLOAD_AMOUNT_START_NOTE 10 |
| #define WORKLOAD_AMOUNT_STOP_NOTE 10 |
| #define WORKLOAD_AMOUNT_KEY_GROUP 10 |
| #define WORKLOAD_AMOUNT_POLY_LIMIT 10 |
| |
| /* pointer to base sound library */ |
| extern S_EAS easSoundLib; |
| |
| #ifdef TEST_HARNESS |
| extern S_EAS easTestLib; |
| EAS_SNDLIB_HANDLE VMGetLibHandle(EAS_INT libNum) |
| { |
| switch (libNum) |
| { |
| case 0: |
| return &easSoundLib; |
| #ifdef _WT_SYNTH |
| case 1: |
| return &easTestLib; |
| #endif |
| default: |
| return NULL; |
| } |
| } |
| #endif |
| |
| /* pointer to synthesizer interface(s) */ |
| #ifdef _WT_SYNTH |
| extern const S_SYNTH_INTERFACE wtSynth; |
| #endif |
| |
| #ifdef _FM_SYNTH |
| extern const S_SYNTH_INTERFACE fmSynth; |
| #endif |
| |
| typedef S_SYNTH_INTERFACE *S_SYNTH_INTERFACE_HANDLE; |
| |
| /* wavetable on MCU */ |
| #if defined(EAS_WT_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &wtSynth; |
| |
| /* FM on MCU */ |
| #elif defined(EAS_FM_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &fmSynth; |
| |
| /* wavetable drums on MCU, FM melodic on DSP */ |
| #elif defined(EAS_HYBRID_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &wtSynth; |
| const S_SYNTH_INTERFACE *const pSecondarySynth = &fmSynth; |
| |
| /* wavetable drums on MCU, wavetable melodic on DSP */ |
| #elif defined(EAS_SPLIT_WT_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &wtSynth; |
| extern const S_FRAME_INTERFACE wtFrameInterface; |
| const S_FRAME_INTERFACE *const pFrameInterface = &wtFrameInterface; |
| |
| /* wavetable drums on MCU, FM melodic on DSP */ |
| #elif defined(EAS_SPLIT_HYBRID_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &wtSynth; |
| const S_SYNTH_INTERFACE *const pSecondarySynth = &fmSynth; |
| extern const S_FRAME_INTERFACE fmFrameInterface; |
| const S_FRAME_INTERFACE *const pFrameInterface = &fmFrameInterface; |
| |
| /* FM on DSP */ |
| #elif defined(EAS_SPLIT_FM_SYNTH) |
| const S_SYNTH_INTERFACE *const pPrimarySynth = &fmSynth; |
| extern const S_FRAME_INTERFACE fmFrameInterface; |
| const S_FRAME_INTERFACE *const pFrameInterface = &fmFrameInterface; |
| |
| #else |
| #error "Undefined architecture option" |
| #endif |
| |
| /*---------------------------------------------------------------------------- |
| * inline functions |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_INLINE const S_REGION* GetRegionPtr (S_SYNTH *pSynth, EAS_U16 regionIndex) |
| { |
| #if defined(DLS_SYNTHESIZER) |
| if (regionIndex & FLAG_RGN_IDX_DLS_SYNTH) |
| return &pSynth->pDLS->pDLSRegions[regionIndex & REGION_INDEX_MASK].wtRegion.region; |
| #endif |
| #if defined(_HYBRID_SYNTH) |
| if (regionIndex & FLAG_RGN_IDX_FM_SYNTH) |
| return &pSynth->pEAS->pFMRegions[regionIndex & REGION_INDEX_MASK].region; |
| else |
| return &pSynth->pEAS->pWTRegions[regionIndex].region; |
| #elif defined(_WT_SYNTH) |
| return &pSynth->pEAS->pWTRegions[regionIndex].region; |
| #elif defined(_FM_SYNTH) |
| return &pSynth->pEAS->pFMRegions[regionIndex].region; |
| #endif |
| } |
| |
| /*lint -esym(715, voiceNum) used in some implementation */ |
| EAS_INLINE const S_SYNTH_INTERFACE* GetSynthPtr (EAS_INT voiceNum) |
| { |
| #if defined(_HYBRID_SYNTH) |
| if (voiceNum < NUM_PRIMARY_VOICES) |
| return pPrimarySynth; |
| else |
| return pSecondarySynth; |
| #else |
| return pPrimarySynth; |
| #endif |
| } |
| |
| EAS_INLINE EAS_INT GetAdjustedVoiceNum (EAS_INT voiceNum) |
| { |
| #if defined(_HYBRID_SYNTH) |
| if (voiceNum >= NUM_PRIMARY_VOICES) |
| return voiceNum - NUM_PRIMARY_VOICES; |
| #endif |
| return voiceNum; |
| } |
| |
| EAS_INLINE EAS_U8 VSynthToChannel (S_SYNTH *pSynth, EAS_U8 channel) |
| { |
| /*lint -e{734} synthNum is always 0-15 */ |
| return channel | (pSynth->vSynthNum << 4); |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * InitVoice() |
| *---------------------------------------------------------------------------- |
| * Initialize a synthesizer voice |
| *---------------------------------------------------------------------------- |
| */ |
| void InitVoice (S_SYNTH_VOICE *pVoice) |
| { |
| pVoice->channel = UNASSIGNED_SYNTH_CHANNEL; |
| pVoice->nextChannel = UNASSIGNED_SYNTH_CHANNEL; |
| pVoice->note = pVoice->nextNote = DEFAULT_KEY_NUMBER; |
| pVoice->velocity = pVoice->nextVelocity = DEFAULT_VELOCITY; |
| pVoice->regionIndex = DEFAULT_REGION_INDEX; |
| pVoice->age = DEFAULT_AGE; |
| pVoice->voiceFlags = DEFAULT_VOICE_FLAGS; |
| pVoice->voiceState = DEFAULT_VOICE_STATE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * IncVoicePoolCount() |
| *---------------------------------------------------------------------------- |
| * Updates the voice pool count when a voice changes state |
| *---------------------------------------------------------------------------- |
| */ |
| static void IncVoicePoolCount (S_VOICE_MGR *pVoiceMgr, S_SYNTH_VOICE *pVoice) |
| { |
| S_SYNTH *pSynth; |
| EAS_INT pool; |
| |
| /* ignore muting voices */ |
| if (pVoice->voiceState == eVoiceStateMuting) |
| return; |
| |
| if (pVoice->voiceState == eVoiceStateStolen) |
| { |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->nextChannel)]; |
| pool = pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool; |
| } |
| else |
| { |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)]; |
| pool = pSynth->channels[GET_CHANNEL(pVoice->channel)].pool; |
| } |
| |
| pSynth->poolCount[pool]++; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "IncVoicePoolCount: Synth=%d pool=%d\n", pSynth->vSynthNum, pool); */ } |
| #endif |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * DecVoicePoolCount() |
| *---------------------------------------------------------------------------- |
| * Updates the voice pool count when a voice changes state |
| *---------------------------------------------------------------------------- |
| */ |
| static void DecVoicePoolCount (S_VOICE_MGR *pVoiceMgr, S_SYNTH_VOICE *pVoice) |
| { |
| S_SYNTH *pSynth; |
| EAS_INT pool; |
| |
| /* ignore muting voices */ |
| if (pVoice->voiceState == eVoiceStateMuting) |
| return; |
| |
| if (pVoice->voiceState == eVoiceStateStolen) |
| { |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->nextChannel)]; |
| pool = pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool; |
| } |
| else |
| { |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)]; |
| pool = pSynth->channels[GET_CHANNEL(pVoice->channel)].pool; |
| } |
| |
| pSynth->poolCount[pool]--; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "DecVoicePoolCount: Synth=%d pool=%d\n", pSynth->vSynthNum, pool); */ } |
| #endif |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitialize() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMInitialize (S_EAS_DATA *pEASData) |
| { |
| S_VOICE_MGR *pVoiceMgr; |
| EAS_INT i; |
| |
| /* check Configuration Module for data allocation */ |
| if (pEASData->staticMemoryModel) |
| pVoiceMgr = EAS_CMEnumData(EAS_CM_SYNTH_DATA); |
| else |
| pVoiceMgr = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_VOICE_MGR)); |
| if (!pVoiceMgr) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitialize: Failed to allocate synthesizer memory\n"); */ } |
| return EAS_ERROR_MALLOC_FAILED; |
| } |
| EAS_HWMemSet(pVoiceMgr, 0, sizeof(S_VOICE_MGR)); |
| |
| /* initialize non-zero variables */ |
| pVoiceMgr->pGlobalEAS = (S_EAS*) &easSoundLib; |
| pVoiceMgr->maxPolyphony = (EAS_U16) MAX_SYNTH_VOICES; |
| |
| #if defined(_SECONDARY_SYNTH) || defined(EAS_SPLIT_WT_SYNTH) |
| pVoiceMgr->maxPolyphonyPrimary = NUM_PRIMARY_VOICES; |
| pVoiceMgr->maxPolyphonySecondary = NUM_SECONDARY_VOICES; |
| #endif |
| |
| /* set max workload to zero */ |
| pVoiceMgr->maxWorkLoad = 0; |
| |
| /* initialize the voice manager parameters */ |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| InitVoice(&pVoiceMgr->voices[i]); |
| |
| /* initialize the synth */ |
| /*lint -e{522} return unused at this time */ |
| pPrimarySynth->pfInitialize(pVoiceMgr); |
| |
| /* initialize the off-chip synth */ |
| #ifdef _HYBRID_SYNTH |
| /*lint -e{522} return unused at this time */ |
| pSecondarySynth->pfInitialize(pVoiceMgr); |
| #endif |
| |
| pEASData->pVoiceMgr = pVoiceMgr; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitMIDI() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMInitMIDI (S_EAS_DATA *pEASData, S_SYNTH **ppSynth) |
| { |
| EAS_RESULT result; |
| S_SYNTH *pSynth; |
| EAS_INT virtualSynthNum; |
| |
| *ppSynth = NULL; |
| |
| /* static memory model only allows one synth */ |
| if (pEASData->staticMemoryModel) |
| { |
| if (pEASData->pVoiceMgr->pSynth[0] != NULL) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI: No virtual synthesizer support for static memory model\n"); */ } |
| return EAS_ERROR_NO_VIRTUAL_SYNTHESIZER; |
| } |
| |
| /* check Configuration Module for data allocation */ |
| pSynth = EAS_CMEnumData(EAS_CM_MIDI_DATA); |
| virtualSynthNum = 0; |
| } |
| |
| /* dynamic memory model */ |
| else |
| { |
| for (virtualSynthNum = 0; virtualSynthNum < MAX_VIRTUAL_SYNTHESIZERS; virtualSynthNum++) |
| if (pEASData->pVoiceMgr->pSynth[virtualSynthNum] == NULL) |
| break; |
| if (virtualSynthNum == MAX_VIRTUAL_SYNTHESIZERS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI: Exceeded number of active virtual synthesizers"); */ } |
| return EAS_ERROR_NO_VIRTUAL_SYNTHESIZER; |
| } |
| pSynth = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_SYNTH)); |
| } |
| |
| /* make sure we have a valid memory pointer */ |
| if (pSynth == NULL) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI: Failed to allocate synthesizer memory\n"); */ } |
| return EAS_ERROR_MALLOC_FAILED; |
| } |
| EAS_HWMemSet(pSynth, 0, sizeof(S_SYNTH)); |
| |
| /* set the sound library pointer */ |
| if ((result = VMSetEASLib(pSynth, pEASData->pVoiceMgr->pGlobalEAS)) != EAS_SUCCESS) |
| { |
| VMMIDIShutdown(pEASData, pSynth); |
| return result; |
| } |
| |
| /* link in DLS bank if downloaded */ |
| #ifdef DLS_SYNTHESIZER |
| if (pEASData->pVoiceMgr->pGlobalDLS) |
| { |
| pSynth->pDLS = pEASData->pVoiceMgr->pGlobalDLS; |
| DLSAddRef(pSynth->pDLS); |
| } |
| #endif |
| |
| /* initialize MIDI state variables */ |
| pSynth->synthFlags = DEFAULT_SYNTH_FLAGS; |
| pSynth->masterVolume = DEFAULT_SYNTH_MASTER_VOLUME; |
| pSynth->refCount = 1; |
| pSynth->priority = DEFAULT_SYNTH_PRIORITY; |
| pSynth->poolAlloc[0] = (EAS_U8) pEASData->pVoiceMgr->maxPolyphony; |
| |
| VMInitializeAllChannels(pEASData->pVoiceMgr, pSynth); |
| |
| pSynth->vSynthNum = (EAS_U8) virtualSynthNum; |
| pEASData->pVoiceMgr->pSynth[virtualSynthNum] = pSynth; |
| |
| *ppSynth = pSynth; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMIncRefCount() |
| *---------------------------------------------------------------------------- |
| * Increment reference count for virtual synth |
| *---------------------------------------------------------------------------- |
| */ |
| void VMIncRefCount (S_SYNTH *pSynth) |
| { |
| pSynth->refCount++; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMReset() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * We call this routine to start the process of reseting the synth. |
| * This routine sets a flag for the entire synth indicating that we want |
| * to reset. |
| * We also force all voices to mute quickly. |
| * However, we do not actually perform any synthesis in this routine. That |
| * is, we do not ramp the voices down from this routine, but instead, we |
| * let the "regular" synth processing steps take care of adding the ramp |
| * down samples to the output buffer. After we are sure that all voices |
| * have completed ramping down, we continue the process of resetting the |
| * synth (from another routine). |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * force - force reset even if voices are active |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * - set a flag (in psSynthObject->m_nFlags) indicating synth reset requested. |
| * - force all voices to update their envelope states to mute |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMReset (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_BOOL force) |
| { |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMReset: request to reset synth. Force = %d\n", force); */ } |
| #endif |
| |
| /* force voices to off state - may cause audio artifacts */ |
| if (force) |
| { |
| pVoiceMgr->activeVoices -= pSynth->numActiveVoices; |
| pSynth->numActiveVoices = 0; |
| VMInitializeAllVoices(pVoiceMgr, pSynth->vSynthNum); |
| } |
| else |
| VMMuteAllVoices(pVoiceMgr, pSynth); |
| |
| /* don't reset if voices are still playing */ |
| if (pSynth->numActiveVoices == 0) |
| { |
| EAS_INT i; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMReset: complete the reset process\n"); */ } |
| #endif |
| |
| VMInitializeAllChannels(pVoiceMgr, pSynth); |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| pSynth->poolCount[i] = 0; |
| |
| /* set polyphony */ |
| if (pSynth->maxPolyphony < pVoiceMgr->maxPolyphony) |
| pSynth->poolAlloc[0] = (EAS_U8) pVoiceMgr->maxPolyphony; |
| else |
| pSynth->poolAlloc[0] = (EAS_U8) pSynth->maxPolyphony; |
| |
| /* clear reset flag */ |
| pSynth->synthFlags &= ~SYNTH_FLAG_RESET_IS_REQUESTED; |
| } |
| |
| /* handle reset after voices are muted */ |
| else |
| pSynth->synthFlags |= SYNTH_FLAG_RESET_IS_REQUESTED; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitializeAllChannels() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMInitializeAllChannels (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_INT i; |
| |
| VMResetControllers(pSynth); |
| |
| /* init each channel */ |
| pChannel = pSynth->channels; |
| |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++, pChannel++) |
| { |
| pChannel->channelFlags = DEFAULT_CHANNEL_FLAGS; |
| pChannel->staticGain = DEFAULT_CHANNEL_STATIC_GAIN; |
| pChannel->staticPitch = DEFAULT_CHANNEL_STATIC_PITCH; |
| pChannel->pool = 0; |
| |
| /* the drum channel needs a different init */ |
| if (i == DEFAULT_DRUM_CHANNEL) |
| { |
| pChannel->bankNum = DEFAULT_RHYTHM_BANK_NUMBER; |
| pChannel->channelFlags |= CHANNEL_FLAG_RHYTHM_CHANNEL; |
| } |
| else |
| pChannel->bankNum = DEFAULT_MELODY_BANK_NUMBER; |
| |
| VMProgramChange(pVoiceMgr, pSynth, (EAS_U8) i, DEFAULT_SYNTH_PROGRAM_NUMBER); |
| } |
| |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMResetControllers() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMResetControllers (S_SYNTH *pSynth) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_INT i; |
| |
| pChannel = pSynth->channels; |
| |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++, pChannel++) |
| { |
| pChannel->pitchBend = DEFAULT_PITCH_BEND; |
| pChannel->modWheel = DEFAULT_MOD_WHEEL; |
| pChannel->volume = DEFAULT_CHANNEL_VOLUME; |
| pChannel->pan = DEFAULT_PAN; |
| pChannel->expression = DEFAULT_EXPRESSION; |
| |
| #ifdef _REVERB |
| pSynth->channels[i].reverbSend = DEFAULT_REVERB_SEND; |
| #endif |
| |
| #ifdef _CHORUS |
| pSynth->channels[i].chorusSend = DEFAULT_CHORUS_SEND; |
| #endif |
| |
| pChannel->channelPressure = DEFAULT_CHANNEL_PRESSURE; |
| pChannel->registeredParam = DEFAULT_REGISTERED_PARAM; |
| pChannel->pitchBendSensitivity = DEFAULT_PITCH_BEND_SENSITIVITY; |
| pChannel->finePitch = DEFAULT_FINE_PITCH; |
| pChannel->coarsePitch = DEFAULT_COARSE_PITCH; |
| |
| /* update all voices on this channel */ |
| pChannel->channelFlags |= CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitializeAllVoices() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMInitializeAllVoices (S_VOICE_MGR *pVoiceMgr, EAS_INT vSynthNum) |
| { |
| EAS_INT i; |
| |
| /* initialize the voice manager parameters */ |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| if (pVoiceMgr->voices[i].voiceState != eVoiceStateStolen) |
| { |
| if (GET_VSYNTH(pVoiceMgr->voices[i].channel) == vSynthNum) |
| InitVoice(&pVoiceMgr->voices[i]); |
| } |
| else |
| { |
| if (GET_VSYNTH(pVoiceMgr->voices[i].nextChannel) == vSynthNum) |
| InitVoice(&pVoiceMgr->voices[i]); |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMMuteVoice() |
| *---------------------------------------------------------------------------- |
| * Mute the selected voice |
| *---------------------------------------------------------------------------- |
| */ |
| void VMMuteVoice (S_VOICE_MGR *pVoiceMgr, EAS_I32 voiceNum) |
| { |
| S_SYNTH *pSynth; |
| S_SYNTH_VOICE *pVoice; |
| |
| /* take no action if voice is already muted */ |
| pVoice = &pVoiceMgr->voices[voiceNum]; |
| if ((pVoice->voiceState == eVoiceStateMuting) || (pVoice->voiceState == eVoiceStateFree)) |
| return; |
| |
| /* one less voice in pool */ |
| DecVoicePoolCount(pVoiceMgr, pVoice); |
| |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)]; |
| GetSynthPtr(voiceNum)->pfMuteVoice(pVoiceMgr, pSynth, pVoice, GetAdjustedVoiceNum(voiceNum)); |
| pVoice->voiceState = eVoiceStateMuting; |
| |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMReleaseVoice() |
| *---------------------------------------------------------------------------- |
| * Release the selected voice |
| *---------------------------------------------------------------------------- |
| */ |
| void VMReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 voiceNum) |
| { |
| S_SYNTH_VOICE *pVoice = &pVoiceMgr->voices[voiceNum]; |
| |
| /* take no action if voice is already free, muting, or releasing */ |
| if (( pVoice->voiceState == eVoiceStateMuting) || |
| (pVoice->voiceState == eVoiceStateFree) || |
| (pVoice->voiceState == eVoiceStateRelease)) |
| return; |
| |
| /* stolen voices should just be muted */ |
| if (pVoice->voiceState == eVoiceStateStolen) |
| VMMuteVoice(pVoiceMgr, voiceNum); |
| |
| /* release this voice */ |
| GetSynthPtr(voiceNum)->pfReleaseVoice(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum], GetAdjustedVoiceNum(voiceNum)); |
| pVoice->voiceState = eVoiceStateRelease; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitMIPTable() |
| *---------------------------------------------------------------------------- |
| * Initialize the SP-MIDI MIP table in preparation for receiving MIP message |
| *---------------------------------------------------------------------------- |
| */ |
| void VMInitMIPTable (S_SYNTH *pSynth) |
| { |
| EAS_INT i; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMInitMIPTable\n"); */ } |
| #endif |
| |
| /* clear SP-MIDI flag */ |
| pSynth->synthFlags &= ~SYNTH_FLAG_SP_MIDI_ON; |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| { |
| pSynth->channels[i].pool = 0; |
| pSynth->channels[i].mip = 0; |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetMIPEntry() |
| *---------------------------------------------------------------------------- |
| * Sets the priority and MIP level for a MIDI channel |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| void VMSetMIPEntry (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 priority, EAS_U8 mip) |
| { |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMSetMIPEntry: channel=%d, priority=%d, MIP=%d\n", channel, priority, mip); */ } |
| #endif |
| |
| /* save data for use by MIP message processing */ |
| if (priority < NUM_SYNTH_CHANNELS) |
| { |
| pSynth->channels[channel].pool = priority; |
| pSynth->channels[channel].mip = mip; |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMMIPUpdateChannelMuting() |
| *---------------------------------------------------------------------------- |
| * This routine is called after an SP-MIDI message is received and |
| * any time the allocated polyphony changes. It mutes or unmutes |
| * channels based on polyphony. |
| *---------------------------------------------------------------------------- |
| */ |
| void VMMIPUpdateChannelMuting (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| EAS_INT i; |
| EAS_INT maxPolyphony; |
| EAS_INT channel; |
| EAS_INT vSynthNum; |
| EAS_INT pool; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMUpdateMIPTable\n"); */ } |
| #endif |
| |
| /* determine max polyphony */ |
| if (pSynth->maxPolyphony) |
| maxPolyphony = pSynth->maxPolyphony; |
| else |
| maxPolyphony = pVoiceMgr->maxPolyphony; |
| |
| /* process channels */ |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| { |
| |
| /* channel must be in MIP message and must meet allocation target */ |
| if ((pSynth->channels[i].mip != 0) && (pSynth->channels[i].mip <= maxPolyphony)) |
| pSynth->channels[i].channelFlags &= ~CHANNEL_FLAG_MUTE; |
| else |
| pSynth->channels[i].channelFlags |= CHANNEL_FLAG_MUTE; |
| |
| /* reset voice pool count */ |
| pSynth->poolCount[i] = 0; |
| } |
| |
| /* mute any voices on muted channels, and count unmuted voices */ |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| |
| /* ignore free voices */ |
| if (pVoiceMgr->voices[i].voiceState == eVoiceStateFree) |
| continue; |
| |
| /* get channel and virtual synth */ |
| if (pVoiceMgr->voices[i].voiceState != eVoiceStateStolen) |
| { |
| vSynthNum = GET_VSYNTH(pVoiceMgr->voices[i].channel); |
| channel = GET_CHANNEL(pVoiceMgr->voices[i].channel); |
| } |
| else |
| { |
| vSynthNum = GET_VSYNTH(pVoiceMgr->voices[i].nextChannel); |
| channel = GET_CHANNEL(pVoiceMgr->voices[i].nextChannel); |
| } |
| |
| /* ignore voices on other synths */ |
| if (vSynthNum != pSynth->vSynthNum) |
| continue; |
| |
| /* count voices */ |
| pool = pSynth->channels[channel].pool; |
| |
| /* deal with muted channels */ |
| if (pSynth->channels[channel].channelFlags & CHANNEL_FLAG_MUTE) |
| { |
| /* mute stolen voices scheduled to play on this channel */ |
| if (pVoiceMgr->voices[i].voiceState == eVoiceStateStolen) |
| pVoiceMgr->voices[i].voiceState = eVoiceStateMuting; |
| |
| /* release voices that aren't already muting */ |
| else if (pVoiceMgr->voices[i].voiceState != eVoiceStateMuting) |
| { |
| VMReleaseVoice(pVoiceMgr, pSynth, i); |
| pSynth->poolCount[pool]++; |
| } |
| } |
| |
| /* not muted, count this voice */ |
| else |
| pSynth->poolCount[pool]++; |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMUpdateMIPTable() |
| *---------------------------------------------------------------------------- |
| * This routine is called at the end of the SysEx message to allow |
| * the Voice Manager to complete the initialization of the MIP |
| * table. It assigns channels to the appropriate voice pool based |
| * on the MIP setting and calculates the voices allocated for each |
| * pool. |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| void VMUpdateMIPTable (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_INT i; |
| EAS_INT currentMIP; |
| EAS_INT currentPool; |
| EAS_INT priority[NUM_SYNTH_CHANNELS]; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMUpdateMIPTable\n"); */ } |
| #endif |
| |
| /* set SP-MIDI flag */ |
| pSynth->synthFlags |= SYNTH_FLAG_SP_MIDI_ON; |
| |
| /* sort channels into priority order */ |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| priority[i] = -1; |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| { |
| if (pSynth->channels[i].pool != DEFAULT_SP_MIDI_PRIORITY) |
| priority[pSynth->channels[i].pool] = i; |
| } |
| |
| /* process channels in priority order */ |
| currentMIP = 0; |
| currentPool = -1; |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| { |
| /* stop when we run out of channels */ |
| if (priority[i] == -1) |
| break; |
| |
| pChannel = &pSynth->channels[priority[i]]; |
| |
| /* when 2 or more channels have the same MIP setting, they |
| * share a common voice pool |
| */ |
| if (pChannel->mip == currentMIP) |
| pChannel->pool = (EAS_U8) currentPool; |
| |
| /* new voice pool */ |
| else |
| { |
| currentPool++; |
| pSynth->poolAlloc[currentPool] = (EAS_U8) (pChannel->mip - currentMIP); |
| currentMIP = pChannel->mip; |
| } |
| } |
| |
| /* set SP-MIDI flag */ |
| pSynth->synthFlags |= SYNTH_FLAG_SP_MIDI_ON; |
| |
| /* update channel muting */ |
| VMMIPUpdateChannelMuting (pVoiceMgr, pSynth); |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMMuteAllVoices() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * We call this in an emergency reset situation. |
| * This forces all voices to mute quickly. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * - forces all voices to update their envelope states to mute |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMMuteAllVoices (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| EAS_INT i; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMMuteAllVoices: about to mute all voices!!\n"); */ } |
| #endif |
| |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| /* for stolen voices, check new channel */ |
| if (pVoiceMgr->voices[i].voiceState == eVoiceStateStolen) |
| { |
| if (GET_VSYNTH(pVoiceMgr->voices[i].nextChannel) == pSynth->vSynthNum) |
| VMMuteVoice(pVoiceMgr, i); |
| } |
| |
| else if (pSynth->vSynthNum == GET_VSYNTH(pVoiceMgr->voices[i].channel)) |
| VMMuteVoice(pVoiceMgr, i); |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMReleaseAllVoices() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * We call this after we've encountered the end of the Midi file. |
| * This ensures all voices are either in release (because we received their |
| * note off already) or forces them to mute quickly. |
| * We use this as a safety to prevent bad midi files from playing forever. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * - forces all voices to update their envelope states to release or mute |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMReleaseAllVoices (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| EAS_INT i; |
| |
| /* release sustain pedal on all channels */ |
| for (i = 0; i < NUM_SYNTH_CHANNELS; i++) |
| { |
| if (pSynth->channels[ i ].channelFlags & CHANNEL_FLAG_SUSTAIN_PEDAL) |
| { |
| VMReleaseAllDeferredNoteOffs(pVoiceMgr, pSynth, (EAS_U8) i); |
| pSynth->channels[i].channelFlags &= ~CHANNEL_FLAG_SUSTAIN_PEDAL; |
| } |
| } |
| |
| /* release all voices */ |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| |
| switch (pVoiceMgr->voices[i].voiceState) |
| { |
| case eVoiceStateStart: |
| case eVoiceStatePlay: |
| /* only release voices on this synth */ |
| if (GET_VSYNTH(pVoiceMgr->voices[i].channel) == pSynth->vSynthNum) |
| VMReleaseVoice(pVoiceMgr, pSynth, i); |
| break; |
| |
| case eVoiceStateStolen: |
| if (GET_VSYNTH(pVoiceMgr->voices[i].nextChannel) == pSynth->vSynthNum) |
| VMMuteVoice(pVoiceMgr, i); |
| break; |
| |
| case eVoiceStateFree: |
| case eVoiceStateRelease: |
| case eVoiceStateMuting: |
| break; |
| |
| case eVoiceStateInvalid: |
| default: |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMReleaseAllVoices: error, %d is an unrecognized state\n", |
| pVoiceMgr->voices[i].voiceState); */ } |
| #endif |
| break; |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMAllNotesOff() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Quickly mute all notes on the given channel. |
| * |
| * Inputs: |
| * nChannel - quickly turn off all notes on this channel |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * - forces all voices on this channel to update their envelope states to mute |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMAllNotesOff (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel) |
| { |
| EAS_INT voiceNum; |
| S_SYNTH_VOICE *pVoice; |
| |
| #ifdef _DEBUG_VM |
| if (channel >= NUM_SYNTH_CHANNELS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMAllNotesOff: error, %d invalid channel number\n", |
| channel); */ } |
| return; |
| } |
| #endif |
| |
| /* increment workload */ |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_SMALL_INCREMENT; |
| |
| /* check each voice */ |
| channel = VSynthToChannel(pSynth, channel); |
| for (voiceNum = 0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| pVoice = &pVoiceMgr->voices[voiceNum]; |
| if (pVoice->voiceState != eVoiceStateFree) |
| { |
| if (((pVoice->voiceState != eVoiceStateStolen) && (channel == pVoice->channel)) || |
| ((pVoice->voiceState == eVoiceStateStolen) && (channel == pVoice->nextChannel))) |
| { |
| /* this voice is assigned to the requested channel */ |
| GetSynthPtr(voiceNum)->pfMuteVoice(pVoiceMgr, pSynth, pVoice, GetAdjustedVoiceNum(voiceNum)); |
| pVoice->voiceState = eVoiceStateMuting; |
| } |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMDeferredStopNote() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Stop the notes that had deferred note-off requests. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * None. |
| * |
| * Side Effects: |
| * voices that have had deferred note-off requests are now put into release |
| * psSynthObject->m_sVoice[i].m_nFlags has the VOICE_FLAG_DEFER_MIDI_NOTE_OFF |
| * cleared |
| *---------------------------------------------------------------------------- |
| */ |
| void VMDeferredStopNote (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| EAS_INT voiceNum; |
| EAS_INT channel; |
| EAS_BOOL deferredNoteOff; |
| |
| deferredNoteOff = EAS_FALSE; |
| |
| /* check each voice to see if it requires a deferred note off */ |
| for (voiceNum=0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| if (pVoiceMgr->voices[voiceNum].voiceFlags & VOICE_FLAG_DEFER_MIDI_NOTE_OFF) |
| { |
| /* check if this voice was stolen */ |
| if (pVoiceMgr->voices[voiceNum].voiceState == eVoiceStateStolen) |
| { |
| /* |
| This voice was stolen, AND it also has a deferred note-off. |
| The stolen note must be completely ramped down at this point. |
| The note that caused the stealing to occur, however, must |
| have received a note-off request before the note that caused |
| stealing ever had a chance to even start. We want to give |
| the note that caused the stealing a chance to play, so we |
| start it on the next update interval, and we defer sending |
| the note-off request until the subsequent update interval. |
| So do not send the note-off request for this voice because |
| this voice was stolen and should have completed ramping down, |
| Also, do not clear the global flag nor this voice's flag |
| because we must indicate that the subsequent update interval, |
| after the note that caused stealing has started, should |
| then send the deferred note-off request. |
| */ |
| deferredNoteOff = EAS_TRUE; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMDeferredStopNote: defer request to stop voice %d (channel=%d note=%d) - voice not started\n", |
| voiceNum, |
| pVoiceMgr->voices[voiceNum].nextChannel, |
| pVoiceMgr->voices[voiceNum].note); */ } |
| |
| /* sanity check: this stolen voice better be ramped to zero */ |
| if (0 != pVoiceMgr->voices[voiceNum].gain) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMDeferredStopNote: warning, this voice did not complete its ramp to zero\n"); */ } |
| } |
| #endif // #ifdef _DEBUG_VM |
| |
| } |
| else |
| { |
| /* clear the flag using exor */ |
| pVoiceMgr->voices[voiceNum].voiceFlags ^= |
| VOICE_FLAG_DEFER_MIDI_NOTE_OFF; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMDeferredStopNote: Stop voice %d (channel=%d note=%d)\n", |
| voiceNum, |
| pVoiceMgr->voices[voiceNum].nextChannel, |
| pVoiceMgr->voices[voiceNum].note); */ } |
| #endif |
| |
| channel = pVoiceMgr->voices[voiceNum].channel & 15; |
| |
| /* check if sustain pedal is on */ |
| if (pSynth->channels[channel].channelFlags & CHANNEL_FLAG_SUSTAIN_PEDAL) |
| { |
| GetSynthPtr(voiceNum)->pfSustainPedal(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum], &pSynth->channels[channel], GetAdjustedVoiceNum(voiceNum)); |
| } |
| |
| /* release this voice */ |
| else |
| VMReleaseVoice(pVoiceMgr, pSynth, voiceNum); |
| |
| } |
| |
| } |
| |
| } |
| |
| /* clear the deferred note-off flag, unless there's another one pending */ |
| if (deferredNoteOff == EAS_FALSE) |
| pSynth->synthFlags ^= SYNTH_FLAG_DEFERRED_MIDI_NOTE_OFF_PENDING; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMReleaseAllDeferredNoteOffs() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Call this functin when the sustain flag is presently set but |
| * we are now transitioning from damper pedal on to |
| * damper pedal off. This means all notes in this channel |
| * that received a note off while the damper pedal was on, and |
| * had their note-off requests deferred, should now proceed to |
| * the release state. |
| * |
| * Inputs: |
| * nChannel - this channel has its sustain pedal transitioning from on to off |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * Side Effects: |
| * any voice with deferred note offs on this channel are updated such that |
| * pVoice->m_sEG1.m_eState = eEnvelopeStateRelease |
| * pVoice->m_sEG1.m_nIncrement = release increment |
| * pVoice->m_nFlags = clear the deferred note off flag |
| *---------------------------------------------------------------------------- |
| */ |
| void VMReleaseAllDeferredNoteOffs (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel) |
| { |
| S_SYNTH_VOICE *pVoice; |
| EAS_INT voiceNum; |
| |
| #ifdef _DEBUG_VM |
| if (channel >= NUM_SYNTH_CHANNELS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMReleaseAllDeferredNoteOffs: error, %d invalid channel number\n", |
| channel); */ } |
| return; |
| } |
| #endif /* #ifdef _DEBUG_VM */ |
| |
| /* increment workload */ |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_SMALL_INCREMENT; |
| |
| /* find all the voices assigned to this channel */ |
| channel = VSynthToChannel(pSynth, channel); |
| for (voiceNum = 0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| |
| pVoice = &pVoiceMgr->voices[voiceNum]; |
| if (channel == pVoice->channel) |
| { |
| |
| /* does this voice have a deferred note off? */ |
| if (pVoice->voiceFlags & VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF) |
| { |
| /* release voice */ |
| VMReleaseVoice(pVoiceMgr, pSynth, voiceNum); |
| |
| /* use exor to flip bit, clear the flag */ |
| pVoice->voiceFlags &= ~VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF; |
| |
| } |
| |
| } |
| } |
| |
| return; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMCatchNotesForSustainPedal() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Call this function when the sustain flag is presently clear and |
| * the damper pedal is off and we are transitioning from damper pedal OFF to |
| * damper pedal ON. Currently sounding notes should be left |
| * unchanged. However, we should try to "catch" notes if possible. |
| * If any notes are in release and have levels >= sustain level, catch them, |
| * otherwise, let them continue to release. |
| * |
| * Inputs: |
| * nChannel - this channel has its sustain pedal transitioning from on to off |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| *---------------------------------------------------------------------------- |
| */ |
| void VMCatchNotesForSustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel) |
| { |
| EAS_INT voiceNum; |
| |
| #ifdef _DEBUG_VM |
| if (channel >= NUM_SYNTH_CHANNELS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMCatchNotesForSustainPedal: error, %d invalid channel number\n", |
| channel); */ } |
| return; |
| } |
| #endif |
| |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_SMALL_INCREMENT; |
| channel = VSynthToChannel(pSynth, channel); |
| |
| /* find all the voices assigned to this channel */ |
| for (voiceNum = 0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| if (channel == pVoiceMgr->voices[voiceNum].channel) |
| { |
| if (eVoiceStateRelease == pVoiceMgr->voices[voiceNum].voiceState) |
| GetSynthPtr(voiceNum)->pfSustainPedal(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum], &pSynth->channels[channel], GetAdjustedVoiceNum(voiceNum)); |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMUpdateAllNotesAge() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Increment the note age for all of the active voices. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * m_nAge for all voices is incremented |
| *---------------------------------------------------------------------------- |
| */ |
| void VMUpdateAllNotesAge (S_VOICE_MGR *pVoiceMgr, EAS_U16 age) |
| { |
| EAS_INT i; |
| |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| if (age - pVoiceMgr->voices[i].age > 0) |
| pVoiceMgr->voices[i].age++; |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMStolenVoice() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * The selected voice is being stolen. Sets the parameters so that the |
| * voice will begin playing the new sound on the next buffer. |
| * |
| * Inputs: |
| * pVoice - pointer to voice to steal |
| * nChannel - the channel to start a note on |
| * nKeyNumber - the key number to start a note for |
| * nNoteVelocity - the key velocity from this note |
| * |
| * Outputs: |
| * None |
| *---------------------------------------------------------------------------- |
| */ |
| static void VMStolenVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 voiceNum, EAS_U8 channel, EAS_U8 note, EAS_U8 velocity, EAS_U16 regionIndex) |
| { |
| S_SYNTH_VOICE *pVoice = &pVoiceMgr->voices[voiceNum]; |
| |
| /* one less voice in old pool */ |
| DecVoicePoolCount(pVoiceMgr, pVoice); |
| |
| /* mute the sound that is currently playing */ |
| GetSynthPtr(voiceNum)->pfMuteVoice(pVoiceMgr, pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)], &pVoiceMgr->voices[voiceNum], GetAdjustedVoiceNum(voiceNum)); |
| pVoice->voiceState = eVoiceStateStolen; |
| |
| /* set new note data */ |
| pVoice->nextChannel = VSynthToChannel(pSynth, channel); |
| pVoice->nextNote = note; |
| pVoice->nextVelocity = velocity; |
| pVoice->nextRegionIndex = regionIndex; |
| |
| /* one more voice in new pool */ |
| IncVoicePoolCount(pVoiceMgr, pVoice); |
| |
| /* clear the deferred flags */ |
| pVoice->voiceFlags &= |
| ~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF | |
| VOICE_FLAG_DEFER_MUTE | |
| VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF); |
| |
| /* all notes older than this one get "younger" */ |
| VMUpdateAllNotesAge(pVoiceMgr, pVoice->age); |
| |
| /* assign current age to this note and increment for the next note */ |
| pVoice->age = pVoiceMgr->age++; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMFreeVoice() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * The selected voice is done playing and being returned to the |
| * pool of free voices |
| * |
| * Inputs: |
| * pVoice - pointer to voice to free |
| * |
| * Outputs: |
| * None |
| *---------------------------------------------------------------------------- |
| */ |
| static void VMFreeVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice) |
| { |
| |
| /* do nothing if voice is already free */ |
| if (pVoice->voiceState == eVoiceStateFree) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMFreeVoice: Attempt to free voice that is already free\n"); */ } |
| return; |
| } |
| |
| /* if we jump directly to free without passing muting stage, |
| * we need to adjust the voice count */ |
| DecVoicePoolCount(pVoiceMgr, pVoice); |
| |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "VMFreeVoice: Synth=%d\n", pSynth->vSynthNum); */ } |
| #endif |
| |
| /* return to free voice pool */ |
| pVoiceMgr->activeVoices--; |
| pSynth->numActiveVoices--; |
| InitVoice(pVoice); |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMFreeVoice: free voice %d\n", pVoice - pVoiceMgr->voices); */ } |
| #endif |
| |
| /* all notes older than this one get "younger" */ |
| VMUpdateAllNotesAge(pVoiceMgr, pVoice->age); |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMRetargetStolenVoice() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * The selected voice has been stolen and needs to be initalized with |
| * the paramters of its new note. |
| * |
| * Inputs: |
| * pVoice - pointer to voice to retarget |
| * |
| * Outputs: |
| * None |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_BOOL VMRetargetStolenVoice (S_VOICE_MGR *pVoiceMgr, EAS_I32 voiceNum) |
| { |
| EAS_U8 flags; |
| S_SYNTH_CHANNEL *pMIDIChannel; |
| S_SYNTH_VOICE *pVoice; |
| S_SYNTH *pSynth; |
| S_SYNTH *pNextSynth; |
| |
| /* establish some pointers */ |
| pVoice = &pVoiceMgr->voices[voiceNum]; |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)]; |
| pMIDIChannel = &pSynth->channels[pVoice->channel & 15]; |
| pNextSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->nextChannel)]; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMRetargetStolenVoice: retargeting stolen voice %d on channel %d\n", |
| voiceNum, pVoice->channel); */ } |
| |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\to channel %d note: %d velocity: %d\n", |
| pVoice->nextChannel, pVoice->nextNote, pVoice->nextVelocity); */ } |
| #endif |
| |
| /* make sure new channel hasn't been muted by SP-MIDI since the voice was stolen */ |
| if ((pSynth->synthFlags & SYNTH_FLAG_SP_MIDI_ON) && |
| (pMIDIChannel->channelFlags & CHANNEL_FLAG_MUTE)) |
| { |
| VMFreeVoice(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum]); |
| return EAS_FALSE; |
| } |
| |
| /* if assigned to a new synth, correct the active voice count */ |
| if (pVoice->channel != pVoice->nextChannel) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMRetargetStolenVoice: Note assigned to different virtual synth, adjusting numActiveVoices\n"); */ } |
| #endif |
| pSynth->numActiveVoices--; |
| pNextSynth->numActiveVoices++; |
| } |
| |
| /* assign new channel number, and increase channel voice count */ |
| pVoice->channel = pVoice->nextChannel; |
| pMIDIChannel = &pNextSynth->channels[pVoice->channel & 15]; |
| |
| /* assign other data */ |
| pVoice->note = pVoice->nextNote; |
| pVoice->velocity = pVoice->nextVelocity; |
| pVoice->nextChannel = UNASSIGNED_SYNTH_CHANNEL; |
| pVoice->regionIndex = pVoice->nextRegionIndex; |
| |
| /* save the flags, pfStartVoice() will clear them */ |
| flags = pVoice->voiceFlags; |
| |
| /* keep track of the note-start related workload */ |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_START_NOTE; |
| |
| /* setup the voice parameters */ |
| pVoice->voiceState = eVoiceStateStart; |
| |
| /*lint -e{522} return not used at this time */ |
| GetSynthPtr(voiceNum)->pfStartVoice(pVoiceMgr, pNextSynth, &pVoiceMgr->voices[voiceNum], GetAdjustedVoiceNum(voiceNum), pVoice->regionIndex); |
| |
| /* did the new note already receive a MIDI note-off request? */ |
| if (flags & VOICE_FLAG_DEFER_MIDI_NOTE_OFF) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMRetargetVoice: stolen note note-off request deferred\n"); */ } |
| #endif |
| pVoice->voiceFlags |= VOICE_FLAG_DEFER_MIDI_NOTE_OFF; |
| pNextSynth->synthFlags |= SYNTH_FLAG_DEFERRED_MIDI_NOTE_OFF_PENDING; |
| } |
| |
| return EAS_TRUE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMCheckKeyGroup() |
| *---------------------------------------------------------------------------- |
| * If the note that we've been asked to start is in the same key group as |
| * any currently playing notes, then we must shut down the currently playing |
| * note in the same key group |
| *---------------------------------------------------------------------------- |
| */ |
| void VMCheckKeyGroup (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U16 keyGroup, EAS_U8 channel) |
| { |
| const S_REGION *pRegion; |
| EAS_INT voiceNum; |
| |
| /* increment frame workload */ |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_KEY_GROUP; |
| |
| /* need to check all voices in case this is a layered sound */ |
| channel = VSynthToChannel(pSynth, channel); |
| for (voiceNum = 0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| if (pVoiceMgr->voices[voiceNum].voiceState != eVoiceStateStolen) |
| { |
| /* voice must be on the same channel */ |
| if (channel == pVoiceMgr->voices[voiceNum].channel) |
| { |
| /* check key group */ |
| pRegion = GetRegionPtr(pSynth, pVoiceMgr->voices[voiceNum].regionIndex); |
| if (keyGroup == (pRegion->keyGroupAndFlags & 0x0f00)) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMCheckKeyGroup: voice %d matches key group %d\n", voiceNum, keyGroup >> 8); */ } |
| #endif |
| |
| /* if this voice was just started, set it to mute on the next buffer */ |
| if (pVoiceMgr->voices[voiceNum].voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET) |
| pVoiceMgr->voices[voiceNum].voiceFlags |= VOICE_FLAG_DEFER_MUTE; |
| |
| /* mute immediately */ |
| else |
| VMMuteVoice(pVoiceMgr, voiceNum); |
| } |
| } |
| } |
| |
| /* for stolen voice, check new values */ |
| else |
| { |
| /* voice must be on the same channel */ |
| if (channel == pVoiceMgr->voices[voiceNum].nextChannel) |
| { |
| /* check key group */ |
| pRegion = GetRegionPtr(pSynth, pVoiceMgr->voices[voiceNum].nextRegionIndex); |
| if (keyGroup == (pRegion->keyGroupAndFlags & 0x0f00)) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMCheckKeyGroup: voice %d matches key group %d\n", voiceNum, keyGroup >> 8); */ } |
| #endif |
| |
| /* if this voice was just started, set it to mute on the next buffer */ |
| if (pVoiceMgr->voices[voiceNum].voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET) |
| pVoiceMgr->voices[voiceNum].voiceFlags |= VOICE_FLAG_DEFER_MUTE; |
| |
| /* mute immediately */ |
| else |
| VMMuteVoice(pVoiceMgr, voiceNum); |
| } |
| } |
| |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMCheckPolyphonyLimiting() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * We only play at most 2 of the same note on a MIDI channel. |
| * E.g., if we are asked to start note 36, and there are already two voices |
| * that are playing note 36, then we must steal the voice playing |
| * the oldest note 36 and use that stolen voice to play the new note 36. |
| * |
| * Inputs: |
| * nChannel - synth channel that wants to start a new note |
| * nKeyNumber - new note's midi note number |
| * nNoteVelocity - new note's velocity |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * pbVoiceStealingRequired - flag: this routine sets true if we needed to |
| * steal a voice |
| * * |
| * Side Effects: |
| * psSynthObject->m_sVoice[free voice num].m_nKeyNumber may be assigned |
| * psSynthObject->m_sVoice[free voice num].m_nVelocity may be assigned |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_BOOL VMCheckPolyphonyLimiting (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 note, EAS_U8 velocity, EAS_U16 regionIndex, EAS_I32 lowVoice, EAS_I32 highVoice) |
| { |
| EAS_INT voiceNum; |
| EAS_INT oldestVoiceNum; |
| EAS_INT numVoicesPlayingNote; |
| EAS_U16 age; |
| EAS_U16 oldestNoteAge; |
| |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_POLY_LIMIT; |
| |
| numVoicesPlayingNote = 0; |
| oldestVoiceNum = MAX_SYNTH_VOICES; |
| oldestNoteAge = 0; |
| channel = VSynthToChannel(pSynth, channel); |
| |
| /* examine each voice on this channel playing this note */ |
| for (voiceNum = lowVoice; voiceNum <= highVoice; voiceNum++) |
| { |
| /* check stolen notes separately */ |
| if (pVoiceMgr->voices[voiceNum].voiceState != eVoiceStateStolen) |
| { |
| |
| /* same channel and note ? */ |
| if ((channel == pVoiceMgr->voices[voiceNum].channel) && (note == pVoiceMgr->voices[voiceNum].note)) |
| { |
| numVoicesPlayingNote++; |
| age = pVoiceMgr->age - pVoiceMgr->voices[voiceNum].age; |
| |
| /* is this the oldest voice for this note? */ |
| if (age >= oldestNoteAge) |
| { |
| oldestNoteAge = age; |
| oldestVoiceNum = voiceNum; |
| } |
| } |
| } |
| |
| /* handle stolen voices */ |
| else |
| { |
| /* same channel and note ? */ |
| if ((channel == pVoiceMgr->voices[voiceNum].nextChannel) && (note == pVoiceMgr->voices[voiceNum].nextNote)) |
| { |
| numVoicesPlayingNote++; |
| } |
| } |
| } |
| |
| /* check to see if we exceeded poly limit */ |
| if (numVoicesPlayingNote < DEFAULT_CHANNEL_POLYPHONY_LIMIT) |
| return EAS_FALSE; |
| |
| /* make sure we have a voice to steal */ |
| if (oldestVoiceNum != MAX_SYNTH_VOICES) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMCheckPolyphonyLimiting: voice %d has the oldest note\n", oldestVoiceNum); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "VMCheckPolyphonyLimiting: polyphony limiting requires shutting down note %d \n", pVoiceMgr->voices[oldestVoiceNum].note); */ } |
| #endif |
| VMStolenVoice(pVoiceMgr, pSynth, oldestVoiceNum, channel, note, velocity, regionIndex); |
| return EAS_TRUE; |
| } |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMCheckPolyphonyLimiting: No oldest voice to steal\n"); */ } |
| #endif |
| return EAS_FALSE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMStartVoice() |
| *---------------------------------------------------------------------------- |
| * Starts a voice given a region index |
| *---------------------------------------------------------------------------- |
| */ |
| void VMStartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 note, EAS_U8 velocity, EAS_U16 regionIndex) |
| { |
| const S_REGION *pRegion; |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_INT voiceNum; |
| EAS_INT maxSynthPoly; |
| EAS_I32 lowVoice, highVoice; |
| EAS_U16 keyGroup; |
| |
| pChannel = &pSynth->channels[channel]; |
| pRegion = GetRegionPtr(pSynth, regionIndex); |
| |
| /* select correct synth */ |
| #if defined(_SECONDARY_SYNTH) || defined(EAS_SPLIT_WT_SYNTH) |
| { |
| #ifdef EAS_SPLIT_WT_SYNTH |
| if ((pRegion->keyGroupAndFlags & REGION_FLAG_OFF_CHIP) == 0) |
| #else |
| if ((regionIndex & FLAG_RGN_IDX_FM_SYNTH) == 0) |
| #endif |
| { |
| lowVoice = 0; |
| highVoice = NUM_PRIMARY_VOICES - 1; |
| } |
| else |
| { |
| lowVoice = NUM_PRIMARY_VOICES; |
| highVoice = MAX_SYNTH_VOICES - 1; |
| } |
| } |
| #else |
| lowVoice = 0; |
| highVoice = MAX_SYNTH_VOICES - 1; |
| #endif |
| |
| /* keep track of the note-start related workload */ |
| pVoiceMgr->workload+= WORKLOAD_AMOUNT_START_NOTE; |
| |
| /* other voices in pool, check for key group and poly limiting */ |
| if (pSynth->poolCount[pChannel->pool] != 0) |
| { |
| |
| /* check for key group exclusivity */ |
| keyGroup = pRegion->keyGroupAndFlags & 0x0f00; |
| if (keyGroup!= 0) |
| VMCheckKeyGroup(pVoiceMgr, pSynth, keyGroup, channel); |
| |
| /* check polyphony limit and steal a voice if necessary */ |
| if ((pRegion->keyGroupAndFlags & REGION_FLAG_NON_SELF_EXCLUSIVE) == 0) |
| { |
| if (VMCheckPolyphonyLimiting(pVoiceMgr, pSynth, channel, note, velocity, regionIndex, lowVoice, highVoice) == EAS_TRUE) |
| return; |
| } |
| } |
| |
| /* check max poly allocation */ |
| if ((pSynth->maxPolyphony == 0) || (pVoiceMgr->maxPolyphony < pSynth->maxPolyphony)) |
| maxSynthPoly = pVoiceMgr->maxPolyphony; |
| else |
| maxSynthPoly = pSynth->maxPolyphony; |
| |
| /* any free voices? */ |
| if ((pVoiceMgr->activeVoices < pVoiceMgr->maxPolyphony) && |
| (pSynth->numActiveVoices < maxSynthPoly) && |
| (EAS_SUCCESS == VMFindAvailableVoice(pVoiceMgr, &voiceNum, lowVoice, highVoice))) |
| { |
| S_SYNTH_VOICE *pVoice = &pVoiceMgr->voices[voiceNum]; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "VMStartVoice: Synth=%d\n", pSynth->vSynthNum); */ } |
| #endif |
| |
| /* bump voice counts */ |
| pVoiceMgr->activeVoices++; |
| pSynth->numActiveVoices++; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStartVoice: voice %d assigned to channel %d note %d velocity %d\n", |
| voiceNum, channel, note, velocity); */ } |
| #endif |
| |
| /* save parameters */ |
| pVoiceMgr->voices[voiceNum].channel = VSynthToChannel(pSynth, channel); |
| pVoiceMgr->voices[voiceNum].note = note; |
| pVoiceMgr->voices[voiceNum].velocity = velocity; |
| |
| /* establish note age for voice stealing */ |
| pVoiceMgr->voices[voiceNum].age = pVoiceMgr->age++; |
| |
| /* setup the synthesis parameters */ |
| pVoiceMgr->voices[voiceNum].voiceState = eVoiceStateStart; |
| |
| /* increment voice pool count */ |
| IncVoicePoolCount(pVoiceMgr, pVoice); |
| |
| /* start voice on correct synth */ |
| /*lint -e{522} return not used at this time */ |
| GetSynthPtr(voiceNum)->pfStartVoice(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum], GetAdjustedVoiceNum(voiceNum), regionIndex); |
| return; |
| } |
| |
| /* no free voices, we have to steal one using appropriate algorithm */ |
| if (VMStealVoice(pVoiceMgr, pSynth, &voiceNum, channel, note, lowVoice, highVoice) == EAS_SUCCESS) |
| VMStolenVoice(pVoiceMgr, pSynth, voiceNum, channel, note, velocity, regionIndex); |
| |
| #ifdef _DEBUG_VM |
| else |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStartVoice: Could not steal a voice for channel %d note %d velocity %d\n", |
| channel, note, velocity); */ } |
| } |
| #endif |
| |
| return; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMStartNote() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Update the synth's state to play the requested note on the requested |
| * channel if possible. |
| * |
| * Inputs: |
| * nChannel - the channel to start a note on |
| * nKeyNumber - the key number to start a note for |
| * nNoteVelocity - the key velocity from this note |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * Side Effects: |
| * psSynthObject->m_nNumActiveVoices may be incremented |
| * psSynthObject->m_sVoice[free voice num].m_nSynthChannel may be assigned |
| * psSynthObject->m_sVoice[free voice num].m_nKeyNumber is assigned |
| * psSynthObject->m_sVoice[free voice num].m_nVelocity is assigned |
| *---------------------------------------------------------------------------- |
| */ |
| void VMStartNote (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 note, EAS_U8 velocity) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_U16 regionIndex; |
| EAS_I16 adjustedNote; |
| |
| /* bump note count */ |
| pSynth->totalNoteCount++; |
| |
| pChannel = &pSynth->channels[channel]; |
| |
| /* check channel mute */ |
| if (pChannel->channelFlags & CHANNEL_FLAG_MUTE) |
| return; |
| |
| #ifdef EXTERNAL_AUDIO |
| /* pass event to external audio when requested */ |
| if ((pChannel->channelFlags & CHANNEL_FLAG_EXTERNAL_AUDIO) && (pSynth->cbEventFunc != NULL)) |
| { |
| S_EXT_AUDIO_EVENT event; |
| event.channel = channel; |
| event.note = note; |
| event.velocity = velocity; |
| event.noteOn = EAS_TRUE; |
| if (pSynth->cbEventFunc(pSynth->pExtAudioInstData, &event)) |
| return; |
| } |
| #endif |
| |
| /* start search at first region */ |
| regionIndex = pChannel->regionIndex; |
| |
| /* handle transposition */ |
| adjustedNote = note; |
| if (pChannel->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL) |
| adjustedNote += pChannel->coarsePitch; |
| else |
| adjustedNote += pChannel->coarsePitch + pSynth->globalTranspose; |
| |
| /* limit adjusted key number so it does not wraparound, over/underflow */ |
| if (adjustedNote < 0) |
| { |
| adjustedNote = 0; |
| } |
| else if (adjustedNote > 127) |
| { |
| adjustedNote = 127; |
| } |
| |
| #if defined(DLS_SYNTHESIZER) |
| if (regionIndex & FLAG_RGN_IDX_DLS_SYNTH) |
| { |
| /* DLS voice */ |
| for (;;) |
| { |
| /*lint -e{740,826} cast OK, we know this is actually a DLS region */ |
| const S_DLS_REGION *pDLSRegion = (S_DLS_REGION*) GetRegionPtr(pSynth, regionIndex); |
| |
| /* check key against this region's key and velocity range */ |
| if (((adjustedNote >= pDLSRegion->wtRegion.region.rangeLow) && (adjustedNote <= pDLSRegion->wtRegion.region.rangeHigh)) && |
| ((velocity >= pDLSRegion->velLow) && (velocity <= pDLSRegion->velHigh))) |
| { |
| VMStartVoice(pVoiceMgr, pSynth, channel, note, velocity, regionIndex); |
| } |
| |
| /* last region in program? */ |
| if (pDLSRegion->wtRegion.region.keyGroupAndFlags & REGION_FLAG_LAST_REGION) |
| break; |
| |
| /* advance to next region */ |
| regionIndex++; |
| } |
| } |
| else |
| #endif |
| |
| /* braces here for #if clause */ |
| { |
| /* EAS voice */ |
| for (;;) |
| { |
| const S_REGION *pRegion = GetRegionPtr(pSynth, regionIndex); |
| |
| /* check key against this region's keyrange */ |
| if ((adjustedNote >= pRegion->rangeLow) && (adjustedNote <= pRegion->rangeHigh)) |
| { |
| VMStartVoice(pVoiceMgr, pSynth, channel, note, velocity, regionIndex); |
| break; |
| } |
| |
| /* last region in program? */ |
| if (pRegion->keyGroupAndFlags & REGION_FLAG_LAST_REGION) |
| break; |
| |
| /* advance to next region */ |
| regionIndex++; |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMStopNote() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Update the synth's state to end the requested note on the requested |
| * channel. |
| * |
| * Inputs: |
| * nChannel - the channel to stop a note on |
| * nKeyNumber - the key number for this note off |
| * nNoteVelocity - the note-off velocity |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * Side Effects: |
| * psSynthObject->m_sVoice[free voice num].m_nSynthChannel may be assigned |
| * psSynthObject->m_sVoice[free voice num].m_nKeyNumber is assigned |
| * psSynthObject->m_sVoice[free voice num].m_nVelocity is assigned |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, velocity) reserved for future use */ |
| void VMStopNote (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 note, EAS_U8 velocity) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_INT voiceNum; |
| |
| pChannel = &(pSynth->channels[channel]); |
| |
| #ifdef EXTERNAL_AUDIO |
| if ((pChannel->channelFlags & CHANNEL_FLAG_EXTERNAL_AUDIO) && (pSynth->cbEventFunc != NULL)) |
| { |
| S_EXT_AUDIO_EVENT event; |
| event.channel = channel; |
| event.note = note; |
| event.velocity = velocity; |
| event.noteOn = EAS_FALSE; |
| if (pSynth->cbEventFunc(pSynth->pExtAudioInstData, &event)) |
| return; |
| } |
| #endif |
| |
| /* keep track of the note-start workload */ |
| pVoiceMgr->workload += WORKLOAD_AMOUNT_STOP_NOTE; |
| |
| channel = VSynthToChannel(pSynth, channel); |
| |
| for (voiceNum=0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| |
| /* stolen notes are handled separately */ |
| if (eVoiceStateStolen != pVoiceMgr->voices[voiceNum].voiceState) |
| { |
| |
| /* channel and key number must match */ |
| if ((channel == pVoiceMgr->voices[voiceNum].channel) && (note == pVoiceMgr->voices[voiceNum].note)) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStopNote: voice %d channel %d note %d\n", |
| voiceNum, channel, note); */ } |
| #endif |
| |
| /* if sustain pedal is down, set deferred note-off flag */ |
| if (pChannel->channelFlags & CHANNEL_FLAG_SUSTAIN_PEDAL) |
| { |
| pVoiceMgr->voices[voiceNum].voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF; |
| continue; |
| } |
| |
| /* if this note just started, wait before we stop it */ |
| if (pVoiceMgr->voices[voiceNum].voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET) |
| { |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tDeferred: Not started yet\n"); */ } |
| #endif |
| pVoiceMgr->voices[voiceNum].voiceFlags |= VOICE_FLAG_DEFER_MIDI_NOTE_OFF; |
| pSynth->synthFlags |= SYNTH_FLAG_DEFERRED_MIDI_NOTE_OFF_PENDING; |
| } |
| |
| /* release voice */ |
| else |
| VMReleaseVoice(pVoiceMgr, pSynth, voiceNum); |
| |
| } |
| } |
| |
| /* process stolen notes, new channel and key number must match */ |
| else if ((channel == pVoiceMgr->voices[voiceNum].nextChannel) && (note == pVoiceMgr->voices[voiceNum].nextNote)) |
| { |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStopNote: voice %d channel %d note %d\n\tDeferred: Stolen voice\n", |
| voiceNum, channel, note); */ } |
| #endif |
| pVoiceMgr->voices[voiceNum].voiceFlags |= VOICE_FLAG_DEFER_MIDI_NOTE_OFF; |
| } |
| } |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMFindAvailableVoice() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Find an available voice and return the voice number if available. |
| * |
| * Inputs: |
| * pnVoiceNumber - really an output, returns the voice number found |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * success - if there is an available voice |
| * failure - otherwise |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMFindAvailableVoice (S_VOICE_MGR *pVoiceMgr, EAS_INT *pVoiceNumber, EAS_I32 lowVoice, EAS_I32 highVoice) |
| { |
| EAS_INT voiceNum; |
| |
| /* Check each voice to see if it has been assigned to a synth channel */ |
| for (voiceNum = lowVoice; voiceNum <= highVoice; voiceNum++) |
| { |
| /* check if this voice has been assigned to a synth channel */ |
| if ( pVoiceMgr->voices[voiceNum].voiceState == eVoiceStateFree) |
| { |
| *pVoiceNumber = voiceNum; /* this voice is available */ |
| return EAS_SUCCESS; |
| } |
| } |
| |
| /* if we reach here, we have not found a free voice */ |
| *pVoiceNumber = UNASSIGNED_SYNTH_VOICE; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMFindAvailableVoice: error, could not find an available voice\n"); */ } |
| #endif |
| return EAS_FAILURE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMStealVoice() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Steal a voice and return the voice number |
| * |
| * Stealing algorithm: steal the best choice with minimal work, taking into |
| * account SP-Midi channel priorities and polyphony allocation. |
| * |
| * In one pass through all the voices, figure out which voice to steal |
| * taking into account a number of different factors: |
| * Priority of the voice's MIDI channel |
| * Number of voices over the polyphony allocation for voice's MIDI channel |
| * Amplitude of the voice |
| * Note age |
| * Key velocity (for voices that haven't been started yet) |
| * If any matching notes are found |
| * |
| * Inputs: |
| * pnVoiceNumber - really an output, see below |
| * nChannel - the channel that this voice wants to be started on |
| * nKeyNumber - the key number for this new voice |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * pnVoiceNumber - voice number of the voice that was stolen |
| * EAS_RESULT EAS_SUCCESS - always successful |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMStealVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_INT *pVoiceNumber, EAS_U8 channel, EAS_U8 note, EAS_I32 lowVoice, EAS_I32 highVoice) |
| { |
| S_SYNTH_VOICE *pCurrVoice; |
| S_SYNTH *pCurrSynth; |
| EAS_INT voiceNum; |
| EAS_INT bestCandidate; |
| EAS_U8 currChannel; |
| EAS_U8 currNote; |
| EAS_I32 bestPriority; |
| EAS_I32 currentPriority; |
| |
| /* determine which voice to steal */ |
| bestPriority = 0; |
| bestCandidate = MAX_SYNTH_VOICES; |
| |
| for (voiceNum = lowVoice; voiceNum <= highVoice; voiceNum++) |
| { |
| pCurrVoice = &pVoiceMgr->voices[voiceNum]; |
| |
| /* ignore free voices */ |
| if (pCurrVoice->voiceState == eVoiceStateFree) |
| continue; |
| |
| /* for stolen voices, use the new parameters, not the old */ |
| if (pCurrVoice->voiceState == eVoiceStateStolen) |
| { |
| pCurrSynth = pVoiceMgr->pSynth[GET_VSYNTH(pCurrVoice->nextChannel)]; |
| currChannel = pCurrVoice->nextChannel; |
| currNote = pCurrVoice->nextNote; |
| } |
| else |
| { |
| pCurrSynth = pVoiceMgr->pSynth[GET_VSYNTH(pCurrVoice->channel)]; |
| currChannel = pCurrVoice->channel; |
| currNote = pCurrVoice->note; |
| } |
| |
| /* ignore voices that are higher priority */ |
| if (pSynth->priority > pCurrSynth->priority) |
| continue; |
| #ifdef _DEBUG_VM |
| // { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStealVoice: New priority = %d exceeds old priority = %d\n", pSynth->priority, pCurrSynth->priority); */ } |
| #endif |
| |
| /* if voice is stolen or just started, reduce the likelihood it will be stolen */ |
| if (( pCurrVoice->voiceState == eVoiceStateStolen) || (pCurrVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET)) |
| { |
| currentPriority = 128 - pCurrVoice->nextVelocity; |
| } |
| else |
| { |
| /* compute the priority of this voice, higher means better for stealing */ |
| /* use not age */ |
| currentPriority = (EAS_I32) pCurrVoice->age << NOTE_AGE_STEAL_WEIGHT; |
| |
| /* include note gain -higher gain is lower steal value */ |
| /*lint -e{704} use shift for performance */ |
| currentPriority += ((32768 >> (12 - NOTE_GAIN_STEAL_WEIGHT)) + 256) - |
| ((EAS_I32) pCurrVoice->gain >> (12 - NOTE_GAIN_STEAL_WEIGHT)); |
| } |
| |
| /* in SP-MIDI mode, include over poly allocation and channel priority */ |
| if (pSynth->synthFlags & SYNTH_FLAG_SP_MIDI_ON) |
| { |
| S_SYNTH_CHANNEL *pChannel = &pCurrSynth->channels[GET_CHANNEL(currChannel)]; |
| /*lint -e{701} use shift for performance */ |
| if (pSynth->poolCount[pChannel->pool] >= pSynth->poolAlloc[pChannel->pool]) |
| currentPriority += (pSynth->poolCount[pChannel->pool] -pSynth->poolAlloc[pChannel->pool] + 1) << CHANNEL_POLY_STEAL_WEIGHT; |
| |
| /* include channel priority */ |
| currentPriority += (EAS_I32)(pChannel->pool << CHANNEL_PRIORITY_STEAL_WEIGHT); |
| } |
| |
| /* if a note is already playing that matches this note, consider stealing it more readily */ |
| if ((note == currNote) && (channel == currChannel)) |
| currentPriority += NOTE_MATCH_PENALTY; |
| |
| /* is this the best choice so far? */ |
| if (currentPriority >= bestPriority) |
| { |
| bestPriority = currentPriority; |
| bestCandidate = voiceNum; |
| } |
| } |
| |
| /* may happen if all voices are allocated to a higher priority virtual synth */ |
| if (bestCandidate == MAX_SYNTH_VOICES) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStealVoice: Unable to allocate a voice\n"); */ } |
| return EAS_ERROR_NO_VOICE_ALLOCATED; |
| } |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMStealVoice: Voice %d stolen\n", bestCandidate); */ } |
| |
| /* are we stealing a stolen voice? */ |
| if (pVoiceMgr->voices[bestCandidate].voiceState == eVoiceStateStolen) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMStealVoice: Voice %d is already marked as stolen and was scheduled to play ch: %d note: %d vel: %d\n", |
| bestCandidate, |
| pVoiceMgr->voices[bestCandidate].nextChannel, |
| pVoiceMgr->voices[bestCandidate].nextNote, |
| pVoiceMgr->voices[bestCandidate].nextVelocity); */ } |
| } |
| #endif |
| |
| *pVoiceNumber = (EAS_U16) bestCandidate; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMChannelPressure() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Change the channel pressure for the given channel |
| * |
| * Inputs: |
| * nChannel - the MIDI channel |
| * nVelocity - the channel pressure value |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * Side Effects: |
| * psSynthObject->m_sChannel[nChannel].m_nChannelPressure is updated |
| *---------------------------------------------------------------------------- |
| */ |
| void VMChannelPressure (S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 value) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| |
| pChannel = &(pSynth->channels[channel]); |
| pChannel->channelPressure = value; |
| |
| /* |
| set a channel flag to request parameter updates |
| for all the voices associated with this channel |
| */ |
| pChannel->channelFlags |= CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMPitchBend() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Change the pitch wheel value for the given channel. |
| * This routine constructs the proper 14-bit argument when the calling routine |
| * passes the pitch LSB and MSB. |
| * |
| * Note: some midi disassemblers display a bipolar pitch bend value. |
| * We can display the bipolar value using |
| * if m_nPitchBend >= 0x2000 |
| * bipolar pitch bend = postive (m_nPitchBend - 0x2000) |
| * else |
| * bipolar pitch bend = negative (0x2000 - m_nPitchBend) |
| * |
| * Inputs: |
| * nChannel - the MIDI channel |
| * nPitchLSB - the LSB byte of the pitch bend message |
| * nPitchMSB - the MSB byte of the pitch bend message |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * psSynthObject->m_sChannel[nChannel].m_nPitchBend is changed |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMPitchBend (S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 nPitchLSB, EAS_U8 nPitchMSB) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| |
| pChannel = &(pSynth->channels[channel]); |
| pChannel->pitchBend = (EAS_I16) ((nPitchMSB << 7) | nPitchLSB); |
| |
| /* |
| set a channel flag to request parameter updates |
| for all the voices associated with this channel |
| */ |
| pChannel->channelFlags |= CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMControlChange() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Change the controller (or mode) for the given channel. |
| * |
| * Inputs: |
| * nChannel - the MIDI channel |
| * nControllerNumber - the MIDI controller number |
| * nControlValue - the value for this controller message |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * Side Effects: |
| * psSynthObject->m_sChannel[nChannel] controller is changed |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMControlChange (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 controller, EAS_U8 value) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| |
| pChannel = &(pSynth->channels[channel]); |
| |
| /* |
| set a channel flag to request parameter updates |
| for all the voices associated with this channel |
| */ |
| pChannel->channelFlags |= CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| |
| switch ( controller ) |
| { |
| case MIDI_CONTROLLER_BANK_SELECT_MSB: |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMControlChange: Bank Select MSB: msb 0x%X\n", value); */ } |
| #endif |
| /* use this MSB with a zero LSB, until we get an LSB message */ |
| pChannel->bankNum = value << 8; |
| break; |
| |
| case MIDI_CONTROLLER_MOD_WHEEL: |
| /* we treat mod wheel as a 7-bit controller and only use the MSB */ |
| pChannel->modWheel = value; |
| break; |
| |
| case MIDI_CONTROLLER_VOLUME: |
| /* we treat volume as a 7-bit controller and only use the MSB */ |
| pChannel->volume = value; |
| break; |
| |
| case MIDI_CONTROLLER_PAN: |
| /* we treat pan as a 7-bit controller and only use the MSB */ |
| pChannel->pan = value; |
| break; |
| |
| case MIDI_CONTROLLER_EXPRESSION: |
| /* we treat expression as a 7-bit controller and only use the MSB */ |
| pChannel->expression = value; |
| break; |
| |
| case MIDI_CONTROLLER_BANK_SELECT_LSB: |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMControlChange: Bank Select LSB: lsb 0x%X\n", value); */ } |
| #endif |
| /* |
| construct bank number as 7-bits (stored as 8) of existing MSB |
| and 7-bits of new LSB (also stored as 8( |
| */ |
| pChannel->bankNum = |
| (pChannel->bankNum & 0xFF00) | value; |
| |
| break; |
| |
| case MIDI_CONTROLLER_SUSTAIN_PEDAL: |
| /* we treat sustain pedal as a boolean on/off bit flag */ |
| if (value < 64) |
| { |
| /* |
| we are requested to turn the pedal off, but first check |
| if the pedal is already on |
| */ |
| if (0 != |
| (pChannel->channelFlags & CHANNEL_FLAG_SUSTAIN_PEDAL) |
| ) |
| { |
| /* |
| The sustain flag is presently set and the damper pedal is on. |
| We are therefore transitioning from damper pedal ON to |
| damper pedal OFF. This means all notes in this channel |
| that received a note off while the damper pedal was on, and |
| had their note-off requests deferred, should now proceed to |
| the release state. |
| */ |
| VMReleaseAllDeferredNoteOffs(pVoiceMgr, pSynth, channel); |
| } /* end if sustain pedal is already on */ |
| |
| /* turn the sustain pedal off */ |
| pChannel->channelFlags &= ~CHANNEL_FLAG_SUSTAIN_PEDAL; |
| } |
| else |
| { |
| /* |
| we are requested to turn the pedal on, but first check |
| if the pedal is already off |
| */ |
| if (0 == |
| (pChannel->channelFlags & CHANNEL_FLAG_SUSTAIN_PEDAL) |
| ) |
| { |
| /* |
| The sustain flag is presently clear and the damper pedal is off. |
| We are therefore transitioning from damper pedal OFF to |
| damper pedal ON. Currently sounding notes should be left |
| unchanged. However, we should try to "catch" notes if possible. |
| If any notes have levels >= sustain level, catch them, |
| otherwise, let them continue to release. |
| */ |
| VMCatchNotesForSustainPedal(pVoiceMgr, pSynth, channel); |
| } |
| |
| /* turn the sustain pedal on */ |
| pChannel->channelFlags |= CHANNEL_FLAG_SUSTAIN_PEDAL; |
| } |
| |
| break; |
| #ifdef _REVERB |
| case MIDI_CONTROLLER_REVERB_SEND: |
| /* we treat send as a 7-bit controller and only use the MSB */ |
| pSynth->channels[channel].reverbSend = value; |
| break; |
| #endif |
| #ifdef _CHORUS |
| case MIDI_CONTROLLER_CHORUS_SEND: |
| /* we treat send as a 7-bit controller and only use the MSB */ |
| pSynth->channels[channel].chorusSend = value; |
| break; |
| #endif |
| case MIDI_CONTROLLER_RESET_CONTROLLERS: |
| /* despite the Midi message name, not ALL controllers are reset */ |
| pChannel->modWheel = DEFAULT_MOD_WHEEL; |
| pChannel->expression = DEFAULT_EXPRESSION; |
| |
| /* turn the sustain pedal off as default/reset */ |
| pChannel->channelFlags &= ~CHANNEL_FLAG_SUSTAIN_PEDAL; |
| pChannel->pitchBend = DEFAULT_PITCH_BEND; |
| |
| /* reset channel pressure */ |
| pChannel->channelPressure = DEFAULT_CHANNEL_PRESSURE; |
| |
| /* reset RPN values */ |
| pChannel->registeredParam = DEFAULT_REGISTERED_PARAM; |
| pChannel->pitchBendSensitivity = DEFAULT_PITCH_BEND_SENSITIVITY; |
| pChannel->finePitch = DEFAULT_FINE_PITCH; |
| pChannel->coarsePitch = DEFAULT_COARSE_PITCH; |
| |
| /* |
| program change, bank select, channel volume CC7, pan CC10 |
| are NOT reset |
| */ |
| break; |
| |
| /* |
| For logical reasons, the RPN data entry are grouped together. |
| However, keep in mind that these cases are not necessarily in |
| ascending order. |
| e.g., MIDI_CONTROLLER_DATA_ENTRY_MSB == 6, |
| whereas MIDI_CONTROLLER_SUSTAIN_PEDAL == 64. |
| So arrange these case statements in whatever manner is more efficient for |
| the processor / compiler. |
| */ |
| case MIDI_CONTROLLER_ENTER_DATA_MSB: |
| case MIDI_CONTROLLER_ENTER_DATA_LSB: |
| case MIDI_CONTROLLER_SELECT_RPN_LSB: |
| case MIDI_CONTROLLER_SELECT_RPN_MSB: |
| case MIDI_CONTROLLER_SELECT_NRPN_MSB: |
| case MIDI_CONTROLLER_SELECT_NRPN_LSB: |
| VMUpdateRPNStateMachine(pSynth, channel, controller, value); |
| break; |
| |
| case MIDI_CONTROLLER_ALL_SOUND_OFF: |
| case MIDI_CONTROLLER_ALL_NOTES_OFF: |
| case MIDI_CONTROLLER_OMNI_OFF: |
| case MIDI_CONTROLLER_OMNI_ON: |
| case MIDI_CONTROLLER_MONO_ON_POLY_OFF: |
| case MIDI_CONTROLLER_POLY_ON_MONO_OFF: |
| /* NOTE: we treat all sounds off the same as all notes off */ |
| VMAllNotesOff(pVoiceMgr, pSynth, channel); |
| break; |
| |
| default: |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMControlChange: controller %d not yet implemented\n", controller); */ } |
| #endif |
| break; |
| |
| } |
| |
| return; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMUpdateRPNStateMachine() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Call this function when we want to parse RPN related controller messages. |
| * We only support RPN0 (pitch bend sensitivity), RPN1 (fine tuning) and |
| * RPN2 (coarse tuning). Any other RPNs or NRPNs are ignored for now. |
| *. |
| * Supports any order, so not a state machine anymore. This function was |
| * rewritten to work correctly regardless of order. |
| * |
| * Inputs: |
| * nChannel - the channel this controller message is coming from |
| * nControllerNumber - which RPN related controller |
| * nControlValue - the value of the RPN related controller |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * returns EAS_RESULT, which is typically EAS_SUCCESS, since there are |
| * few possible errors |
| * |
| * Side Effects: |
| * gsSynthObject.m_sChannel[nChannel].m_nPitchBendSensitivity |
| * (or m_nFinePitch or m_nCoarsePitch) |
| * will be updated if the proper RPN message is received. |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMUpdateRPNStateMachine (S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 controller, EAS_U8 value) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| |
| #ifdef _DEBUG_VM |
| if (channel >= NUM_SYNTH_CHANNELS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMUpdateRPNStateMachines: error, %d invalid channel number\n", |
| channel); */ } |
| return EAS_FAILURE; |
| } |
| #endif |
| |
| pChannel = &(pSynth->channels[channel]); |
| |
| switch (controller) |
| { |
| case MIDI_CONTROLLER_SELECT_NRPN_MSB: |
| case MIDI_CONTROLLER_SELECT_NRPN_LSB: |
| pChannel->registeredParam = DEFAULT_REGISTERED_PARAM; |
| break; |
| case MIDI_CONTROLLER_SELECT_RPN_MSB: |
| pChannel->registeredParam = |
| (pChannel->registeredParam & 0x7F) | (value<<7); |
| break; |
| case MIDI_CONTROLLER_SELECT_RPN_LSB: |
| pChannel->registeredParam = |
| (pChannel->registeredParam & 0x7F00) | value; |
| break; |
| case MIDI_CONTROLLER_ENTER_DATA_MSB: |
| switch (pChannel->registeredParam) |
| { |
| case 0: |
| pChannel->pitchBendSensitivity = value * 100; |
| break; |
| case 1: |
| /*lint -e{702} <avoid division for performance reasons>*/ |
| pChannel->finePitch = (EAS_I8)((((value << 7) - 8192) * 100) >> 13); |
| break; |
| case 2: |
| pChannel->coarsePitch = (EAS_I8)(value - 64); |
| break; |
| default: |
| break; |
| } |
| break; |
| case MIDI_CONTROLLER_ENTER_DATA_LSB: |
| switch (pChannel->registeredParam) |
| { |
| case 0: |
| //ignore lsb |
| break; |
| case 1: |
| //ignore lsb |
| break; |
| case 2: |
| //ignore lsb |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| return EAS_FAILURE; //not a RPN related controller |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMUpdateStaticChannelParameters() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Update all of the static channel parameters for channels that have had |
| * a controller change values |
| * Or if the synth has signalled that all channels must forcibly |
| * be updated |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * none |
| * |
| * Side Effects: |
| * - psSynthObject->m_sChannel[].m_nStaticGain and m_nStaticPitch |
| * are updated for channels whose controller values have changed |
| * or if the synth has signalled that all channels must forcibly |
| * be updated |
| *---------------------------------------------------------------------------- |
| */ |
| void VMUpdateStaticChannelParameters (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth) |
| { |
| EAS_INT channel; |
| |
| if (pSynth->synthFlags & SYNTH_FLAG_UPDATE_ALL_CHANNEL_PARAMETERS) |
| { |
| /* |
| the synth wants us to forcibly update all channel |
| parameters. This event occurs when we are about to |
| finish resetting the synth |
| */ |
| for (channel = 0; channel < NUM_SYNTH_CHANNELS; channel++) |
| { |
| #ifdef _HYBRID_SYNTH |
| if (pSynth->channels[channel].regionIndex & FLAG_RGN_IDX_FM_SYNTH) |
| pSecondarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| else |
| pPrimarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| #else |
| pPrimarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| #endif |
| } |
| |
| /* |
| clear the flag to indicates we have now forcibly |
| updated all channel parameters |
| */ |
| pSynth->synthFlags &= ~SYNTH_FLAG_UPDATE_ALL_CHANNEL_PARAMETERS; |
| } |
| else |
| { |
| |
| /* only update channel params if signalled by a channel flag */ |
| for (channel = 0; channel < NUM_SYNTH_CHANNELS; channel++) |
| { |
| if ( 0 != (pSynth->channels[channel].channelFlags & CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS)) |
| { |
| #ifdef _HYBRID_SYNTH |
| if (pSynth->channels[channel].regionIndex & FLAG_RGN_IDX_FM_SYNTH) |
| pSecondarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| else |
| pPrimarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| #else |
| pPrimarySynth->pfUpdateChannel(pVoiceMgr, pSynth, (EAS_U8) channel); |
| #endif |
| } |
| } |
| |
| } |
| |
| return; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMFindProgram() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Look up an individual program in sound library. This function |
| * searches the bank list for a program, then the individual program |
| * list. |
| * |
| * Inputs: |
| * |
| * Outputs: |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT VMFindProgram (const S_EAS *pEAS, EAS_U32 bank, EAS_U8 programNum, EAS_U16 *pRegionIndex) |
| { |
| EAS_U32 locale; |
| const S_PROGRAM *p; |
| EAS_U16 i; |
| EAS_U16 regionIndex; |
| |
| /* make sure we have a valid sound library */ |
| if (pEAS == NULL) |
| return EAS_FAILURE; |
| |
| /* search the banks */ |
| for (i = 0; i < pEAS->numBanks; i++) |
| { |
| if (bank == (EAS_U32) pEAS->pBanks[i].locale) |
| { |
| regionIndex = pEAS->pBanks[i].regionIndex[programNum]; |
| if (regionIndex != INVALID_REGION_INDEX) |
| { |
| *pRegionIndex = regionIndex; |
| return EAS_SUCCESS; |
| } |
| break; |
| } |
| } |
| |
| /* establish locale */ |
| locale = ( bank << 8) | programNum; |
| |
| /* search for program */ |
| for (i = 0, p = pEAS->pPrograms; i < pEAS->numPrograms; i++, p++) |
| { |
| if (p->locale == locale) |
| { |
| *pRegionIndex = p->regionIndex; |
| return EAS_SUCCESS; |
| } |
| } |
| |
| return EAS_FAILURE; |
| } |
| |
| #ifdef DLS_SYNTHESIZER |
| /*---------------------------------------------------------------------------- |
| * VMFindDLSProgram() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Look up an individual program in sound library. This function |
| * searches the bank list for a program, then the individual program |
| * list. |
| * |
| * Inputs: |
| * |
| * Outputs: |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT VMFindDLSProgram (const S_DLS *pDLS, EAS_U32 bank, EAS_U8 programNum, EAS_U16 *pRegionIndex) |
| { |
| EAS_U32 locale; |
| const S_PROGRAM *p; |
| EAS_U16 i; |
| |
| /* make sure we have a valid sound library */ |
| if (pDLS == NULL) |
| return EAS_FAILURE; |
| |
| /* establish locale */ |
| locale = (bank << 8) | programNum; |
| |
| /* search for program */ |
| for (i = 0, p = pDLS->pDLSPrograms; i < pDLS->numDLSPrograms; i++, p++) |
| { |
| if (p->locale == locale) |
| { |
| *pRegionIndex = p->regionIndex; |
| return EAS_SUCCESS; |
| } |
| } |
| |
| return EAS_FAILURE; |
| } |
| #endif |
| |
| /*---------------------------------------------------------------------------- |
| * VMProgramChange() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Change the instrument (program) for the given channel. |
| * |
| * Depending on the program number, and the bank selected for this channel, the |
| * program may be in ROM, RAM (from SMAF or CMX related RAM wavetable), or |
| * Alternate wavetable (from mobile DLS or other DLS file) |
| * |
| * This function figures out what wavetable should be used, and sets it up as the |
| * wavetable to use for this channel. Also the channel may switch from a melodic |
| * channel to a rhythm channel, or vice versa. |
| * |
| * Inputs: |
| * |
| * Outputs: |
| * Side Effects: |
| * gsSynthObject.m_sChannel[nChannel].m_nProgramNumber is likely changed |
| * gsSynthObject.m_sChannel[nChannel].m_psEAS may be changed |
| * gsSynthObject.m_sChannel[nChannel].m_bRhythmChannel may be changed |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| void VMProgramChange (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel, EAS_U8 program) |
| { |
| S_SYNTH_CHANNEL *pChannel; |
| EAS_U32 bank; |
| EAS_U16 regionIndex; |
| |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "VMProgramChange: vSynthNum=%d, channel=%d, program=%d\n", pSynth->vSynthNum, channel, program); */ } |
| #endif |
| |
| /* setup pointer to MIDI channel data */ |
| pChannel = &pSynth->channels[channel]; |
| bank = pChannel->bankNum; |
| |
| /* allow channels to switch between being melodic or rhythm channels, using GM2 CC values */ |
| if ((bank & 0xFF00) == DEFAULT_RHYTHM_BANK_NUMBER) |
| { |
| /* make it a rhythm channel */ |
| pChannel->channelFlags |= CHANNEL_FLAG_RHYTHM_CHANNEL; |
| } |
| else if ((bank & 0xFF00) == DEFAULT_MELODY_BANK_NUMBER) |
| { |
| /* make it a melody channel */ |
| pChannel->channelFlags &= ~CHANNEL_FLAG_RHYTHM_CHANNEL; |
| } |
| |
| regionIndex = DEFAULT_REGION_INDEX; |
| |
| #ifdef EXTERNAL_AUDIO |
| /* give the external audio interface a chance to handle it */ |
| if (pSynth->cbProgChgFunc != NULL) |
| { |
| S_EXT_AUDIO_PRG_CHG prgChg; |
| prgChg.channel = channel; |
| prgChg.bank = (EAS_U16) bank; |
| prgChg.program = program; |
| if (pSynth->cbProgChgFunc(pSynth->pExtAudioInstData, &prgChg)) |
| pChannel->channelFlags |= CHANNEL_FLAG_EXTERNAL_AUDIO; |
| } |
| |
| #endif |
| |
| |
| #ifdef DLS_SYNTHESIZER |
| /* first check for DLS program that may overlay the internal instrument */ |
| if (VMFindDLSProgram(pSynth->pDLS, bank, program, ®ionIndex) != EAS_SUCCESS) |
| #endif |
| |
| /* braces to support 'if' clause above */ |
| { |
| |
| /* look in the internal banks */ |
| if (VMFindProgram(pSynth->pEAS, bank, program, ®ionIndex) != EAS_SUCCESS) |
| |
| /* fall back to default bank */ |
| { |
| if (pSynth->channels[channel].channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL) |
| bank = DEFAULT_RHYTHM_BANK_NUMBER; |
| else |
| bank = DEFAULT_MELODY_BANK_NUMBER; |
| |
| if (VMFindProgram(pSynth->pEAS, bank, program, ®ionIndex) != EAS_SUCCESS) |
| |
| /* switch to program 0 in the default bank */ |
| { |
| if (VMFindProgram(pSynth->pEAS, bank, 0, ®ionIndex) != EAS_SUCCESS) |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMProgramChange: No program @ %03d:%03d:%03d\n", |
| (bank >> 8) & 0x7f, bank & 0x7f, program); */ } |
| } |
| } |
| } |
| |
| /* we have our new program change for this channel */ |
| pChannel->programNum = program; |
| pChannel->regionIndex = regionIndex; |
| |
| /* |
| set a channel flag to request parameter updates |
| for all the voices associated with this channel |
| */ |
| pChannel->channelFlags |= CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| |
| return; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMAddSamples() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Synthesize the requested number of samples (block based processing) |
| * |
| * Inputs: |
| * nNumSamplesToAdd - number of samples to write to buffer |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * number of voices rendered |
| * |
| * Side Effects: |
| * - samples are added to the presently free buffer |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_I32 VMAddSamples (S_VOICE_MGR *pVoiceMgr, EAS_I32 *pMixBuffer, EAS_I32 numSamples) |
| { |
| S_SYNTH *pSynth; |
| EAS_INT voicesRendered; |
| EAS_INT voiceNum; |
| EAS_BOOL done; |
| |
| #ifdef _REVERB |
| EAS_PCM *pReverbSendBuffer; |
| #endif // ifdef _REVERB |
| |
| #ifdef _CHORUS |
| EAS_PCM *pChorusSendBuffer; |
| #endif // ifdef _CHORUS |
| |
| voicesRendered = 0; |
| for (voiceNum = 0; voiceNum < MAX_SYNTH_VOICES; voiceNum++) |
| { |
| |
| /* retarget stolen voices */ |
| if ((pVoiceMgr->voices[voiceNum].voiceState == eVoiceStateStolen) && (pVoiceMgr->voices[voiceNum].gain <= 0)) |
| VMRetargetStolenVoice(pVoiceMgr, voiceNum); |
| |
| /* get pointer to virtual synth */ |
| pSynth = pVoiceMgr->pSynth[pVoiceMgr->voices[voiceNum].channel >> 4]; |
| |
| /* synthesize active voices */ |
| if (pVoiceMgr->voices[voiceNum].voiceState != eVoiceStateFree) |
| { |
| done = GetSynthPtr(voiceNum)->pfUpdateVoice(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum], GetAdjustedVoiceNum(voiceNum), pMixBuffer, numSamples); |
| voicesRendered++; |
| |
| /* voice is finished */ |
| if (done == EAS_TRUE) |
| { |
| /* set gain of stolen voice to zero so it will be restarted */ |
| if (pVoiceMgr->voices[voiceNum].voiceState == eVoiceStateStolen) |
| pVoiceMgr->voices[voiceNum].gain = 0; |
| |
| /* or return it to the free voice pool */ |
| else |
| VMFreeVoice(pVoiceMgr, pSynth, &pVoiceMgr->voices[voiceNum]); |
| } |
| |
| /* if this voice is scheduled to be muted, set the mute flag */ |
| if (pVoiceMgr->voices[voiceNum].voiceFlags & VOICE_FLAG_DEFER_MUTE) |
| { |
| pVoiceMgr->voices[voiceNum].voiceFlags &= ~(VOICE_FLAG_DEFER_MUTE | VOICE_FLAG_DEFER_MIDI_NOTE_OFF); |
| VMMuteVoice(pVoiceMgr, voiceNum); |
| } |
| |
| /* if voice just started, advance state to play */ |
| if (pVoiceMgr->voices[voiceNum].voiceState == eVoiceStateStart) |
| pVoiceMgr->voices[voiceNum].voiceState = eVoiceStatePlay; |
| } |
| } |
| |
| return voicesRendered; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMRender() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * This routine renders a frame of audio |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * pVoicesRendered - number of voices rendered this frame |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMRender (S_VOICE_MGR *pVoiceMgr, EAS_I32 numSamples, EAS_I32 *pMixBuffer, EAS_I32 *pVoicesRendered) |
| { |
| S_SYNTH *pSynth; |
| EAS_INT i; |
| EAS_INT channel; |
| |
| #ifdef _CHECKED_BUILD |
| SanityCheck(pVoiceMgr); |
| #endif |
| |
| /* update MIDI channel parameters */ |
| *pVoicesRendered = 0; |
| for (i = 0; i < MAX_VIRTUAL_SYNTHESIZERS; i++) |
| { |
| if (pVoiceMgr->pSynth[i] != NULL) |
| VMUpdateStaticChannelParameters(pVoiceMgr, pVoiceMgr->pSynth[i]); |
| } |
| |
| /* synthesize a buffer of audio */ |
| *pVoicesRendered = VMAddSamples(pVoiceMgr, pMixBuffer, numSamples); |
| |
| /* |
| * check for deferred note-off messages |
| * If flag is set, that means one or more voices are expecting deferred |
| * midi note-off messages because the midi note-on and corresponding midi |
| * note-off requests occurred during the same update interval. The goal |
| * is the defer the note-off request so that the note can at least start. |
| */ |
| for (i = 0; i < MAX_VIRTUAL_SYNTHESIZERS; i++) |
| { |
| pSynth = pVoiceMgr->pSynth[i]; |
| |
| if (pSynth== NULL) |
| continue; |
| |
| if (pSynth->synthFlags & SYNTH_FLAG_DEFERRED_MIDI_NOTE_OFF_PENDING) |
| VMDeferredStopNote(pVoiceMgr, pSynth); |
| |
| /* check if we need to reset the synth */ |
| if ((pSynth->synthFlags & SYNTH_FLAG_RESET_IS_REQUESTED) && |
| (pSynth->numActiveVoices == 0)) |
| { |
| /* |
| complete the process of resetting the synth now that |
| all voices have muted |
| */ |
| #ifdef _DEBUG_VM |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "VMAddSamples: complete the reset process\n"); */ } |
| #endif |
| |
| VMInitializeAllChannels(pVoiceMgr, pSynth); |
| VMInitializeAllVoices(pVoiceMgr, pSynth->vSynthNum); |
| |
| /* clear the reset flag */ |
| pSynth->synthFlags &= ~SYNTH_FLAG_RESET_IS_REQUESTED; |
| } |
| |
| /* clear channel update flags */ |
| for (channel = 0; channel < NUM_SYNTH_CHANNELS; channel++) |
| pSynth->channels[channel].channelFlags &= ~CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; |
| |
| } |
| |
| #ifdef _CHECKED_BUILD |
| SanityCheck(pVoiceMgr); |
| #endif |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMInitWorkload() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Clears the workload counter |
| * |
| * Inputs: |
| * pVoiceMgr - pointer to instance data |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMInitWorkload (S_VOICE_MGR *pVoiceMgr) |
| { |
| pVoiceMgr->workload = 0; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetWorkload() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the max workload for a single frame. |
| * |
| * Inputs: |
| * pVoiceMgr - pointer to instance data |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMSetWorkload (S_VOICE_MGR *pVoiceMgr, EAS_I32 maxWorkLoad) |
| { |
| pVoiceMgr->maxWorkLoad = maxWorkLoad; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMCheckWorkload() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Checks to see if work load has been exceeded on this frame. |
| * |
| * Inputs: |
| * pVoiceMgr - pointer to instance data |
| * |
| * Outputs: |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_BOOL VMCheckWorkload (S_VOICE_MGR *pVoiceMgr) |
| { |
| if (pVoiceMgr->maxWorkLoad > 0) |
| return (EAS_BOOL) (pVoiceMgr->workload >= pVoiceMgr->maxWorkLoad); |
| return EAS_FALSE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMActiveVoices() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Returns the number of active voices in the synthesizer. |
| * |
| * Inputs: |
| * pEASData - pointer to instance data |
| * |
| * Outputs: |
| * Returns the number of active voices |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_I32 VMActiveVoices (S_SYNTH *pSynth) |
| { |
| return pSynth->numActiveVoices; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetSynthPolyphony() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Set the synth to a new polyphony value. Value must be >= 1 and |
| * <= MAX_SYNTH_VOICES. This function will pin the polyphony at those limits |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * polyphonyCount desired polyphony count |
| * synth synthesizer number (0 = onboard, 1 = DSP) |
| * |
| * Outputs: |
| * Returns error code |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetSynthPolyphony (S_VOICE_MGR *pVoiceMgr, EAS_I32 synth, EAS_I32 polyphonyCount) |
| { |
| EAS_INT i; |
| EAS_INT activeVoices; |
| |
| /* lower limit */ |
| if (polyphonyCount < 1) |
| polyphonyCount = 1; |
| |
| /* split architecture */ |
| #if defined(_SECONDARY_SYNTH) || defined(EAS_SPLIT_WT_SYNTH) |
| if (synth == EAS_MCU_SYNTH) |
| { |
| if (polyphonyCount > NUM_PRIMARY_VOICES) |
| polyphonyCount = NUM_PRIMARY_VOICES; |
| if (pVoiceMgr->maxPolyphonyPrimary == polyphonyCount) |
| return EAS_SUCCESS; |
| pVoiceMgr->maxPolyphonyPrimary = (EAS_U16) polyphonyCount; |
| } |
| else if (synth == EAS_DSP_SYNTH) |
| { |
| if (polyphonyCount > NUM_SECONDARY_VOICES) |
| polyphonyCount = NUM_SECONDARY_VOICES; |
| if (pVoiceMgr->maxPolyphonySecondary == polyphonyCount) |
| return EAS_SUCCESS; |
| pVoiceMgr->maxPolyphonySecondary = (EAS_U16) polyphonyCount; |
| } |
| else |
| return EAS_ERROR_PARAMETER_RANGE; |
| |
| /* setting for SP-MIDI */ |
| pVoiceMgr->maxPolyphony = pVoiceMgr->maxPolyphonyPrimary + pVoiceMgr->maxPolyphonySecondary; |
| |
| /* standard architecture */ |
| #else |
| if (synth != EAS_MCU_SYNTH) |
| return EAS_ERROR_PARAMETER_RANGE; |
| |
| /* pin desired value to possible limits */ |
| if (polyphonyCount > MAX_SYNTH_VOICES) |
| polyphonyCount = MAX_SYNTH_VOICES; |
| |
| /* set polyphony, if value is different than current value */ |
| if (pVoiceMgr->maxPolyphony == polyphonyCount) |
| return EAS_SUCCESS; |
| |
| pVoiceMgr->maxPolyphony = (EAS_U16) polyphonyCount; |
| #endif |
| |
| /* if SPMIDI enabled, update channel masking based on new polyphony */ |
| for (i = 0; i < MAX_VIRTUAL_SYNTHESIZERS; i++) |
| { |
| if (pVoiceMgr->pSynth[i]) |
| { |
| if (pVoiceMgr->pSynth[i]->synthFlags & SYNTH_FLAG_SP_MIDI_ON) |
| VMMIPUpdateChannelMuting(pVoiceMgr, pVoiceMgr->pSynth[i]); |
| else |
| pVoiceMgr->pSynth[i]->poolAlloc[0] = (EAS_U8) polyphonyCount; |
| } |
| } |
| |
| /* are we under polyphony limit? */ |
| if (pVoiceMgr->activeVoices <= polyphonyCount) |
| return EAS_SUCCESS; |
| |
| /* count the number of active voices */ |
| activeVoices = 0; |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| |
| /* is voice active? */ |
| if ((pVoiceMgr->voices[i].voiceState != eVoiceStateFree) && (pVoiceMgr->voices[i].voiceState != eVoiceStateMuting)) |
| activeVoices++; |
| } |
| |
| /* we may have to mute voices to reach new target */ |
| while (activeVoices > polyphonyCount) |
| { |
| S_SYNTH *pSynth; |
| S_SYNTH_VOICE *pVoice; |
| EAS_I32 currentPriority, bestPriority; |
| EAS_INT bestCandidate; |
| |
| /* find the lowest priority voice */ |
| bestPriority = bestCandidate = -1; |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| |
| pVoice = &pVoiceMgr->voices[i]; |
| |
| /* ignore free and muting voices */ |
| if ((pVoice->voiceState == eVoiceStateFree) || (pVoice->voiceState == eVoiceStateMuting)) |
| continue; |
| |
| pSynth = pVoiceMgr->pSynth[GET_VSYNTH(pVoice->channel)]; |
| |
| /* if voice is stolen or just started, reduce the likelihood it will be stolen */ |
| if (( pVoice->voiceState == eVoiceStateStolen) || (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET)) |
| { |
| /* include velocity */ |
| currentPriority = 128 - pVoice->nextVelocity; |
| |
| /* include channel priority */ |
| currentPriority += pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool << CHANNEL_PRIORITY_STEAL_WEIGHT; |
| } |
| else |
| { |
| /* include age */ |
| currentPriority = (EAS_I32) pVoice->age << NOTE_AGE_STEAL_WEIGHT; |
| |
| /* include note gain -higher gain is lower steal value */ |
| /*lint -e{704} use shift for performance */ |
| currentPriority += ((32768 >> (12 - NOTE_GAIN_STEAL_WEIGHT)) + 256) - |
| ((EAS_I32) pVoice->gain >> (12 - NOTE_GAIN_STEAL_WEIGHT)); |
| |
| /* include channel priority */ |
| currentPriority += pSynth->channels[GET_CHANNEL(pVoice->channel)].pool << CHANNEL_PRIORITY_STEAL_WEIGHT; |
| } |
| |
| /* include synth priority */ |
| currentPriority += pSynth->priority << SYNTH_PRIORITY_WEIGHT; |
| |
| /* is this the best choice so far? */ |
| if (currentPriority > bestPriority) |
| { |
| bestPriority = currentPriority; |
| bestCandidate = i; |
| } |
| } |
| |
| /* shutdown best candidate */ |
| if (bestCandidate < 0) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMSetPolyphony: Unable to reduce polyphony\n"); */ } |
| break; |
| } |
| |
| /* shut down this voice */ |
| /*lint -e{771} pSynth is initialized if bestCandidate >= 0 */ |
| VMMuteVoice(pVoiceMgr, bestCandidate); |
| activeVoices--; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetSynthPolyphony() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Returns the current polyphony setting |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * synth synthesizer number (0 = onboard, 1 = DSP) |
| * |
| * Outputs: |
| * Returns actual polyphony value set, as pinned by limits |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMGetSynthPolyphony (S_VOICE_MGR *pVoiceMgr, EAS_I32 synth, EAS_I32 *pPolyphonyCount) |
| { |
| |
| #if defined(_SECONDARY_SYNTH) || defined(EAS_SPLIT_WT_SYNTH) |
| if (synth == EAS_MCU_SYNTH) |
| *pPolyphonyCount = pVoiceMgr->maxPolyphonyPrimary; |
| else if (synth == EAS_DSP_SYNTH) |
| *pPolyphonyCount = pVoiceMgr->maxPolyphonySecondary; |
| else |
| return EAS_ERROR_PARAMETER_RANGE; |
| #else |
| if (synth != EAS_MCU_SYNTH) |
| return EAS_ERROR_PARAMETER_RANGE; |
| *pPolyphonyCount = pVoiceMgr->maxPolyphony; |
| #endif |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetPolyphony() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Set the virtual synth polyphony. 0 = no limit (i.e. can use |
| * all available voices). |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * polyphonyCount desired polyphony count |
| * pSynth pointer to virtual synth |
| * |
| * Outputs: |
| * Returns error code |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetPolyphony (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 polyphonyCount) |
| { |
| EAS_INT i; |
| EAS_INT activeVoices; |
| |
| /* check limits */ |
| if (polyphonyCount < 0) |
| return EAS_ERROR_PARAMETER_RANGE; |
| |
| /* zero is max polyphony */ |
| if ((polyphonyCount == 0) || (polyphonyCount > MAX_SYNTH_VOICES)) |
| { |
| pSynth->maxPolyphony = 0; |
| return EAS_SUCCESS; |
| } |
| |
| /* set new polyphony */ |
| pSynth->maxPolyphony = (EAS_U16) polyphonyCount; |
| |
| /* max polyphony is minimum of virtual synth and actual synth */ |
| if (polyphonyCount > pVoiceMgr->maxPolyphony) |
| polyphonyCount = pVoiceMgr->maxPolyphony; |
| |
| /* if SP-MIDI mode, update the channel muting */ |
| if (pSynth->synthFlags & SYNTH_FLAG_SP_MIDI_ON) |
| VMMIPUpdateChannelMuting(pVoiceMgr, pSynth); |
| else |
| pSynth->poolAlloc[0] = (EAS_U8) polyphonyCount; |
| |
| /* are we under polyphony limit? */ |
| if (pSynth->numActiveVoices <= polyphonyCount) |
| return EAS_SUCCESS; |
| |
| /* count the number of active voices */ |
| activeVoices = 0; |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| /* this synth? */ |
| if (GET_VSYNTH(pVoiceMgr->voices[i].nextChannel) != pSynth->vSynthNum) |
| continue; |
| |
| /* is voice active? */ |
| if ((pVoiceMgr->voices[i].voiceState != eVoiceStateFree) && (pVoiceMgr->voices[i].voiceState != eVoiceStateMuting)) |
| activeVoices++; |
| } |
| |
| /* we may have to mute voices to reach new target */ |
| while (activeVoices > polyphonyCount) |
| { |
| S_SYNTH_VOICE *pVoice; |
| EAS_I32 currentPriority, bestPriority; |
| EAS_INT bestCandidate; |
| |
| /* find the lowest priority voice */ |
| bestPriority = bestCandidate = -1; |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| pVoice = &pVoiceMgr->voices[i]; |
| |
| /* this synth? */ |
| if (GET_VSYNTH(pVoice->nextChannel) != pSynth->vSynthNum) |
| continue; |
| |
| /* if voice is stolen or just started, reduce the likelihood it will be stolen */ |
| if (( pVoice->voiceState == eVoiceStateStolen) || (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET)) |
| { |
| /* include velocity */ |
| currentPriority = 128 - pVoice->nextVelocity; |
| |
| /* include channel priority */ |
| currentPriority += pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool << CHANNEL_PRIORITY_STEAL_WEIGHT; |
| } |
| else |
| { |
| /* include age */ |
| currentPriority = (EAS_I32) pVoice->age << NOTE_AGE_STEAL_WEIGHT; |
| |
| /* include note gain -higher gain is lower steal value */ |
| /*lint -e{704} use shift for performance */ |
| currentPriority += ((32768 >> (12 - NOTE_GAIN_STEAL_WEIGHT)) + 256) - |
| ((EAS_I32) pVoice->gain >> (12 - NOTE_GAIN_STEAL_WEIGHT)); |
| |
| /* include channel priority */ |
| currentPriority += pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool << CHANNEL_PRIORITY_STEAL_WEIGHT; |
| } |
| |
| /* is this the best choice so far? */ |
| if (currentPriority > bestPriority) |
| { |
| bestPriority = currentPriority; |
| bestCandidate = i; |
| } |
| } |
| |
| /* shutdown best candidate */ |
| if (bestCandidate < 0) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "VMSetPolyphony: Unable to reduce polyphony\n"); */ } |
| break; |
| } |
| |
| /* shut down this voice */ |
| VMMuteVoice(pVoiceMgr, bestCandidate); |
| activeVoices--; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetPolyphony() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Get the virtual synth polyphony |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * pPolyphonyCount pointer to variable to hold polyphony count |
| * pSynth pointer to virtual synth |
| * |
| * Outputs: |
| * Returns error code |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| EAS_RESULT VMGetPolyphony (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 *pPolyphonyCount) |
| { |
| *pPolyphonyCount = (EAS_U16) pSynth->maxPolyphony; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetPriority() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Set the virtual synth priority |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * priority new priority |
| * pSynth pointer to virtual synth |
| * |
| * Outputs: |
| * Returns error code |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| EAS_RESULT VMSetPriority (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 priority) |
| { |
| pSynth->priority = (EAS_U8) priority ; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetPriority() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Get the virtual synth priority |
| * |
| * Inputs: |
| * pVoiceMgr pointer to synthesizer data |
| * pPriority pointer to variable to hold priority |
| * pSynth pointer to virtual synth |
| * |
| * Outputs: |
| * Returns error code |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pVoiceMgr) reserved for future use */ |
| EAS_RESULT VMGetPriority (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_I32 *pPriority) |
| { |
| *pPriority = pSynth->priority; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetVolume() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Set the master volume for this synthesizer for this sequence. |
| * |
| * Inputs: |
| * nSynthVolume - the desired master volume |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * overrides any previously set master volume from sysex |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMSetVolume (S_SYNTH *pSynth, EAS_U16 masterVolume) |
| { |
| pSynth->masterVolume = masterVolume; |
| pSynth->synthFlags |= SYNTH_FLAG_UPDATE_ALL_CHANNEL_PARAMETERS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetPitchBendRange() |
| *---------------------------------------------------------------------------- |
| * Set the pitch bend range for the given channel. |
| *---------------------------------------------------------------------------- |
| */ |
| void VMSetPitchBendRange (S_SYNTH *pSynth, EAS_INT channel, EAS_I16 pitchBendRange) |
| { |
| pSynth->channels[channel].pitchBendSensitivity = pitchBendRange; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMValidateEASLib() |
| *---------------------------------------------------------------------------- |
| * Validates an EAS library |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMValidateEASLib (EAS_SNDLIB_HANDLE pEAS) |
| { |
| /* validate the sound library */ |
| if (pEAS) |
| { |
| if (pEAS->identifier != _EAS_LIBRARY_VERSION) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMValidateEASLib: Sound library mismatch in sound library: Read 0x%08x, expected 0x%08x\n", |
| pEAS->identifier, _EAS_LIBRARY_VERSION); */ } |
| return EAS_ERROR_SOUND_LIBRARY; |
| } |
| |
| /* check sample rate */ |
| if ((pEAS->libAttr & LIBFORMAT_SAMPLE_RATE_MASK) != _OUTPUT_SAMPLE_RATE) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMValidateEASLib: Sample rate mismatch in sound library: Read %lu, expected %lu\n", |
| pEAS->libAttr & LIBFORMAT_SAMPLE_RATE_MASK, _OUTPUT_SAMPLE_RATE); */ } |
| return EAS_ERROR_SOUND_LIBRARY; |
| } |
| |
| #ifdef _WT_SYNTH |
| /* check sample bit depth */ |
| #ifdef _8_BIT_SAMPLES |
| if (pEAS->libAttr & LIB_FORMAT_16_BIT_SAMPLES) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMValidateEASLib: Expected 8-bit samples and found 16-bit\n", |
| pEAS->libAttr & LIBFORMAT_SAMPLE_RATE_MASK, _OUTPUT_SAMPLE_RATE); */ } |
| return EAS_ERROR_SOUND_LIBRARY; |
| } |
| #endif |
| #ifdef _16_BIT_SAMPLES |
| if ((pEAS->libAttr & LIB_FORMAT_16_BIT_SAMPLES) == 0) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMValidateEASLib: Expected 16-bit samples and found 8-bit\n", |
| pEAS->libAttr & LIBFORMAT_SAMPLE_RATE_MASK, _OUTPUT_SAMPLE_RATE); */ } |
| return EAS_ERROR_SOUND_LIBRARY; |
| } |
| #endif |
| #endif |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetGlobalEASLib() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the EAS library to be used by the synthesizer |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetGlobalEASLib (S_VOICE_MGR *pVoiceMgr, EAS_SNDLIB_HANDLE pEAS) |
| { |
| EAS_RESULT result; |
| |
| result = VMValidateEASLib(pEAS); |
| if (result != EAS_SUCCESS) |
| return result; |
| |
| pVoiceMgr->pGlobalEAS = pEAS; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetEASLib() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the EAS library to be used by the synthesizer |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetEASLib (S_SYNTH *pSynth, EAS_SNDLIB_HANDLE pEAS) |
| { |
| EAS_RESULT result; |
| |
| result = VMValidateEASLib(pEAS); |
| if (result != EAS_SUCCESS) |
| return result; |
| |
| pSynth->pEAS = pEAS; |
| return EAS_SUCCESS; |
| } |
| |
| #ifdef DLS_SYNTHESIZER |
| /*---------------------------------------------------------------------------- |
| * VMSetGlobalDLSLib() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the DLS library to be used by the synthesizer |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetGlobalDLSLib (EAS_DATA_HANDLE pEASData, EAS_DLSLIB_HANDLE pDLS) |
| { |
| |
| if (pEASData->pVoiceMgr->pGlobalDLS) |
| DLSCleanup(pEASData->hwInstData, pEASData->pVoiceMgr->pGlobalDLS); |
| |
| pEASData->pVoiceMgr->pGlobalDLS = pDLS; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetDLSLib() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the DLS library to be used by the synthesizer |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSetDLSLib (S_SYNTH *pSynth, EAS_DLSLIB_HANDLE pDLS) |
| { |
| pSynth->pDLS = pDLS; |
| return EAS_SUCCESS; |
| } |
| #endif |
| |
| /*---------------------------------------------------------------------------- |
| * VMSetTranposition() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets the global key transposition used by the synthesizer. |
| * Transposes all melodic instruments up or down by the specified |
| * amount. Range is limited to +/-12 semitones. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMSetTranposition (S_SYNTH *pSynth, EAS_I32 transposition) |
| { |
| pSynth->globalTranspose = (EAS_I8) transposition; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetTranposition() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Gets the global key transposition used by the synthesizer. |
| * Transposes all melodic instruments up or down by the specified |
| * amount. Range is limited to +/-12 semitones. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMGetTranposition (S_SYNTH *pSynth, EAS_I32 *pTransposition) |
| { |
| *pTransposition = pSynth->globalTranspose; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetNoteCount() |
| *---------------------------------------------------------------------------- |
| * Returns the total note count |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_I32 VMGetNoteCount (S_SYNTH *pSynth) |
| { |
| return pSynth->totalNoteCount; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMMIDIShutdown() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Clean up any Synth related system issues. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * None |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMMIDIShutdown (S_EAS_DATA *pEASData, S_SYNTH *pSynth) |
| { |
| EAS_INT vSynthNum; |
| |
| /* decrement reference count, free if all references are gone */ |
| if (--pSynth->refCount > 0) |
| return; |
| |
| vSynthNum = pSynth->vSynthNum; |
| |
| /* cleanup DLS load */ |
| #ifdef DLS_SYNTHESIZER |
| /*lint -e{550} result used only in debugging code */ |
| if (pSynth->pDLS != NULL) |
| { |
| EAS_RESULT result; |
| if ((result = DLSCleanup(pEASData->hwInstData, pSynth->pDLS)) != EAS_SUCCESS) |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMMIDIShutdown: Error %ld cleaning up DLS collection\n", result); */ } |
| pSynth->pDLS = NULL; |
| } |
| #endif |
| |
| VMReset(pEASData->pVoiceMgr, pSynth, EAS_TRUE); |
| |
| /* check Configuration Module for static memory allocation */ |
| if (!pEASData->staticMemoryModel) |
| EAS_HWFree(pEASData->hwInstData, pSynth); |
| |
| /* clear pointer to MIDI state */ |
| pEASData->pVoiceMgr->pSynth[vSynthNum] = NULL; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMShutdown() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Clean up any Synth related system issues. |
| * |
| * Inputs: |
| * psEASData - pointer to overall EAS data structure |
| * |
| * Outputs: |
| * None |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| void VMShutdown (S_EAS_DATA *pEASData) |
| { |
| |
| /* don't free a NULL pointer */ |
| if (pEASData->pVoiceMgr == NULL) |
| return; |
| |
| #ifdef DLS_SYNTHESIZER |
| /* if we have a global DLS collection, clean it up */ |
| if (pEASData->pVoiceMgr->pGlobalDLS) |
| { |
| DLSCleanup(pEASData->hwInstData, pEASData->pVoiceMgr->pGlobalDLS); |
| pEASData->pVoiceMgr->pGlobalDLS = NULL; |
| } |
| #endif |
| |
| /* check Configuration Module for static memory allocation */ |
| if (!pEASData->staticMemoryModel) |
| EAS_HWFree(pEASData->hwInstData, pEASData->pVoiceMgr); |
| pEASData->pVoiceMgr = NULL; |
| } |
| |
| #ifdef EXTERNAL_AUDIO |
| /*---------------------------------------------------------------------------- |
| * EAS_RegExtAudioCallback() |
| *---------------------------------------------------------------------------- |
| * Register a callback for external audio processing |
| *---------------------------------------------------------------------------- |
| */ |
| void VMRegExtAudioCallback (S_SYNTH *pSynth, EAS_VOID_PTR pInstData, EAS_EXT_PRG_CHG_FUNC cbProgChgFunc, EAS_EXT_EVENT_FUNC cbEventFunc) |
| { |
| pSynth->pExtAudioInstData = pInstData; |
| pSynth->cbProgChgFunc = cbProgChgFunc; |
| pSynth->cbEventFunc = cbEventFunc; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMGetMIDIControllers() |
| *---------------------------------------------------------------------------- |
| * Returns the MIDI controller values on the specified channel |
| *---------------------------------------------------------------------------- |
| */ |
| void VMGetMIDIControllers (S_SYNTH *pSynth, EAS_U8 channel, S_MIDI_CONTROLLERS *pControl) |
| { |
| pControl->modWheel = pSynth->channels[channel].modWheel; |
| pControl->volume = pSynth->channels[channel].volume; |
| pControl->pan = pSynth->channels[channel].pan; |
| pControl->expression = pSynth->channels[channel].expression; |
| pControl->channelPressure = pSynth->channels[channel].channelPressure; |
| |
| #ifdef _REVERB |
| pControl->reverbSend = pSynth->channels[channel].reverbSend; |
| #endif |
| |
| #ifdef _CHORUSE |
| pControl->chorusSend = pSynth->channels[channel].chorusSend; |
| #endif |
| } |
| #endif |
| |
| #ifdef _SPLIT_ARCHITECTURE |
| /*---------------------------------------------------------------------------- |
| * VMStartFrame() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Starts an audio frame |
| * |
| * Inputs: |
| * |
| * Outputs: |
| * Returns true if EAS_MixEnginePrep should be called (onboard mixing) |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_BOOL VMStartFrame (S_EAS_DATA *pEASData) |
| { |
| |
| /* init counter for voices starts in split architecture */ |
| #ifdef MAX_VOICE_STARTS |
| pVoiceMgr->numVoiceStarts = 0; |
| #endif |
| |
| return pFrameInterface->pfStartFrame(pEASData->pVoiceMgr->pFrameBuffer); |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * VMEndFrame() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Stops an audio frame |
| * |
| * Inputs: |
| * |
| * Outputs: |
| * Returns true if EAS_MixEnginePost should be called (onboard mixing) |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_BOOL VMEndFrame (S_EAS_DATA *pEASData) |
| { |
| |
| return pFrameInterface->pfEndFrame(pEASData->pVoiceMgr->pFrameBuffer, pEASData->pMixBuffer, pEASData->masterGain); |
| } |
| #endif |
| |
| #ifdef TEST_HARNESS |
| /*---------------------------------------------------------------------------- |
| * SanityCheck() |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT VMSanityCheck (EAS_DATA_HANDLE pEASData) |
| { |
| S_SYNTH_VOICE *pVoice; |
| S_SYNTH *pSynth; |
| EAS_INT i; |
| EAS_INT j; |
| EAS_INT freeVoices; |
| EAS_INT activeVoices; |
| EAS_INT playingVoices; |
| EAS_INT stolenVoices; |
| EAS_INT releasingVoices; |
| EAS_INT mutingVoices; |
| EAS_INT poolCount[MAX_VIRTUAL_SYNTHESIZERS][NUM_SYNTH_CHANNELS]; |
| EAS_INT vSynthNum; |
| EAS_RESULT result = EAS_SUCCESS; |
| |
| /* initialize counts */ |
| EAS_HWMemSet(poolCount, 0, sizeof(poolCount)); |
| freeVoices = activeVoices = playingVoices = stolenVoices = releasingVoices = mutingVoices = 0; |
| |
| /* iterate through all voices */ |
| for (i = 0; i < MAX_SYNTH_VOICES; i++) |
| { |
| pVoice = &pEASData->pVoiceMgr->voices[i]; |
| if (pVoice->voiceState != eVoiceStateFree) |
| { |
| vSynthNum = GET_VSYNTH(pVoice->channel); |
| if (vSynthNum >= MAX_VIRTUAL_SYNTHESIZERS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMSanityCheck: Voice %d has invalid virtual synth number %d\n", i, vSynthNum); */ } |
| result = EAS_FAILURE; |
| continue; |
| } |
| pSynth = pEASData->pVoiceMgr->pSynth[vSynthNum]; |
| |
| switch (pVoice->voiceState) |
| { |
| case eVoiceStateMuting: |
| activeVoices++; |
| mutingVoices++; |
| break; |
| |
| case eVoiceStateStolen: |
| vSynthNum = GET_VSYNTH(pVoice->nextChannel); |
| if (vSynthNum >= MAX_VIRTUAL_SYNTHESIZERS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMSanityCheck: Voice %d has invalid virtual synth number %d\n", i, vSynthNum); */ } |
| result = EAS_FAILURE; |
| continue; |
| } |
| pSynth = pEASData->pVoiceMgr->pSynth[vSynthNum]; |
| activeVoices++; |
| stolenVoices++; |
| poolCount[vSynthNum][pSynth->channels[GET_CHANNEL(pVoice->nextChannel)].pool]++; |
| break; |
| |
| case eVoiceStateStart: |
| case eVoiceStatePlay: |
| activeVoices++; |
| playingVoices++; |
| poolCount[vSynthNum][pSynth->channels[GET_CHANNEL(pVoice->channel)].pool]++; |
| break; |
| |
| case eVoiceStateRelease: |
| activeVoices++; |
| releasingVoices++; |
| poolCount[vSynthNum][pSynth->channels[GET_CHANNEL(pVoice->channel)].pool]++; |
| break; |
| |
| default: |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMSanityCheck : voice %d in invalid state\n", i); */ } |
| result = EAS_FAILURE; |
| break; |
| } |
| } |
| |
| /* count free voices */ |
| else |
| freeVoices++; |
| } |
| |
| /* dump state info */ |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d free\n", freeVoices); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d active\n", activeVoices); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d playing\n", playingVoices); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d releasing\n", releasingVoices); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d muting\n", mutingVoices); */ } |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "%d stolen\n", stolenVoices); */ } |
| |
| if (pEASData->pVoiceMgr->activeVoices != activeVoices) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Active voice mismatch was %d should be %d\n", |
| pEASData->pVoiceMgr->activeVoices, activeVoices); */ } |
| result = EAS_FAILURE; |
| } |
| |
| /* check virtual synth status */ |
| for (i = 0; i < MAX_VIRTUAL_SYNTHESIZERS; i++) |
| { |
| if (pEASData->pVoiceMgr->pSynth[i] == NULL) |
| continue; |
| |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Synth %d numActiveVoices: %d\n", i, pEASData->pVoiceMgr->pSynth[i]->numActiveVoices); */ } |
| if (pEASData->pVoiceMgr->pSynth[i]->numActiveVoices > MAX_SYNTH_VOICES) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMSanityCheck: Synth %d illegal count for numActiveVoices: %d\n", i, pEASData->pVoiceMgr->pSynth[i]->numActiveVoices); */ } |
| result = EAS_FAILURE; |
| } |
| for (j = 0; j < NUM_SYNTH_CHANNELS; j++) |
| { |
| if (poolCount[i][j] != pEASData->pVoiceMgr->pSynth[i]->poolCount[j]) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Pool count mismatch synth %d pool %d, was %d, should be %d\n", |
| i, j, pEASData->pVoiceMgr->pSynth[i]->poolCount[j], poolCount[i][j]); */ } |
| result = EAS_FAILURE; |
| } |
| } |
| } |
| |
| return result; |
| } |
| #endif |
| |
| |