svapp  1.9
ClipMixer.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2006 Chris Cannam, 2006-2014 QMUL.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "ClipMixer.h"
00017 
00018 #include <sndfile.h>
00019 #include <cmath>
00020 
00021 #include "base/Debug.h"
00022 
00023 ClipMixer::ClipMixer(int channels, int sampleRate, int blockSize) :
00024     m_channels(channels),
00025     m_sampleRate(sampleRate),
00026     m_blockSize(blockSize),
00027     m_clipData(0),
00028     m_clipLength(0),
00029     m_clipF0(0),
00030     m_clipRate(0)
00031 {
00032 }
00033 
00034 ClipMixer::~ClipMixer()
00035 {
00036     if (m_clipData) free(m_clipData);
00037 }
00038 
00039 void
00040 ClipMixer::setChannelCount(int channels)
00041 {
00042     m_channels = channels;
00043 }
00044 
00045 bool
00046 ClipMixer::loadClipData(QString path, float f0, float level)
00047 {
00048     if (m_clipData) {
00049         cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
00050         return false;
00051     }
00052 
00053     SF_INFO info;
00054     SNDFILE *file;
00055     float *tmpFrames;
00056     int i;
00057 
00058     info.format = 0;
00059     file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
00060     if (!file) {
00061         cerr << "ClipMixer::loadClipData: Failed to open file path \""
00062              << path << "\": " << sf_strerror(file) << endl;
00063         return false;
00064     }
00065 
00066     tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
00067     if (!tmpFrames) {
00068         cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
00069         return false;
00070     }
00071 
00072     sf_readf_float(file, tmpFrames, info.frames);
00073     sf_close(file);
00074 
00075     m_clipData = (float *)malloc(info.frames * sizeof(float));
00076     if (!m_clipData) {
00077         cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
00078         free(tmpFrames);
00079         return false;
00080     }
00081 
00082     for (i = 0; i < info.frames; ++i) {
00083         int j;
00084         m_clipData[i] = 0.0f;
00085         for (j = 0; j < info.channels; ++j) {
00086             m_clipData[i] += tmpFrames[i * info.channels + j] * level;
00087         }
00088     }
00089 
00090     free(tmpFrames);
00091 
00092     m_clipLength = info.frames;
00093     m_clipF0 = f0;
00094     m_clipRate = info.samplerate;
00095 
00096     return true;
00097 }
00098 
00099 void
00100 ClipMixer::reset()
00101 {
00102     m_playing.clear();
00103 }
00104 
00105 float
00106 ClipMixer::getResampleRatioFor(float frequency)
00107 {
00108     if (!m_clipData || !m_clipRate) return 1.0;
00109     float pitchRatio = m_clipF0 / frequency;
00110     float resampleRatio = m_sampleRate / m_clipRate;
00111     return pitchRatio * resampleRatio;
00112 }
00113 
00114 int
00115 ClipMixer::getResampledClipDuration(float frequency)
00116 {
00117     return int(ceil(m_clipLength * getResampleRatioFor(frequency)));
00118 }
00119 
00120 void
00121 ClipMixer::mix(float **toBuffers, 
00122                float gain,
00123                std::vector<NoteStart> newNotes, 
00124                std::vector<NoteEnd> endingNotes)
00125 {
00126     foreach (NoteStart note, newNotes) {
00127         if (note.frequency > 20 && 
00128             note.frequency < 5000) {
00129             m_playing.push_back(note);
00130         }
00131     }
00132 
00133     std::vector<NoteStart> remaining;
00134 
00135     float *levels = new float[m_channels];
00136 
00137 #ifdef DEBUG_CLIP_MIXER
00138     cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)"
00139          << " and " << endingNotes.size() << " note(s) ending here"
00140          << endl;
00141 #endif
00142 
00143     foreach (NoteStart note, m_playing) {
00144 
00145         for (int c = 0; c < m_channels; ++c) {
00146             levels[c] = note.level * gain;
00147         }
00148         if (note.pan != 0.0 && m_channels == 2) {
00149             levels[0] *= 1.0 - note.pan;
00150             levels[1] *= note.pan + 1.0;
00151         }
00152 
00153         int start = note.frameOffset;
00154         int durationHere = m_blockSize;
00155         if (start > 0) durationHere = m_blockSize - start;
00156 
00157         bool ending = false;
00158 
00159         foreach (NoteEnd end, endingNotes) {
00160             if (end.frequency == note.frequency && 
00161                 end.frameOffset >= start &&
00162                 end.frameOffset <= m_blockSize) {
00163                 ending = true;
00164                 durationHere = end.frameOffset;
00165                 if (start > 0) durationHere = end.frameOffset - start;
00166                 break;
00167             }
00168         }
00169 
00170         int clipDuration = getResampledClipDuration(note.frequency);
00171         if (start + clipDuration > 0) {
00172             if (start < 0 && start + clipDuration < durationHere) {
00173                 durationHere = start + clipDuration;
00174             }
00175             if (durationHere > 0) {
00176                 mixNote(toBuffers,
00177                         levels,
00178                         note.frequency,
00179                         start < 0 ? -start : 0,
00180                         start > 0 ?  start : 0,
00181                         durationHere,
00182                         ending);
00183             }
00184         }
00185 
00186         if (!ending) {
00187             NoteStart adjusted = note;
00188             adjusted.frameOffset -= m_blockSize;
00189             remaining.push_back(adjusted);
00190         }
00191     }
00192 
00193     delete[] levels;
00194 
00195     m_playing = remaining;
00196 }
00197 
00198 void
00199 ClipMixer::mixNote(float **toBuffers,
00200                    float *levels,
00201                    float frequency,
00202                    int sourceOffset,
00203                    int targetOffset,
00204                    int sampleCount,
00205                    bool isEnd)
00206 {
00207     if (!m_clipData) return;
00208 
00209     float ratio = getResampleRatioFor(frequency);
00210     
00211     float releaseTime = 0.01;
00212     int releaseSampleCount = round(releaseTime * m_sampleRate);
00213     if (releaseSampleCount > sampleCount) {
00214         releaseSampleCount = sampleCount;
00215     }
00216     float releaseFraction = 1.f/releaseSampleCount;
00217 
00218     for (int i = 0; i < sampleCount; ++i) {
00219 
00220         int s = sourceOffset + i;
00221 
00222         float os = s / ratio;
00223         int osi = int(floor(os));
00224 
00228         float value = 0.f;
00229         if (osi < m_clipLength) {
00230             value += m_clipData[osi];
00231         }
00232         if (osi + 1 < m_clipLength) {
00233             value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi);
00234         }
00235          
00236         if (isEnd && i + releaseSampleCount > sampleCount) {
00237             value *= releaseFraction * (sampleCount - i); // linear ramp for release
00238         }
00239 
00240         for (int c = 0; c < m_channels; ++c) {
00241             toBuffers[c][targetOffset + i] += levels[c] * value;
00242         }
00243     }
00244 }
00245 
00246