Chris@305: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@305: Chris@305: /* Chris@305: Sonic Visualiser Chris@305: An audio file viewer and annotation editor. Chris@305: Centre for Digital Music, Queen Mary, University of London. Chris@305: This file copyright 2006 Chris Cannam, 2006-2014 QMUL. Chris@305: Chris@305: This program is free software; you can redistribute it and/or Chris@305: modify it under the terms of the GNU General Public License as Chris@305: published by the Free Software Foundation; either version 2 of the Chris@305: License, or (at your option) any later version. See the file Chris@305: COPYING included with this distribution for more information. Chris@305: */ Chris@305: Chris@305: #include "ClipMixer.h" Chris@305: Chris@305: #include Chris@310: #include Chris@305: Chris@305: #include "base/Debug.h" Chris@305: Chris@305: ClipMixer::ClipMixer(int channels, int sampleRate, int blockSize) : Chris@305: m_channels(channels), Chris@305: m_sampleRate(sampleRate), Chris@305: m_blockSize(blockSize), Chris@305: m_clipData(0) Chris@305: { Chris@305: } Chris@305: Chris@305: ClipMixer::~ClipMixer() Chris@305: { Chris@317: if (m_clipData) free(m_clipData); Chris@305: } Chris@305: Chris@308: void Chris@308: ClipMixer::setChannelCount(int channels) Chris@308: { Chris@308: m_channels = channels; Chris@308: } Chris@308: Chris@305: bool Chris@349: ClipMixer::loadClipData(QString path, float f0, float level) Chris@305: { Chris@305: if (m_clipData) { Chris@307: cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl; Chris@305: return false; Chris@305: } Chris@305: Chris@305: SF_INFO info; Chris@305: SNDFILE *file; Chris@305: int sampleCount = 0; Chris@305: float *tmpFrames; Chris@305: size_t i; Chris@305: Chris@305: info.format = 0; Chris@305: file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); Chris@305: if (!file) { Chris@308: cerr << "ClipMixer::loadClipData: Failed to open file path \"" Chris@308: << path << "\": " << sf_strerror(file) << endl; Chris@305: return false; Chris@305: } Chris@305: Chris@305: tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); Chris@305: if (!tmpFrames) { Chris@307: cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl; Chris@305: return false; Chris@305: } Chris@305: Chris@305: sf_readf_float(file, tmpFrames, info.frames); Chris@305: sf_close(file); Chris@305: Chris@305: m_clipData = (float *)malloc(info.frames * sizeof(float)); Chris@305: if (!m_clipData) { Chris@307: cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl; Chris@305: free(tmpFrames); Chris@305: return false; Chris@305: } Chris@305: Chris@305: for (i = 0; i < info.frames; ++i) { Chris@305: int j; Chris@305: m_clipData[i] = 0.0f; Chris@305: for (j = 0; j < info.channels; ++j) { Chris@349: m_clipData[i] += tmpFrames[i * info.channels + j] * level; Chris@305: } Chris@305: } Chris@305: Chris@305: free(tmpFrames); Chris@305: Chris@305: m_clipLength = info.frames; Chris@305: m_clipF0 = f0; Chris@305: m_clipRate = info.samplerate; Chris@310: Chris@310: return true; Chris@305: } Chris@307: Chris@307: void Chris@308: ClipMixer::reset() Chris@308: { Chris@310: m_playing.clear(); Chris@310: } Chris@310: Chris@310: float Chris@310: ClipMixer::getResampleRatioFor(float frequency) Chris@310: { Chris@310: if (!m_clipData) return 1.0; Chris@311: float pitchRatio = m_clipF0 / frequency; Chris@310: float resampleRatio = m_sampleRate / m_clipRate; Chris@310: return pitchRatio * resampleRatio; Chris@310: } Chris@310: Chris@310: int Chris@310: ClipMixer::getResampledClipDuration(float frequency) Chris@310: { Chris@310: return int(ceil(m_clipLength * getResampleRatioFor(frequency))); Chris@308: } Chris@308: Chris@308: void Chris@307: ClipMixer::mix(float **toBuffers, Chris@308: float gain, Chris@307: std::vector newNotes, Chris@307: std::vector endingNotes) Chris@307: { Chris@310: foreach (NoteStart note, newNotes) { matthiasm@321: if (note.frequency > 20 && matthiasm@322: note.frequency < 5000) { matthiasm@321: m_playing.push_back(note); matthiasm@321: } Chris@310: } Chris@310: Chris@310: std::vector remaining; Chris@310: Chris@310: float *levels = new float[m_channels]; Chris@310: Chris@310: foreach (NoteStart note, m_playing) { Chris@310: Chris@310: for (int c = 0; c < m_channels; ++c) { Chris@345: levels[c] = note.level * gain; Chris@310: } Chris@310: if (note.pan != 0.0 && m_channels == 2) { Chris@310: levels[0] *= 1.0 - note.pan; Chris@310: levels[1] *= note.pan + 1.0; Chris@310: } Chris@310: Chris@310: int start = note.frameOffset; Chris@310: int durationHere = m_blockSize; Chris@310: if (start > 0) durationHere = m_blockSize - start; Chris@310: Chris@310: bool ending = false; Chris@310: Chris@310: foreach (NoteEnd end, endingNotes) { Chris@310: if (end.frequency == note.frequency && Chris@310: end.frameOffset >= start && Chris@310: end.frameOffset <= m_blockSize) { Chris@310: ending = true; Chris@310: durationHere = end.frameOffset; Chris@310: if (start > 0) durationHere = end.frameOffset - start; Chris@310: break; Chris@310: } Chris@310: } Chris@310: Chris@310: int clipDuration = getResampledClipDuration(note.frequency); Chris@310: if (start + clipDuration > 0) { Chris@310: if (start < 0 && start + clipDuration < durationHere) { Chris@311: durationHere = start + clipDuration; Chris@310: } Chris@310: if (durationHere > 0) { Chris@310: mixNote(toBuffers, Chris@310: levels, Chris@310: note.frequency, Chris@310: start < 0 ? -start : 0, Chris@310: start > 0 ? start : 0, matthiasm@320: durationHere, matthiasm@320: ending); Chris@310: } Chris@310: } Chris@310: Chris@310: if (!ending) { Chris@310: NoteStart adjusted = note; Chris@310: adjusted.frameOffset -= m_blockSize; Chris@310: remaining.push_back(adjusted); Chris@310: } Chris@310: } Chris@310: Chris@313: delete[] levels; Chris@313: Chris@310: m_playing = remaining; Chris@307: } Chris@307: Chris@310: void Chris@310: ClipMixer::mixNote(float **toBuffers, Chris@310: float *levels, Chris@310: float frequency, Chris@310: int sourceOffset, Chris@310: int targetOffset, matthiasm@320: int sampleCount, matthiasm@320: bool isEnd) Chris@310: { Chris@310: if (!m_clipData) return; Chris@310: Chris@310: float ratio = getResampleRatioFor(frequency); Chris@310: matthiasm@320: float releaseTime = 0.01; matthiasm@320: int releaseSampleCount = round(releaseTime * m_sampleRate); matthiasm@320: if (releaseSampleCount > sampleCount) { matthiasm@320: releaseSampleCount = sampleCount; matthiasm@320: } matthiasm@320: float releaseFraction = 1.f/releaseSampleCount; Chris@310: Chris@310: for (int i = 0; i < sampleCount; ++i) { Chris@310: Chris@310: int s = sourceOffset + i; Chris@310: Chris@310: float os = s / ratio; Chris@310: int osi = int(floor(os)); Chris@310: Chris@310: //!!! just linear interpolation for now (same as SV's sample Chris@310: //!!! player). a small sinc kernel would be better and Chris@310: //!!! probably "good enough" Chris@310: float value = 0.f; Chris@310: if (osi < m_clipLength) { Chris@310: value += m_clipData[osi]; Chris@310: } Chris@310: if (osi + 1 < m_clipLength) { Chris@310: value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi); Chris@310: } matthiasm@320: matthiasm@320: if (isEnd && i + releaseSampleCount > sampleCount) { matthiasm@320: value *= releaseFraction * (sampleCount - i); // linear ramp for release matthiasm@320: } matthiasm@320: Chris@310: for (int c = 0; c < m_channels; ++c) { Chris@311: toBuffers[c][targetOffset + i] += levels[c] * value; Chris@310: } Chris@310: } Chris@310: } Chris@310: Chris@310: