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 <dssi.h>
Chris@0: #include <cmath>
Chris@0: 
Chris@0: #include <QMutexLocker>
Chris@0: #include <QDir>
Chris@0: #include <QFileInfo>
Chris@0: 
Chris@0: #include <sndfile.h>
Chris@0: #include <samplerate.h>
Chris@0: #include <iostream>
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@143:     "Tuning of A (Hz)",
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@143:     { LADSPA_HINT_DEFAULT_440 | LADSPA_HINT_LOGARITHMIC |
Chris@144:       LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 400, 499 },
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@143:     m_concertA(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@83:     m_sampleDir("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@143:         &player->m_concertA,
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@83:     if (key && !strcmp(key, "sampledir")) {
Chris@75: 
Chris@75:         SamplePlayer *player = (SamplePlayer *)handle;
Chris@75: 
Chris@75: 	QMutexLocker locker(&player->m_mutex);
Chris@75: 
Chris@83:         if (QFileInfo(value).exists() &&
Chris@83:             QFileInfo(value).isDir()) {
Chris@81: 
Chris@83:             player->m_sampleDir = value;
Chris@81: 
Chris@83:             if (player->m_sampleSearchComplete) {
Chris@83:                 player->m_sampleSearchComplete = false;
Chris@83:                 player->searchSamples();
Chris@83:             }
Chris@75: 
Chris@83:             return 0;
Chris@83: 
Chris@83:         } else {
Chris@197:             char *buffer = (char *)malloc(strlen(value) + 80);
Chris@197:             sprintf(buffer, "Sample directory \"%s\" does not exist, leaving unchanged", value);
Chris@197:             return buffer;
Chris@75:         }
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@117: //	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@83:     std::cerr << "SamplePlayer::searchSamples: Directory is \""
Chris@83: 	      << m_sampleDir.toLocal8Bit().data() << "\"" << std::endl;
Chris@0: 
Chris@83:     QDir dir(m_sampleDir, "*.wav");
Chris@83:     
Chris@83:     for (unsigned int i = 0; i < dir.count(); ++i) {
Chris@83:         QFileInfo file(dir.filePath(dir[i]));
Chris@83:         if (file.isReadable()) {
Chris@83:             m_samples.push_back(std::pair<QString, QString>
Chris@83:                                 (file.baseName(), file.filePath()));
Chris@117: //            std::cerr << "Found: " << dir[i].toLocal8Bit().data() << std::endl;
Chris@75:         }
Chris@0:     }
Chris@83:     
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@143:     float ratio = 1.f;
Chris@143:     float gain = 1.f;
Chris@0:     unsigned long i, s;
Chris@0: 
Chris@0:     if (m_retune && *m_retune) {
Chris@143:         if (m_concertA) {
Chris@143:             ratio *= *m_concertA / 440.f;
Chris@143:         }
Chris@0: 	if (m_basePitch && n != *m_basePitch) {
Chris@143: 	    ratio *= powf(1.059463094f, 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: }