zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

Reverse.cpp (10550B)


      1 /*
      2   ZynAddSubFX - a software synthesizer
      3 
      4   Reverse.cpp - Reverse Delay Effect
      5   Copyright (C) 2023-2024 Michael Kirchner
      6   Author: Michael Kirchner
      7 
      8   This program is free software; you can redistribute it and/or
      9   modify it under the terms of the GNU General Public License
     10   as published by the Free Software Foundation; either version 2
     11   of the License, or (at your option) any later version.
     12 */
     13 
     14 #include <cmath>
     15 #include <rtosc/ports.h>
     16 #include <rtosc/port-sugar.h>
     17 #include "../DSP/Reverter.h"
     18 #include "../Misc/Allocator.h"
     19 #include "../Misc/Util.h"
     20 #include "../Misc/Time.h"
     21 #include "Reverse.h"
     22 
     23 namespace zyn {
     24 #define HZ2BPM 60.0f // frequency (Hz) * HZ2BPM = frequency (BPM)
     25 
     26 #define rObject Reverse
     27 #define rBegin [](const char *msg, rtosc::RtData &d) {
     28 #define rEnd }
     29 
     30 rtosc::Ports Reverse::ports = {
     31     {"preset::i", rProp(parameter)
     32               rOptions(noteon, noteonoff, auto)
     33               rProp(alias)
     34               rShort("preset")
     35               rDefault(0)
     36               rDoc("Instrument Presets"), 0,
     37               rBegin;
     38               rObject *o = (rObject*)d.obj;
     39               if(rtosc_narguments(msg))
     40                   o->setpreset(rtosc_argument(msg, 0).i);
     41               else
     42                   d.reply(d.loc, "i", o->Ppreset);
     43               rEnd},
     44     rPresetForVolume,
     45     rEffParVol(rDefaultDepends(presetOfVolume),
     46             rPresets(32, 32, 32),
     47             rPresetsAt(16, 64, 64, 64)),
     48     rEffParPan(),
     49     rEffPar(Pdelay,   2, rShort("delay"), rDefault(25),
     50             "Length of Reversed Segment"),
     51     rEffParTF(Pstereo,3, rShort("stereo"), rDefault(false),
     52             "Enable Stereo Processing"),
     53     rEffPar(Pphase,   4, rShort("phase"), rDefault(64),
     54             "Phase offset for Reversed Segment"),
     55     rEffPar(Pcrossfade, 5, rShort("fade"), rPresets(32, 16, 50), rUnit(1/100 s),
     56             "Cross Fade Time between Reversed Segments"),
     57     rEffParOpt(Psyncmode,    6, rShort("mode"), rPresets(NOTEON, NOTEONOFF, AUTO),
     58             rOptions(SYNCMODES),
     59             "Sync Mode"),
     60 };
     61 #undef rBegin
     62 #undef rEnd
     63 #undef rObject
     64 
     65 Reverse::Reverse(EffectParams pars, const AbsTime *time_)
     66     :Effect(pars),Pvolume(insertion?64:32),Pdelay(25),Pphase(64), Pcrossfade(32),
     67      PsyncMode(Reverter::SyncMode::NOTEON), Pstereo(0),time(time_), tick_hist(0)
     68 {
     69     float tRef = float(time->time());
     70     reverterL = memory.alloc<Reverter>(&memory, float(Pdelay+1)/128.0f*MAX_REV_DELAY_SECONDS, samplerate, buffersize, tRef, time);
     71     reverterR = memory.alloc<Reverter>(&memory, float(Pdelay+1)/128.0f*MAX_REV_DELAY_SECONDS, samplerate, buffersize, tRef, time);
     72     setpanning(64);
     73     setvolume(Pvolume);
     74     tick_at_host_buffer_start = (time->beat - 1) * time->ppq + time->tick;
     75     playing_hist = false;
     76 }
     77 
     78 Reverse::~Reverse()
     79 {
     80     memory.dealloc(reverterL);
     81     memory.dealloc(reverterR);
     82 }
     83 
     84 //Cleanup the effect
     85 void Reverse::cleanup(void)
     86 {
     87     reverterR->reset();
     88     reverterL->reset();
     89 }
     90 
     91 //Effect output
     92 void Reverse::out(const Stereo<float *> &input)
     93 {
     94     // prepare the processing buffers
     95     if(Pstereo) //Stereo
     96         for(int i = 0; i < buffersize; ++i) {
     97             efxoutl[i] = input.l[i] * pangainL;
     98             efxoutr[i] = input.r[i] * pangainR;
     99         }
    100     else //Mono
    101         for(int i = 0; i < buffersize; ++i)
    102             efxoutl[i] = (input.l[i] * pangainL + input.r[i] * pangainR);
    103 
    104     // process external timecode for syncing to host beat
    105     // but only if we have timing info, speedfactor is set and we are in host mode.
    106     if (time->tempo && speedfactor && (PsyncMode == Reverter::SyncMode::HOST)) {
    107         // in host mode we want to find out if (condition) and when (position) a beat happens inside the buffer
    108         // and call sync at that position
    109         // condition: at the end of the buffer: ticks % ticks_per_beat < ticks_per buffer
    110         // position: ticks % ticks_per_beat
    111         //
    112         // to complicate things:
    113         // when running as plugin, the host may have a larger buffer than buffersize.
    114         // in this case the processing function is called multiple times for one host buffer
    115         // and we have to interpolate the ticks for each internal buffer
    116         //
    117         // 1:01:00 (start of "song")       1:02:00                internal buffer end
    118         // |                                                           |
    119         // |-------------------------------|-------------------------------|
    120         //               1 beat
    121         //
    122         //
    123         // |-----------------------------------------------------------|
    124         //         "tick"
    125         //                                 |--"ticks_since_last_beat"--|
    126         //
    127         //              |---"internal_buffer_ticks"------------------| if beat change inside internal buffer
    128         //                                 |-"internal_buffer_ticks"-| if beat change not inside internal buffer
    129 
    130 
    131         // number of ticks during the length of one internal buffer
    132         const float internal_buffer_ticks = ((float)buffersize / (float)samplerate) * // seconds per host buffer *
    133                                             ((float)time->tempo / HZ2BPM) *     // beats per second        *
    134                                             (float)time->ppq;                  // ticks per beat = ticks per buffer
    135 
    136         // check if there is new timing information
    137         // that indicates a new host buffer
    138         // therefore we reset the current subbuffer index.
    139         // and calculate the new tick at time of host buffer start
    140         if(time->tick != tick_hist) {
    141             currentSubbufferIndex = 0;
    142             tick_hist = time->tick;
    143             tick_at_host_buffer_start = (((time->bar - 1) * time->beatsPerBar) + (time->beat - 1)) * time->ppq + time->tick;
    144             if(time->playing != playing_hist) {
    145                 reverterL->sync(0);
    146                 if (Pstereo) reverterR->sync(0);
    147             }
    148             playing_hist = time->playing;
    149 
    150         }
    151         else
    152             currentSubbufferIndex++;
    153 
    154         {
    155             // tick offset from the host buffer to the current internal buffer
    156             const float tick_offset = internal_buffer_ticks * (float)currentSubbufferIndex;
    157             // tick at time of internal buffer start
    158             const float tick_at_buffer_start = tick_at_host_buffer_start + tick_offset;
    159             // now calculate the tick at time of internal buffer end
    160             // this is needed to determine if a beat change will happen during this internal buffer
    161 
    162             tick = tick_at_buffer_start + internal_buffer_ticks;
    163         }
    164 
    165 
    166         const float ticks_per_beat = (float)time->ppq / speedfactor;
    167         const float ticks_since_last_beat = fmodf(tick, ticks_per_beat);
    168         if(ticks_since_last_beat < internal_buffer_ticks) { // Ensure beat is inside the buffer
    169             const float syncPos = ( (internal_buffer_ticks - ticks_since_last_beat) / (float)internal_buffer_ticks) * (float)buffersize;
    170             reverterL->sync(syncPos);
    171             if (Pstereo) reverterR->sync(syncPos);
    172         }
    173     }
    174 
    175     // do the actual processing
    176     reverterL->filterout(efxoutl);
    177     if(Pstereo) reverterR->filterout(efxoutr);
    178     else memcpy(efxoutr, efxoutl, bufferbytes);
    179 }
    180 
    181 void Reverse::update()
    182 {
    183     using SyncMode = Reverter::SyncMode;
    184     // process noteon trigger
    185     if( (PsyncMode == SyncMode::NOTEON || PsyncMode == SyncMode::NOTEONOFF) ) {
    186         reverterL->sync(0.0f);
    187         if(Pstereo) reverterR->sync(0.0f);
    188     }
    189 }
    190 //Parameter control
    191 void Reverse::setvolume(unsigned char _Pvolume)
    192 {
    193     Pvolume = _Pvolume;
    194 
    195     if(insertion == 0) {
    196         if (Pvolume == 0) {
    197             outvolume = 0.0f;
    198         } else {
    199             outvolume = powf(0.01f, (1.0f - Pvolume / 127.0f)) * 4.0f;
    200         }
    201         volume    = 1.0f;
    202     }
    203     else
    204         volume = outvolume = Pvolume / 127.0f;
    205     if(Pvolume == 0)
    206         cleanup();
    207 }
    208 
    209 void Reverse::setdelay(unsigned char value)
    210 {
    211     Pdelay = limit(value,static_cast<unsigned char>(0),static_cast<unsigned char>(127));
    212     reverterL->setdelay(float(Pdelay+1)/128.0f*MAX_REV_DELAY_SECONDS);
    213     reverterR->setdelay(float(Pdelay+1)/128.0f*MAX_REV_DELAY_SECONDS);
    214 }
    215 
    216 void Reverse::setphase(unsigned char value)
    217 {
    218     Pphase = value;
    219     reverterL->setphase(float(Pphase)/127.0f);
    220     reverterR->setphase(float(Pphase)/127.0f);
    221 }
    222 
    223 void Reverse::setcrossfade(unsigned char value)
    224 {
    225     Pcrossfade = value;
    226     reverterL->setcrossfade(float(value+1)/100.0f);
    227     reverterR->setcrossfade(float(value+1)/100.0f);
    228 }
    229 
    230 void Reverse::setsyncMode(unsigned char value)
    231 {
    232     PsyncMode = value;
    233     using SyncMode = Reverter::SyncMode;
    234     reverterL->setsyncMode((SyncMode)value);
    235     reverterR->setsyncMode((SyncMode)value);
    236 }
    237 
    238 unsigned char Reverse::getpresetpar(unsigned char npreset, unsigned int npar)
    239 {
    240 #define	PRESET_SIZE 7
    241 #define	NUM_PRESETS 3
    242     static const unsigned char presets[NUM_PRESETS][PRESET_SIZE] = {
    243         //NOTEON
    244         {64, 64, 25, 0, 64,   32, Reverter::NOTEON},
    245         //NOTEONOFF
    246         {64, 64, 25, 0, 64,   16, Reverter::NOTEONOFF},
    247         //AUTO
    248         {64, 64, 25, 0, 64,   50, Reverter::AUTO}
    249     };
    250     if(npreset < NUM_PRESETS && npar < PRESET_SIZE) {
    251         if (npar == 0 && insertion == 0) {
    252             /* lower the volume if this is system effect */
    253             return presets[npreset][npar] / 2;
    254         }
    255         return presets[npreset][npar];
    256     }
    257     return 0;
    258 }
    259 
    260 void Reverse::setpreset(unsigned char npreset)
    261 {
    262     if(npreset >= NUM_PRESETS)
    263         npreset = NUM_PRESETS - 1;
    264     for(int n = 0; n != 128; n++)
    265         changepar(n, getpresetpar(npreset, n));
    266     Ppreset = npreset;
    267 }
    268 
    269 void Reverse::changepar(int npar, unsigned char value)
    270 {
    271     switch(npar) {
    272         case 0:
    273             setvolume(value);
    274             break;
    275         case 1:
    276             setpanning(value);
    277             break;
    278         case 2:
    279             setdelay(value);
    280             break;
    281         case 3:
    282             Pstereo = (value > 1) ? 1 : value;
    283             break;
    284         case 4:
    285             setphase(value);
    286             break;
    287         case 5:
    288             setcrossfade(value);
    289             break;
    290         case 6:
    291             setsyncMode(value);
    292             break;
    293     }
    294 }
    295 
    296 unsigned char Reverse::getpar(int npar) const
    297 {
    298     switch(npar) {
    299         case 0:  return Pvolume;
    300         case 1:  return Ppanning;
    301         case 2:  return Pdelay;
    302         case 3:  return Pstereo;
    303         case 4:  return Pphase;
    304         case 5:  return Pcrossfade;
    305         case 6:  return PsyncMode;
    306         default: return 0; // in case of bogus parameter number
    307     }
    308 }
    309 
    310 }