/[svn]/linuxsampler/trunk/src/engines/common/AbstractVoice.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/engines/common/AbstractVoice.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3016 - (hide annotations) (download)
Tue Oct 18 21:01:46 2016 UTC (4 years, 11 months ago) by schoenebeck
File size: 42487 byte(s)
- Follow-up of previous fix.

1 iliev 2015 /***************************************************************************
2     * *
3     * LinuxSampler - modular, streaming capable sampler *
4     * *
5     * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck *
6 persson 2045 * Copyright (C) 2005-2008 Christian Schoenebeck *
7 persson 2837 * Copyright (C) 2009-2015 Christian Schoenebeck and Grigor Iliev *
8 iliev 2015 * *
9     * This program is free software; you can redistribute it and/or modify *
10     * it under the terms of the GNU General Public License as published by *
11     * the Free Software Foundation; either version 2 of the License, or *
12     * (at your option) any later version. *
13     * *
14     * This program is distributed in the hope that it will be useful, *
15     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17     * GNU General Public License for more details. *
18     * *
19     * You should have received a copy of the GNU General Public License *
20     * along with this program; if not, write to the Free Software *
21     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
22     * MA 02111-1307 USA *
23     ***************************************************************************/
24    
25     #include "AbstractVoice.h"
26    
27     namespace LinuxSampler {
28    
29 iliev 2217 AbstractVoice::AbstractVoice(SignalUnitRack* pRack): pSignalUnitRack(pRack) {
30 iliev 2015 pEngineChannel = NULL;
31 persson 2175 pLFO1 = new LFOUnsigned(1.0f); // amplitude LFO (0..1 range)
32     pLFO2 = new LFOUnsigned(1.0f); // filter LFO (0..1 range)
33     pLFO3 = new LFOSigned(1200.0f); // pitch LFO (-1200..+1200 range)
34 iliev 2015 PlaybackState = playback_state_end;
35     SynthesisMode = 0; // set all mode bits to 0 first
36     // select synthesis implementation (asm core is not supported ATM)
37     #if 0 // CONFIG_ASM && ARCH_X86
38     SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, Features::supportsMMX() && Features::supportsSSE());
39     #else
40     SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, false);
41     #endif
42     SYNTHESIS_MODE_SET_PROFILING(SynthesisMode, gig::Profiler::isEnabled());
43    
44     finalSynthesisParameters.filterLeft.Reset();
45     finalSynthesisParameters.filterRight.Reset();
46 iliev 2298
47     pEq = NULL;
48     bEqSupport = false;
49 iliev 2015 }
50    
51     AbstractVoice::~AbstractVoice() {
52     if (pLFO1) delete pLFO1;
53     if (pLFO2) delete pLFO2;
54     if (pLFO3) delete pLFO3;
55 iliev 2298
56     if(pEq != NULL) delete pEq;
57 iliev 2015 }
58 iliev 2298
59     void AbstractVoice::CreateEq() {
60     if(!bEqSupport) return;
61     if(pEq != NULL) delete pEq;
62     pEq = new EqSupport;
63     pEq->InitEffect(GetEngine()->pAudioOutputDevice);
64     }
65 persson 2045
66 iliev 2015 /**
67     * Resets voice variables. Should only be called if rendering process is
68     * suspended / not running.
69     */
70     void AbstractVoice::Reset() {
71     finalSynthesisParameters.filterLeft.Reset();
72     finalSynthesisParameters.filterRight.Reset();
73     DiskStreamRef.pStream = NULL;
74     DiskStreamRef.hStream = 0;
75     DiskStreamRef.State = Stream::state_unused;
76     DiskStreamRef.OrderID = 0;
77     PlaybackState = playback_state_end;
78     itTriggerEvent = Pool<Event>::Iterator();
79     itKillEvent = Pool<Event>::Iterator();
80     }
81    
82     /**
83     * Initializes and triggers the voice, a disk stream will be launched if
84     * needed.
85     *
86     * @param pEngineChannel - engine channel on which this voice was ordered
87     * @param itNoteOnEvent - event that caused triggering of this voice
88     * @param PitchBend - MIDI detune factor (-8192 ... +8191)
89     * @param pRegion- points to the region which provides sample wave(s) and articulation data
90     * @param VoiceType - type of this voice
91     * @param iKeyGroup - a value > 0 defines a key group in which this voice is member of
92     * @returns 0 on success, a value < 0 if the voice wasn't triggered
93     * (either due to an error or e.g. because no region is
94     * defined for the given key)
95     */
96     int AbstractVoice::Trigger (
97     AbstractEngineChannel* pEngineChannel,
98     Pool<Event>::Iterator& itNoteOnEvent,
99     int PitchBend,
100     type_t VoiceType,
101     int iKeyGroup
102     ) {
103     this->pEngineChannel = pEngineChannel;
104     Orphan = false;
105    
106     #if CONFIG_DEVMODE
107     if (itNoteOnEvent->FragmentPos() > GetEngine()->MaxSamplesPerCycle) { // just a sanity check for debugging
108     dmsg(1,("Voice::Trigger(): ERROR, TriggerDelay > Totalsamples\n"));
109     }
110     #endif // CONFIG_DEVMODE
111    
112     Type = VoiceType;
113 schoenebeck 2879 pNote = pEngineChannel->pEngine->NoteByID( itNoteOnEvent->Param.Note.ID );
114 iliev 2015 PlaybackState = playback_state_init; // mark voice as triggered, but no audio rendered yet
115     Delay = itNoteOnEvent->FragmentPos();
116     itTriggerEvent = itNoteOnEvent;
117     itKillEvent = Pool<Event>::Iterator();
118 schoenebeck 2879 MidiKeyBase* pKeyInfo = GetMidiKeyInfo(MIDIKey());
119 iliev 2015
120 persson 2114 pGroupEvents = iKeyGroup ? pEngineChannel->ActiveKeyGroups[iKeyGroup] : 0;
121    
122 iliev 2015 SmplInfo = GetSampleInfo();
123     RgnInfo = GetRegionInfo();
124     InstrInfo = GetInstrumentInfo();
125 iliev 2205
126 persson 2382 MIDIPan = CalculatePan(pEngineChannel->iLastPanRequest);
127    
128 iliev 2205 AboutToTrigger();
129 iliev 2015
130     // calculate volume
131     const double velocityAttenuation = GetVelocityAttenuation(itNoteOnEvent->Param.Note.Velocity);
132 schoenebeck 2121 float volume = CalculateVolume(velocityAttenuation) * pKeyInfo->Volume;
133 persson 2032 if (volume <= 0) return -1;
134 iliev 2015
135     // select channel mode (mono or stereo)
136     SYNTHESIS_MODE_SET_CHANNELS(SynthesisMode, SmplInfo.ChannelCount == 2);
137     // select bit depth (16 or 24)
138     SYNTHESIS_MODE_SET_BITDEPTH24(SynthesisMode, SmplInfo.BitDepth == 24);
139    
140     // get starting crossfade volume level
141     float crossfadeVolume = CalculateCrossfadeVolume(itNoteOnEvent->Param.Note.Velocity);
142    
143 persson 2382 VolumeLeft = volume * pKeyInfo->PanLeft;
144     VolumeRight = volume * pKeyInfo->PanRight;
145 iliev 2015
146 schoenebeck 2963 // this rate is used for rather mellow volume fades
147     const float subfragmentRate = GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE;
148     // this rate is used for very fast volume fades
149     const float quickRampRate = RTMath::Min(subfragmentRate, GetEngine()->SampleRate * 0.001f /* 1ms */);
150 iliev 2015 CrossfadeSmoother.trigger(crossfadeVolume, subfragmentRate);
151 schoenebeck 2963
152 iliev 2015 VolumeSmoother.trigger(pEngineChannel->MidiVolume, subfragmentRate);
153 schoenebeck 2963 NoteVolumeSmoother.trigger(pNote ? pNote->Override.Volume : 1.f, quickRampRate);
154 iliev 2015
155     // Check if the sample needs disk streaming or is too short for that
156     long cachedsamples = GetSampleCacheSize() / SmplInfo.FrameSize;
157     DiskVoice = cachedsamples < SmplInfo.TotalFrameCount;
158    
159 iliev 2216 SetSampleStartOffset();
160    
161 iliev 2015 if (DiskVoice) { // voice to be streamed from disk
162     if (cachedsamples > (GetEngine()->MaxSamplesPerCycle << CONFIG_MAX_PITCH)) {
163     MaxRAMPos = cachedsamples - (GetEngine()->MaxSamplesPerCycle << CONFIG_MAX_PITCH) / SmplInfo.ChannelCount; //TODO: this calculation is too pessimistic and may better be moved to Render() method, so it calculates MaxRAMPos dependent to the current demand of sample points to be rendered (e.g. in case of JACK)
164     } else {
165     // The cache is too small to fit a max sample buffer.
166     // Setting MaxRAMPos to 0 will probably cause a click
167     // in the audio, but it's better than not handling
168     // this case at all, which would have caused the
169     // unsigned MaxRAMPos to be set to a negative number.
170     MaxRAMPos = 0;
171     }
172    
173     // check if there's a loop defined which completely fits into the cached (RAM) part of the sample
174     RAMLoop = (SmplInfo.HasLoops && (SmplInfo.LoopStart + SmplInfo.LoopLength) <= MaxRAMPos);
175    
176     if (OrderNewStream()) return -1;
177 persson 2837 dmsg(4,("Disk voice launched (cached samples: %ld, total Samples: %d, MaxRAMPos: %lu, RAMLooping: %s)\n", cachedsamples, SmplInfo.TotalFrameCount, MaxRAMPos, (RAMLoop) ? "yes" : "no"));
178 iliev 2015 }
179     else { // RAM only voice
180     MaxRAMPos = cachedsamples;
181     RAMLoop = (SmplInfo.HasLoops);
182     dmsg(4,("RAM only voice launched (Looping: %s)\n", (RAMLoop) ? "yes" : "no"));
183     }
184     if (RAMLoop) {
185     loop.uiTotalCycles = SmplInfo.LoopPlayCount;
186     loop.uiCyclesLeft = SmplInfo.LoopPlayCount;
187     loop.uiStart = SmplInfo.LoopStart;
188     loop.uiEnd = SmplInfo.LoopStart + SmplInfo.LoopLength;
189     loop.uiSize = SmplInfo.LoopLength;
190     }
191    
192     Pitch = CalculatePitchInfo(PitchBend);
193 schoenebeck 2931 NotePitch = (pNote) ? pNote->Override.Pitch : 1.0f;
194 schoenebeck 2935 NoteCutoff = (pNote) ? pNote->Override.Cutoff : 1.0f;
195     NoteResonance = (pNote) ? pNote->Override.Resonance : 1.0f;
196 iliev 2015
197     // the length of the decay and release curves are dependent on the velocity
198     const double velrelease = 1 / GetVelocityRelease(itNoteOnEvent->Param.Note.Velocity);
199    
200 iliev 2217 if (pSignalUnitRack == NULL) { // setup EG 1 (VCA EG)
201 iliev 2015 // get current value of EG1 controller
202     double eg1controllervalue = GetEG1ControllerValue(itNoteOnEvent->Param.Note.Velocity);
203    
204     // calculate influence of EG1 controller on EG1's parameters
205     EGInfo egInfo = CalculateEG1ControllerInfluence(eg1controllervalue);
206    
207 schoenebeck 2953 if (pNote) {
208     egInfo.Attack *= pNote->Override.Attack;
209     egInfo.Decay *= pNote->Override.Decay;
210     egInfo.Release *= pNote->Override.Release;
211     }
212    
213 persson 2055 TriggerEG1(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, itNoteOnEvent->Param.Note.Velocity);
214 iliev 2205 } else {
215 iliev 2217 pSignalUnitRack->Trigger();
216 iliev 2015 }
217    
218 schoenebeck 2931 const uint8_t pan = (pSignalUnitRack) ? pSignalUnitRack->GetEndpointUnit()->CalculatePan(MIDIPan) : MIDIPan;
219     NotePanLeft = (pNote) ? AbstractEngine::PanCurveValueNorm(pNote->Override.Pan, 0 /*left*/ ) : 1.f;
220     NotePanRight = (pNote) ? AbstractEngine::PanCurveValueNorm(pNote->Override.Pan, 1 /*right*/) : 1.f;
221     PanLeftSmoother.trigger(
222     AbstractEngine::PanCurve[128 - pan] * NotePanLeft,
223 schoenebeck 2963 quickRampRate //NOTE: maybe we should have 2 separate pan smoothers, one for MIDI CC10 (with slow rate) and one for instrument script change_pan() calls (with fast rate)
224 schoenebeck 2931 );
225     PanRightSmoother.trigger(
226     AbstractEngine::PanCurve[pan] * NotePanRight,
227 schoenebeck 2963 quickRampRate //NOTE: maybe we should have 2 separate pan smoothers, one for MIDI CC10 (with slow rate) and one for instrument script change_pan() calls (with fast rate)
228 schoenebeck 2931 );
229 persson 2382
230 iliev 2015 #ifdef CONFIG_INTERPOLATE_VOLUME
231     // setup initial volume in synthesis parameters
232     #ifdef CONFIG_PROCESS_MUTED_CHANNELS
233     if (pEngineChannel->GetMute()) {
234     finalSynthesisParameters.fFinalVolumeLeft = 0;
235     finalSynthesisParameters.fFinalVolumeRight = 0;
236     }
237     else
238     #else
239     {
240 iliev 2205 float finalVolume;
241 iliev 2217 if (pSignalUnitRack == NULL) {
242 iliev 2205 finalVolume = pEngineChannel->MidiVolume * crossfadeVolume * pEG1->getLevel();
243     } else {
244 iliev 2217 finalVolume = pEngineChannel->MidiVolume * crossfadeVolume * pSignalUnitRack->GetEndpointUnit()->GetVolume();
245 iliev 2205 }
246 iliev 2015
247 persson 2382 finalSynthesisParameters.fFinalVolumeLeft = finalVolume * VolumeLeft * PanLeftSmoother.render();
248     finalSynthesisParameters.fFinalVolumeRight = finalVolume * VolumeRight * PanRightSmoother.render();
249 iliev 2015 }
250     #endif
251     #endif
252    
253 iliev 2217 if (pSignalUnitRack == NULL) {
254 iliev 2205 // setup EG 2 (VCF Cutoff EG)
255     {
256     // get current value of EG2 controller
257     double eg2controllervalue = GetEG2ControllerValue(itNoteOnEvent->Param.Note.Velocity);
258 iliev 2015
259 iliev 2205 // calculate influence of EG2 controller on EG2's parameters
260     EGInfo egInfo = CalculateEG2ControllerInfluence(eg2controllervalue);
261 iliev 2015
262 iliev 2205 TriggerEG2(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, itNoteOnEvent->Param.Note.Velocity);
263     }
264 iliev 2015
265    
266 iliev 2205 // setup EG 3 (VCO EG)
267     {
268     // if portamento mode is on, we dedicate EG3 purely for portamento, otherwise if portamento is off we do as told by the patch
269     bool bPortamento = pEngineChannel->PortamentoMode && pEngineChannel->PortamentoPos >= 0.0f;
270     float eg3depth = (bPortamento)
271 schoenebeck 2879 ? RTMath::CentsToFreqRatio((pEngineChannel->PortamentoPos - (float) MIDIKey()) * 100)
272 iliev 2205 : RTMath::CentsToFreqRatio(RgnInfo.EG3Depth);
273     float eg3time = (bPortamento)
274     ? pEngineChannel->PortamentoTime
275     : RgnInfo.EG3Attack;
276     EG3.trigger(eg3depth, eg3time, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
277     dmsg(5,("PortamentoPos=%f, depth=%f, time=%f\n", pEngineChannel->PortamentoPos, eg3depth, eg3time));
278     }
279 iliev 2015
280    
281 iliev 2205 // setup LFO 1 (VCA LFO)
282     InitLFO1();
283     // setup LFO 2 (VCF Cutoff LFO)
284     InitLFO2();
285     // setup LFO 3 (VCO LFO)
286     InitLFO3();
287     }
288 iliev 2015
289    
290     #if CONFIG_FORCE_FILTER
291     const bool bUseFilter = true;
292     #else // use filter only if instrument file told so
293     const bool bUseFilter = RgnInfo.VCFEnabled;
294     #endif // CONFIG_FORCE_FILTER
295     SYNTHESIS_MODE_SET_FILTER(SynthesisMode, bUseFilter);
296     if (bUseFilter) {
297     #ifdef CONFIG_OVERRIDE_CUTOFF_CTRL
298     VCFCutoffCtrl.controller = CONFIG_OVERRIDE_CUTOFF_CTRL;
299     #else // use the one defined in the instrument file
300     VCFCutoffCtrl.controller = GetVCFCutoffCtrl();
301     #endif // CONFIG_OVERRIDE_CUTOFF_CTRL
302    
303     #ifdef CONFIG_OVERRIDE_RESONANCE_CTRL
304     VCFResonanceCtrl.controller = CONFIG_OVERRIDE_RESONANCE_CTRL;
305     #else // use the one defined in the instrument file
306     VCFResonanceCtrl.controller = GetVCFResonanceCtrl();
307     #endif // CONFIG_OVERRIDE_RESONANCE_CTRL
308    
309     #ifndef CONFIG_OVERRIDE_FILTER_TYPE
310     finalSynthesisParameters.filterLeft.SetType(RgnInfo.VCFType);
311     finalSynthesisParameters.filterRight.SetType(RgnInfo.VCFType);
312     #else // override filter type
313     finalSynthesisParameters.filterLeft.SetType(CONFIG_OVERRIDE_FILTER_TYPE);
314     finalSynthesisParameters.filterRight.SetType(CONFIG_OVERRIDE_FILTER_TYPE);
315     #endif // CONFIG_OVERRIDE_FILTER_TYPE
316    
317     VCFCutoffCtrl.value = pEngineChannel->ControllerTable[VCFCutoffCtrl.controller];
318     VCFResonanceCtrl.value = pEngineChannel->ControllerTable[VCFResonanceCtrl.controller];
319    
320     // calculate cutoff frequency
321     CutoffBase = CalculateCutoffBase(itNoteOnEvent->Param.Note.Velocity);
322    
323     VCFCutoffCtrl.fvalue = CalculateFinalCutoff(CutoffBase);
324    
325     // calculate resonance
326     float resonance = (float) (VCFResonanceCtrl.controller ? VCFResonanceCtrl.value : RgnInfo.VCFResonance);
327     VCFResonanceCtrl.fvalue = resonance;
328     } else {
329     VCFCutoffCtrl.controller = 0;
330     VCFResonanceCtrl.controller = 0;
331     }
332 iliev 2299
333     const bool bEq =
334     pSignalUnitRack != NULL && pSignalUnitRack->HasEq() && pEq->HasSupport();
335 iliev 2015
336 iliev 2299 if (bEq) {
337     pEq->GetInChannelLeft()->Clear();
338     pEq->GetInChannelRight()->Clear();
339     pEq->RenderAudio(GetEngine()->pAudioOutputDevice->MaxSamplesPerCycle());
340     }
341    
342 iliev 2015 return 0; // success
343     }
344 iliev 2216
345     void AbstractVoice::SetSampleStartOffset() {
346     finalSynthesisParameters.dPos = RgnInfo.SampleStartOffset; // offset where we should start playback of sample (0 - 2000 sample points)
347     Pos = RgnInfo.SampleStartOffset;
348     }
349 iliev 2015
350     /**
351     * Synthesizes the current audio fragment for this voice.
352     *
353     * @param Samples - number of sample points to be rendered in this audio
354     * fragment cycle
355     * @param pSrc - pointer to input sample data
356     * @param Skip - number of sample points to skip in output buffer
357     */
358     void AbstractVoice::Synthesize(uint Samples, sample_t* pSrc, uint Skip) {
359 iliev 2297 bool delay = false; // Whether the voice playback should be delayed for this call
360    
361     if (pSignalUnitRack != NULL) {
362     uint delaySteps = pSignalUnitRack->GetEndpointUnit()->DelayTrigger();
363     if (delaySteps > 0) { // delay on the endpoint unit means delay of the voice playback
364     if (delaySteps >= Samples) {
365     pSignalUnitRack->GetEndpointUnit()->DecreaseDelay(Samples);
366     delay = true;
367     } else {
368     pSignalUnitRack->GetEndpointUnit()->DecreaseDelay(delaySteps);
369     Samples -= delaySteps;
370     Skip += delaySteps;
371     }
372     }
373     }
374    
375 iliev 2015 AbstractEngineChannel* pChannel = pEngineChannel;
376 schoenebeck 2879 MidiKeyBase* pMidiKeyInfo = GetMidiKeyInfo(MIDIKey());
377 iliev 2015
378 schoenebeck 2121 const bool bVoiceRequiresDedicatedRouting =
379     pEngineChannel->GetFxSendCount() > 0 &&
380     (pMidiKeyInfo->ReverbSend || pMidiKeyInfo->ChorusSend);
381 iliev 2296
382     const bool bEq =
383 iliev 2298 pSignalUnitRack != NULL && pSignalUnitRack->HasEq() && pEq->HasSupport();
384 schoenebeck 2121
385 iliev 2296 if (bEq) {
386 iliev 2298 pEq->GetInChannelLeft()->Clear();
387     pEq->GetInChannelRight()->Clear();
388     finalSynthesisParameters.pOutLeft = &pEq->GetInChannelLeft()->Buffer()[Skip];
389     finalSynthesisParameters.pOutRight = &pEq->GetInChannelRight()->Buffer()[Skip];
390     pSignalUnitRack->UpdateEqSettings(pEq);
391 iliev 2296 } else if (bVoiceRequiresDedicatedRouting) {
392 schoenebeck 2121 finalSynthesisParameters.pOutLeft = &GetEngine()->pDedicatedVoiceChannelLeft->Buffer()[Skip];
393     finalSynthesisParameters.pOutRight = &GetEngine()->pDedicatedVoiceChannelRight->Buffer()[Skip];
394     } else {
395     finalSynthesisParameters.pOutLeft = &pChannel->pChannelLeft->Buffer()[Skip];
396     finalSynthesisParameters.pOutRight = &pChannel->pChannelRight->Buffer()[Skip];
397     }
398     finalSynthesisParameters.pSrc = pSrc;
399    
400 iliev 2015 RTList<Event>::Iterator itCCEvent = pChannel->pEvents->first();
401     RTList<Event>::Iterator itNoteEvent;
402 schoenebeck 2879 GetFirstEventOnKey(HostKey(), itNoteEvent);
403 iliev 2015
404 persson 2114 RTList<Event>::Iterator itGroupEvent;
405 persson 2352 if (pGroupEvents && !Orphan) itGroupEvent = pGroupEvents->first();
406 persson 2114
407 iliev 2015 if (itTriggerEvent) { // skip events that happened before this voice was triggered
408     while (itCCEvent && itCCEvent->FragmentPos() <= Skip) ++itCCEvent;
409 persson 2114 while (itGroupEvent && itGroupEvent->FragmentPos() <= Skip) ++itGroupEvent;
410    
411 iliev 2015 // we can't simply compare the timestamp here, because note events
412     // might happen on the same time stamp, so we have to deal on the
413     // actual sequence the note events arrived instead (see bug #112)
414     for (; itNoteEvent; ++itNoteEvent) {
415     if (itTriggerEvent == itNoteEvent) {
416     ++itNoteEvent;
417     break;
418     }
419     }
420     }
421    
422     uint killPos;
423     if (itKillEvent) {
424     int maxFadeOutPos = Samples - GetEngine()->GetMinFadeOutSamples();
425     if (maxFadeOutPos < 0) {
426     // There's not enough space in buffer to do a fade out
427     // from max volume (this can only happen for audio
428     // drivers that use Samples < MaxSamplesPerCycle).
429     // End the EG1 here, at pos 0, with a shorter max fade
430     // out time.
431 iliev 2217 if (pSignalUnitRack == NULL) {
432 iliev 2205 pEG1->enterFadeOutStage(Samples / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
433     } else {
434 persson 2327 pSignalUnitRack->EnterFadeOutStage(Samples / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
435 iliev 2205 }
436 iliev 2015 itKillEvent = Pool<Event>::Iterator();
437     } else {
438     killPos = RTMath::Min(itKillEvent->FragmentPos(), maxFadeOutPos);
439     }
440     }
441    
442     uint i = Skip;
443     while (i < Samples) {
444     int iSubFragmentEnd = RTMath::Min(i + CONFIG_DEFAULT_SUBFRAGMENT_SIZE, Samples);
445    
446     // initialize all final synthesis parameters
447     fFinalCutoff = VCFCutoffCtrl.fvalue;
448     fFinalResonance = VCFResonanceCtrl.fvalue;
449    
450 schoenebeck 2559 // process MIDI control change, aftertouch and pitchbend events for this subfragment
451 iliev 2015 processCCEvents(itCCEvent, iSubFragmentEnd);
452 iliev 2219 uint8_t pan = MIDIPan;
453 persson 2382 if (pSignalUnitRack != NULL) pan = pSignalUnitRack->GetEndpointUnit()->CalculatePan(MIDIPan);
454 iliev 2015
455 schoenebeck 2931 PanLeftSmoother.update(AbstractEngine::PanCurve[128 - pan] * NotePanLeft);
456     PanRightSmoother.update(AbstractEngine::PanCurve[pan] * NotePanRight);
457    
458     finalSynthesisParameters.fFinalPitch = Pitch.PitchBase * Pitch.PitchBend * NotePitch;
459    
460     float fFinalVolume = VolumeSmoother.render() * CrossfadeSmoother.render() * NoteVolumeSmoother.render();
461 iliev 2015 #ifdef CONFIG_PROCESS_MUTED_CHANNELS
462     if (pChannel->GetMute()) fFinalVolume = 0;
463     #endif
464    
465     // process transition events (note on, note off & sustain pedal)
466     processTransitionEvents(itNoteEvent, iSubFragmentEnd);
467 persson 2114 processGroupEvents(itGroupEvent, iSubFragmentEnd);
468 iliev 2297
469 iliev 2217 if (pSignalUnitRack == NULL) {
470 iliev 2205 // if the voice was killed in this subfragment, or if the
471     // filter EG is finished, switch EG1 to fade out stage
472     if ((itKillEvent && killPos <= iSubFragmentEnd) ||
473     (SYNTHESIS_MODE_GET_FILTER(SynthesisMode) &&
474     pEG2->getSegmentType() == EG::segment_end)) {
475     pEG1->enterFadeOutStage();
476     itKillEvent = Pool<Event>::Iterator();
477     }
478 iliev 2015
479 iliev 2205 // process envelope generators
480     switch (pEG1->getSegmentType()) {
481     case EG::segment_lin:
482     fFinalVolume *= pEG1->processLin();
483     break;
484     case EG::segment_exp:
485     fFinalVolume *= pEG1->processExp();
486     break;
487     case EG::segment_end:
488     fFinalVolume *= pEG1->getLevel();
489     break; // noop
490     case EG::segment_pow:
491     fFinalVolume *= pEG1->processPow();
492     break;
493     }
494     switch (pEG2->getSegmentType()) {
495     case EG::segment_lin:
496     fFinalCutoff *= pEG2->processLin();
497     break;
498     case EG::segment_exp:
499     fFinalCutoff *= pEG2->processExp();
500     break;
501     case EG::segment_end:
502     fFinalCutoff *= pEG2->getLevel();
503     break; // noop
504     case EG::segment_pow:
505     fFinalCutoff *= pEG2->processPow();
506     break;
507     }
508     if (EG3.active()) finalSynthesisParameters.fFinalPitch *= EG3.render();
509 iliev 2015
510 iliev 2205 // process low frequency oscillators
511     if (bLFO1Enabled) fFinalVolume *= (1.0f - pLFO1->render());
512 persson 2673 if (bLFO2Enabled) fFinalCutoff *= (1.0f - pLFO2->render());
513 iliev 2205 if (bLFO3Enabled) finalSynthesisParameters.fFinalPitch *= RTMath::CentsToFreqRatio(pLFO3->render());
514     } else {
515 iliev 2322 // if the voice was killed in this subfragment, enter fade out stage
516     if (itKillEvent && killPos <= iSubFragmentEnd) {
517     pSignalUnitRack->EnterFadeOutStage();
518     itKillEvent = Pool<Event>::Iterator();
519     }
520    
521     // if the filter EG is finished, switch EG1 to fade out stage
522     /*if (SYNTHESIS_MODE_GET_FILTER(SynthesisMode) &&
523     pEG2->getSegmentType() == EG::segment_end) {
524 iliev 2205 pEG1->enterFadeOutStage();
525     itKillEvent = Pool<Event>::Iterator();
526     }*/
527     // TODO: ^^^
528 iliev 2015
529 iliev 2217 fFinalVolume *= pSignalUnitRack->GetEndpointUnit()->GetVolume();
530     fFinalCutoff = pSignalUnitRack->GetEndpointUnit()->CalculateFilterCutoff(fFinalCutoff);
531     fFinalResonance = pSignalUnitRack->GetEndpointUnit()->CalculateResonance(fFinalResonance);
532 iliev 2205
533     finalSynthesisParameters.fFinalPitch =
534 iliev 2217 pSignalUnitRack->GetEndpointUnit()->CalculatePitch(finalSynthesisParameters.fFinalPitch);
535 iliev 2205
536     }
537 schoenebeck 2935
538     fFinalCutoff *= NoteCutoff;
539     fFinalResonance *= NoteResonance;
540    
541 iliev 2015 // limit the pitch so we don't read outside the buffer
542     finalSynthesisParameters.fFinalPitch = RTMath::Min(finalSynthesisParameters.fFinalPitch, float(1 << CONFIG_MAX_PITCH));
543    
544     // if filter enabled then update filter coefficients
545     if (SYNTHESIS_MODE_GET_FILTER(SynthesisMode)) {
546     finalSynthesisParameters.filterLeft.SetParameters(fFinalCutoff, fFinalResonance, GetEngine()->SampleRate);
547     finalSynthesisParameters.filterRight.SetParameters(fFinalCutoff, fFinalResonance, GetEngine()->SampleRate);
548     }
549    
550     // do we need resampling?
551     const float __PLUS_ONE_CENT = 1.000577789506554859250142541782224725466f;
552     const float __MINUS_ONE_CENT = 0.9994225441413807496009516495583113737666f;
553     const bool bResamplingRequired = !(finalSynthesisParameters.fFinalPitch <= __PLUS_ONE_CENT &&
554     finalSynthesisParameters.fFinalPitch >= __MINUS_ONE_CENT);
555     SYNTHESIS_MODE_SET_INTERPOLATE(SynthesisMode, bResamplingRequired);
556    
557     // prepare final synthesis parameters structure
558     finalSynthesisParameters.uiToGo = iSubFragmentEnd - i;
559     #ifdef CONFIG_INTERPOLATE_VOLUME
560     finalSynthesisParameters.fFinalVolumeDeltaLeft =
561     (fFinalVolume * VolumeLeft * PanLeftSmoother.render() -
562     finalSynthesisParameters.fFinalVolumeLeft) / finalSynthesisParameters.uiToGo;
563     finalSynthesisParameters.fFinalVolumeDeltaRight =
564     (fFinalVolume * VolumeRight * PanRightSmoother.render() -
565     finalSynthesisParameters.fFinalVolumeRight) / finalSynthesisParameters.uiToGo;
566     #else
567     finalSynthesisParameters.fFinalVolumeLeft =
568     fFinalVolume * VolumeLeft * PanLeftSmoother.render();
569     finalSynthesisParameters.fFinalVolumeRight =
570     fFinalVolume * VolumeRight * PanRightSmoother.render();
571     #endif
572     // render audio for one subfragment
573 iliev 2297 if (!delay) RunSynthesisFunction(SynthesisMode, &finalSynthesisParameters, &loop);
574 iliev 2015
575 iliev 2217 if (pSignalUnitRack == NULL) {
576 iliev 2205 // stop the rendering if volume EG is finished
577     if (pEG1->getSegmentType() == EG::segment_end) break;
578     } else {
579     // stop the rendering if the endpoint unit is not active
580 iliev 2217 if (!pSignalUnitRack->GetEndpointUnit()->Active()) break;
581 iliev 2205 }
582 iliev 2015
583     const double newPos = Pos + (iSubFragmentEnd - i) * finalSynthesisParameters.fFinalPitch;
584    
585 iliev 2217 if (pSignalUnitRack == NULL) {
586 iliev 2205 // increment envelopes' positions
587     if (pEG1->active()) {
588 iliev 2015
589 iliev 2205 // if sample has a loop and loop start has been reached in this subfragment, send a special event to EG1 to let it finish the attack hold stage
590     if (SmplInfo.HasLoops && Pos <= SmplInfo.LoopStart && SmplInfo.LoopStart < newPos) {
591     pEG1->update(EG::event_hold_end, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
592     }
593    
594     pEG1->increment(1);
595     if (!pEG1->toStageEndLeft()) pEG1->update(EG::event_stage_end, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
596 iliev 2015 }
597 iliev 2205 if (pEG2->active()) {
598     pEG2->increment(1);
599     if (!pEG2->toStageEndLeft()) pEG2->update(EG::event_stage_end, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
600     }
601     EG3.increment(1);
602     if (!EG3.toEndLeft()) EG3.update(); // neutralize envelope coefficient if end reached
603     } else {
604     // if sample has a loop and loop start has been reached in this subfragment, send a special event to EG1 to let it finish the attack hold stage
605     /*if (SmplInfo.HasLoops && Pos <= SmplInfo.LoopStart && SmplInfo.LoopStart < newPos) {
606     pEG1->update(EG::event_hold_end, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
607     }*/
608     // TODO: ^^^
609    
610 iliev 2297 if (!delay) pSignalUnitRack->Increment();
611 iliev 2015 }
612    
613     Pos = newPos;
614     i = iSubFragmentEnd;
615     }
616 iliev 2297
617     if (delay) return;
618 schoenebeck 2121
619     if (bVoiceRequiresDedicatedRouting) {
620 iliev 2296 if (bEq) {
621 iliev 2298 pEq->RenderAudio(Samples);
622     pEq->GetOutChannelLeft()->CopyTo(GetEngine()->pDedicatedVoiceChannelLeft, Samples);
623     pEq->GetOutChannelRight()->CopyTo(GetEngine()->pDedicatedVoiceChannelRight, Samples);
624 iliev 2296 }
625 schoenebeck 2121 optional<float> effectSendLevels[2] = {
626     pMidiKeyInfo->ReverbSend,
627     pMidiKeyInfo->ChorusSend
628     };
629     GetEngine()->RouteDedicatedVoiceChannels(pEngineChannel, effectSendLevels, Samples);
630 iliev 2296 } else if (bEq) {
631 iliev 2298 pEq->RenderAudio(Samples);
632     pEq->GetOutChannelLeft()->MixTo(pChannel->pChannelLeft, Samples);
633     pEq->GetOutChannelRight()->MixTo(pChannel->pChannelRight, Samples);
634 schoenebeck 2121 }
635 iliev 2015 }
636 persson 2045
637 iliev 2015 /**
638 schoenebeck 2559 * Process given list of MIDI control change, aftertouch and pitch bend
639     * events for the given time.
640 iliev 2015 *
641     * @param itEvent - iterator pointing to the next event to be processed
642     * @param End - youngest time stamp where processing should be stopped
643     */
644     void AbstractVoice::processCCEvents(RTList<Event>::Iterator& itEvent, uint End) {
645     for (; itEvent && itEvent->FragmentPos() <= End; ++itEvent) {
646 schoenebeck 3016 if ((itEvent->Type == Event::type_control_change || itEvent->Type == Event::type_channel_pressure)
647     && itEvent->Param.CC.Controller) { // if (valid) MIDI control change event
648 iliev 2015 if (itEvent->Param.CC.Controller == VCFCutoffCtrl.controller) {
649     ProcessCutoffEvent(itEvent);
650     }
651     if (itEvent->Param.CC.Controller == VCFResonanceCtrl.controller) {
652     processResonanceEvent(itEvent);
653     }
654 iliev 2217 if (pSignalUnitRack == NULL) {
655 iliev 2205 if (itEvent->Param.CC.Controller == pLFO1->ExtController) {
656     pLFO1->update(itEvent->Param.CC.Value);
657     }
658     if (itEvent->Param.CC.Controller == pLFO2->ExtController) {
659     pLFO2->update(itEvent->Param.CC.Value);
660     }
661     if (itEvent->Param.CC.Controller == pLFO3->ExtController) {
662     pLFO3->update(itEvent->Param.CC.Value);
663     }
664 iliev 2015 }
665     if (itEvent->Param.CC.Controller == 7) { // volume
666     VolumeSmoother.update(AbstractEngine::VolumeCurve[itEvent->Param.CC.Value]);
667     } else if (itEvent->Param.CC.Controller == 10) { // panpot
668 persson 2382 MIDIPan = CalculatePan(itEvent->Param.CC.Value);
669 iliev 2015 }
670     } else if (itEvent->Type == Event::type_pitchbend) { // if pitch bend event
671     processPitchEvent(itEvent);
672 schoenebeck 2559 } else if (itEvent->Type == Event::type_channel_pressure) {
673     ProcessChannelPressureEvent(itEvent);
674     } else if (itEvent->Type == Event::type_note_pressure) {
675     ProcessPolyphonicKeyPressureEvent(itEvent);
676 iliev 2015 }
677    
678     ProcessCCEvent(itEvent);
679 iliev 2217 if (pSignalUnitRack != NULL) {
680     pSignalUnitRack->ProcessCCEvent(itEvent);
681 iliev 2205 }
682 iliev 2015 }
683     }
684    
685     void AbstractVoice::processPitchEvent(RTList<Event>::Iterator& itEvent) {
686     Pitch.PitchBend = RTMath::CentsToFreqRatio(itEvent->Param.Pitch.Pitch * Pitch.PitchBendRange);
687     }
688    
689     void AbstractVoice::processResonanceEvent(RTList<Event>::Iterator& itEvent) {
690     // convert absolute controller value to differential
691     const int ctrldelta = itEvent->Param.CC.Value - VCFResonanceCtrl.value;
692     VCFResonanceCtrl.value = itEvent->Param.CC.Value;
693     const float resonancedelta = (float) ctrldelta;
694     fFinalResonance += resonancedelta;
695     // needed for initialization of parameter
696     VCFResonanceCtrl.fvalue = itEvent->Param.CC.Value;
697     }
698    
699     /**
700 schoenebeck 2931 * Process given list of MIDI note on, note off, sustain pedal events and
701     * note synthesis parameter events for the given time.
702 iliev 2015 *
703     * @param itEvent - iterator pointing to the next event to be processed
704     * @param End - youngest time stamp where processing should be stopped
705     */
706     void AbstractVoice::processTransitionEvents(RTList<Event>::Iterator& itEvent, uint End) {
707     for (; itEvent && itEvent->FragmentPos() <= End; ++itEvent) {
708 persson 2115 // some voice types ignore note off
709     if (!(Type & (Voice::type_one_shot | Voice::type_release_trigger | Voice::type_controller_triggered))) {
710 schoenebeck 2938 if (itEvent->Type == Event::type_release_key) {
711 persson 2114 EnterReleaseStage();
712 schoenebeck 2938 } else if (itEvent->Type == Event::type_cancel_release_key) {
713 iliev 2217 if (pSignalUnitRack == NULL) {
714 iliev 2205 pEG1->update(EG::event_cancel_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
715     pEG2->update(EG::event_cancel_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
716     } else {
717 iliev 2217 pSignalUnitRack->CancelRelease();
718 iliev 2205 }
719 persson 2114 }
720 iliev 2015 }
721 schoenebeck 2938 // process stop-note events (caused by built-in instrument script function note_off())
722     if (itEvent->Type == Event::type_release_note && pNote &&
723     pEngineChannel->pEngine->NoteByID( itEvent->Param.Note.ID ) == pNote)
724     {
725     EnterReleaseStage();
726     }
727 schoenebeck 2931 // process synthesis parameter events (caused by built-in realt-time instrument script functions)
728     if (itEvent->Type == Event::type_note_synth_param && pNote &&
729     pEngineChannel->pEngine->NoteByID( itEvent->Param.NoteSynthParam.NoteID ) == pNote)
730     {
731     switch (itEvent->Param.NoteSynthParam.Type) {
732     case Event::synth_param_volume:
733     NoteVolumeSmoother.update(itEvent->Param.NoteSynthParam.AbsValue);
734     break;
735     case Event::synth_param_pitch:
736     NotePitch = itEvent->Param.NoteSynthParam.AbsValue;
737     break;
738     case Event::synth_param_pan:
739     NotePanLeft = AbstractEngine::PanCurveValueNorm(itEvent->Param.NoteSynthParam.AbsValue, 0 /*left*/);
740     NotePanRight = AbstractEngine::PanCurveValueNorm(itEvent->Param.NoteSynthParam.AbsValue, 1 /*right*/);
741     break;
742 schoenebeck 2935 case Event::synth_param_cutoff:
743     NoteCutoff = itEvent->Param.NoteSynthParam.AbsValue;
744     break;
745     case Event::synth_param_resonance:
746     NoteResonance = itEvent->Param.NoteSynthParam.AbsValue;
747     break;
748 schoenebeck 2931 }
749     }
750 iliev 2015 }
751     }
752    
753 persson 2114 /**
754     * Process given list of events aimed at all voices in a key group.
755     *
756     * @param itEvent - iterator pointing to the next event to be processed
757     * @param End - youngest time stamp where processing should be stopped
758     */
759     void AbstractVoice::processGroupEvents(RTList<Event>::Iterator& itEvent, uint End) {
760     for (; itEvent && itEvent->FragmentPos() <= End; ++itEvent) {
761     ProcessGroupEvent(itEvent);
762     }
763     }
764    
765 iliev 2015 /** @brief Update current portamento position.
766     *
767     * Will be called when portamento mode is enabled to get the final
768     * portamento position of this active voice from where the next voice(s)
769     * might continue to slide on.
770     *
771     * @param itNoteOffEvent - event which causes this voice to die soon
772     */
773     void AbstractVoice::UpdatePortamentoPos(Pool<Event>::Iterator& itNoteOffEvent) {
774 iliev 2217 if (pSignalUnitRack == NULL) {
775 iliev 2205 const float fFinalEG3Level = EG3.level(itNoteOffEvent->FragmentPos());
776 schoenebeck 2879 pEngineChannel->PortamentoPos = (float) MIDIKey() + RTMath::FreqRatioToCents(fFinalEG3Level) * 0.01f;
777 iliev 2205 } else {
778     // TODO:
779     }
780 iliev 2015 }
781    
782     /**
783     * Kill the voice in regular sense. Let the voice render audio until
784     * the kill event actually occured and then fade down the volume level
785     * very quickly and let the voice die finally. Unlike a normal release
786     * of a voice, a kill process cannot be cancalled and is therefore
787     * usually used for voice stealing and key group conflicts.
788     *
789     * @param itKillEvent - event which caused the voice to be killed
790     */
791     void AbstractVoice::Kill(Pool<Event>::Iterator& itKillEvent) {
792     #if CONFIG_DEVMODE
793     if (!itKillEvent) dmsg(1,("AbstractVoice::Kill(): ERROR, !itKillEvent !!!\n"));
794     if (itKillEvent && !itKillEvent.isValid()) dmsg(1,("AbstractVoice::Kill(): ERROR, itKillEvent invalid !!!\n"));
795     #endif // CONFIG_DEVMODE
796    
797     if (itTriggerEvent && itKillEvent->FragmentPos() <= itTriggerEvent->FragmentPos()) return;
798     this->itKillEvent = itKillEvent;
799     }
800    
801     Voice::PitchInfo AbstractVoice::CalculatePitchInfo(int PitchBend) {
802     PitchInfo pitch;
803 schoenebeck 2879 double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey() % 12];
804 iliev 2015
805     // GSt behaviour: maximum transpose up is 40 semitones. If
806     // MIDI key is more than 40 semitones above unity note,
807     // the transpose is not done.
808 schoenebeck 2879 if (!SmplInfo.Unpitched && (MIDIKey() - (int) RgnInfo.UnityNote) < 40) pitchbasecents += (MIDIKey() - (int) RgnInfo.UnityNote) * 100;
809 iliev 2015
810     pitch.PitchBase = RTMath::CentsToFreqRatioUnlimited(pitchbasecents) * (double(SmplInfo.SampleRate) / double(GetEngine()->SampleRate));
811     pitch.PitchBendRange = 1.0 / 8192.0 * 100.0 * InstrInfo.PitchbendRange;
812     pitch.PitchBend = RTMath::CentsToFreqRatio(PitchBend * pitch.PitchBendRange);
813    
814     return pitch;
815     }
816 schoenebeck 2448
817     void AbstractVoice::onScaleTuningChanged() {
818     PitchInfo pitch = this->Pitch;
819 schoenebeck 2879 double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey() % 12];
820 schoenebeck 2448
821     // GSt behaviour: maximum transpose up is 40 semitones. If
822     // MIDI key is more than 40 semitones above unity note,
823     // the transpose is not done.
824 schoenebeck 2879 if (!SmplInfo.Unpitched && (MIDIKey() - (int) RgnInfo.UnityNote) < 40) pitchbasecents += (MIDIKey() - (int) RgnInfo.UnityNote) * 100;
825 schoenebeck 2448
826     pitch.PitchBase = RTMath::CentsToFreqRatioUnlimited(pitchbasecents) * (double(SmplInfo.SampleRate) / double(GetEngine()->SampleRate));
827     this->Pitch = pitch;
828     }
829 iliev 2015
830     double AbstractVoice::CalculateVolume(double velocityAttenuation) {
831     // For 16 bit samples, we downscale by 32768 to convert from
832     // int16 value range to DSP value range (which is
833     // -1.0..1.0). For 24 bit, we downscale from int32.
834     float volume = velocityAttenuation / (SmplInfo.BitDepth == 16 ? 32768.0f : 32768.0f * 65536.0f);
835    
836     volume *= GetSampleAttenuation() * pEngineChannel->GlobalVolume * GLOBAL_VOLUME;
837    
838     // the volume of release triggered samples depends on note length
839 persson 2115 if (Type & Voice::type_release_trigger) {
840 iliev 2015 float noteLength = float(GetEngine()->FrameTime + Delay -
841 schoenebeck 2879 GetNoteOnTime(MIDIKey()) ) / GetEngine()->SampleRate;
842 iliev 2015
843 persson 2061 volume *= GetReleaseTriggerAttenuation(noteLength);
844 iliev 2015 }
845    
846     return volume;
847     }
848 persson 2061
849     float AbstractVoice::GetReleaseTriggerAttenuation(float noteLength) {
850     return 1 - RgnInfo.ReleaseTriggerDecay * noteLength;
851     }
852 persson 2114
853     void AbstractVoice::EnterReleaseStage() {
854 iliev 2217 if (pSignalUnitRack == NULL) {
855 iliev 2205 pEG1->update(EG::event_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
856     pEG2->update(EG::event_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
857     } else {
858 iliev 2217 pSignalUnitRack->EnterReleaseStage();
859 iliev 2205 }
860 persson 2114 }
861    
862 iliev 2205 bool AbstractVoice::EG1Finished() {
863 iliev 2217 if (pSignalUnitRack == NULL) {
864 iliev 2205 return pEG1->getSegmentType() == EG::segment_end;
865     } else {
866 iliev 2217 return !pSignalUnitRack->GetEndpointUnit()->Active();
867 iliev 2205 }
868     }
869    
870 iliev 2015 } // namespace LinuxSampler

  ViewVC Help
Powered by ViewVC