Chris@49: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@52: Sonic Visualiser Chris@52: An audio file viewer and annotation editor. Chris@52: Centre for Digital Music, Queen Mary, University of London. Chris@0: Chris@52: This program is free software; you can redistribute it and/or Chris@52: modify it under the terms of the GNU General Public License as Chris@52: published by the Free Software Foundation; either version 2 of the Chris@52: License, or (at your option) any later version. See the file Chris@52: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: /* Chris@0: Based on trivial_sampler from the DSSI distribution Chris@0: (by Chris Cannam, public domain). Chris@0: */ Chris@0: Chris@0: #include "SamplePlayer.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: const char *const Chris@0: SamplePlayer::portNames[PortCount] = Chris@0: { Chris@0: "Output", Chris@0: "Tuned (on/off)", Chris@0: "Base Pitch (MIDI)", Chris@0: "Sustain (on/off)", Chris@0: "Release time (s)" Chris@0: }; Chris@0: Chris@0: const LADSPA_PortDescriptor Chris@0: SamplePlayer::ports[PortCount] = Chris@0: { Chris@0: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, Chris@0: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, Chris@0: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, Chris@0: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, Chris@0: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL Chris@0: }; Chris@0: Chris@0: const LADSPA_PortRangeHint Chris@0: SamplePlayer::hints[PortCount] = Chris@0: { Chris@0: { 0, 0, 0 }, Chris@0: { LADSPA_HINT_DEFAULT_MAXIMUM | LADSPA_HINT_INTEGER | Chris@0: LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, Chris@0: { LADSPA_HINT_DEFAULT_MIDDLE | LADSPA_HINT_INTEGER | Chris@0: LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 120 }, Chris@0: { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_INTEGER | Chris@0: LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, Chris@0: { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_LOGARITHMIC | Chris@0: LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001, 2.0 } Chris@0: }; Chris@0: Chris@0: const LADSPA_Properties Chris@0: SamplePlayer::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; Chris@0: Chris@0: const LADSPA_Descriptor Chris@0: SamplePlayer::ladspaDescriptor = Chris@0: { Chris@0: 0, // "Unique" ID Chris@0: "sample_player", // Label Chris@0: properties, Chris@0: "Library Sample Player", // Name Chris@0: "Chris Cannam", // Maker Chris@0: "GPL", // Copyright Chris@0: PortCount, Chris@0: ports, Chris@0: portNames, Chris@0: hints, Chris@0: 0, // Implementation data Chris@0: instantiate, Chris@0: connectPort, Chris@0: activate, Chris@0: run, Chris@0: 0, // Run adding Chris@0: 0, // Set run adding gain Chris@0: deactivate, Chris@0: cleanup Chris@0: }; Chris@0: Chris@0: const DSSI_Descriptor Chris@0: SamplePlayer::dssiDescriptor = Chris@0: { Chris@0: 2, // DSSI API version Chris@0: &ladspaDescriptor, Chris@75: configure, Chris@0: getProgram, Chris@0: selectProgram, Chris@0: getMidiController, Chris@0: runSynth, Chris@0: 0, // Run synth adding Chris@0: 0, // Run multiple synths Chris@0: 0, // Run multiple synths adding Chris@0: receiveHostDescriptor Chris@0: }; Chris@0: Chris@0: const DSSI_Host_Descriptor * Chris@0: SamplePlayer::hostDescriptor = 0; Chris@0: Chris@0: Chris@0: const DSSI_Descriptor * Chris@0: SamplePlayer::getDescriptor(unsigned long index) Chris@0: { Chris@0: if (index == 0) return &dssiDescriptor; Chris@0: return 0; Chris@0: } Chris@0: Chris@0: SamplePlayer::SamplePlayer(int sampleRate) : Chris@0: m_output(0), Chris@0: m_retune(0), Chris@0: m_basePitch(0), Chris@0: m_sustain(0), Chris@0: m_release(0), Chris@0: m_sampleData(0), Chris@0: m_sampleCount(0), Chris@0: m_sampleRate(sampleRate), Chris@0: m_sampleNo(0), Chris@75: m_samplePath("samples"), Chris@0: m_sampleSearchComplete(false), Chris@0: m_pendingProgramChange(-1) Chris@0: { Chris@0: } Chris@0: Chris@0: SamplePlayer::~SamplePlayer() Chris@0: { Chris@0: if (m_sampleData) free(m_sampleData); Chris@0: } Chris@0: Chris@0: LADSPA_Handle Chris@0: SamplePlayer::instantiate(const LADSPA_Descriptor *, unsigned long rate) Chris@0: { Chris@0: if (!hostDescriptor || !hostDescriptor->request_non_rt_thread) { Chris@0: std::cerr << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << std::endl; Chris@0: return 0; Chris@0: } Chris@0: Chris@0: SamplePlayer *player = new SamplePlayer(rate); Chris@0: Chris@0: if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) { Chris@0: std::cerr << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << std::endl; Chris@0: delete player; Chris@0: return 0; Chris@0: } Chris@0: Chris@0: return player; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::connectPort(LADSPA_Handle handle, Chris@0: unsigned long port, LADSPA_Data *location) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: Chris@0: float **ports[PortCount] = { Chris@0: &player->m_output, Chris@0: &player->m_retune, Chris@0: &player->m_basePitch, Chris@0: &player->m_sustain, Chris@0: &player->m_release Chris@0: }; Chris@0: Chris@0: *ports[port] = (float *)location; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::activate(LADSPA_Handle handle) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: QMutexLocker locker(&player->m_mutex); Chris@0: Chris@0: player->m_sampleNo = 0; Chris@0: Chris@0: for (size_t i = 0; i < Polyphony; ++i) { Chris@0: player->m_ons[i] = -1; Chris@0: player->m_offs[i] = -1; Chris@0: player->m_velocities[i] = 0; Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::run(LADSPA_Handle handle, unsigned long samples) Chris@0: { Chris@0: runSynth(handle, samples, 0, 0); Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::deactivate(LADSPA_Handle handle) Chris@0: { Chris@0: activate(handle); // both functions just reset the plugin Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::cleanup(LADSPA_Handle handle) Chris@0: { Chris@0: delete (SamplePlayer *)handle; Chris@0: } Chris@0: Chris@75: char * Chris@75: SamplePlayer::configure(LADSPA_Handle handle, const char *key, const char *value) Chris@75: { Chris@75: if (key && !strcmp(key, "samplepath")) { Chris@75: Chris@75: SamplePlayer *player = (SamplePlayer *)handle; Chris@75: Chris@75: QMutexLocker locker(&player->m_mutex); Chris@75: Chris@81: Chris@81: //!!! What do we do if receiving an antique path pointing at things that no longer exist? Chris@81: Chris@75: player->m_samplePath = value; Chris@75: Chris@75: if (player->m_sampleSearchComplete) { Chris@75: player->m_sampleSearchComplete = false; Chris@75: player->searchSamples(); Chris@75: } Chris@75: Chris@75: return 0; Chris@75: } Chris@75: Chris@75: return strdup("Unknown configure key"); Chris@75: } Chris@75: Chris@0: const DSSI_Program_Descriptor * Chris@0: SamplePlayer::getProgram(LADSPA_Handle handle, unsigned long program) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: Chris@0: if (!player->m_sampleSearchComplete) { Chris@0: QMutexLocker locker(&player->m_mutex); Chris@0: if (!player->m_sampleSearchComplete) { Chris@0: player->searchSamples(); Chris@0: } Chris@0: } Chris@0: if (program >= player->m_samples.size()) return 0; Chris@0: Chris@0: static DSSI_Program_Descriptor descriptor; Chris@0: static char name[60]; Chris@0: Chris@0: strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 60); Chris@0: name[59] = '\0'; Chris@0: Chris@0: descriptor.Bank = 0; Chris@0: descriptor.Program = program; Chris@0: descriptor.Name = name; Chris@0: Chris@0: return &descriptor; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::selectProgram(LADSPA_Handle handle, Chris@0: unsigned long, Chris@0: unsigned long program) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: player->m_pendingProgramChange = program; Chris@0: } Chris@0: Chris@0: int Chris@0: SamplePlayer::getMidiController(LADSPA_Handle, unsigned long port) Chris@0: { Chris@0: int controllers[PortCount] = { Chris@0: DSSI_NONE, Chris@0: DSSI_CC(12), Chris@0: DSSI_CC(13), Chris@0: DSSI_CC(64), Chris@0: DSSI_CC(72) Chris@0: }; Chris@0: Chris@0: return controllers[port]; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::runSynth(LADSPA_Handle handle, unsigned long samples, Chris@0: snd_seq_event_t *events, unsigned long eventCount) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: Chris@0: player->runImpl(samples, events, eventCount); Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor) Chris@0: { Chris@0: hostDescriptor = descriptor; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::workThreadCallback(LADSPA_Handle handle) Chris@0: { Chris@0: SamplePlayer *player = (SamplePlayer *)handle; Chris@0: Chris@0: if (player->m_pendingProgramChange >= 0) { Chris@0: Chris@0: std::cerr << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << std::endl; Chris@0: Chris@0: player->m_mutex.lock(); Chris@0: Chris@0: int program = player->m_pendingProgramChange; Chris@0: player->m_pendingProgramChange = -1; Chris@0: Chris@0: if (!player->m_sampleSearchComplete) { Chris@0: player->searchSamples(); Chris@0: } Chris@0: Chris@0: if (program < int(player->m_samples.size())) { Chris@0: QString path = player->m_samples[program].second; Chris@0: QString programName = player->m_samples[program].first; Chris@0: if (programName != player->m_program) { Chris@0: player->m_program = programName; Chris@0: player->m_mutex.unlock(); Chris@0: player->loadSampleData(path); Chris@0: } else { Chris@0: player->m_mutex.unlock(); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!player->m_sampleSearchComplete) { Chris@0: Chris@0: QMutexLocker locker(&player->m_mutex); Chris@0: Chris@0: if (!player->m_sampleSearchComplete) { Chris@0: player->searchSamples(); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::searchSamples() Chris@0: { Chris@0: if (m_sampleSearchComplete) return; Chris@0: Chris@81: m_samples.clear(); Chris@81: Chris@0: std::cerr << "Current working directory is \"" << getcwd(0, 0) << "\"" << std::endl; Chris@0: Chris@0: std::cerr << "SamplePlayer::searchSamples: Path is \"" Chris@75: << m_samplePath.toLocal8Bit().data() << "\"" << std::endl; Chris@0: Chris@75: QStringList dirList = m_samplePath.split(QRegExp("[:;]")); Chris@75: Chris@75: for (QStringList::iterator i = dirList.begin(); i != dirList.end(); ++i) { Chris@75: Chris@75: QDir dir(*i, "*.wav"); Chris@75: Chris@75: for (unsigned int i = 0; i < dir.count(); ++i) { Chris@75: QFileInfo file(dir.filePath(dir[i])); Chris@75: if (file.isReadable()) { Chris@75: m_samples.push_back(std::pair Chris@75: (file.baseName(), file.filePath())); Chris@75: std::cerr << "Found: " << dir[i].toLocal8Bit().data() << std::endl; Chris@75: } Chris@75: } Chris@0: } Chris@0: Chris@0: m_sampleSearchComplete = true; Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::loadSampleData(QString path) Chris@0: { Chris@0: SF_INFO info; Chris@0: SNDFILE *file; Chris@0: size_t samples = 0; Chris@0: float *tmpFrames, *tmpSamples, *tmpResamples, *tmpOld; Chris@0: size_t i; Chris@0: Chris@0: info.format = 0; Chris@0: file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); Chris@0: if (!file) { Chris@0: std::cerr << "SamplePlayer::loadSampleData: Failed to open file " Chris@0: << path.toLocal8Bit().data() << ": " Chris@0: << sf_strerror(file) << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: samples = info.frames; Chris@0: tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); Chris@0: if (!tmpFrames) return; Chris@0: Chris@0: sf_readf_float(file, tmpFrames, info.frames); Chris@0: sf_close(file); Chris@0: Chris@0: tmpResamples = 0; Chris@0: Chris@0: if (info.samplerate != m_sampleRate) { Chris@0: Chris@0: double ratio = (double)m_sampleRate / (double)info.samplerate; Chris@0: size_t target = (size_t)(info.frames * ratio); Chris@0: SRC_DATA data; Chris@0: Chris@0: tmpResamples = (float *)malloc(target * info.channels * sizeof(float)); Chris@0: if (!tmpResamples) { Chris@0: free(tmpFrames); Chris@0: return; Chris@0: } Chris@0: Chris@0: memset(tmpResamples, 0, target * info.channels * sizeof(float)); Chris@0: Chris@0: data.data_in = tmpFrames; Chris@0: data.data_out = tmpResamples; Chris@0: data.input_frames = info.frames; Chris@0: data.output_frames = target; Chris@0: data.src_ratio = ratio; Chris@0: Chris@0: if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) { Chris@0: free(tmpFrames); Chris@0: tmpFrames = tmpResamples; Chris@0: samples = target; Chris@0: } else { Chris@0: free(tmpResamples); Chris@0: } Chris@0: } Chris@0: Chris@0: /* add an extra sample for linear interpolation */ Chris@0: tmpSamples = (float *)malloc((samples + 1) * sizeof(float)); Chris@0: if (!tmpSamples) { Chris@0: free(tmpFrames); Chris@0: return; Chris@0: } Chris@0: Chris@0: for (i = 0; i < samples; ++i) { Chris@0: int j; Chris@0: tmpSamples[i] = 0.0f; Chris@0: for (j = 0; j < info.channels; ++j) { Chris@0: tmpSamples[i] += tmpFrames[i * info.channels + j]; Chris@0: } Chris@0: } Chris@0: Chris@0: free(tmpFrames); Chris@0: Chris@0: /* add an extra sample for linear interpolation */ Chris@0: tmpSamples[samples] = 0.0f; Chris@0: Chris@0: QMutexLocker locker(&m_mutex); Chris@0: Chris@0: tmpOld = m_sampleData; Chris@0: m_sampleData = tmpSamples; Chris@0: m_sampleCount = samples; Chris@0: Chris@0: for (i = 0; i < Polyphony; ++i) { Chris@0: m_ons[i] = -1; Chris@0: m_offs[i] = -1; Chris@0: m_velocities[i] = 0; Chris@0: } Chris@0: Chris@0: if (tmpOld) free(tmpOld); Chris@0: Chris@0: 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); Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::runImpl(unsigned long sampleCount, Chris@0: snd_seq_event_t *events, Chris@0: unsigned long eventCount) Chris@0: { Chris@0: unsigned long pos; Chris@0: unsigned long count; Chris@0: unsigned long event_pos; Chris@0: int i; Chris@0: Chris@0: memset(m_output, 0, sampleCount * sizeof(float)); Chris@0: Chris@0: if (!m_mutex.tryLock()) return; Chris@0: Chris@0: if (!m_sampleData || !m_sampleCount) { Chris@0: m_sampleNo += sampleCount; Chris@0: m_mutex.unlock(); Chris@0: return; Chris@0: } Chris@0: Chris@0: for (pos = 0, event_pos = 0; pos < sampleCount; ) { Chris@0: Chris@0: while (event_pos < eventCount Chris@0: && pos >= events[event_pos].time.tick) { Chris@0: Chris@0: if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) { Chris@0: snd_seq_ev_note_t n = events[event_pos].data.note; Chris@0: if (n.velocity > 0) { Chris@0: m_ons[n.note] = Chris@0: m_sampleNo + events[event_pos].time.tick; Chris@0: m_offs[n.note] = -1; Chris@0: m_velocities[n.note] = n.velocity; Chris@0: } else { Chris@0: if (!m_sustain || (*m_sustain < 0.001)) { Chris@0: m_offs[n.note] = Chris@0: m_sampleNo + events[event_pos].time.tick; Chris@0: } Chris@0: } Chris@0: } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF && Chris@0: (!m_sustain || (*m_sustain < 0.001))) { Chris@0: snd_seq_ev_note_t n = events[event_pos].data.note; Chris@0: m_offs[n.note] = Chris@0: m_sampleNo + events[event_pos].time.tick; Chris@0: } Chris@0: Chris@0: ++event_pos; Chris@0: } Chris@0: Chris@0: count = sampleCount - pos; Chris@0: if (event_pos < eventCount && Chris@0: events[event_pos].time.tick < sampleCount) { Chris@0: count = events[event_pos].time.tick - pos; Chris@0: } Chris@0: Chris@0: for (i = 0; i < Polyphony; ++i) { Chris@0: if (m_ons[i] >= 0) { Chris@0: addSample(i, pos, count); Chris@0: } Chris@0: } Chris@0: Chris@0: pos += count; Chris@0: } Chris@0: Chris@0: m_sampleNo += sampleCount; Chris@0: m_mutex.unlock(); Chris@0: } Chris@0: Chris@0: void Chris@0: SamplePlayer::addSample(int n, unsigned long pos, unsigned long count) Chris@0: { Chris@0: float ratio = 1.0; Chris@0: float gain = 1.0; Chris@0: unsigned long i, s; Chris@0: Chris@0: if (m_retune && *m_retune) { Chris@0: if (m_basePitch && n != *m_basePitch) { Chris@0: ratio = powf(1.059463094, n - *m_basePitch); Chris@0: } Chris@0: } Chris@0: Chris@0: if (long(pos + m_sampleNo) < m_ons[n]) return; Chris@0: Chris@0: gain = (float)m_velocities[n] / 127.0f; Chris@0: Chris@0: for (i = 0, s = pos + m_sampleNo - m_ons[n]; Chris@0: i < count; Chris@0: ++i, ++s) { Chris@0: Chris@0: float lgain = gain; Chris@0: float rs = s * ratio; Chris@0: unsigned long rsi = lrintf(floor(rs)); Chris@0: Chris@0: if (rsi >= m_sampleCount) { Chris@0: m_ons[n] = -1; Chris@0: break; Chris@0: } Chris@0: Chris@0: if (m_offs[n] >= 0 && Chris@0: long(pos + i + m_sampleNo) > m_offs[n]) { Chris@0: Chris@0: unsigned long dist = Chris@0: pos + i + m_sampleNo - m_offs[n]; Chris@0: Chris@0: unsigned long releaseFrames = 200; Chris@0: if (m_release) { Chris@0: releaseFrames = long(*m_release * m_sampleRate + 0.0001); Chris@0: } Chris@0: Chris@0: if (dist > releaseFrames) { Chris@0: m_ons[n] = -1; Chris@0: break; Chris@0: } else { Chris@0: lgain = lgain * (float)(releaseFrames - dist) / Chris@0: (float)releaseFrames; Chris@0: } Chris@0: } Chris@0: Chris@0: float sample = m_sampleData[rsi] + Chris@0: ((m_sampleData[rsi + 1] - Chris@0: m_sampleData[rsi]) * Chris@0: (rs - (float)rsi)); Chris@0: Chris@0: m_output[pos + i] += lgain * sample; Chris@0: } Chris@0: }