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;
+    }
+}