annotate audio/ClipMixer.cpp @ 489:111e976f9ed4 3.0-integration

Merge from branch "recording"
author Chris Cannam
date Thu, 17 Sep 2015 13:46:07 +0100
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