--- linuxsampler/trunk/src/engines/gig/Voice.cpp 2004/09/02 21:52:29 230 +++ linuxsampler/trunk/src/engines/gig/Voice.cpp 2004/09/15 13:59:08 242 @@ -27,8 +27,6 @@ namespace LinuxSampler { namespace gig { - // FIXME: no support for layers (nor crossfades) yet - const float Voice::FILTER_CUTOFF_COEFF(CalculateFilterCutoffCoeff()); const int Voice::FILTER_UPDATE_MASK(CalculateFilterUpdateMask()); @@ -57,6 +55,7 @@ pLFO1 = NULL; pLFO2 = NULL; pLFO3 = NULL; + KeyGroup = 0; } Voice::~Voice() { @@ -104,31 +103,36 @@ * Initializes and triggers the voice, a disk stream will be launched if * needed. * - * @param pNoteOnEvent - event that caused triggering of this voice - * @param PitchBend - MIDI detune factor (-8192 ... +8191) - * @param pInstrument - points to the loaded instrument which provides sample wave(s) and articulation data - * @returns 0 on success, a value < 0 if something failed + * @param pNoteOnEvent - event that caused triggering of this voice + * @param PitchBend - MIDI detune factor (-8192 ... +8191) + * @param pInstrument - points to the loaded instrument which provides sample wave(s) and articulation data + * @param iLayer - layer number this voice refers to (only if this is a layered sound of course) + * @param ReleaseTriggerVoice - if this new voice is a release trigger voice (optional, default = false) + * @returns 0 on success, a value < 0 if something failed */ - int Voice::Trigger(Event* pNoteOnEvent, int PitchBend, ::gig::Instrument* pInstrument) { + int Voice::Trigger(Event* pNoteOnEvent, int PitchBend, ::gig::Instrument* pInstrument, int iLayer, bool ReleaseTriggerVoice) { if (!pInstrument) { dmsg(1,("voice::trigger: !pInstrument\n")); exit(EXIT_FAILURE); } + Type = type_normal; Active = true; MIDIKey = pNoteOnEvent->Key; pRegion = pInstrument->GetRegion(MIDIKey); PlaybackState = playback_state_ram; // we always start playback from RAM cache and switch then to disk if needed - Pos = 0; Delay = pNoteOnEvent->FragmentPos(); pTriggerEvent = pNoteOnEvent; + pKillEvent = NULL; if (!pRegion) { std::cerr << "gig::Voice: No Region defined for MIDI key " << MIDIKey << std::endl << std::flush; - Kill(); + KillImmediately(); return -1; } + KeyGroup = pRegion->KeyGroup; + // get current dimension values to select the right dimension region //FIXME: controller values for selecting the dimension region here are currently not sample accurate uint DimValues[5] = {0,0,0,0,0}; @@ -138,7 +142,11 @@ DimValues[i] = 0; //TODO: we currently ignore this dimension break; case ::gig::dimension_layer: - DimValues[i] = 0; //TODO: we currently ignore this dimension + DimValues[i] = iLayer; + // if this is the 1st layer then spawn further voices for all the other layers + if (iLayer == 0) + for (int iNewLayer = 1; iNewLayer < pRegion->pDimensionDefinitions[i].zones; iNewLayer++) + pEngine->LaunchVoice(pNoteOnEvent, iNewLayer, ReleaseTriggerVoice); break; case ::gig::dimension_velocity: DimValues[i] = pNoteOnEvent->Velocity; @@ -147,7 +155,8 @@ DimValues[i] = 0; //TODO: we currently ignore this dimension break; case ::gig::dimension_releasetrigger: - DimValues[i] = 0; //TODO: we currently ignore this dimension + Type = (ReleaseTriggerVoice) ? type_release_trigger : (!iLayer) ? type_release_trigger_required : type_normal; + DimValues[i] = (uint) ReleaseTriggerVoice; break; case ::gig::dimension_keyboard: DimValues[i] = (uint) pNoteOnEvent->Key; @@ -228,10 +237,28 @@ std::cerr << "gig::Voice::Trigger() Error: Unknown dimension\n" << std::flush; } } - ::gig::DimensionRegion* pDimRgn = pRegion->GetDimensionRegionByValue(DimValues[4],DimValues[3],DimValues[2],DimValues[1],DimValues[0]); + pDimRgn = pRegion->GetDimensionRegionByValue(DimValues[4],DimValues[3],DimValues[2],DimValues[1],DimValues[0]); + + // get starting crossfade volume level + switch (pDimRgn->AttenuationController.type) { + case ::gig::attenuation_ctrl_t::type_channelaftertouch: + CrossfadeVolume = 1.0f; //TODO: aftertouch not supported yet + break; + case ::gig::attenuation_ctrl_t::type_velocity: + CrossfadeVolume = CrossfadeAttenuation(pNoteOnEvent->Velocity); + break; + case ::gig::attenuation_ctrl_t::type_controlchange: //FIXME: currently not sample accurate + CrossfadeVolume = CrossfadeAttenuation(pEngine->ControllerTable[pDimRgn->AttenuationController.controller_number]); + break; + case ::gig::attenuation_ctrl_t::type_none: // no crossfade defined + default: + CrossfadeVolume = 1.0f; + } pSample = pDimRgn->pSample; // sample won't change until the voice is finished + Pos = pDimRgn->SampleStartOffset; // offset where we should start playback of sample (0 - 2000 sample points) + // Check if the sample needs disk streaming or is too short for that long cachedsamples = pSample->GetCache().Size / pSample->FrameSize; DiskVoice = cachedsamples < pSample->SamplesTotal; @@ -248,7 +275,7 @@ if (pDiskThread->OrderNewStream(&DiskStreamRef, pSample, MaxRAMPos, !RAMLoop) < 0) { dmsg(1,("Disk stream order failed!\n")); - Kill(); + KillImmediately(); return -1; } dmsg(4,("Disk voice launched (cached samples: %d, total Samples: %d, MaxRAMPos: %d, RAMLooping: %s)\n", cachedsamples, pSample->SamplesTotal, MaxRAMPos, (RAMLoop) ? "yes" : "no")); @@ -268,7 +295,7 @@ { double pitchbasecents = pDimRgn->FineTune * 10; if (pDimRgn->PitchTrack) pitchbasecents += (MIDIKey - (int) pDimRgn->UnityNote) * 100; - this->PitchBase = RTMath::CentsToFreqRatio(pitchbasecents); + this->PitchBase = RTMath::CentsToFreqRatio(pitchbasecents) * (double(pSample->SamplesPerSecond) / double(pEngine->pAudioOutputDevice->SampleRate())); this->PitchBend = RTMath::CentsToFreqRatio(((double) PitchBend / 8192.0) * 200.0); // pitchbend wheel +-2 semitones = 200 cents } @@ -579,10 +606,6 @@ } #endif // ENABLE_FILTER - // ************************************************ - // TODO: ARTICULATION DATA HANDLING IS MISSING HERE - // ************************************************ - return 0; // success } @@ -600,7 +623,7 @@ void Voice::Render(uint Samples) { // Reset the synthesis parameter matrix - pEngine->ResetSynthesisParameters(Event::destination_vca, this->Volume * pEngine->GlobalVolume); + pEngine->ResetSynthesisParameters(Event::destination_vca, this->Volume * this->CrossfadeVolume * pEngine->GlobalVolume); pEngine->ResetSynthesisParameters(Event::destination_vco, this->PitchBase); #if ENABLE_FILTER pEngine->ResetSynthesisParameters(Event::destination_vcfc, VCFCutoffCtrl.fvalue); @@ -613,7 +636,7 @@ // Let all modulators write their parameter changes to the synthesis parameter matrix for the current audio fragment - pEG1->Process(Samples, pEngine->pMIDIKeyInfo[MIDIKey].pEvents, pTriggerEvent, this->Pos, this->PitchBase * this->PitchBend); + pEG1->Process(Samples, pEngine->pMIDIKeyInfo[MIDIKey].pEvents, pTriggerEvent, this->Pos, this->PitchBase * this->PitchBend, pKillEvent); #if ENABLE_FILTER pEG2->Process(Samples, pEngine->pMIDIKeyInfo[MIDIKey].pEvents, pTriggerEvent, this->Pos, this->PitchBase * this->PitchBend); #endif // ENABLE_FILTER @@ -654,7 +677,7 @@ DiskStreamRef.pStream = pDiskThread->AskForCreatedStream(DiskStreamRef.OrderID); if (!DiskStreamRef.pStream) { std::cout << stderr << "Disk stream not available in time!" << std::endl << std::flush; - Kill(); + KillImmediately(); return; } DiskStreamRef.pStream->IncrementReadPos(pSample->Channels * (RTMath::DoubleToInt(Pos) - MaxRAMPos)); @@ -675,13 +698,14 @@ break; case playback_state_end: - Kill(); // free voice + KillImmediately(); // free voice break; } - #if ENABLE_FILTER // Reset synthesis event lists (except VCO, as VCO events apply channel wide currently) + pEngine->pSynthesisEvents[Event::destination_vca]->clear(); + #if ENABLE_FILTER pEngine->pSynthesisEvents[Event::destination_vcfc]->clear(); pEngine->pSynthesisEvents[Event::destination_vcfr]->clear(); #endif // ENABLE_FILTER @@ -745,6 +769,10 @@ if (pCCEvent->Controller == pLFO3->ExtController) { pLFO3->SendEvent(pCCEvent); } + if (pDimRgn->AttenuationController.type == ::gig::attenuation_ctrl_t::type_controlchange && + pCCEvent->Controller == pDimRgn->AttenuationController.controller_number) { // if crossfade event + pEngine->pSynthesisEvents[Event::destination_vca]->alloc_assign(*pCCEvent); + } } pCCEvent = pEngine->pCCEvents->next(); @@ -784,6 +812,33 @@ if (pVCOEventList->last()) this->PitchBend = pitch; } + // process volume / attenuation events (TODO: we only handle and _expect_ crossfade events here ATM !) + { + RTEList* pVCAEventList = pEngine->pSynthesisEvents[Event::destination_vca]; + Event* pVCAEvent = pVCAEventList->first(); + if (Delay) { // skip events that happened before this voice was triggered + while (pVCAEvent && pVCAEvent->FragmentPos() <= Delay) pVCAEvent = pVCAEventList->next(); + } + float crossfadevolume; + while (pVCAEvent) { + Event* pNextVCAEvent = pVCAEventList->next(); + + // calculate the influence length of this event (in sample points) + uint end = (pNextVCAEvent) ? pNextVCAEvent->FragmentPos() : Samples; + + crossfadevolume = CrossfadeAttenuation(pVCAEvent->Value); + + float effective_volume = crossfadevolume * this->Volume * pEngine->GlobalVolume; + + // apply volume value to the volume parameter sequence + for (uint i = pVCAEvent->FragmentPos(); i < end; i++) { + pEngine->pSynthesisParameters[Event::destination_vca][i] = effective_volume; + } + + pVCAEvent = pNextVCAEvent; + } + if (pVCAEventList->last()) this->CrossfadeVolume = crossfadevolume; + } #if ENABLE_FILTER // process filter cutoff events @@ -1008,13 +1063,32 @@ } /** - * Immediately kill the voice. + * Immediately kill the voice. This method should not be used to kill + * a normal, active voice, because it doesn't take care of things like + * fading down the volume level to avoid clicks and regular processing + * until the kill event actually occured! + * + * @see Kill() */ - void Voice::Kill() { + void Voice::KillImmediately() { if (DiskVoice && DiskStreamRef.State != Stream::state_unused) { pDiskThread->OrderDeletionOfStream(&DiskStreamRef); } Reset(); } + /** + * Kill the voice in regular sense. Let the voice render audio until + * the kill event actually occured and then fade down the volume level + * very quickly and let the voice die finally. Unlike a normal release + * of a voice, a kill process cannot be cancalled and is therefore + * usually used for voice stealing and key group conflicts. + * + * @param pKillEvent - event which caused the voice to be killed + */ + void Voice::Kill(Event* pKillEvent) { + if (pTriggerEvent && pKillEvent->FragmentPos() <= pTriggerEvent->FragmentPos()) return; + this->pKillEvent = pKillEvent; + } + }} // namespace LinuxSampler::gig