/[svn]/linuxsampler/trunk/src/audiothread.cpp
ViewVC logotype

Diff of /linuxsampler/trunk/src/audiothread.cpp

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

revision 10 by senoner, Tue Nov 11 23:30:47 2003 UTC revision 31 by schoenebeck, Sun Jan 18 20:31:31 2004 UTC
# Line 22  Line 22 
22    
23  #include "audiothread.h"  #include "audiothread.h"
24    
25  AudioThread::AudioThread(AudioIO* pAudioIO, DiskThread* pDiskThread, gig::Instrument* pInstrument) : Thread(true, 1, 0) {  AudioThread::AudioThread(AudioIO* pAudioIO, DiskThread* pDiskThread, gig::Instrument* pInstrument) {
26      this->pAudioIO    = pAudioIO;      this->pAudioIO    = pAudioIO;
27      this->pDiskThread = pDiskThread;      this->pDiskThread = pDiskThread;
28      this->pInstrument = pInstrument;      this->pInstrument = pInstrument;
29      pCommandQueue     = new RingBuffer<command_t>(1024);      pCommandQueue     = new RingBuffer<command_t>(1024);
30      pVoices           = new Voice*[MAX_AUDIO_VOICES];      pVoices           = new Voice*[MAX_AUDIO_VOICES];
31      // allocate the ActiveVoicePool (for each midi key there is a variable size linked list      // allocate the ActiveVoicePool (for each midi key there is a variable size linked list
32      // of pointers to Voice classes)      // of pointers to Voice objects)
33      ActiveVoicePool=new RTELMemoryPool<Voice *>(MAX_AUDIO_VOICES);      ActiveVoicePool = new RTELMemoryPool<Voice*>(MAX_AUDIO_VOICES);
34      for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {      for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {
35          pVoices[i] = new Voice(pDiskThread);          pVoices[i] = new Voice(pDiskThread);
36      }      }
37      for (uint i = 0; i < 128; i++) {      for (uint i = 0; i < 128; i++) {
38          pActiveVoices[i] = new RTEList<Voice *>;          pMIDIKeyInfo[i].pActiveVoices    = new RTEList<Voice*>;
39      }          pMIDIKeyInfo[i].hSustainPtr      = NULL;
40            pMIDIKeyInfo[i].Sustained        = false;
41      SustainedKeyPool=new RTELMemoryPool<sustained_key_t>(200);          pMIDIKeyInfo[i].KeyPressed       = false;
42            pMIDIKeyInfo[i].pSustainPoolNode = NULL;
43        }
44        SustainedKeyPool = new RTELMemoryPool<uint>(128);
45      pAudioSumBuffer = new float[pAudioIO->FragmentSize * pAudioIO->Channels];  
46        // FIXME: assuming stereo output
47        pAudioSumBuffer[0] = new float[pAudioIO->MaxSamplesPerCycle() * pAudioIO->Channels()];
48        pAudioSumBuffer[1] = &pAudioSumBuffer[0][pAudioIO->MaxSamplesPerCycle()];
49    
50      // set all voice outputs to the AudioSumBuffer      // set all voice outputs to the AudioSumBuffer
51      for (int i = 0; i < MAX_AUDIO_VOICES; i++) {      for (int i = 0; i < MAX_AUDIO_VOICES; i++) { //FIXME: assuming stereo
52          pVoices[i]->SetOutput(pAudioSumBuffer, pAudioIO->FragmentSize * 2);  //FIXME: assuming stereo          pVoices[i]->SetOutputLeft(pAudioSumBuffer[0],  pAudioIO->MaxSamplesPerCycle());
53            pVoices[i]->SetOutputRight(pAudioSumBuffer[1], pAudioIO->MaxSamplesPerCycle());
54      }      }
55    
56      // cache initial samples points (for actually needed samples)      // cache initial samples points (for actually needed samples)
57      dmsg(("Caching initial samples..."));      dmsg(1,("Caching initial samples..."));
58      gig::Region* pRgn = this->pInstrument->GetFirstRegion();      gig::Region* pRgn = this->pInstrument->GetFirstRegion();
59      while (pRgn) {      while (pRgn) {
60          if (!pRgn->GetSample()->GetCache().Size) {          if (!pRgn->GetSample()->GetCache().Size) {
61  //printf("C");              dmsg(2,("C"));
62              CacheInitialSamples(pRgn->GetSample());              CacheInitialSamples(pRgn->GetSample());
63          }          }
64          for (uint i = 0; i < pRgn->DimensionRegions; i++) {          for (uint i = 0; i < pRgn->DimensionRegions; i++) {
# Line 64  AudioThread::AudioThread(AudioIO* pAudio Line 68  AudioThread::AudioThread(AudioIO* pAudio
68          pRgn = this->pInstrument->GetNextRegion();          pRgn = this->pInstrument->GetNextRegion();
69      }      }
70    
71        // initialize modulation system
72        ModulationSystem::Initialize(pAudioIO->SampleRate(), pAudioIO->MaxSamplesPerCycle());
73    
74      // sustain pedal value      // sustain pedal value
75      PrevHoldCCValue=0;      PrevHoldCCValue = 0;
76      SustainPedal=0;      SustainPedal    = 0;
77    
78      dmsg(("OK\n"));      dmsg(1,("OK\n"));
79  }  }
80    
81  AudioThread::~AudioThread() {  AudioThread::~AudioThread() {
82        ModulationSystem::Close();
83      if (pCommandQueue) delete pCommandQueue;      if (pCommandQueue) delete pCommandQueue;
84      if (pVoices) {      if (pVoices) {
85          for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {          for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {
# Line 79  AudioThread::~AudioThread() { Line 87  AudioThread::~AudioThread() {
87          }          }
88      }      }
89      delete[] pVoices;      delete[] pVoices;
90        delete[] pAudioSumBuffer[0]; // this also frees the right channel buffer
91  }  }
92    
93  int AudioThread::Main() {  int AudioThread::RenderAudio(uint Samples) {
     dmsg(("Audio thread running\n"));  
     //int fifofd=open("/tmp/fifo1",O_WRONLY);  
94    
95        // read and process commands from the queue
96      while (true) {      while (true) {
97            command_t command;
98            if (!pCommandQueue->pop(&command)) break;
99    
100          // read and process commands from the queue          switch (command.type) {
101          while (true) {              case command_type_note_on:
102              command_t command;                  dmsg(5,("Audio Thread: Note on received\n"));
103              if (pCommandQueue->read(&command, 1) == 0) break;                  ProcessNoteOn(command.pitch, command.velocity);
104                    break;
105              switch (command.type) {              case command_type_note_off:
106                  case command_type_note_on:                  dmsg(5,("Audio Thread: Note off received\n"));
107                      dmsg(("Audio Thread: Note on received\n"));                  ProcessNoteOff(command.pitch, command.velocity);
108                      ActivateVoice(command.pitch, command.velocity);                  break;
109                      break;              case command_type_continuous_controller:
110                  case command_type_note_off:                  dmsg(5,("Audio Thread: MIDI CC received\n"));
111                      dmsg(("Audio Thread: Note off received\n"));                  ProcessControlChange(command.channel, command.number, command.value);
112                      ReleaseVoice(command.pitch, command.velocity);                  break;
                     break;  
                 case command_type_continuous_controller:  
                     dmsg(("Audio Thread: MIDI CC received\n"));  
                     ContinuousController(command.channel, command.number, command.value);  
                     break;  
             }  
113          }          }
114        }
115    
116    
117          // zero out the sum buffer      // zero out the output sum buffer (left and right channel)
118          for (uint u = 0; u < pAudioIO->FragmentSize * pAudioIO->Channels; u++) {      memset(pAudioSumBuffer[0], 0, Samples * pAudioIO->Channels() * sizeof(float));
             pAudioSumBuffer[u] = 0.0;  
         }  
119    
120    
121          // render audio from all active voices      // render audio from all active voices
122          int act_voices=0;      int active_voices = 0;
123          for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {      for (uint i = 0; i < MAX_AUDIO_VOICES; i++) {
124              if (pVoices[i]->IsActive()) {          if (pVoices[i]->IsActive()) {
125                  pVoices[i]->RenderAudio();              pVoices[i]->Render(Samples);
126                  act_voices++;              if (pVoices[i]->IsActive()) active_voices++; // still active
127                else { // voice reached end, is now inactive
128                    KillVoice(pVoices[i]); // remove voice from the list of active voices
129              }              }
130          }          }
131          // write that to the disk thread class so that it can print it      }
132          // on the console for debugging purposes      // write that to the disk thread class so that it can print it
133          ActiveVoiceCount=act_voices;      // on the console for debugging purposes
134        ActiveVoiceCount = active_voices;
135        if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount;
         // check clipping in the audio sum, convert to sample_type  
         // (from 32bit to 16bit sample) and copy to output buffer  
         float sample_point;  
         for (uint u = 0; u < pAudioIO->FragmentSize * pAudioIO->Channels; u++) {  
             sample_point = this->pAudioSumBuffer[u] / 4; // FIXME division by 4 just for testing purposes (to give a bit of head room when mixing multiple voices together)  
             if (sample_point < -32768.0) sample_point = -32768.0;  
             if (sample_point > 32767.0)  sample_point = 32767.0;  
             this->pAudioIO->pOutputBuffer[u] = (sample_t) sample_point;  
         }  
   
136    
         // call audio driver to output sound  
         int res = this->pAudioIO->Output();  
         if (res < 0) exit(EXIT_FAILURE);  
137    
138          // FIXME remove because we use it only to write to a fifo to save the audio      return 0;
         //write(fifofd, pAudioIO->pOutputBuffer, pAudioIO->FragmentSize * pAudioIO->Channels * sizeof(short));  
           
     }  
139  }  }
140    
141  /// Will be called by the MIDIIn Thread to let the audio thread trigger a new voice.  /// Will be called by the MIDIIn Thread to let the audio thread trigger a new voice.
142  void AudioThread::ProcessNoteOn(uint8_t Pitch, uint8_t Velocity) {  void AudioThread::SendNoteOn(uint8_t Pitch, uint8_t Velocity) {
143      command_t cmd;      command_t cmd;
144      cmd.type     = command_type_note_on;      cmd.type     = command_type_note_on;
145      cmd.pitch    = Pitch;      cmd.pitch    = Pitch;
146      cmd.velocity = Velocity;      cmd.velocity = Velocity;
147      this->pCommandQueue->write(&cmd, 1);      if (this->pCommandQueue->write_space() > 0) this->pCommandQueue->push(&cmd);
148        else dmsg(1,("AudioThread: Command queue full!"));
149  }  }
150    
151  /// Will be called by the MIDIIn Thread to signal the audio thread to release a voice.  /// Will be called by the MIDIIn Thread to signal the audio thread to release voice(s).
152  void AudioThread::ProcessNoteOff(uint8_t Pitch, uint8_t Velocity) {  void AudioThread::SendNoteOff(uint8_t Pitch, uint8_t Velocity) {
153      command_t cmd;      command_t cmd;
154      cmd.type     = command_type_note_off;      cmd.type     = command_type_note_off;
155      cmd.pitch    = Pitch;      cmd.pitch    = Pitch;
156      cmd.velocity = Velocity;      cmd.velocity = Velocity;
157      this->pCommandQueue->write(&cmd, 1);      if (this->pCommandQueue->write_space() > 0) this->pCommandQueue->push(&cmd);
158        else dmsg(1,("AudioThread: Command queue full!"));
159  }  }
160    
161  // Will be called by the MIDIIn Thead to send MIDI continuos controller events  // Will be called by the MIDIIn Thread to signal the audio thread that a continuous controller value has changed.
162  void AudioThread::ProcessContinuousController(uint8_t Channel, uint8_t Number, uint8_t Value) {  void AudioThread::SendControlChange(uint8_t Channel, uint8_t Number, uint8_t Value) {
163      command_t cmd;      command_t cmd;
164      cmd.type     = command_type_continuous_controller;      cmd.type     = command_type_continuous_controller;
165      cmd.channel  = Channel;      cmd.channel  = Channel;
166      cmd.number   = Number;      cmd.number   = Number;
167      cmd.value    = Value;      cmd.value    = Value;
168      this->pCommandQueue->write(&cmd, 1);      if (this->pCommandQueue->write_space() > 0) this->pCommandQueue->push(&cmd);
169        else dmsg(1,("AudioThread: Command queue full!"));
170  }  }
171    
172    /**
173  void AudioThread::ActivateVoice(uint8_t MIDIKey, uint8_t Velocity) {   *  Assigns and triggers a new voice for the respective MIDI key.
174     */
175    void AudioThread::ProcessNoteOn(uint8_t MIDIKey, uint8_t Velocity) {
176        pMIDIKeyInfo[MIDIKey].KeyPressed = true; // the MIDI key was currently pressed down
177      for (int i = 0; i < MAX_AUDIO_VOICES; i++) {      for (int i = 0; i < MAX_AUDIO_VOICES; i++) {
178          if (pVoices[i]->IsActive()) continue;          if (pVoices[i]->IsActive()) continue; // search for a free voice
179          pVoices[i]->Trigger(MIDIKey, Velocity, this->pInstrument);  
180            // launch the new voice
181            if (pVoices[i]->Trigger(MIDIKey, Velocity, this->pInstrument) < 0) {
182                return; // failed to trigger the new voice
183            }
184    
185          // add (append) a new voice to the corresponding MIDIKey active voices list          // add (append) a new voice to the corresponding MIDIKey active voices list
186          Voice **new_voice_ptr=ActiveVoicePool->alloc_append(pActiveVoices[MIDIKey]);          Voice** new_voice_ptr = ActiveVoicePool->alloc_append(pMIDIKeyInfo[MIDIKey].pActiveVoices);
187          *new_voice_ptr=pVoices[i];          *new_voice_ptr = pVoices[i];
188            pVoices[i]->pSelfPtr = new_voice_ptr; // FIXME: hack to allow fast deallocation
189    
190            // update key info
191            if (!pMIDIKeyInfo[MIDIKey].hSustainPtr) {
192                dmsg(4,("ActivateVoice(uint,uint): hSustainPtr == null, setting release pointer to the last voice on the key...\n"));
193                pMIDIKeyInfo[MIDIKey].pActiveVoices->last();
194                pMIDIKeyInfo[MIDIKey].hSustainPtr = pMIDIKeyInfo[MIDIKey].pActiveVoices->current();
195            }
196          return;          return;
197      }      }
198      std::cerr << "No free voice!" << std::endl << std::flush;      std::cerr << "No free voice!" << std::endl << std::flush;
199  }  }
200    
201  void AudioThread::ReleaseVoice(uint8_t MIDIKey, uint8_t Velocity) {  /**
202     *  Releases the voices on the given key if sustain pedal is not pressed.
203     *  If sustain is pressed, the release of the note will be postponed until
204     *  sustain pedal will be released or voice turned inactive by itself (e.g.
205     *  due to completion of sample playback).
206     */
207    void AudioThread::ProcessNoteOff(uint8_t MIDIKey, uint8_t Velocity) {
208        pMIDIKeyInfo[MIDIKey].KeyPressed = false; // the MIDI key was currently released
209        midi_key_info_t* pmidikey = &pMIDIKeyInfo[MIDIKey];
210        if (SustainPedal) { // if sustain pedal is pressed postpone the Note-Off
211            if (pmidikey->hSustainPtr) {
212                // stick the note-off information to the respective voice
213                Voice** pVoiceToRelease = pmidikey->pActiveVoices->set_current(pmidikey->hSustainPtr);
214                if (pVoiceToRelease) {
215                    (*pVoiceToRelease)->ReleaseVelocity = Velocity;
216                    // now increment the sustain pointer
217                    pmidikey->pActiveVoices->next();
218                    pmidikey->hSustainPtr = pmidikey->pActiveVoices->current();
219                    // if the key was not sustained yet, add it's MIDI key number to the sustained key pool
220                    if (!pmidikey->Sustained) {
221                        uint* sustainedmidikey     = SustainedKeyPool->alloc();
222                        *sustainedmidikey          = MIDIKey;
223                        pmidikey->pSustainPoolNode = sustainedmidikey;
224                        pmidikey->Sustained        = true;
225                    }
226                }
227                else dmsg(3,("Ignoring NOTE OFF --> pVoiceToRelease == null!\n"));
228            }
229            else dmsg(3,("Ignoring NOTE OFF, seems like more Note-Offs than Note-Ons or no free voices available?\n"));
230        }
231        else {
232            // release all active voices on the midi key
233            Voice** pVoicePtr = pmidikey->pActiveVoices->first();
234            while (pVoicePtr) {
235                Voice** pVoicePtrNext = pMIDIKeyInfo[MIDIKey].pActiveVoices->next();
236                (*pVoicePtr)->Release();
237                pVoicePtr = pVoicePtrNext;
238            }
239        }
240    }
241    
242      // get the first voice in the list of active voices on the MIDI Key  /**
243      Voice** pVoicePtr = pActiveVoices[MIDIKey]->first();   *  Immediately kills the voice given with pVoice (no matter if sustain is
244      Voice *pVoice=*pVoicePtr;   *  pressed or not) and removes it from the MIDI key's list of active voice.
245         *  This method will e.g. be called if a voice went inactive by itself. If
246     *  sustain pedal is pressed the method takes care to free those sustain
247     *  informations of the voice.
248     */
249    void AudioThread::KillVoice(Voice* pVoice) {
250      if (pVoice) {      if (pVoice) {
251            if (pVoice->IsActive()) pVoice->Kill();
252    
253            if (pMIDIKeyInfo[pVoice->MIDIKey].Sustained) {
254                // check if the sustain pointer has to be moved, now that we kill the voice
255                RTEList<Voice*>::NodeHandle hSustainPtr = pMIDIKeyInfo[pVoice->MIDIKey].hSustainPtr;
256                if (hSustainPtr) {
257                    Voice** pVoicePtr = pMIDIKeyInfo[pVoice->MIDIKey].pActiveVoices->set_current(hSustainPtr);
258                    if (pVoicePtr) {
259                        if (*pVoicePtr == pVoice) { // move sustain pointer to the next sustained voice
260                            dmsg(3,("Correcting sustain pointer\n"));
261                            pMIDIKeyInfo[pVoice->MIDIKey].pActiveVoices->next();
262                            pMIDIKeyInfo[pVoice->MIDIKey].hSustainPtr = pMIDIKeyInfo[pVoice->MIDIKey].pActiveVoices->current();
263                        }
264                        else dmsg(4,("ReleaseVoice(Voice*): *hSustain != pVoice\n"));
265                    }
266                    else dmsg(3,("ReleaseVoice(Voice*): pVoicePtr == null\n"));
267                }
268                else dmsg(3,("ReleaseVoice(Voice*): hSustainPtr == null\n"));
269            }
270    
271          // if sustain pedal is pressed postpone the Note-Off          // remove the voice from the list associated with this MIDI key
272          if(SustainPedal) {          ActiveVoicePool->free(pVoice->pSelfPtr);
             // alloc an element in the SustainedKeyPool and add the current midikey to it  
             sustained_key_t *key=SustainedKeyPool->alloc();  
             if(key == NULL) { /* FIXME */ printf("ERROR: SustainedKeyPool FULL ! exiting\n"); exit(0); }  
             key->midikey=MIDIKey;  
             key->velocity=Velocity;  
           return;  
         }  
   
         pVoice->Kill(); //TODO: for now we're rude and just kill the poor, poor voice immediately :), later we add a Release() method to the Voice class and call it here to let the voice go through it's release phase  
   
         // remove the voice from the list associated to this MIDI key  
         ActiveVoicePool->free(pVoicePtr);  
     }  
     else std::cerr << "Couldn't find active voice for note off command!" << std::endl << std::flush;  
 }  
   
 void AudioThread::ContinuousController(uint8_t Channel, uint8_t Number, uint8_t Value) {  
 //printf("AudioThread::ContinuousController c=%d n=%d v=%d\n",Channel, Number, Value);  
   if(Number == 64) {  
     if(Value >=64 && PrevHoldCCValue < 64) {  
       //printf("PEDAL DOWN\n");  
       SustainPedal=1;  
     }  
     if(Value < 64 && PrevHoldCCValue >=64) {  
       //printf("PEDAL UP\n");  
       SustainPedal=0;  
       sustained_key_t *key;  
       for(key = SustainedKeyPool->first(); key ; key=SustainedKeyPool->next() ) {  
         ReleaseVoice(key->midikey, key->velocity);  
       }  
       // empty the SustainedKeyPool (free all the elements)  
       SustainedKeyPool->empty();  
273    
274                  // check if there are no voices left on the MIDI key and update the key info if so
275            if (pMIDIKeyInfo[pVoice->MIDIKey].pActiveVoices->is_empty()) {
276                pMIDIKeyInfo[pVoice->MIDIKey].hSustainPtr = NULL;
277                if (pMIDIKeyInfo[pVoice->MIDIKey].Sustained) {
278                    SustainedKeyPool->free(pMIDIKeyInfo[pVoice->MIDIKey].pSustainPoolNode);
279                    pMIDIKeyInfo[pVoice->MIDIKey].pSustainPoolNode = NULL;
280                    pMIDIKeyInfo[pVoice->MIDIKey].Sustained        = false;
281                }
282                dmsg(3,("Key has no more voices now\n"));
283            }
284      }      }
285      PrevHoldCCValue=Value;      else std::cerr << "Couldn't release voice! (pVoice == NULL)\n" << std::flush;
   }  
   
286  }  }
287    
288    void AudioThread::ProcessControlChange(uint8_t Channel, uint8_t Number, uint8_t Value) {
289        dmsg(4,("AudioThread::ContinuousController c=%d n=%d v=%d\n", Channel, Number, Value));
290        if (Number == 64) {
291            if (Value >= 64 && PrevHoldCCValue < 64) {
292                dmsg(4,("PEDAL DOWN\n"));
293                SustainPedal = true;
294            }
295            if (Value < 64 && PrevHoldCCValue >= 64) {
296                dmsg(4,("PEDAL UP\n"));
297                SustainPedal = false;
298                // iterate through all keys that are currently sustained
299                for (uint* key = SustainedKeyPool->first(); key; key = SustainedKeyPool->next()) {
300                    if (!pMIDIKeyInfo[*key].KeyPressed) { // release the voices on the key, if the key is not pressed anymore
301                        // release all active voices on the midi key
302                        Voice** pVoicePtr = pMIDIKeyInfo[*key].pActiveVoices->first();
303                        while (pVoicePtr) {
304                            Voice** pVoicePtrNext = pMIDIKeyInfo[*key].pActiveVoices->next();
305                            dmsg(3,("Sustain CC: releasing voice on midi key %d\n", *key));
306                            (*pVoicePtr)->Release();
307                            pVoicePtr = pVoicePtrNext;
308                        }
309                        SustainedKeyPool->free(pMIDIKeyInfo[*key].pSustainPoolNode);
310                        pMIDIKeyInfo[*key].pSustainPoolNode = NULL;
311                        pMIDIKeyInfo[*key].Sustained        = false;
312                        pMIDIKeyInfo[*key].hSustainPtr      = NULL;
313                    }
314                }
315                //SustainedKeyPool->empty();
316            }
317            PrevHoldCCValue = Value;
318        }
319    }
320    
321  void AudioThread::CacheInitialSamples(gig::Sample* pSample) {  void AudioThread::CacheInitialSamples(gig::Sample* pSample) {
322      if (!pSample || pSample->GetCache().Size) return;      if (!pSample || pSample->GetCache().Size) return;
# Line 250  void AudioThread::CacheInitialSamples(gi Line 326  void AudioThread::CacheInitialSamples(gi
326          // number of '0' samples (silence samples) behind the official buffer          // number of '0' samples (silence samples) behind the official buffer
327          // border, to allow the interpolator do it's work even at the end of          // border, to allow the interpolator do it's work even at the end of
328          // the sample.          // the sample.
329          gig::buffer_t buf = pSample->LoadSampleDataWithNullSamplesExtension(pAudioIO->FragmentSize << MAX_PITCH);          gig::buffer_t buf = pSample->LoadSampleDataWithNullSamplesExtension((pAudioIO->MaxSamplesPerCycle() << MAX_PITCH) + 3);
330          dmsg(("Cached %d Bytes, %d silence bytes.\n", buf.Size, buf.NullExtensionSize));          dmsg(4,("Cached %d Bytes, %d silence bytes.\n", buf.Size, buf.NullExtensionSize));
331      }      }
332      else { // we only cache NUM_RAM_PRELOAD_SAMPLES and stream the other sample points from disk      else { // we only cache NUM_RAM_PRELOAD_SAMPLES and stream the other sample points from disk
333          pSample->LoadSampleData(NUM_RAM_PRELOAD_SAMPLES);          pSample->LoadSampleData(NUM_RAM_PRELOAD_SAMPLES);

Legend:
Removed from v.10  
changed lines
  Added in v.31

  ViewVC Help
Powered by ViewVC