annotate audio/ClipMixer.cpp @ 570:6f54789f3127 3.0-integration

Fix race condition in first-time recording, where adding the recording wave model would prompt the audio play source to note that its channel count had increased (from 0 to, say, 2) and thus to cause the audio device to be reopened, stopping recording. Fix is to make this only happen if channel count increases beyond that of the device, which shouldn't happen in the recording case
author Chris Cannam
date Wed, 04 Jan 2017 11:48:03 +0000
parents 56acd9368532
children b23bebfdfaba
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@308 63 cerr << "ClipMixer::loadClipData: Failed to open file path \""
Chris@308 64 << path << "\": " << sf_strerror(file) << endl;
Chris@305 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@305 80 free(tmpFrames);
Chris@305 81 return false;
Chris@305 82 }
Chris@305 83
Chris@305 84 for (i = 0; i < info.frames; ++i) {
Chris@305 85 int j;
Chris@305 86 m_clipData[i] = 0.0f;
Chris@305 87 for (j = 0; j < info.channels; ++j) {
Chris@436 88 m_clipData[i] += tmpFrames[i * info.channels + j] * float(level);
Chris@305 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@310 162 if (end.frequency == note.frequency &&
Chris@310 163 end.frameOffset >= start &&
Chris@310 164 end.frameOffset <= m_blockSize) {
Chris@310 165 ending = true;
Chris@310 166 durationHere = end.frameOffset;
Chris@310 167 if (start > 0) durationHere = end.frameOffset - start;
Chris@310 168 break;
Chris@310 169 }
Chris@310 170 }
Chris@310 171
Chris@436 172 sv_frame_t clipDuration = getResampledClipDuration(note.frequency);
Chris@310 173 if (start + clipDuration > 0) {
Chris@310 174 if (start < 0 && start + clipDuration < durationHere) {
Chris@311 175 durationHere = start + clipDuration;
Chris@310 176 }
Chris@310 177 if (durationHere > 0) {
Chris@310 178 mixNote(toBuffers,
Chris@310 179 levels,
Chris@310 180 note.frequency,
Chris@310 181 start < 0 ? -start : 0,
Chris@310 182 start > 0 ? start : 0,
matthiasm@320 183 durationHere,
matthiasm@320 184 ending);
Chris@310 185 }
Chris@310 186 }
Chris@310 187
Chris@310 188 if (!ending) {
Chris@310 189 NoteStart adjusted = note;
Chris@310 190 adjusted.frameOffset -= m_blockSize;
Chris@310 191 remaining.push_back(adjusted);
Chris@310 192 }
Chris@310 193 }
Chris@310 194
Chris@313 195 delete[] levels;
Chris@313 196
Chris@310 197 m_playing = remaining;
Chris@307 198 }
Chris@307 199
Chris@310 200 void
Chris@310 201 ClipMixer::mixNote(float **toBuffers,
Chris@310 202 float *levels,
Chris@310 203 float frequency,
Chris@436 204 sv_frame_t sourceOffset,
Chris@436 205 sv_frame_t targetOffset,
Chris@436 206 sv_frame_t sampleCount,
matthiasm@320 207 bool isEnd)
Chris@310 208 {
Chris@310 209 if (!m_clipData) return;
Chris@310 210
Chris@436 211 double ratio = getResampleRatioFor(frequency);
Chris@310 212
Chris@436 213 double releaseTime = 0.01;
Chris@436 214 sv_frame_t releaseSampleCount = sv_frame_t(round(releaseTime * m_sampleRate));
matthiasm@320 215 if (releaseSampleCount > sampleCount) {
matthiasm@320 216 releaseSampleCount = sampleCount;
matthiasm@320 217 }
Chris@436 218 double releaseFraction = 1.0/double(releaseSampleCount);
Chris@310 219
Chris@436 220 for (sv_frame_t i = 0; i < sampleCount; ++i) {
Chris@310 221
Chris@436 222 sv_frame_t s = sourceOffset + i;
Chris@310 223
Chris@436 224 double os = double(s) / ratio;
Chris@436 225 sv_frame_t osi = sv_frame_t(floor(os));
Chris@310 226
Chris@310 227 //!!! just linear interpolation for now (same as SV's sample
Chris@310 228 //!!! player). a small sinc kernel would be better and
Chris@310 229 //!!! probably "good enough"
Chris@436 230 double value = 0.0;
Chris@310 231 if (osi < m_clipLength) {
Chris@310 232 value += m_clipData[osi];
Chris@310 233 }
Chris@310 234 if (osi + 1 < m_clipLength) {
Chris@436 235 value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - double(osi));
Chris@310 236 }
matthiasm@320 237
matthiasm@320 238 if (isEnd && i + releaseSampleCount > sampleCount) {
Chris@436 239 value *= releaseFraction * double(sampleCount - i); // linear ramp for release
matthiasm@320 240 }
matthiasm@320 241
Chris@310 242 for (int c = 0; c < m_channels; ++c) {
Chris@436 243 toBuffers[c][targetOffset + i] += float(levels[c] * value);
Chris@310 244 }
Chris@310 245 }
Chris@310 246 }
Chris@310 247
Chris@310 248