/[svn]/linuxsampler/trunk/src/engines/gig/Voice.cpp
ViewVC logotype

Diff of /linuxsampler/trunk/src/engines/gig/Voice.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 877 by persson, Sun Jun 25 13:54:17 2006 UTC revision 1748 by persson, Sun Jun 22 14:46:46 2008 UTC
# Line 3  Line 3 
3   *   LinuxSampler - modular, streaming capable sampler                     *   *   LinuxSampler - modular, streaming capable sampler                     *
4   *                                                                         *   *                                                                         *
5   *   Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck   *   *   Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck   *
6   *   Copyright (C) 2005, 2006 Christian Schoenebeck                        *   *   Copyright (C) 2005 - 2007 Christian Schoenebeck                       *
7   *                                                                         *   *                                                                         *
8   *   This program is free software; you can redistribute it and/or modify  *   *   This program is free software; you can redistribute it and/or modify  *
9   *   it under the terms of the GNU General Public License as published by  *   *   it under the terms of the GNU General Public License as published by  *
# Line 38  namespace LinuxSampler { namespace gig { Line 38  namespace LinuxSampler { namespace gig {
38          pLFO3 = new LFOSigned(1200.0f); // pitch EG (-1200..+1200 range)          pLFO3 = new LFOSigned(1200.0f); // pitch EG (-1200..+1200 range)
39          KeyGroup = 0;          KeyGroup = 0;
40          SynthesisMode = 0; // set all mode bits to 0 first          SynthesisMode = 0; // set all mode bits to 0 first
41          // select synthesis implementation (currently either pure C++ or MMX+SSE(1))          // select synthesis implementation (asm core is not supported ATM)
42          #if CONFIG_ASM && ARCH_X86          #if 0 // CONFIG_ASM && ARCH_X86
43          SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, Features::supportsMMX() && Features::supportsSSE());          SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, Features::supportsMMX() && Features::supportsSSE());
44          #else          #else
45          SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, false);          SYNTHESIS_MODE_SET_IMPLEMENTATION(SynthesisMode, false);
# Line 79  namespace LinuxSampler { namespace gig { Line 79  namespace LinuxSampler { namespace gig {
79      int Voice::Trigger(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent, int PitchBend, ::gig::DimensionRegion* pDimRgn, type_t VoiceType, int iKeyGroup) {      int Voice::Trigger(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent, int PitchBend, ::gig::DimensionRegion* pDimRgn, type_t VoiceType, int iKeyGroup) {
80          this->pEngineChannel = pEngineChannel;          this->pEngineChannel = pEngineChannel;
81          this->pDimRgn        = pDimRgn;          this->pDimRgn        = pDimRgn;
82            Orphan = false;
83    
84          #if CONFIG_DEVMODE          #if CONFIG_DEVMODE
85          if (itNoteOnEvent->FragmentPos() > pEngine->MaxSamplesPerCycle) { // just a sanity check for debugging          if (itNoteOnEvent->FragmentPos() > pEngine->MaxSamplesPerCycle) { // just a sanity check for debugging
# Line 98  namespace LinuxSampler { namespace gig { Line 99  namespace LinuxSampler { namespace gig {
99          // calculate volume          // calculate volume
100          const double velocityAttenuation = pDimRgn->GetVelocityAttenuation(itNoteOnEvent->Param.Note.Velocity);          const double velocityAttenuation = pDimRgn->GetVelocityAttenuation(itNoteOnEvent->Param.Note.Velocity);
101    
102          float volume = velocityAttenuation / 32768.0f; // we downscale by 32768 to convert from int16 value range to DSP value range (which is -1.0..1.0)          // For 16 bit samples, we downscale by 32768 to convert from
103            // int16 value range to DSP value range (which is
104            // -1.0..1.0). For 24 bit, we downscale from int32.
105            float volume = velocityAttenuation / (pSample->BitDepth == 16 ? 32768.0f : 32768.0f * 65536.0f);
106    
107          volume *= pDimRgn->SampleAttenuation;          volume *= pDimRgn->SampleAttenuation * pEngineChannel->GlobalVolume * GLOBAL_VOLUME;
108    
109          // the volume of release triggered samples depends on note length          // the volume of release triggered samples depends on note length
110          if (Type == type_release_trigger) {          if (Type == type_release_trigger) {
# Line 113  namespace LinuxSampler { namespace gig { Line 117  namespace LinuxSampler { namespace gig {
117    
118          // select channel mode (mono or stereo)          // select channel mode (mono or stereo)
119          SYNTHESIS_MODE_SET_CHANNELS(SynthesisMode, pSample->Channels == 2);          SYNTHESIS_MODE_SET_CHANNELS(SynthesisMode, pSample->Channels == 2);
120            // select bit depth (16 or 24)
121            SYNTHESIS_MODE_SET_BITDEPTH24(SynthesisMode, pSample->BitDepth == 24);
122    
123          // get starting crossfade volume level          // get starting crossfade volume level
124          float crossfadeVolume;          float crossfadeVolume;
125          switch (pDimRgn->AttenuationController.type) {          switch (pDimRgn->AttenuationController.type) {
126              case ::gig::attenuation_ctrl_t::type_channelaftertouch:              case ::gig::attenuation_ctrl_t::type_channelaftertouch:
127                  crossfadeVolume = 1.0f; //TODO: aftertouch not supported yet                  crossfadeVolume = Engine::CrossfadeCurve[CrossfadeAttenuation(pEngineChannel->ControllerTable[128])];
128                  break;                  break;
129              case ::gig::attenuation_ctrl_t::type_velocity:              case ::gig::attenuation_ctrl_t::type_velocity:
130                  crossfadeVolume = Engine::CrossfadeCurve[CrossfadeAttenuation(itNoteOnEvent->Param.Note.Velocity)];                  crossfadeVolume = Engine::CrossfadeCurve[CrossfadeAttenuation(itNoteOnEvent->Param.Note.Velocity)];
# Line 136  namespace LinuxSampler { namespace gig { Line 142  namespace LinuxSampler { namespace gig {
142    
143          float subfragmentRate = pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE;          float subfragmentRate = pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE;
144          CrossfadeSmoother.trigger(crossfadeVolume, subfragmentRate);          CrossfadeSmoother.trigger(crossfadeVolume, subfragmentRate);
145          VolumeSmoother.trigger(pEngineChannel->GlobalVolume, subfragmentRate);          VolumeSmoother.trigger(pEngineChannel->MidiVolume, subfragmentRate);
146          PanLeftSmoother.trigger(pEngineChannel->GlobalPanLeft, subfragmentRate);          PanLeftSmoother.trigger(pEngineChannel->GlobalPanLeft, subfragmentRate);
147          PanRightSmoother.trigger(pEngineChannel->GlobalPanRight, subfragmentRate);          PanRightSmoother.trigger(pEngineChannel->GlobalPanRight, subfragmentRate);
148    
# Line 178  namespace LinuxSampler { namespace gig { Line 184  namespace LinuxSampler { namespace gig {
184          // calculate initial pitch value          // calculate initial pitch value
185          {          {
186              double pitchbasecents = pDimRgn->FineTune + (int) pEngine->ScaleTuning[MIDIKey % 12];              double pitchbasecents = pDimRgn->FineTune + (int) pEngine->ScaleTuning[MIDIKey % 12];
187              if (pDimRgn->PitchTrack) pitchbasecents += (MIDIKey - (int) pDimRgn->UnityNote) * 100;  
188                // GSt behaviour: maximum transpose up is 40 semitones. If
189                // MIDI key is more than 40 semitones above unity note,
190                // the transpose is not done.
191                if (pDimRgn->PitchTrack && (MIDIKey - (int) pDimRgn->UnityNote) < 40) pitchbasecents += (MIDIKey - (int) pDimRgn->UnityNote) * 100;
192    
193              this->PitchBase = RTMath::CentsToFreqRatio(pitchbasecents) * (double(pSample->SamplesPerSecond) / double(pEngine->SampleRate));              this->PitchBase = RTMath::CentsToFreqRatio(pitchbasecents) * (double(pSample->SamplesPerSecond) / double(pEngine->SampleRate));
194              this->PitchBend = RTMath::CentsToFreqRatio(((double) PitchBend / 8192.0) * 200.0); // pitchbend wheel +-2 semitones = 200 cents              this->PitchBend = RTMath::CentsToFreqRatio(((double) PitchBend / 8192.0) * 200.0); // pitchbend wheel +-2 semitones = 200 cents
195          }          }
# Line 195  namespace LinuxSampler { namespace gig { Line 206  namespace LinuxSampler { namespace gig {
206                      eg1controllervalue = 0;                      eg1controllervalue = 0;
207                      break;                      break;
208                  case ::gig::eg1_ctrl_t::type_channelaftertouch:                  case ::gig::eg1_ctrl_t::type_channelaftertouch:
209                      eg1controllervalue = 0; // TODO: aftertouch not yet supported                      eg1controllervalue = pEngineChannel->ControllerTable[128];
210                      break;                      break;
211                  case ::gig::eg1_ctrl_t::type_velocity:                  case ::gig::eg1_ctrl_t::type_velocity:
212                      eg1controllervalue = itNoteOnEvent->Param.Note.Velocity;                      eg1controllervalue = itNoteOnEvent->Param.Note.Velocity;
# Line 236  namespace LinuxSampler { namespace gig { Line 247  namespace LinuxSampler { namespace gig {
247          else          else
248  #else  #else
249          {          {
250              float finalVolume = pEngineChannel->GlobalVolume * crossfadeVolume * EG1.getLevel();              float finalVolume = pEngineChannel->MidiVolume * crossfadeVolume * EG1.getLevel();
251    
252              finalSynthesisParameters.fFinalVolumeLeft  = finalVolume * VolumeLeft  * pEngineChannel->GlobalPanLeft;              finalSynthesisParameters.fFinalVolumeLeft  = finalVolume * VolumeLeft  * pEngineChannel->GlobalPanLeft;
253              finalSynthesisParameters.fFinalVolumeRight = finalVolume * VolumeRight * pEngineChannel->GlobalPanRight;              finalSynthesisParameters.fFinalVolumeRight = finalVolume * VolumeRight * pEngineChannel->GlobalPanRight;
# Line 253  namespace LinuxSampler { namespace gig { Line 264  namespace LinuxSampler { namespace gig {
264                      eg2controllervalue = 0;                      eg2controllervalue = 0;
265                      break;                      break;
266                  case ::gig::eg2_ctrl_t::type_channelaftertouch:                  case ::gig::eg2_ctrl_t::type_channelaftertouch:
267                      eg2controllervalue = 0; // TODO: aftertouch not yet supported                      eg2controllervalue = pEngineChannel->ControllerTable[128];
268                      break;                      break;
269                  case ::gig::eg2_ctrl_t::type_velocity:                  case ::gig::eg2_ctrl_t::type_velocity:
270                      eg2controllervalue = itNoteOnEvent->Param.Note.Velocity;                      eg2controllervalue = itNoteOnEvent->Param.Note.Velocity;
# Line 333  namespace LinuxSampler { namespace gig { Line 344  namespace LinuxSampler { namespace gig {
344              }              }
345              if (bLFO1Enabled) {              if (bLFO1Enabled) {
346                  pLFO1->trigger(pDimRgn->LFO1Frequency,                  pLFO1->trigger(pDimRgn->LFO1Frequency,
347                                 start_level_max,                                 start_level_min,
348                                 lfo1_internal_depth,                                 lfo1_internal_depth,
349                                 pDimRgn->LFO1ControlDepth,                                 pDimRgn->LFO1ControlDepth,
350                                 pDimRgn->LFO1FlipPhase,                                 pDimRgn->LFO1FlipPhase,
# Line 405  namespace LinuxSampler { namespace gig { Line 416  namespace LinuxSampler { namespace gig {
416                      break;                      break;
417                  case ::gig::lfo3_ctrl_aftertouch:                  case ::gig::lfo3_ctrl_aftertouch:
418                      lfo3_internal_depth  = 0;                      lfo3_internal_depth  = 0;
419                      pLFO3->ExtController = 0; // TODO: aftertouch not implemented yet                      pLFO3->ExtController = 128;
420                      bLFO3Enabled         = false; // see TODO comment in line above                      bLFO3Enabled         = true;
421                      break;                      break;
422                  case ::gig::lfo3_ctrl_internal_modwheel:                  case ::gig::lfo3_ctrl_internal_modwheel:
423                      lfo3_internal_depth  = pDimRgn->LFO3InternalDepth;                      lfo3_internal_depth  = pDimRgn->LFO3InternalDepth;
# Line 415  namespace LinuxSampler { namespace gig { Line 426  namespace LinuxSampler { namespace gig {
426                      break;                      break;
427                  case ::gig::lfo3_ctrl_internal_aftertouch:                  case ::gig::lfo3_ctrl_internal_aftertouch:
428                      lfo3_internal_depth  = pDimRgn->LFO3InternalDepth;                      lfo3_internal_depth  = pDimRgn->LFO3InternalDepth;
429                      pLFO1->ExtController = 0; // TODO: aftertouch not implemented yet                      pLFO1->ExtController = 128;
430                      bLFO3Enabled         = (lfo3_internal_depth > 0 /*|| pDimRgn->LFO3ControlDepth > 0*/); // see TODO comment in line above                      bLFO3Enabled         = (lfo3_internal_depth > 0 || pDimRgn->LFO3ControlDepth > 0);
431                      break;                      break;
432                  default:                  default:
433                      lfo3_internal_depth  = 0;                      lfo3_internal_depth  = 0;
# Line 473  namespace LinuxSampler { namespace gig { Line 484  namespace LinuxSampler { namespace gig {
484                  case ::gig::vcf_cutoff_ctrl_genpurpose8:                  case ::gig::vcf_cutoff_ctrl_genpurpose8:
485                      VCFCutoffCtrl.controller = 83;                      VCFCutoffCtrl.controller = 83;
486                      break;                      break;
487                  case ::gig::vcf_cutoff_ctrl_aftertouch: //TODO: not implemented yet                  case ::gig::vcf_cutoff_ctrl_aftertouch:
488                        VCFCutoffCtrl.controller = 128;
489                        break;
490                  case ::gig::vcf_cutoff_ctrl_none:                  case ::gig::vcf_cutoff_ctrl_none:
491                  default:                  default:
492                      VCFCutoffCtrl.controller = 0;                      VCFCutoffCtrl.controller = 0;
# Line 507  namespace LinuxSampler { namespace gig { Line 520  namespace LinuxSampler { namespace gig {
520              finalSynthesisParameters.filterLeft.SetType(pDimRgn->VCFType);              finalSynthesisParameters.filterLeft.SetType(pDimRgn->VCFType);
521              finalSynthesisParameters.filterRight.SetType(pDimRgn->VCFType);              finalSynthesisParameters.filterRight.SetType(pDimRgn->VCFType);
522              #else // override filter type              #else // override filter type
523              FilterLeft.SetType(CONFIG_OVERRIDE_FILTER_TYPE);              finalSynthesisParameters.filterLeft.SetType(CONFIG_OVERRIDE_FILTER_TYPE);
524              FilterRight.SetType(CONFIG_OVERRIDE_FILTER_TYPE);              finalSynthesisParameters.filterRight.SetType(CONFIG_OVERRIDE_FILTER_TYPE);
525              #endif // CONFIG_OVERRIDE_FILTER_TYPE              #endif // CONFIG_OVERRIDE_FILTER_TYPE
526    
527              VCFCutoffCtrl.value    = pEngineChannel->ControllerTable[VCFCutoffCtrl.controller];              VCFCutoffCtrl.value    = pEngineChannel->ControllerTable[VCFCutoffCtrl.controller];
# Line 614  namespace LinuxSampler { namespace gig { Line 627  namespace LinuxSampler { namespace gig {
627                          }                          }
628                      }                      }
629    
630                      sample_t* ptr = DiskStreamRef.pStream->GetReadPtr(); // get the current read_ptr within the ringbuffer where we read the samples from                      sample_t* ptr = (sample_t*)DiskStreamRef.pStream->GetReadPtr(); // get the current read_ptr within the ringbuffer where we read the samples from
631    
632                      // render current audio fragment                      // render current audio fragment
633                      Synthesize(Samples, ptr, Delay);                      Synthesize(Samples, ptr, Delay);
# Line 712  namespace LinuxSampler { namespace gig { Line 725  namespace LinuxSampler { namespace gig {
725                      CrossfadeSmoother.update(Engine::CrossfadeCurve[CrossfadeAttenuation(itEvent->Param.CC.Value)]);                      CrossfadeSmoother.update(Engine::CrossfadeCurve[CrossfadeAttenuation(itEvent->Param.CC.Value)]);
726                  }                  }
727                  if (itEvent->Param.CC.Controller == 7) { // volume                  if (itEvent->Param.CC.Controller == 7) { // volume
728                      VolumeSmoother.update(Engine::VolumeCurve[itEvent->Param.CC.Value] * CONFIG_GLOBAL_ATTENUATION);                      VolumeSmoother.update(Engine::VolumeCurve[itEvent->Param.CC.Value]);
729                  } else if (itEvent->Param.CC.Controller == 10) { // panpot                  } else if (itEvent->Param.CC.Controller == 10) { // panpot
730                      PanLeftSmoother.update(Engine::PanCurve[128 - itEvent->Param.CC.Value]);                      PanLeftSmoother.update(Engine::PanCurve[128 - itEvent->Param.CC.Value]);
731                      PanRightSmoother.update(Engine::PanCurve[itEvent->Param.CC.Value]);                      PanRightSmoother.update(Engine::PanCurve[itEvent->Param.CC.Value]);
# Line 761  namespace LinuxSampler { namespace gig { Line 774  namespace LinuxSampler { namespace gig {
774       *  @param Skip    - number of sample points to skip in output buffer       *  @param Skip    - number of sample points to skip in output buffer
775       */       */
776      void Voice::Synthesize(uint Samples, sample_t* pSrc, uint Skip) {      void Voice::Synthesize(uint Samples, sample_t* pSrc, uint Skip) {
777          finalSynthesisParameters.pOutLeft  = &pEngineChannel->pOutputLeft[Skip];          finalSynthesisParameters.pOutLeft  = &pEngineChannel->pChannelLeft->Buffer()[Skip];
778          finalSynthesisParameters.pOutRight = &pEngineChannel->pOutputRight[Skip];          finalSynthesisParameters.pOutRight = &pEngineChannel->pChannelRight->Buffer()[Skip];
779          finalSynthesisParameters.pSrc      = pSrc;          finalSynthesisParameters.pSrc      = pSrc;
780    
781          RTList<Event>::Iterator itCCEvent = pEngineChannel->pEvents->first();          RTList<Event>::Iterator itCCEvent = pEngineChannel->pEvents->first();
782          RTList<Event>::Iterator itNoteEvent = pEngineChannel->pMIDIKeyInfo[MIDIKey].pEvents->first();          RTList<Event>::Iterator itNoteEvent = pEngineChannel->pMIDIKeyInfo[MIDIKey].pEvents->first();
783    
784          if (Skip) { // skip events that happened before this voice was triggered          if (itTriggerEvent) { // skip events that happened before this voice was triggered
785              while (itCCEvent && itCCEvent->FragmentPos() <= Skip) ++itCCEvent;              while (itCCEvent && itCCEvent->FragmentPos() <= Skip) ++itCCEvent;
786              while (itNoteEvent && itNoteEvent->FragmentPos() <= Skip) ++itNoteEvent;              while (itNoteEvent && itNoteEvent->FragmentPos() <= Skip) ++itNoteEvent;
787          }          }
788    
789          uint killPos;          uint killPos;
790          if (itKillEvent) killPos = RTMath::Min(itKillEvent->FragmentPos(), pEngine->MaxFadeOutPos);          if (itKillEvent) {
791                int maxFadeOutPos = Samples - pEngine->MinFadeOutSamples;
792                if (maxFadeOutPos < 0) {
793                    // There's not enough space in buffer to do a fade out
794                    // from max volume (this can only happen for audio
795                    // drivers that use Samples < MaxSamplesPerCycle).
796                    // End the EG1 here, at pos 0, with a shorter max fade
797                    // out time.
798                    EG1.enterFadeOutStage(Samples / CONFIG_DEFAULT_SUBFRAGMENT_SIZE);
799                    itKillEvent = Pool<Event>::Iterator();
800                } else {
801                    killPos = RTMath::Min(itKillEvent->FragmentPos(), maxFadeOutPos);
802                }
803            }
804    
805          uint i = Skip;          uint i = Skip;
806          while (i < Samples) {          while (i < Samples) {
# Line 796  namespace LinuxSampler { namespace gig { Line 822  namespace LinuxSampler { namespace gig {
822              // process transition events (note on, note off & sustain pedal)              // process transition events (note on, note off & sustain pedal)
823              processTransitionEvents(itNoteEvent, iSubFragmentEnd);              processTransitionEvents(itNoteEvent, iSubFragmentEnd);
824    
825              // if the voice was killed in this subfragment switch EG1 to fade out stage              // if the voice was killed in this subfragment, or if the
826              if (itKillEvent && killPos <= iSubFragmentEnd) {              // filter EG is finished, switch EG1 to fade out stage
827                if ((itKillEvent && killPos <= iSubFragmentEnd) ||
828                    (SYNTHESIS_MODE_GET_FILTER(SynthesisMode) &&
829                     EG2.getSegmentType() == EGADSR::segment_end)) {
830                  EG1.enterFadeOutStage();                  EG1.enterFadeOutStage();
831                  itKillEvent = Pool<Event>::Iterator();                  itKillEvent = Pool<Event>::Iterator();
832              }              }
# Line 828  namespace LinuxSampler { namespace gig { Line 857  namespace LinuxSampler { namespace gig {
857              if (EG3.active()) finalSynthesisParameters.fFinalPitch *= EG3.render();              if (EG3.active()) finalSynthesisParameters.fFinalPitch *= EG3.render();
858    
859              // process low frequency oscillators              // process low frequency oscillators
860              if (bLFO1Enabled) fFinalVolume *= pLFO1->render();              if (bLFO1Enabled) fFinalVolume *= (1.0f - pLFO1->render());
861              if (bLFO2Enabled) fFinalCutoff *= pLFO2->render();              if (bLFO2Enabled) fFinalCutoff *= pLFO2->render();
862              if (bLFO3Enabled) finalSynthesisParameters.fFinalPitch *= RTMath::CentsToFreqRatio(pLFO3->render());              if (bLFO3Enabled) finalSynthesisParameters.fFinalPitch *= RTMath::CentsToFreqRatio(pLFO3->render());
863    
# Line 910  namespace LinuxSampler { namespace gig { Line 939  namespace LinuxSampler { namespace gig {
939       *  fading down the volume level to avoid clicks and regular processing       *  fading down the volume level to avoid clicks and regular processing
940       *  until the kill event actually occured!       *  until the kill event actually occured!
941       *       *
942       *  @see Kill()       * If it's necessary to know when the voice's disk stream was actually
943         * deleted, then one can set the optional @a bRequestNotification
944         * parameter and this method will then return the handle of the disk
945         * stream (unique identifier) and one can use this handle to poll the
946         * disk thread if this stream has been deleted. In any case this method
947         * will return immediately and will not block until the stream actually
948         * was deleted.
949         *
950         * @param bRequestNotification - (optional) whether the disk thread shall
951         *                                provide a notification once it deleted
952         *                               the respective disk stream
953         *                               (default=false)
954         * @returns handle to the voice's disk stream or @c Stream::INVALID_HANDLE
955         *          if the voice did not use a disk stream at all
956         * @see Kill()
957       */       */
958      void Voice::KillImmediately() {      Stream::Handle Voice::KillImmediately(bool bRequestNotification) {
959            Stream::Handle hStream = Stream::INVALID_HANDLE;
960          if (DiskVoice && DiskStreamRef.State != Stream::state_unused) {          if (DiskVoice && DiskStreamRef.State != Stream::state_unused) {
961              pDiskThread->OrderDeletionOfStream(&DiskStreamRef);              pDiskThread->OrderDeletionOfStream(&DiskStreamRef, bRequestNotification);
962                hStream = DiskStreamRef.hStream;
963          }          }
964          Reset();          Reset();
965            return hStream;
966      }      }
967    
968      /**      /**

Legend:
Removed from v.877  
changed lines
  Added in v.1748

  ViewVC Help
Powered by ViewVC