annotate audio/ClipMixer.cpp @ 596:c99892f0c5c3

Proper handling for notes that end at the same frame as a subsequent note of the same pitch begins. The note-off needs to be associated with the prior note, not a spurious zero-duration version of the subsequent note.
author Chris Cannam
date Wed, 18 Apr 2018 15:19:09 +0100
parents b23bebfdfaba
children e2715204feaa
rev   line source
Chris@305 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@305 2
Chris@305 3 /*
Chris@305 4 Sonic Visualiser
Chris@305 5 An audio file viewer and annotation editor.
Chris@305 6 Centre for Digital Music, Queen Mary, University of London.
Chris@305 7 This file copyright 2006 Chris Cannam, 2006-2014 QMUL.
Chris@305 8
Chris@305 9 This program is free software; you can redistribute it and/or
Chris@305 10 modify it under the terms of the GNU General Public License as
Chris@305 11 published by the Free Software Foundation; either version 2 of the
Chris@305 12 License, or (at your option) any later version. See the file
Chris@305 13 COPYING included with this distribution for more information.
Chris@305 14 */
Chris@305 15
Chris@305 16 #include "ClipMixer.h"
Chris@305 17
Chris@305 18 #include <sndfile.h>
Chris@310 19 #include <cmath>
Chris@305 20
Chris@305 21 #include "base/Debug.h"
Chris@305 22
Chris@442 23 //#define DEBUG_CLIP_MIXER 1
Chris@442 24
Chris@436 25 ClipMixer::ClipMixer(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize) :
Chris@305 26 m_channels(channels),
Chris@305 27 m_sampleRate(sampleRate),
Chris@305 28 m_blockSize(blockSize),
Chris@407 29 m_clipData(0),
Chris@407 30 m_clipLength(0),
Chris@407 31 m_clipF0(0),
Chris@407 32 m_clipRate(0)
Chris@305 33 {
Chris@305 34 }
Chris@305 35
Chris@305 36 ClipMixer::~ClipMixer()
Chris@305 37 {
Chris@317 38 if (m_clipData) free(m_clipData);
Chris@305 39 }
Chris@305 40
Chris@308 41 void
Chris@308 42 ClipMixer::setChannelCount(int channels)
Chris@308 43 {
Chris@308 44 m_channels = channels;
Chris@308 45 }
Chris@308 46
Chris@305 47 bool
Chris@436 48 ClipMixer::loadClipData(QString path, double f0, double level)
Chris@305 49 {
Chris@305 50 if (m_clipData) {
Chris@307 51 cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
Chris@305 52 return false;
Chris@305 53 }
Chris@305 54
Chris@305 55 SF_INFO info;
Chris@305 56 SNDFILE *file;
Chris@305 57 float *tmpFrames;
Chris@436 58 sv_frame_t i;
Chris@305 59
Chris@305 60 info.format = 0;
Chris@305 61 file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
Chris@305 62 if (!file) {
Chris@595 63 cerr << "ClipMixer::loadClipData: Failed to open file path \""
Chris@308 64 << path << "\": " << sf_strerror(file) << endl;
Chris@595 65 return false;
Chris@305 66 }
Chris@305 67
Chris@305 68 tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
Chris@305 69 if (!tmpFrames) {
Chris@307 70 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
Chris@305 71 return false;
Chris@305 72 }
Chris@305 73
Chris@305 74 sf_readf_float(file, tmpFrames, info.frames);
Chris@305 75 sf_close(file);
Chris@305 76
Chris@305 77 m_clipData = (float *)malloc(info.frames * sizeof(float));
Chris@305 78 if (!m_clipData) {
Chris@307 79 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
Chris@595 80 free(tmpFrames);
Chris@595 81 return false;
Chris@305 82 }
Chris@305 83
Chris@305 84 for (i = 0; i < info.frames; ++i) {
Chris@595 85 int j;
Chris@595 86 m_clipData[i] = 0.0f;
Chris@595 87 for (j = 0; j < info.channels; ++j) {
Chris@595 88 m_clipData[i] += tmpFrames[i * info.channels + j] * float(level);
Chris@595 89 }
Chris@305 90 }
Chris@305 91
Chris@305 92 free(tmpFrames);
Chris@305 93
Chris@305 94 m_clipLength = info.frames;
Chris@305 95 m_clipF0 = f0;
Chris@305 96 m_clipRate = info.samplerate;
Chris@310 97
Chris@310 98 return true;
Chris@305 99 }
Chris@307 100
Chris@307 101 void
Chris@308 102 ClipMixer::reset()
Chris@308 103 {
Chris@310 104 m_playing.clear();
Chris@310 105 }
Chris@310 106
Chris@436 107 double
Chris@436 108 ClipMixer::getResampleRatioFor(double frequency)
Chris@310 109 {
Chris@407 110 if (!m_clipData || !m_clipRate) return 1.0;
Chris@436 111 double pitchRatio = m_clipF0 / frequency;
Chris@436 112 double resampleRatio = m_sampleRate / m_clipRate;
Chris@310 113 return pitchRatio * resampleRatio;
Chris@310 114 }
Chris@310 115
Chris@436 116 sv_frame_t
Chris@436 117 ClipMixer::getResampledClipDuration(double frequency)
Chris@310 118 {
Chris@436 119 return sv_frame_t(ceil(double(m_clipLength) * getResampleRatioFor(frequency)));
Chris@308 120 }
Chris@308 121
Chris@308 122 void
Chris@307 123 ClipMixer::mix(float **toBuffers,
Chris@308 124 float gain,
Chris@307 125 std::vector<NoteStart> newNotes,
Chris@307 126 std::vector<NoteEnd> endingNotes)
Chris@307 127 {
Chris@310 128 foreach (NoteStart note, newNotes) {
matthiasm@321 129 if (note.frequency > 20 &&
matthiasm@322 130 note.frequency < 5000) {
matthiasm@321 131 m_playing.push_back(note);
matthiasm@321 132 }
Chris@310 133 }
Chris@310 134
Chris@310 135 std::vector<NoteStart> remaining;
Chris@310 136
Chris@310 137 float *levels = new float[m_channels];
Chris@310 138
Chris@397 139 #ifdef DEBUG_CLIP_MIXER
Chris@397 140 cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)"
Chris@397 141 << " and " << endingNotes.size() << " note(s) ending here"
Chris@397 142 << endl;
Chris@397 143 #endif
Chris@397 144
Chris@310 145 foreach (NoteStart note, m_playing) {
Chris@310 146
Chris@310 147 for (int c = 0; c < m_channels; ++c) {
Chris@345 148 levels[c] = note.level * gain;
Chris@310 149 }
Chris@310 150 if (note.pan != 0.0 && m_channels == 2) {
Chris@436 151 levels[0] *= 1.0f - note.pan;
Chris@436 152 levels[1] *= note.pan + 1.0f;
Chris@310 153 }
Chris@310 154
Chris@436 155 sv_frame_t start = note.frameOffset;
Chris@436 156 sv_frame_t durationHere = m_blockSize;
Chris@310 157 if (start > 0) durationHere = m_blockSize - start;
Chris@310 158
Chris@310 159 bool ending = false;
Chris@310 160
Chris@310 161 foreach (NoteEnd end, endingNotes) {
Chris@596 162 if (end.frequency == note.frequency &&
Chris@596 163 // This is > rather than >= because if we have a
Chris@596 164 // note-off and a note-on at the same time, the
Chris@596 165 // note-off must be switching off an earlier note-on,
Chris@596 166 // not the current one (zero-duration notes are
Chris@596 167 // forbidden earlier in the pipeline)
Chris@596 168 end.frameOffset > start &&
Chris@310 169 end.frameOffset <= m_blockSize) {
Chris@310 170 ending = true;
Chris@310 171 durationHere = end.frameOffset;
Chris@310 172 if (start > 0) durationHere = end.frameOffset - start;
Chris@310 173 break;
Chris@310 174 }
Chris@310 175 }
Chris@310 176
Chris@436 177 sv_frame_t clipDuration = getResampledClipDuration(note.frequency);
Chris@310 178 if (start + clipDuration > 0) {
Chris@310 179 if (start < 0 && start + clipDuration < durationHere) {
Chris@311 180 durationHere = start + clipDuration;
Chris@310 181 }
Chris@310 182 if (durationHere > 0) {
Chris@310 183 mixNote(toBuffers,
Chris@310 184 levels,
Chris@310 185 note.frequency,
Chris@310 186 start < 0 ? -start : 0,
Chris@310 187 start > 0 ? start : 0,
matthiasm@320 188 durationHere,
matthiasm@320 189 ending);
Chris@310 190 }
Chris@310 191 }
Chris@310 192
Chris@310 193 if (!ending) {
Chris@310 194 NoteStart adjusted = note;
Chris@310 195 adjusted.frameOffset -= m_blockSize;
Chris@310 196 remaining.push_back(adjusted);
Chris@310 197 }
Chris@310 198 }
Chris@310 199
Chris@313 200 delete[] levels;
Chris@313 201
Chris@310 202 m_playing = remaining;
Chris@307 203 }
Chris@307 204
Chris@310 205 void
Chris@310 206 ClipMixer::mixNote(float **toBuffers,
Chris@310 207 float *levels,
Chris@310 208 float frequency,
Chris@436 209 sv_frame_t sourceOffset,
Chris@436 210 sv_frame_t targetOffset,
Chris@436 211 sv_frame_t sampleCount,
matthiasm@320 212 bool isEnd)
Chris@310 213 {
Chris@310 214 if (!m_clipData) return;
Chris@310 215
Chris@436 216 double ratio = getResampleRatioFor(frequency);
Chris@310 217
Chris@436 218 double releaseTime = 0.01;
Chris@436 219 sv_frame_t releaseSampleCount = sv_frame_t(round(releaseTime * m_sampleRate));
matthiasm@320 220 if (releaseSampleCount > sampleCount) {
matthiasm@320 221 releaseSampleCount = sampleCount;
matthiasm@320 222 }
Chris@436 223 double releaseFraction = 1.0/double(releaseSampleCount);
Chris@310 224
Chris@436 225 for (sv_frame_t i = 0; i < sampleCount; ++i) {
Chris@310 226
Chris@436 227 sv_frame_t s = sourceOffset + i;
Chris@310 228
Chris@436 229 double os = double(s) / ratio;
Chris@436 230 sv_frame_t osi = sv_frame_t(floor(os));
Chris@310 231
Chris@310 232 //!!! just linear interpolation for now (same as SV's sample
Chris@310 233 //!!! player). a small sinc kernel would be better and
Chris@310 234 //!!! probably "good enough"
Chris@436 235 double value = 0.0;
Chris@310 236 if (osi < m_clipLength) {
Chris@310 237 value += m_clipData[osi];
Chris@310 238 }
Chris@310 239 if (osi + 1 < m_clipLength) {
Chris@436 240 value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - double(osi));
Chris@310 241 }
matthiasm@320 242
matthiasm@320 243 if (isEnd && i + releaseSampleCount > sampleCount) {
Chris@436 244 value *= releaseFraction * double(sampleCount - i); // linear ramp for release
matthiasm@320 245 }
matthiasm@320 246
Chris@310 247 for (int c = 0; c < m_channels; ++c) {
Chris@436 248 toBuffers[c][targetOffset + i] += float(levels[c] * value);
Chris@310 249 }
Chris@310 250 }
Chris@310 251 }
Chris@310 252
Chris@310 253