Mercurial > hg > svcore
comparison plugin/plugins/SamplePlayer.cpp @ 0:da6937383da8
initial import
author | Chris Cannam |
---|---|
date | Tue, 10 Jan 2006 16:33:16 +0000 |
parents | |
children | d86891498eef |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:da6937383da8 |
---|---|
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 A waveform viewer and audio annotation editor. | |
5 Chris Cannam, Queen Mary University of London, 2005 | |
6 | |
7 This is experimental software. Not for distribution. | |
8 */ | |
9 | |
10 /* | |
11 Based on trivial_sampler from the DSSI distribution | |
12 (by Chris Cannam, public domain). | |
13 */ | |
14 | |
15 #include "SamplePlayer.h" | |
16 | |
17 #include <dssi.h> | |
18 #include <cmath> | |
19 | |
20 #include <QMutexLocker> | |
21 #include <QDir> | |
22 #include <QFileInfo> | |
23 | |
24 #include <sndfile.h> | |
25 #include <samplerate.h> | |
26 #include <iostream> | |
27 | |
28 const char *const | |
29 SamplePlayer::portNames[PortCount] = | |
30 { | |
31 "Output", | |
32 "Tuned (on/off)", | |
33 "Base Pitch (MIDI)", | |
34 "Sustain (on/off)", | |
35 "Release time (s)" | |
36 }; | |
37 | |
38 const LADSPA_PortDescriptor | |
39 SamplePlayer::ports[PortCount] = | |
40 { | |
41 LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, | |
42 LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, | |
43 LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, | |
44 LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, | |
45 LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL | |
46 }; | |
47 | |
48 const LADSPA_PortRangeHint | |
49 SamplePlayer::hints[PortCount] = | |
50 { | |
51 { 0, 0, 0 }, | |
52 { LADSPA_HINT_DEFAULT_MAXIMUM | LADSPA_HINT_INTEGER | | |
53 LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, | |
54 { LADSPA_HINT_DEFAULT_MIDDLE | LADSPA_HINT_INTEGER | | |
55 LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 120 }, | |
56 { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_INTEGER | | |
57 LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, | |
58 { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_LOGARITHMIC | | |
59 LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001, 2.0 } | |
60 }; | |
61 | |
62 const LADSPA_Properties | |
63 SamplePlayer::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; | |
64 | |
65 const LADSPA_Descriptor | |
66 SamplePlayer::ladspaDescriptor = | |
67 { | |
68 0, // "Unique" ID | |
69 "sample_player", // Label | |
70 properties, | |
71 "Library Sample Player", // Name | |
72 "Chris Cannam", // Maker | |
73 "GPL", // Copyright | |
74 PortCount, | |
75 ports, | |
76 portNames, | |
77 hints, | |
78 0, // Implementation data | |
79 instantiate, | |
80 connectPort, | |
81 activate, | |
82 run, | |
83 0, // Run adding | |
84 0, // Set run adding gain | |
85 deactivate, | |
86 cleanup | |
87 }; | |
88 | |
89 const DSSI_Descriptor | |
90 SamplePlayer::dssiDescriptor = | |
91 { | |
92 2, // DSSI API version | |
93 &ladspaDescriptor, | |
94 0, // Configure | |
95 getProgram, | |
96 selectProgram, | |
97 getMidiController, | |
98 runSynth, | |
99 0, // Run synth adding | |
100 0, // Run multiple synths | |
101 0, // Run multiple synths adding | |
102 receiveHostDescriptor | |
103 }; | |
104 | |
105 const DSSI_Host_Descriptor * | |
106 SamplePlayer::hostDescriptor = 0; | |
107 | |
108 | |
109 const DSSI_Descriptor * | |
110 SamplePlayer::getDescriptor(unsigned long index) | |
111 { | |
112 if (index == 0) return &dssiDescriptor; | |
113 return 0; | |
114 } | |
115 | |
116 SamplePlayer::SamplePlayer(int sampleRate) : | |
117 m_output(0), | |
118 m_retune(0), | |
119 m_basePitch(0), | |
120 m_sustain(0), | |
121 m_release(0), | |
122 m_sampleData(0), | |
123 m_sampleCount(0), | |
124 m_sampleRate(sampleRate), | |
125 m_sampleNo(0), | |
126 m_sampleSearchComplete(false), | |
127 m_pendingProgramChange(-1) | |
128 { | |
129 } | |
130 | |
131 SamplePlayer::~SamplePlayer() | |
132 { | |
133 if (m_sampleData) free(m_sampleData); | |
134 } | |
135 | |
136 LADSPA_Handle | |
137 SamplePlayer::instantiate(const LADSPA_Descriptor *, unsigned long rate) | |
138 { | |
139 if (!hostDescriptor || !hostDescriptor->request_non_rt_thread) { | |
140 std::cerr << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << std::endl; | |
141 return 0; | |
142 } | |
143 | |
144 SamplePlayer *player = new SamplePlayer(rate); | |
145 | |
146 if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) { | |
147 std::cerr << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << std::endl; | |
148 delete player; | |
149 return 0; | |
150 } | |
151 | |
152 return player; | |
153 } | |
154 | |
155 void | |
156 SamplePlayer::connectPort(LADSPA_Handle handle, | |
157 unsigned long port, LADSPA_Data *location) | |
158 { | |
159 SamplePlayer *player = (SamplePlayer *)handle; | |
160 | |
161 float **ports[PortCount] = { | |
162 &player->m_output, | |
163 &player->m_retune, | |
164 &player->m_basePitch, | |
165 &player->m_sustain, | |
166 &player->m_release | |
167 }; | |
168 | |
169 *ports[port] = (float *)location; | |
170 } | |
171 | |
172 void | |
173 SamplePlayer::activate(LADSPA_Handle handle) | |
174 { | |
175 SamplePlayer *player = (SamplePlayer *)handle; | |
176 QMutexLocker locker(&player->m_mutex); | |
177 | |
178 player->m_sampleNo = 0; | |
179 | |
180 for (size_t i = 0; i < Polyphony; ++i) { | |
181 player->m_ons[i] = -1; | |
182 player->m_offs[i] = -1; | |
183 player->m_velocities[i] = 0; | |
184 } | |
185 } | |
186 | |
187 void | |
188 SamplePlayer::run(LADSPA_Handle handle, unsigned long samples) | |
189 { | |
190 runSynth(handle, samples, 0, 0); | |
191 } | |
192 | |
193 void | |
194 SamplePlayer::deactivate(LADSPA_Handle handle) | |
195 { | |
196 activate(handle); // both functions just reset the plugin | |
197 } | |
198 | |
199 void | |
200 SamplePlayer::cleanup(LADSPA_Handle handle) | |
201 { | |
202 delete (SamplePlayer *)handle; | |
203 } | |
204 | |
205 const DSSI_Program_Descriptor * | |
206 SamplePlayer::getProgram(LADSPA_Handle handle, unsigned long program) | |
207 { | |
208 SamplePlayer *player = (SamplePlayer *)handle; | |
209 | |
210 if (!player->m_sampleSearchComplete) { | |
211 QMutexLocker locker(&player->m_mutex); | |
212 if (!player->m_sampleSearchComplete) { | |
213 player->searchSamples(); | |
214 } | |
215 } | |
216 if (program >= player->m_samples.size()) return 0; | |
217 | |
218 static DSSI_Program_Descriptor descriptor; | |
219 static char name[60]; | |
220 | |
221 strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 60); | |
222 name[59] = '\0'; | |
223 | |
224 descriptor.Bank = 0; | |
225 descriptor.Program = program; | |
226 descriptor.Name = name; | |
227 | |
228 return &descriptor; | |
229 } | |
230 | |
231 void | |
232 SamplePlayer::selectProgram(LADSPA_Handle handle, | |
233 unsigned long, | |
234 unsigned long program) | |
235 { | |
236 SamplePlayer *player = (SamplePlayer *)handle; | |
237 player->m_pendingProgramChange = program; | |
238 } | |
239 | |
240 int | |
241 SamplePlayer::getMidiController(LADSPA_Handle, unsigned long port) | |
242 { | |
243 int controllers[PortCount] = { | |
244 DSSI_NONE, | |
245 DSSI_CC(12), | |
246 DSSI_CC(13), | |
247 DSSI_CC(64), | |
248 DSSI_CC(72) | |
249 }; | |
250 | |
251 return controllers[port]; | |
252 } | |
253 | |
254 void | |
255 SamplePlayer::runSynth(LADSPA_Handle handle, unsigned long samples, | |
256 snd_seq_event_t *events, unsigned long eventCount) | |
257 { | |
258 SamplePlayer *player = (SamplePlayer *)handle; | |
259 | |
260 player->runImpl(samples, events, eventCount); | |
261 } | |
262 | |
263 void | |
264 SamplePlayer::receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor) | |
265 { | |
266 hostDescriptor = descriptor; | |
267 } | |
268 | |
269 void | |
270 SamplePlayer::workThreadCallback(LADSPA_Handle handle) | |
271 { | |
272 SamplePlayer *player = (SamplePlayer *)handle; | |
273 | |
274 if (player->m_pendingProgramChange >= 0) { | |
275 | |
276 std::cerr << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << std::endl; | |
277 | |
278 player->m_mutex.lock(); | |
279 | |
280 int program = player->m_pendingProgramChange; | |
281 player->m_pendingProgramChange = -1; | |
282 | |
283 if (!player->m_sampleSearchComplete) { | |
284 player->searchSamples(); | |
285 } | |
286 | |
287 if (program < int(player->m_samples.size())) { | |
288 QString path = player->m_samples[program].second; | |
289 QString programName = player->m_samples[program].first; | |
290 if (programName != player->m_program) { | |
291 player->m_program = programName; | |
292 player->m_mutex.unlock(); | |
293 player->loadSampleData(path); | |
294 } else { | |
295 player->m_mutex.unlock(); | |
296 } | |
297 } | |
298 } | |
299 | |
300 if (!player->m_sampleSearchComplete) { | |
301 | |
302 QMutexLocker locker(&player->m_mutex); | |
303 | |
304 if (!player->m_sampleSearchComplete) { | |
305 player->searchSamples(); | |
306 } | |
307 } | |
308 } | |
309 | |
310 void | |
311 SamplePlayer::searchSamples() | |
312 { | |
313 if (m_sampleSearchComplete) return; | |
314 | |
315 //!!! | |
316 // QString path = "/usr/share/hydrogen/data/drumkits/EasternHop-1"; | |
317 | |
318 std::cerr << "Current working directory is \"" << getcwd(0, 0) << "\"" << std::endl; | |
319 | |
320 QString path = "samples"; | |
321 | |
322 std::cerr << "SamplePlayer::searchSamples: Path is \"" | |
323 << path.toLocal8Bit().data() << "\"" << std::endl; | |
324 | |
325 QDir dir(path, "*.wav"); | |
326 for (unsigned int i = 0; i < dir.count(); ++i) { | |
327 QFileInfo file(dir.filePath(dir[i])); | |
328 m_samples.push_back(std::pair<QString, QString> | |
329 (file.baseName(), file.filePath())); | |
330 std::cerr << "Found: " << dir[i].toLocal8Bit().data() << std::endl; | |
331 } | |
332 | |
333 m_sampleSearchComplete = true; | |
334 } | |
335 | |
336 void | |
337 SamplePlayer::loadSampleData(QString path) | |
338 { | |
339 SF_INFO info; | |
340 SNDFILE *file; | |
341 size_t samples = 0; | |
342 float *tmpFrames, *tmpSamples, *tmpResamples, *tmpOld; | |
343 size_t i; | |
344 | |
345 info.format = 0; | |
346 file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); | |
347 if (!file) { | |
348 std::cerr << "SamplePlayer::loadSampleData: Failed to open file " | |
349 << path.toLocal8Bit().data() << ": " | |
350 << sf_strerror(file) << std::endl; | |
351 return; | |
352 } | |
353 | |
354 samples = info.frames; | |
355 tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); | |
356 if (!tmpFrames) return; | |
357 | |
358 sf_readf_float(file, tmpFrames, info.frames); | |
359 sf_close(file); | |
360 | |
361 tmpResamples = 0; | |
362 | |
363 if (info.samplerate != m_sampleRate) { | |
364 | |
365 double ratio = (double)m_sampleRate / (double)info.samplerate; | |
366 size_t target = (size_t)(info.frames * ratio); | |
367 SRC_DATA data; | |
368 | |
369 tmpResamples = (float *)malloc(target * info.channels * sizeof(float)); | |
370 if (!tmpResamples) { | |
371 free(tmpFrames); | |
372 return; | |
373 } | |
374 | |
375 memset(tmpResamples, 0, target * info.channels * sizeof(float)); | |
376 | |
377 data.data_in = tmpFrames; | |
378 data.data_out = tmpResamples; | |
379 data.input_frames = info.frames; | |
380 data.output_frames = target; | |
381 data.src_ratio = ratio; | |
382 | |
383 if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) { | |
384 free(tmpFrames); | |
385 tmpFrames = tmpResamples; | |
386 samples = target; | |
387 } else { | |
388 free(tmpResamples); | |
389 } | |
390 } | |
391 | |
392 /* add an extra sample for linear interpolation */ | |
393 tmpSamples = (float *)malloc((samples + 1) * sizeof(float)); | |
394 if (!tmpSamples) { | |
395 free(tmpFrames); | |
396 return; | |
397 } | |
398 | |
399 for (i = 0; i < samples; ++i) { | |
400 int j; | |
401 tmpSamples[i] = 0.0f; | |
402 for (j = 0; j < info.channels; ++j) { | |
403 tmpSamples[i] += tmpFrames[i * info.channels + j]; | |
404 } | |
405 } | |
406 | |
407 free(tmpFrames); | |
408 | |
409 /* add an extra sample for linear interpolation */ | |
410 tmpSamples[samples] = 0.0f; | |
411 | |
412 QMutexLocker locker(&m_mutex); | |
413 | |
414 tmpOld = m_sampleData; | |
415 m_sampleData = tmpSamples; | |
416 m_sampleCount = samples; | |
417 | |
418 for (i = 0; i < Polyphony; ++i) { | |
419 m_ons[i] = -1; | |
420 m_offs[i] = -1; | |
421 m_velocities[i] = 0; | |
422 } | |
423 | |
424 if (tmpOld) free(tmpOld); | |
425 | |
426 printf("%s: loaded %s (%ld samples from original %ld channels resampled from %ld frames at %ld Hz)\n", "sampler", path.toLocal8Bit().data(), (long)samples, (long)info.channels, (long)info.frames, (long)info.samplerate); | |
427 } | |
428 | |
429 void | |
430 SamplePlayer::runImpl(unsigned long sampleCount, | |
431 snd_seq_event_t *events, | |
432 unsigned long eventCount) | |
433 { | |
434 unsigned long pos; | |
435 unsigned long count; | |
436 unsigned long event_pos; | |
437 int i; | |
438 | |
439 memset(m_output, 0, sampleCount * sizeof(float)); | |
440 | |
441 if (!m_mutex.tryLock()) return; | |
442 | |
443 if (!m_sampleData || !m_sampleCount) { | |
444 m_sampleNo += sampleCount; | |
445 m_mutex.unlock(); | |
446 return; | |
447 } | |
448 | |
449 for (pos = 0, event_pos = 0; pos < sampleCount; ) { | |
450 | |
451 while (event_pos < eventCount | |
452 && pos >= events[event_pos].time.tick) { | |
453 | |
454 if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) { | |
455 snd_seq_ev_note_t n = events[event_pos].data.note; | |
456 if (n.velocity > 0) { | |
457 m_ons[n.note] = | |
458 m_sampleNo + events[event_pos].time.tick; | |
459 m_offs[n.note] = -1; | |
460 m_velocities[n.note] = n.velocity; | |
461 } else { | |
462 if (!m_sustain || (*m_sustain < 0.001)) { | |
463 m_offs[n.note] = | |
464 m_sampleNo + events[event_pos].time.tick; | |
465 } | |
466 } | |
467 } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF && | |
468 (!m_sustain || (*m_sustain < 0.001))) { | |
469 snd_seq_ev_note_t n = events[event_pos].data.note; | |
470 m_offs[n.note] = | |
471 m_sampleNo + events[event_pos].time.tick; | |
472 } | |
473 | |
474 ++event_pos; | |
475 } | |
476 | |
477 count = sampleCount - pos; | |
478 if (event_pos < eventCount && | |
479 events[event_pos].time.tick < sampleCount) { | |
480 count = events[event_pos].time.tick - pos; | |
481 } | |
482 | |
483 for (i = 0; i < Polyphony; ++i) { | |
484 if (m_ons[i] >= 0) { | |
485 addSample(i, pos, count); | |
486 } | |
487 } | |
488 | |
489 pos += count; | |
490 } | |
491 | |
492 m_sampleNo += sampleCount; | |
493 m_mutex.unlock(); | |
494 } | |
495 | |
496 void | |
497 SamplePlayer::addSample(int n, unsigned long pos, unsigned long count) | |
498 { | |
499 float ratio = 1.0; | |
500 float gain = 1.0; | |
501 unsigned long i, s; | |
502 | |
503 if (m_retune && *m_retune) { | |
504 if (m_basePitch && n != *m_basePitch) { | |
505 ratio = powf(1.059463094, n - *m_basePitch); | |
506 } | |
507 } | |
508 | |
509 if (long(pos + m_sampleNo) < m_ons[n]) return; | |
510 | |
511 gain = (float)m_velocities[n] / 127.0f; | |
512 | |
513 for (i = 0, s = pos + m_sampleNo - m_ons[n]; | |
514 i < count; | |
515 ++i, ++s) { | |
516 | |
517 float lgain = gain; | |
518 float rs = s * ratio; | |
519 unsigned long rsi = lrintf(floor(rs)); | |
520 | |
521 if (rsi >= m_sampleCount) { | |
522 m_ons[n] = -1; | |
523 break; | |
524 } | |
525 | |
526 if (m_offs[n] >= 0 && | |
527 long(pos + i + m_sampleNo) > m_offs[n]) { | |
528 | |
529 unsigned long dist = | |
530 pos + i + m_sampleNo - m_offs[n]; | |
531 | |
532 unsigned long releaseFrames = 200; | |
533 if (m_release) { | |
534 releaseFrames = long(*m_release * m_sampleRate + 0.0001); | |
535 } | |
536 | |
537 if (dist > releaseFrames) { | |
538 m_ons[n] = -1; | |
539 break; | |
540 } else { | |
541 lgain = lgain * (float)(releaseFrames - dist) / | |
542 (float)releaseFrames; | |
543 } | |
544 } | |
545 | |
546 float sample = m_sampleData[rsi] + | |
547 ((m_sampleData[rsi + 1] - | |
548 m_sampleData[rsi]) * | |
549 (rs - (float)rsi)); | |
550 | |
551 m_output[pos + i] += lgain * sample; | |
552 } | |
553 } |