/* ZynAddSubFX - a software synthesizer Unison.cpp - Unison effect (multivoice chorus) Copyright (C) 2002-2009 Nasca Octavian Paul Author: Nasca Octavian Paul This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License (version 2 or later) for more details. You should have received a copy of the GNU General Public License (version 2) along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "../Misc/Allocator.h" #include "Unison.h" #include "globals.h" #ifndef errx #include #endif Unison::Unison(Allocator *alloc_, int update_period_samples_, float max_delay_sec_, float srate_f) :unison_size(0), base_freq(1.0f), uv(NULL), update_period_samples(update_period_samples_), update_period_sample_k(0), max_delay((int)(srate_f * max_delay_sec_) + 1), delay_k(0), first_time(false), delay_buffer(NULL), unison_amplitude_samples(0.0f), unison_bandwidth_cents(10.0f), samplerate_f(srate_f), alloc(*alloc_) { if(max_delay < 10) max_delay = 10; delay_buffer = alloc.valloc(max_delay); memset(delay_buffer, 0, max_delay * sizeof(float)); setSize(1); } Unison::~Unison() { alloc.devalloc(delay_buffer); alloc.devalloc(uv); } void Unison::setSize(int new_size) { if(new_size < 1) new_size = 1; unison_size = new_size; alloc.devalloc(uv); uv = alloc.valloc(unison_size); first_time = true; updateParameters(); } void Unison::setBaseFrequency(float freq) { base_freq = freq; updateParameters(); } void Unison::setBandwidth(float bandwidth) { if(bandwidth < 0) bandwidth = 0.0f; if(bandwidth > 1200.0f) bandwidth = 1200.0f; /* If the bandwidth is too small, the audio may cancel itself out * (due to the sign change of the outputs) * TODO figure out the acceptable lower bound and codify it */ unison_bandwidth_cents = bandwidth; updateParameters(); } void Unison::updateParameters(void) { if(!uv) return; float increments_per_second = samplerate_f / (float) update_period_samples; // printf("#%g, %g\n",increments_per_second,base_freq); for(int i = 0; i < unison_size; ++i) { float base = powf(UNISON_FREQ_SPAN, SYNTH_T::numRandom() * 2.0f - 1.0f); uv[i].relative_amplitude = base; float period = base / base_freq; float m = 4.0f / (period * increments_per_second); if(SYNTH_T::numRandom() < 0.5f) m = -m; uv[i].step = m; // printf("%g %g\n",uv[i].relative_amplitude,period); } float max_speed = powf(2.0f, unison_bandwidth_cents / 1200.0f); unison_amplitude_samples = 0.125f * (max_speed - 1.0f) * samplerate_f / base_freq; //If functions exceed this limit, they should have requested a bigguer delay //and thus are buggy if(unison_amplitude_samples >= max_delay - 1) { warnx("BUG: Unison amplitude samples too big"); warnx("Unision max_delay should be larger"); unison_amplitude_samples = max_delay - 2; } updateUnisonData(); } void Unison::process(int bufsize, float *inbuf, float *outbuf) { if(!uv) return; if(!outbuf) outbuf = inbuf; float volume = 1.0f / sqrtf(unison_size); float xpos_step = 1.0f / (float) update_period_samples; float xpos = (float) update_period_sample_k * xpos_step; for(int i = 0; i < bufsize; ++i) { if(update_period_sample_k++ >= update_period_samples) { updateUnisonData(); update_period_sample_k = 0; xpos = 0.0f; } xpos += xpos_step; float in = inbuf[i], out = 0.0f; float sign = 1.0f; for(int k = 0; k < unison_size; ++k) { float vpos = uv[k].realpos1 * (1.0f - xpos) + uv[k].realpos2 * xpos; //optimize float pos = (float)(delay_k + max_delay) - vpos - 1.0f; int posi; F2I(pos, posi); //optimize! int posi_next = posi + 1; if(posi >= max_delay) posi -= max_delay; if(posi_next >= max_delay) posi_next -= max_delay; float posf = pos - floorf(pos); out += ((1.0f - posf) * delay_buffer[posi] + posf * delay_buffer[posi_next]) * sign; sign = -sign; } outbuf[i] = out * volume; // printf("%d %g\n",i,outbuf[i]); delay_buffer[delay_k] = in; delay_k = (++delay_k < max_delay) ? delay_k : 0; } } void Unison::updateUnisonData() { if(!uv) return; for(int k = 0; k < unison_size; ++k) { float pos = uv[k].position; float step = uv[k].step; pos += step; if(pos <= -1.0f) { pos = -1.0f; step = -step; } else if(pos >= 1.0f) { pos = 1.0f; step = -step; } float vibratto_val = (pos - 0.333333333f * pos * pos * pos) * 1.5f; //make the vibratto lfo smoother //Relative amplitude is utilized, so the delay may be larger than the //whole buffer, if the buffer is too small, this indicates a buggy call //to Unison() float newval = 1.0f + 0.5f * (vibratto_val + 1.0f) * unison_amplitude_samples * uv[k].relative_amplitude; if(first_time) uv[k].realpos1 = uv[k].realpos2 = newval; else { uv[k].realpos1 = uv[k].realpos2; uv[k].realpos2 = newval; } uv[k].position = pos; uv[k].step = step; } first_time = false; }