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