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@442: //#define DEBUG_CLIP_MIXER 1 Chris@442: Chris@436: ClipMixer::ClipMixer(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize) : Chris@305: m_channels(channels), Chris@305: m_sampleRate(sampleRate), Chris@305: m_blockSize(blockSize), Chris@636: m_clipData(nullptr), Chris@407: m_clipLength(0), Chris@407: m_clipF0(0), Chris@407: m_clipRate(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@436: ClipMixer::loadClipData(QString path, double f0, double 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: float *tmpFrames; Chris@436: sv_frame_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@595: cerr << "ClipMixer::loadClipData: Failed to open file path \"" Chris@308: << path << "\": " << sf_strerror(file) << endl; Chris@595: 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@595: free(tmpFrames); Chris@595: return false; Chris@305: } Chris@305: Chris@305: for (i = 0; i < info.frames; ++i) { Chris@595: int j; Chris@595: m_clipData[i] = 0.0f; Chris@595: for (j = 0; j < info.channels; ++j) { Chris@595: m_clipData[i] += tmpFrames[i * info.channels + j] * float(level); Chris@595: } 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@436: double Chris@436: ClipMixer::getResampleRatioFor(double frequency) Chris@310: { Chris@407: if (!m_clipData || !m_clipRate) return 1.0; Chris@436: double pitchRatio = m_clipF0 / frequency; Chris@436: double resampleRatio = m_sampleRate / m_clipRate; Chris@310: return pitchRatio * resampleRatio; Chris@310: } Chris@310: Chris@436: sv_frame_t Chris@436: ClipMixer::getResampledClipDuration(double frequency) Chris@310: { Chris@436: return sv_frame_t(ceil(double(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@397: #ifdef DEBUG_CLIP_MIXER Chris@397: cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)" Chris@397: << " and " << endingNotes.size() << " note(s) ending here" Chris@397: << endl; Chris@397: #endif Chris@397: 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@436: levels[0] *= 1.0f - note.pan; Chris@436: levels[1] *= note.pan + 1.0f; Chris@310: } Chris@310: Chris@436: sv_frame_t start = note.frameOffset; Chris@436: sv_frame_t 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@596: if (end.frequency == note.frequency && Chris@596: // This is > rather than >= because if we have a Chris@596: // note-off and a note-on at the same time, the Chris@596: // note-off must be switching off an earlier note-on, Chris@596: // not the current one (zero-duration notes are Chris@596: // forbidden earlier in the pipeline) Chris@596: 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@436: sv_frame_t 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@436: sv_frame_t sourceOffset, Chris@436: sv_frame_t targetOffset, Chris@436: sv_frame_t sampleCount, matthiasm@320: bool isEnd) Chris@310: { Chris@310: if (!m_clipData) return; Chris@310: Chris@436: double ratio = getResampleRatioFor(frequency); Chris@310: Chris@436: double releaseTime = 0.01; Chris@436: sv_frame_t releaseSampleCount = sv_frame_t(round(releaseTime * m_sampleRate)); matthiasm@320: if (releaseSampleCount > sampleCount) { matthiasm@320: releaseSampleCount = sampleCount; matthiasm@320: } Chris@436: double releaseFraction = 1.0/double(releaseSampleCount); Chris@310: Chris@436: for (sv_frame_t i = 0; i < sampleCount; ++i) { Chris@310: Chris@436: sv_frame_t s = sourceOffset + i; Chris@310: Chris@436: double os = double(s) / ratio; Chris@436: sv_frame_t osi = sv_frame_t(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@436: double value = 0.0; Chris@310: if (osi < m_clipLength) { Chris@310: value += m_clipData[osi]; Chris@310: } Chris@310: if (osi + 1 < m_clipLength) { Chris@436: value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - double(osi)); Chris@310: } matthiasm@320: matthiasm@320: if (isEnd && i + releaseSampleCount > sampleCount) { Chris@436: value *= releaseFraction * double(sampleCount - i); // linear ramp for release matthiasm@320: } matthiasm@320: Chris@310: for (int c = 0; c < m_channels; ++c) { Chris@436: toBuffers[c][targetOffset + i] += float(levels[c] * value); Chris@310: } Chris@310: } Chris@310: } Chris@310: Chris@310: