You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3623 lines
120 KiB
3623 lines
120 KiB
/*----------------------------------------------------------------------------
|
|
*
|
|
* 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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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 && currentPool != -1)
|
|
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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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
|
|
/*----------------------------------------------------------------------------
|
|
* 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;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* 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
|
|
|
|
|