view audioio/ClipMixer.cpp @ 403:eb84b06301da

Restore the old prev/next layer commands (that were never enabled because they didn't work) using the new fixed order layer list (so they now do work)
author Chris Cannam
date Tue, 02 Sep 2014 16:06:41 +0100
parents f747be6743ab
children b0b49da13975
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006 Chris Cannam, 2006-2014 QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "ClipMixer.h"

#include <sndfile.h>
#include <cmath>

#include "base/Debug.h"

ClipMixer::ClipMixer(int channels, int sampleRate, int blockSize) :
    m_channels(channels),
    m_sampleRate(sampleRate),
    m_blockSize(blockSize),
    m_clipData(0)
{
}

ClipMixer::~ClipMixer()
{
    if (m_clipData) free(m_clipData);
}

void
ClipMixer::setChannelCount(int channels)
{
    m_channels = channels;
}

bool
ClipMixer::loadClipData(QString path, float f0, float level)
{
    if (m_clipData) {
        cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
        return false;
    }

    SF_INFO info;
    SNDFILE *file;
    float *tmpFrames;
    int i;

    info.format = 0;
    file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
    if (!file) {
	cerr << "ClipMixer::loadClipData: Failed to open file path \""
             << path << "\": " << sf_strerror(file) << endl;
	return false;
    }

    tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
    if (!tmpFrames) {
        cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
        return false;
    }

    sf_readf_float(file, tmpFrames, info.frames);
    sf_close(file);

    m_clipData = (float *)malloc(info.frames * sizeof(float));
    if (!m_clipData) {
        cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
	free(tmpFrames);
	return false;
    }

    for (i = 0; i < info.frames; ++i) {
	int j;
	m_clipData[i] = 0.0f;
	for (j = 0; j < info.channels; ++j) {
	    m_clipData[i] += tmpFrames[i * info.channels + j] * level;
	}
    }

    free(tmpFrames);

    m_clipLength = info.frames;
    m_clipF0 = f0;
    m_clipRate = info.samplerate;

    return true;
}

void
ClipMixer::reset()
{
    m_playing.clear();
}

float
ClipMixer::getResampleRatioFor(float frequency)
{
    if (!m_clipData) return 1.0;
    float pitchRatio = m_clipF0 / frequency;
    float resampleRatio = m_sampleRate / m_clipRate;
    return pitchRatio * resampleRatio;
}

int
ClipMixer::getResampledClipDuration(float frequency)
{
    return int(ceil(m_clipLength * getResampleRatioFor(frequency)));
}

void
ClipMixer::mix(float **toBuffers, 
               float gain,
               std::vector<NoteStart> newNotes, 
               std::vector<NoteEnd> endingNotes)
{
    foreach (NoteStart note, newNotes) {
        if (note.frequency > 20 && 
            note.frequency < 5000) {
            m_playing.push_back(note);
        }
    }

    std::vector<NoteStart> remaining;

    float *levels = new float[m_channels];

#ifdef DEBUG_CLIP_MIXER
    cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)"
         << " and " << endingNotes.size() << " note(s) ending here"
         << endl;
#endif

    foreach (NoteStart note, m_playing) {

        for (int c = 0; c < m_channels; ++c) {
            levels[c] = note.level * gain;
        }
        if (note.pan != 0.0 && m_channels == 2) {
            levels[0] *= 1.0 - note.pan;
            levels[1] *= note.pan + 1.0;
        }

        int start = note.frameOffset;
        int durationHere = m_blockSize;
        if (start > 0) durationHere = m_blockSize - start;

        bool ending = false;

        foreach (NoteEnd end, endingNotes) {
            if (end.frequency == note.frequency && 
                end.frameOffset >= start &&
                end.frameOffset <= m_blockSize) {
                ending = true;
                durationHere = end.frameOffset;
                if (start > 0) durationHere = end.frameOffset - start;
                break;
            }
        }

        int clipDuration = getResampledClipDuration(note.frequency);
        if (start + clipDuration > 0) {
            if (start < 0 && start + clipDuration < durationHere) {
                durationHere = start + clipDuration;
            }
            if (durationHere > 0) {
                mixNote(toBuffers,
                        levels,
                        note.frequency,
                        start < 0 ? -start : 0,
                        start > 0 ?  start : 0,
                        durationHere,
                        ending);
            }
        }

        if (!ending) {
            NoteStart adjusted = note;
            adjusted.frameOffset -= m_blockSize;
            remaining.push_back(adjusted);
        }
    }

    delete[] levels;

    m_playing = remaining;
}

void
ClipMixer::mixNote(float **toBuffers,
                   float *levels,
                   float frequency,
                   int sourceOffset,
                   int targetOffset,
                   int sampleCount,
                   bool isEnd)
{
    if (!m_clipData) return;

    float ratio = getResampleRatioFor(frequency);
    
    float releaseTime = 0.01;
    int releaseSampleCount = round(releaseTime * m_sampleRate);
    if (releaseSampleCount > sampleCount) {
        releaseSampleCount = sampleCount;
    }
    float releaseFraction = 1.f/releaseSampleCount;

    for (int i = 0; i < sampleCount; ++i) {

        int s = sourceOffset + i;

        float os = s / ratio;
        int osi = int(floor(os));

        //!!! just linear interpolation for now (same as SV's sample
        //!!! player). a small sinc kernel would be better and
        //!!! probably "good enough"
        float value = 0.f;
        if (osi < m_clipLength) {
            value += m_clipData[osi];
        }
        if (osi + 1 < m_clipLength) {
            value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi);
        }
         
        if (isEnd && i + releaseSampleCount > sampleCount) {
            value *= releaseFraction * (sampleCount - i); // linear ramp for release
        }

        for (int c = 0; c < m_channels; ++c) {
            toBuffers[c][targetOffset + i] += levels[c] * value;
        }
    }
}