annotate audioio/ClipMixer.cpp @ 342:4eccff14b4d8 tonioni

Much fiddling toward getting sessions and individual audio files to load cleanly when they need quite different handling after load
author Chris Cannam
date Wed, 02 Apr 2014 21:25:56 +0100
parents 4fdf70be6671
children a4b8f0864835
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@305 23 ClipMixer::ClipMixer(int channels, int sampleRate, int blockSize) :
Chris@305 24 m_channels(channels),
Chris@305 25 m_sampleRate(sampleRate),
Chris@305 26 m_blockSize(blockSize),
Chris@305 27 m_clipData(0)
Chris@305 28 {
Chris@305 29 }
Chris@305 30
Chris@305 31 ClipMixer::~ClipMixer()
Chris@305 32 {
Chris@317 33 if (m_clipData) free(m_clipData);
Chris@305 34 }
Chris@305 35
Chris@308 36 void
Chris@308 37 ClipMixer::setChannelCount(int channels)
Chris@308 38 {
Chris@308 39 m_channels = channels;
Chris@308 40 }
Chris@308 41
Chris@305 42 bool
Chris@305 43 ClipMixer::loadClipData(QString path, float f0)
Chris@305 44 {
Chris@305 45 if (m_clipData) {
Chris@307 46 cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
Chris@305 47 return false;
Chris@305 48 }
Chris@305 49
Chris@305 50 SF_INFO info;
Chris@305 51 SNDFILE *file;
Chris@305 52 int sampleCount = 0;
Chris@305 53 float *tmpFrames;
Chris@305 54 size_t i;
Chris@305 55
Chris@305 56 info.format = 0;
Chris@305 57 file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
Chris@305 58 if (!file) {
Chris@308 59 cerr << "ClipMixer::loadClipData: Failed to open file path \""
Chris@308 60 << path << "\": " << sf_strerror(file) << endl;
Chris@305 61 return false;
Chris@305 62 }
Chris@305 63
Chris@305 64 tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
Chris@305 65 if (!tmpFrames) {
Chris@307 66 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
Chris@305 67 return false;
Chris@305 68 }
Chris@305 69
Chris@305 70 sf_readf_float(file, tmpFrames, info.frames);
Chris@305 71 sf_close(file);
Chris@305 72
Chris@305 73 m_clipData = (float *)malloc(info.frames * sizeof(float));
Chris@305 74 if (!m_clipData) {
Chris@307 75 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
Chris@305 76 free(tmpFrames);
Chris@305 77 return false;
Chris@305 78 }
Chris@305 79
Chris@305 80 for (i = 0; i < info.frames; ++i) {
Chris@305 81 int j;
Chris@305 82 m_clipData[i] = 0.0f;
Chris@305 83 for (j = 0; j < info.channels; ++j) {
Chris@305 84 m_clipData[i] += tmpFrames[i * info.channels + j];
Chris@305 85 }
Chris@305 86 }
Chris@305 87
Chris@305 88 free(tmpFrames);
Chris@305 89
Chris@305 90 m_clipLength = info.frames;
Chris@305 91 m_clipF0 = f0;
Chris@305 92 m_clipRate = info.samplerate;
Chris@310 93
Chris@310 94 return true;
Chris@305 95 }
Chris@307 96
Chris@307 97 void
Chris@308 98 ClipMixer::reset()
Chris@308 99 {
Chris@310 100 m_playing.clear();
Chris@310 101 }
Chris@310 102
Chris@310 103 float
Chris@310 104 ClipMixer::getResampleRatioFor(float frequency)
Chris@310 105 {
Chris@310 106 if (!m_clipData) return 1.0;
Chris@311 107 float pitchRatio = m_clipF0 / frequency;
Chris@310 108 float resampleRatio = m_sampleRate / m_clipRate;
Chris@310 109 return pitchRatio * resampleRatio;
Chris@310 110 }
Chris@310 111
Chris@310 112 int
Chris@310 113 ClipMixer::getResampledClipDuration(float frequency)
Chris@310 114 {
Chris@310 115 return int(ceil(m_clipLength * getResampleRatioFor(frequency)));
Chris@308 116 }
Chris@308 117
Chris@308 118 void
Chris@307 119 ClipMixer::mix(float **toBuffers,
Chris@308 120 float gain,
Chris@307 121 std::vector<NoteStart> newNotes,
Chris@307 122 std::vector<NoteEnd> endingNotes)
Chris@307 123 {
Chris@310 124 foreach (NoteStart note, newNotes) {
matthiasm@321 125 if (note.frequency > 20 &&
matthiasm@322 126 note.frequency < 5000) {
matthiasm@321 127 m_playing.push_back(note);
matthiasm@321 128 }
Chris@310 129 }
Chris@310 130
Chris@310 131 std::vector<NoteStart> remaining;
Chris@310 132
Chris@310 133 float *levels = new float[m_channels];
Chris@310 134
Chris@310 135 foreach (NoteStart note, m_playing) {
Chris@310 136
Chris@310 137 for (int c = 0; c < m_channels; ++c) {
Chris@310 138 levels[c] = gain;
Chris@310 139 }
Chris@310 140 if (note.pan != 0.0 && m_channels == 2) {
Chris@310 141 levels[0] *= 1.0 - note.pan;
Chris@310 142 levels[1] *= note.pan + 1.0;
Chris@310 143 }
Chris@310 144
Chris@310 145 int start = note.frameOffset;
Chris@310 146 int durationHere = m_blockSize;
Chris@310 147 if (start > 0) durationHere = m_blockSize - start;
Chris@310 148
Chris@310 149 bool ending = false;
Chris@310 150
Chris@310 151 foreach (NoteEnd end, endingNotes) {
Chris@310 152 if (end.frequency == note.frequency &&
Chris@310 153 end.frameOffset >= start &&
Chris@310 154 end.frameOffset <= m_blockSize) {
Chris@310 155 ending = true;
Chris@310 156 durationHere = end.frameOffset;
Chris@310 157 if (start > 0) durationHere = end.frameOffset - start;
Chris@310 158 break;
Chris@310 159 }
Chris@310 160 }
Chris@310 161
Chris@310 162 int clipDuration = getResampledClipDuration(note.frequency);
Chris@310 163 if (start + clipDuration > 0) {
Chris@310 164 if (start < 0 && start + clipDuration < durationHere) {
Chris@311 165 durationHere = start + clipDuration;
Chris@310 166 }
Chris@310 167 if (durationHere > 0) {
Chris@310 168 mixNote(toBuffers,
Chris@310 169 levels,
Chris@310 170 note.frequency,
Chris@310 171 start < 0 ? -start : 0,
Chris@310 172 start > 0 ? start : 0,
matthiasm@320 173 durationHere,
matthiasm@320 174 ending);
Chris@310 175 }
Chris@310 176 }
Chris@310 177
Chris@310 178 if (!ending) {
Chris@310 179 NoteStart adjusted = note;
Chris@310 180 adjusted.frameOffset -= m_blockSize;
Chris@310 181 remaining.push_back(adjusted);
Chris@310 182 }
Chris@310 183 }
Chris@310 184
Chris@313 185 delete[] levels;
Chris@313 186
Chris@310 187 m_playing = remaining;
Chris@307 188 }
Chris@307 189
Chris@310 190 void
Chris@310 191 ClipMixer::mixNote(float **toBuffers,
Chris@310 192 float *levels,
Chris@310 193 float frequency,
Chris@310 194 int sourceOffset,
Chris@310 195 int targetOffset,
matthiasm@320 196 int sampleCount,
matthiasm@320 197 bool isEnd)
Chris@310 198 {
Chris@310 199 if (!m_clipData) return;
Chris@310 200
Chris@310 201 float ratio = getResampleRatioFor(frequency);
Chris@310 202
matthiasm@320 203 float releaseTime = 0.01;
matthiasm@320 204 int releaseSampleCount = round(releaseTime * m_sampleRate);
matthiasm@320 205 if (releaseSampleCount > sampleCount) {
matthiasm@320 206 releaseSampleCount = sampleCount;
matthiasm@320 207 }
matthiasm@320 208 float releaseFraction = 1.f/releaseSampleCount;
Chris@310 209
Chris@310 210 for (int i = 0; i < sampleCount; ++i) {
Chris@310 211
Chris@310 212 int s = sourceOffset + i;
Chris@310 213
Chris@310 214 float os = s / ratio;
Chris@310 215 int osi = int(floor(os));
Chris@310 216
Chris@310 217 //!!! just linear interpolation for now (same as SV's sample
Chris@310 218 //!!! player). a small sinc kernel would be better and
Chris@310 219 //!!! probably "good enough"
Chris@310 220 float value = 0.f;
Chris@310 221 if (osi < m_clipLength) {
Chris@310 222 value += m_clipData[osi];
Chris@310 223 }
Chris@310 224 if (osi + 1 < m_clipLength) {
Chris@310 225 value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi);
Chris@310 226 }
matthiasm@320 227
matthiasm@320 228 if (isEnd && i + releaseSampleCount > sampleCount) {
matthiasm@320 229 value *= releaseFraction * (sampleCount - i); // linear ramp for release
matthiasm@320 230 }
matthiasm@320 231
Chris@310 232 for (int c = 0; c < m_channels; ++c) {
Chris@311 233 toBuffers[c][targetOffset + i] += levels[c] * value;
Chris@310 234 }
Chris@310 235 }
Chris@310 236 }
Chris@310 237
Chris@310 238