Mercurial > hg > svcore
diff plugin/plugins/SamplePlayer.cpp @ 0:da6937383da8
initial import
author | Chris Cannam |
---|---|
date | Tue, 10 Jan 2006 16:33:16 +0000 |
parents | |
children | d86891498eef |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/plugins/SamplePlayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,553 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +/* + Based on trivial_sampler from the DSSI distribution + (by Chris Cannam, public domain). +*/ + +#include "SamplePlayer.h" + +#include <dssi.h> +#include <cmath> + +#include <QMutexLocker> +#include <QDir> +#include <QFileInfo> + +#include <sndfile.h> +#include <samplerate.h> +#include <iostream> + +const char *const +SamplePlayer::portNames[PortCount] = +{ + "Output", + "Tuned (on/off)", + "Base Pitch (MIDI)", + "Sustain (on/off)", + "Release time (s)" +}; + +const LADSPA_PortDescriptor +SamplePlayer::ports[PortCount] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL +}; + +const LADSPA_PortRangeHint +SamplePlayer::hints[PortCount] = +{ + { 0, 0, 0 }, + { LADSPA_HINT_DEFAULT_MAXIMUM | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, + { LADSPA_HINT_DEFAULT_MIDDLE | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 120 }, + { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, + { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_LOGARITHMIC | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001, 2.0 } +}; + +const LADSPA_Properties +SamplePlayer::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; + +const LADSPA_Descriptor +SamplePlayer::ladspaDescriptor = +{ + 0, // "Unique" ID + "sample_player", // Label + properties, + "Library Sample Player", // Name + "Chris Cannam", // Maker + "GPL", // Copyright + PortCount, + ports, + portNames, + hints, + 0, // Implementation data + instantiate, + connectPort, + activate, + run, + 0, // Run adding + 0, // Set run adding gain + deactivate, + cleanup +}; + +const DSSI_Descriptor +SamplePlayer::dssiDescriptor = +{ + 2, // DSSI API version + &ladspaDescriptor, + 0, // Configure + getProgram, + selectProgram, + getMidiController, + runSynth, + 0, // Run synth adding + 0, // Run multiple synths + 0, // Run multiple synths adding + receiveHostDescriptor +}; + +const DSSI_Host_Descriptor * +SamplePlayer::hostDescriptor = 0; + + +const DSSI_Descriptor * +SamplePlayer::getDescriptor(unsigned long index) +{ + if (index == 0) return &dssiDescriptor; + return 0; +} + +SamplePlayer::SamplePlayer(int sampleRate) : + m_output(0), + m_retune(0), + m_basePitch(0), + m_sustain(0), + m_release(0), + m_sampleData(0), + m_sampleCount(0), + m_sampleRate(sampleRate), + m_sampleNo(0), + m_sampleSearchComplete(false), + m_pendingProgramChange(-1) +{ +} + +SamplePlayer::~SamplePlayer() +{ + if (m_sampleData) free(m_sampleData); +} + +LADSPA_Handle +SamplePlayer::instantiate(const LADSPA_Descriptor *, unsigned long rate) +{ + if (!hostDescriptor || !hostDescriptor->request_non_rt_thread) { + std::cerr << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << std::endl; + return 0; + } + + SamplePlayer *player = new SamplePlayer(rate); + + if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) { + std::cerr << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << std::endl; + delete player; + return 0; + } + + return player; +} + +void +SamplePlayer::connectPort(LADSPA_Handle handle, + unsigned long port, LADSPA_Data *location) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + float **ports[PortCount] = { + &player->m_output, + &player->m_retune, + &player->m_basePitch, + &player->m_sustain, + &player->m_release + }; + + *ports[port] = (float *)location; +} + +void +SamplePlayer::activate(LADSPA_Handle handle) +{ + SamplePlayer *player = (SamplePlayer *)handle; + QMutexLocker locker(&player->m_mutex); + + player->m_sampleNo = 0; + + for (size_t i = 0; i < Polyphony; ++i) { + player->m_ons[i] = -1; + player->m_offs[i] = -1; + player->m_velocities[i] = 0; + } +} + +void +SamplePlayer::run(LADSPA_Handle handle, unsigned long samples) +{ + runSynth(handle, samples, 0, 0); +} + +void +SamplePlayer::deactivate(LADSPA_Handle handle) +{ + activate(handle); // both functions just reset the plugin +} + +void +SamplePlayer::cleanup(LADSPA_Handle handle) +{ + delete (SamplePlayer *)handle; +} + +const DSSI_Program_Descriptor * +SamplePlayer::getProgram(LADSPA_Handle handle, unsigned long program) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + if (!player->m_sampleSearchComplete) { + QMutexLocker locker(&player->m_mutex); + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + } + if (program >= player->m_samples.size()) return 0; + + static DSSI_Program_Descriptor descriptor; + static char name[60]; + + strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 60); + name[59] = '\0'; + + descriptor.Bank = 0; + descriptor.Program = program; + descriptor.Name = name; + + return &descriptor; +} + +void +SamplePlayer::selectProgram(LADSPA_Handle handle, + unsigned long, + unsigned long program) +{ + SamplePlayer *player = (SamplePlayer *)handle; + player->m_pendingProgramChange = program; +} + +int +SamplePlayer::getMidiController(LADSPA_Handle, unsigned long port) +{ + int controllers[PortCount] = { + DSSI_NONE, + DSSI_CC(12), + DSSI_CC(13), + DSSI_CC(64), + DSSI_CC(72) + }; + + return controllers[port]; +} + +void +SamplePlayer::runSynth(LADSPA_Handle handle, unsigned long samples, + snd_seq_event_t *events, unsigned long eventCount) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + player->runImpl(samples, events, eventCount); +} + +void +SamplePlayer::receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor) +{ + hostDescriptor = descriptor; +} + +void +SamplePlayer::workThreadCallback(LADSPA_Handle handle) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + if (player->m_pendingProgramChange >= 0) { + + std::cerr << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << std::endl; + + player->m_mutex.lock(); + + int program = player->m_pendingProgramChange; + player->m_pendingProgramChange = -1; + + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + + if (program < int(player->m_samples.size())) { + QString path = player->m_samples[program].second; + QString programName = player->m_samples[program].first; + if (programName != player->m_program) { + player->m_program = programName; + player->m_mutex.unlock(); + player->loadSampleData(path); + } else { + player->m_mutex.unlock(); + } + } + } + + if (!player->m_sampleSearchComplete) { + + QMutexLocker locker(&player->m_mutex); + + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + } +} + +void +SamplePlayer::searchSamples() +{ + if (m_sampleSearchComplete) return; + + //!!! +// QString path = "/usr/share/hydrogen/data/drumkits/EasternHop-1"; + + std::cerr << "Current working directory is \"" << getcwd(0, 0) << "\"" << std::endl; + + QString path = "samples"; + + std::cerr << "SamplePlayer::searchSamples: Path is \"" + << path.toLocal8Bit().data() << "\"" << std::endl; + + QDir dir(path, "*.wav"); + for (unsigned int i = 0; i < dir.count(); ++i) { + QFileInfo file(dir.filePath(dir[i])); + m_samples.push_back(std::pair<QString, QString> + (file.baseName(), file.filePath())); + std::cerr << "Found: " << dir[i].toLocal8Bit().data() << std::endl; + } + + m_sampleSearchComplete = true; +} + +void +SamplePlayer::loadSampleData(QString path) +{ + SF_INFO info; + SNDFILE *file; + size_t samples = 0; + float *tmpFrames, *tmpSamples, *tmpResamples, *tmpOld; + size_t i; + + info.format = 0; + file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); + if (!file) { + std::cerr << "SamplePlayer::loadSampleData: Failed to open file " + << path.toLocal8Bit().data() << ": " + << sf_strerror(file) << std::endl; + return; + } + + samples = info.frames; + tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); + if (!tmpFrames) return; + + sf_readf_float(file, tmpFrames, info.frames); + sf_close(file); + + tmpResamples = 0; + + if (info.samplerate != m_sampleRate) { + + double ratio = (double)m_sampleRate / (double)info.samplerate; + size_t target = (size_t)(info.frames * ratio); + SRC_DATA data; + + tmpResamples = (float *)malloc(target * info.channels * sizeof(float)); + if (!tmpResamples) { + free(tmpFrames); + return; + } + + memset(tmpResamples, 0, target * info.channels * sizeof(float)); + + data.data_in = tmpFrames; + data.data_out = tmpResamples; + data.input_frames = info.frames; + data.output_frames = target; + data.src_ratio = ratio; + + if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) { + free(tmpFrames); + tmpFrames = tmpResamples; + samples = target; + } else { + free(tmpResamples); + } + } + + /* add an extra sample for linear interpolation */ + tmpSamples = (float *)malloc((samples + 1) * sizeof(float)); + if (!tmpSamples) { + free(tmpFrames); + return; + } + + for (i = 0; i < samples; ++i) { + int j; + tmpSamples[i] = 0.0f; + for (j = 0; j < info.channels; ++j) { + tmpSamples[i] += tmpFrames[i * info.channels + j]; + } + } + + free(tmpFrames); + + /* add an extra sample for linear interpolation */ + tmpSamples[samples] = 0.0f; + + QMutexLocker locker(&m_mutex); + + tmpOld = m_sampleData; + m_sampleData = tmpSamples; + m_sampleCount = samples; + + for (i = 0; i < Polyphony; ++i) { + m_ons[i] = -1; + m_offs[i] = -1; + m_velocities[i] = 0; + } + + if (tmpOld) free(tmpOld); + + 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); +} + +void +SamplePlayer::runImpl(unsigned long sampleCount, + snd_seq_event_t *events, + unsigned long eventCount) +{ + unsigned long pos; + unsigned long count; + unsigned long event_pos; + int i; + + memset(m_output, 0, sampleCount * sizeof(float)); + + if (!m_mutex.tryLock()) return; + + if (!m_sampleData || !m_sampleCount) { + m_sampleNo += sampleCount; + m_mutex.unlock(); + return; + } + + for (pos = 0, event_pos = 0; pos < sampleCount; ) { + + while (event_pos < eventCount + && pos >= events[event_pos].time.tick) { + + if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) { + snd_seq_ev_note_t n = events[event_pos].data.note; + if (n.velocity > 0) { + m_ons[n.note] = + m_sampleNo + events[event_pos].time.tick; + m_offs[n.note] = -1; + m_velocities[n.note] = n.velocity; + } else { + if (!m_sustain || (*m_sustain < 0.001)) { + m_offs[n.note] = + m_sampleNo + events[event_pos].time.tick; + } + } + } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF && + (!m_sustain || (*m_sustain < 0.001))) { + snd_seq_ev_note_t n = events[event_pos].data.note; + m_offs[n.note] = + m_sampleNo + events[event_pos].time.tick; + } + + ++event_pos; + } + + count = sampleCount - pos; + if (event_pos < eventCount && + events[event_pos].time.tick < sampleCount) { + count = events[event_pos].time.tick - pos; + } + + for (i = 0; i < Polyphony; ++i) { + if (m_ons[i] >= 0) { + addSample(i, pos, count); + } + } + + pos += count; + } + + m_sampleNo += sampleCount; + m_mutex.unlock(); +} + +void +SamplePlayer::addSample(int n, unsigned long pos, unsigned long count) +{ + float ratio = 1.0; + float gain = 1.0; + unsigned long i, s; + + if (m_retune && *m_retune) { + if (m_basePitch && n != *m_basePitch) { + ratio = powf(1.059463094, n - *m_basePitch); + } + } + + if (long(pos + m_sampleNo) < m_ons[n]) return; + + gain = (float)m_velocities[n] / 127.0f; + + for (i = 0, s = pos + m_sampleNo - m_ons[n]; + i < count; + ++i, ++s) { + + float lgain = gain; + float rs = s * ratio; + unsigned long rsi = lrintf(floor(rs)); + + if (rsi >= m_sampleCount) { + m_ons[n] = -1; + break; + } + + if (m_offs[n] >= 0 && + long(pos + i + m_sampleNo) > m_offs[n]) { + + unsigned long dist = + pos + i + m_sampleNo - m_offs[n]; + + unsigned long releaseFrames = 200; + if (m_release) { + releaseFrames = long(*m_release * m_sampleRate + 0.0001); + } + + if (dist > releaseFrames) { + m_ons[n] = -1; + break; + } else { + lgain = lgain * (float)(releaseFrames - dist) / + (float)releaseFrames; + } + } + + float sample = m_sampleData[rsi] + + ((m_sampleData[rsi + 1] - + m_sampleData[rsi]) * + (rs - (float)rsi)); + + m_output[pos + i] += lgain * sample; + } +}