aboutsummaryrefslogtreecommitdiffstats
path: root/src/mdaLooplex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mdaLooplex.cpp')
-rw-r--r--src/mdaLooplex.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/src/mdaLooplex.cpp b/src/mdaLooplex.cpp
new file mode 100644
index 0000000..33c5e29
--- /dev/null
+++ b/src/mdaLooplex.cpp
@@ -0,0 +1,585 @@
+//
+// Plug-in: "mdaLooplex" v1.0
+//
+// Copyright(c)1999-2000 Paul Kellett (maxim digital audio)
+//
+
+#include "mdaLooplex.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+
+AudioEffect *createEffectInstance(audioMasterCallback audioMaster)
+{
+ return new mdaLooplex(audioMaster);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//replacement for fxIdle that Steinberg stupidly removed from VST2.4 forcing loads of plug-ins to set up their own timers...
+
+#define IDLE_MSEC 40 //25 Hz
+
+class IdleList //wzDSP objects to receive idle timer (same timer is used for all instances)
+{
+public:
+ IdleList(mdaLooplex *effect, IdleList *next);
+
+ mdaLooplex *effect;
+ IdleList *next;
+ bool remove;
+
+} static idleList(0, 0); //idleList->next points to start of idle list
+
+
+#if _WIN32
+ #include <windows.h>
+ #pragma comment(lib, "user32.lib")
+ static UINT timer = 0;
+ VOID CALLBACK TimerCallback(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
+#else //OSX
+ #include <Carbon/Carbon.h>
+ EventLoopTimerRef timer = 0;
+ pascal void TimerCallback(EventLoopTimerRef timerRef, void *userData)
+#endif
+{
+ IdleList *prev = &idleList;
+ IdleList *item = prev->next;
+ while(item)
+ {
+ if(item->remove)
+ {
+ prev->next = item->next; //remove item from list and close gap
+ delete item;
+ item = prev->next;
+
+ if(prev == &idleList && prev->next == 0) //stop timer after last item removed
+ {
+ #if _WIN32
+ KillTimer(NULL, timer);
+ #else //OSX
+ RemoveEventLoopTimer(timer);
+ #endif
+ timer = 0;
+ }
+ }
+ else
+ {
+ item->effect->idle(); //call idle() for each item in list
+ prev = item;
+ item = item->next;
+ }
+ }
+}
+
+
+IdleList::IdleList(mdaLooplex *effect, IdleList *next) : effect(effect), next(next), remove(false)
+{
+ if(effect && !timer) //start timer
+ {
+ #if WIN32
+ timer = SetTimer(NULL, 0, IDLE_MSEC, TimerCallback);
+ #else //OSX
+ double ms = kEventDurationMillisecond * (double)IDLE_MSEC;
+ InstallEventLoopTimer(GetCurrentEventLoop(), ms, ms, NewEventLoopTimerUPP(TimerCallback), 0, &timer);
+ #endif
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+mdaLooplexProgram::mdaLooplexProgram()
+{
+ param[0] = 0.00f; //max delay
+ param[1] = 0.00f; //reset (malloc)
+ param[2] = 0.00f; //record (sus ped)
+ param[3] = 1.00f; //input mix
+ param[4] = 0.50f; //input pan
+ param[5] = 1.00f; //feedback (mod whl)
+ param[6] = 1.00f; //out mix
+ //param[7] = 0.00f;
+
+ strcpy (name, "mda Looplex");
+}
+
+
+mdaLooplex::mdaLooplex(audioMasterCallback audioMaster) : AudioEffectX(audioMaster, NPROGS, NPARAMS)
+{
+
+ Fs = 44100.0f; //won't we know the sample rate by the time we need it?
+
+ //initialise...
+ bypass = bypassed = busy = status = 0; //0=not recorded 1=first pass 2=looping
+ bufmax = 882000;
+ buffer = new short[bufmax + 10];
+ memset(buffer, 0, (bufmax + 10) * sizeof(short));
+ bufpos = 0;
+ buflen = 0;
+ recreq = 0;
+ oldParam0 = oldParam1 = oldParam2 = 0.0f;
+ modwhl = 1.0f;
+
+ programs = new mdaLooplexProgram[NPROGS];
+ if(programs) setProgram(0);
+
+ if(audioMaster)
+ {
+ setNumInputs(NOUTS);
+ setNumOutputs(NOUTS);
+ canProcessReplacing();
+ //needIdle(); idle is broken in VST2.4
+ setUniqueID("MDA~"); ///
+ }
+
+ update();
+ suspend();
+
+ idleList.next = new IdleList(this, idleList.next); //add to idle list, start timer if not running...
+}
+
+
+void mdaLooplex::update() //parameter change
+{
+ if(fabs(param[1] - oldParam1) > 0.1f)
+ {
+ oldParam1 = param[1];
+ if(fabs(oldParam0 - param[0]) > 0.01f)
+ {
+ oldParam0 = param[0];
+ bypass = 1; //re-assign memory
+ }
+ }
+
+ if(param[2] > 0.5f && oldParam2 < 0.5f)
+ {
+ if(recreq == 0) recreq = 1;
+ oldParam2 = param[2];
+ }
+ if(param[2] < 0.5f && oldParam2 > 0.5f)
+ {
+ if(recreq == 1) recreq = 0;
+ oldParam2 = param[2];
+ }
+
+ in_mix = 2.0f * param[3] * param[3];
+
+ in_pan = param[4];
+
+ feedback = param[5];
+
+ out_mix = 0.000030517578f * param[6] * param[6];
+}
+
+
+void mdaLooplex::setSampleRate(float sampleRate)
+{
+ AudioEffectX::setSampleRate(sampleRate);
+ Fs = sampleRate;
+}
+
+
+void mdaLooplex::resume()
+{
+ //should reset position here...
+ bufpos = 0;
+
+ //needIdle(); //idle broken in VST2.4
+ DECLARE_LVZ_DEPRECATED (wantEvents) ();
+}
+
+
+void mdaLooplex::idle()
+{
+ if(bypassed)
+ {
+ if(busy) return; //only do once per bypass
+ busy = 1;
+
+ bufmax = 2 * (long)Fs * (long)(10.5f + 190.0f * param[0]);
+ if(buffer) delete [] buffer;
+ buffer = new short[bufmax + 10];
+ if(buffer) memset(buffer, 0, (bufmax + 10) * sizeof(short)); else bufmax = 0;
+
+ bypass = busy = status = bufpos = buflen = 0;
+ }
+}
+
+
+mdaLooplex::~mdaLooplex () //destroy any buffers...
+{
+ for(IdleList *item=idleList.next; item; item=item->next) //remove from idle list, stop timer if last item
+ {
+ if(item->effect == this)
+ {
+ item->remove = true;
+ #if _WIN32 //and stop timer in case our last instance is about to unload
+ TimerCallback(0, 0, 0, 0);
+ #else
+ TimerCallback(0, 0);
+ #endif
+ break;
+ }
+ }
+
+ if(programs) delete [] programs;
+
+ if(buffer)
+ {
+ FILE *fp; //dump loop to file
+
+ if(buflen && (fp = fopen("looplex.wav", "wb")))
+ {
+ char wh[44];
+ memcpy(wh, "RIFF____WAVEfmt \20\0\0\0\1\0\2\0________\4\0\20\0data____", 44);
+
+ long l = 36 + buflen * 2;
+ wh[4] = (char)(l & 0xFF); l >>= 8;
+ wh[5] = (char)(l & 0xFF); l >>= 8;
+ wh[6] = (char)(l & 0xFF); l >>= 8;
+ wh[7] = (char)(l & 0xFF);
+
+ l = (long)(Fs + 0.5f);
+ wh[24] = (char)(l & 0xFF); l >>= 8;
+ wh[25] = (char)(l & 0xFF); l >>= 8;
+ wh[26] = (char)(l & 0xFF); l >>= 8;
+ wh[27] = (char)(l & 0xFF);
+
+ l = 4 * (long)(Fs + 0.5f);
+ wh[28] = (char)(l & 0xFF); l >>= 8;
+ wh[29] = (char)(l & 0xFF); l >>= 8;
+ wh[30] = (char)(l & 0xFF); l >>= 8;
+ wh[31] = (char)(l & 0xFF);
+
+ l = buflen * 2;
+ wh[40] = (char)(l & 0xFF); l >>= 8;
+ wh[41] = (char)(l & 0xFF); l >>= 8;
+ wh[42] = (char)(l & 0xFF); l >>= 8;
+ wh[43] = (char)(l & 0xFF);
+
+ fwrite(wh, 1, 44, fp);
+
+ #if __BIG_ENDIAN__
+ char *c = (char *)buffer;
+ char t;
+
+ for(l=0; l<buflen; l++) //swap endian-ness
+ {
+ t = *c;
+ *c = *(c + 1);
+ c++;
+ *c = t;
+ c++;
+ }
+ #endif
+
+ fwrite(buffer, sizeof(short), buflen, fp);
+ fclose(fp);
+ }
+
+ delete [] buffer;
+ }
+}
+
+
+void mdaLooplex::setProgram(LvzInt32 program)
+{
+ long i;
+
+ mdaLooplexProgram *p = &programs[program];
+ curProgram = program;
+ setProgramName(p->name);
+ for(i=0; i<NPARAMS; i++) param[i] = p->param[i];
+ update();
+}
+
+
+void mdaLooplex::setParameter(LvzInt32 index, float value)
+{
+ mdaLooplexProgram *p = &programs[curProgram];
+ param[index] = p->param[index] = value;
+ update();
+}
+
+
+float mdaLooplex::getParameter(LvzInt32 index) { return param[index]; }
+void mdaLooplex::setProgramName(char *name) { strcpy(programs[curProgram].name, name); }
+void mdaLooplex::getProgramName(char *name) { strcpy(name, programs[curProgram].name); }
+void mdaLooplex::setBlockSize(LvzInt32 blockSize) { AudioEffectX::setBlockSize(blockSize); }
+bool mdaLooplex::getEffectName(char* name) { strcpy(name, "Looplex"); return true; }
+bool mdaLooplex::getVendorString(char* text) { strcpy(text, "mda"); return true; }
+bool mdaLooplex::getProductString(char* text) { strcpy(text, "mda Looplex"); return true; }
+
+
+bool mdaLooplex::getProgramNameIndexed(LvzInt32 category, LvzInt32 index, char* text)
+{
+ if(index<NPROGS)
+ {
+ strcpy(text, programs[index].name);
+ return true;
+ }
+ return false;
+}
+
+
+bool mdaLooplex::copyProgram(LvzInt32 destination)
+{
+ if(destination<NPROGS)
+ {
+ programs[destination] = programs[curProgram];
+ return true;
+ }
+ return false;
+}
+
+
+LvzInt32 mdaLooplex::canDo(char* text)
+{
+ if(strcmp(text, "receiveLvzEvents") == 0) return 1;
+ if(strcmp(text, "receiveLvzMidiEvent") == 0) return 1;
+ return -1;
+}
+
+
+void mdaLooplex::getParameterName(LvzInt32 index, char *label)
+{
+ switch (index)
+ {
+ case 0: strcpy(label, "Max Del "); break;
+ case 1: strcpy(label, "Reset "); break;
+ case 2: strcpy(label, "Record "); break;
+ case 3: strcpy(label, "In Mix "); break;
+ case 4: strcpy(label, "In Pan "); break;
+ case 5: strcpy(label, "Feedback"); break;
+ case 6: strcpy(label, "Out Mix "); break;
+
+ default: strcpy(label, " ");
+ }
+}
+
+
+void mdaLooplex::getParameterDisplay(LvzInt32 index, char *text)
+{
+ char string[16];
+
+ switch(index)
+ {
+ case 0: sprintf(string, "%4d s", (int)(10.5f + 190.0f * param[index])); break; //10 to 200 sec
+
+ case 1: sprintf(string, "%5.1f MB", (float)bufmax / 524288.0f); break;
+
+ case 2: if(recreq) strcpy(string, "RECORD"); else strcpy(string, "MONITOR"); break;
+
+ case 3:
+ case 6: if(param[index] < 0.01f) strcpy(string, "OFF");
+ else sprintf(string, "%.1f dB", 20.0f * log10(param[index] * param[index])); break;
+
+ case 5: if(param[index] < 0.01f) strcpy(string, "OFF");
+ else sprintf(string, "%.1f dB", 20.0f * log10(param[index])); break;
+
+ case 4: if(param[index] < 0.505f)
+ {
+ if(param[index] > 0.495f) strcpy(string, "C"); else
+ sprintf(string, "L%.0f", 100.0f - 200.0f * param[index]);
+ }
+ else sprintf(string, "R%.0f", 200.0f * param[index] - 100.0f); break;
+
+ default: strcpy(string, " ");
+ }
+ string[8] = 0;
+ strcpy(text, (char *)string);
+}
+
+
+void mdaLooplex::getParameterLabel(LvzInt32 index, char *label)
+{
+ switch(index)
+ {
+ case 2: strcpy(label, "(susped)"); break;
+ case 5: strcpy(label, "(modwhl)"); break;
+
+ default: strcpy(label, " ");
+ }
+}
+
+
+void mdaLooplex::process(float **inputs, float **outputs, LvzInt32 sampleFrames)
+{
+ notes[0] = EVENTS_DONE; //mark events buffer as done
+}
+
+
+void mdaLooplex::processReplacing(float **inputs, float **outputs, LvzInt32 sampleFrames)
+{
+ float* in1 = inputs[0];
+ float* in2 = inputs[1];
+ float* out1 = outputs[0];
+ float* out2 = outputs[1];
+ long event=0, frame=0, frames;
+ float l, r, dl, dr, d0 = 0.0f, d1;
+ float imix = in_mix, ipan = in_pan, omix = out_mix, fb = feedback * modwhl;
+ long x;
+
+ if((bypassed = bypass)) return;
+
+ while(frame<sampleFrames)
+ {
+ frames = notes[event++];
+ if(frames>sampleFrames) frames = sampleFrames;
+ frames -= frame;
+ frame += frames;
+
+ while(--frames>=0)
+ {
+ l = *in1++;
+ r = *in2++;
+
+ //input mix
+ r *= imix * ipan;
+ l *= imix - ipan * imix;
+
+ if(recreq == 1 && status == 0) status = 1; //first pass
+ if(recreq == 0 && status == 1) status = 2; //later pass
+
+ if(status)
+ {
+ //dither
+ d1 = d0;
+ #if WIN32
+ d0 = 0.000061f * (float)(rand() - 16384);
+ #else
+ d0 = 0.000061f * (float)((rand() & 32767) - 16384);
+ #endif
+
+ //left delay
+ dl = fb * (float)buffer[bufpos];
+ if(recreq)
+ {
+ x = (long)(32768.0f * l + dl + d0 - d1 + 100000.5f) - 100000;
+ if(x > 32767) x = 32767; else if(x < -32768) x = -32768;
+ buffer[bufpos] = (short)x;
+ }
+ bufpos++;
+
+ //right delay
+ dr = fb * (float)buffer[bufpos];
+ if(recreq)
+ {
+ x = (long)(32768.0f * r + dr - d0 + d1 + 100000.5f) - 100000;
+ if(x > 32767) x = 32767; else if(x < -32768) x = -32768;
+ buffer[bufpos] = (short)x;
+ }
+ bufpos++;
+
+ //looping
+ if(bufpos >= bufmax)
+ {
+ buflen = bufmax;
+ bufpos -= buflen;
+ status = 2;
+ }
+ else
+ {
+ if(status == 1) buflen = bufpos; else if(bufpos >= buflen) bufpos -= buflen;
+ }
+
+ //output
+ l += omix * dl;
+ r += omix * dr;
+ }
+
+ *out1++ = l;
+ *out2++ = r;
+ }
+
+ if(frame<sampleFrames)
+ {
+ long note = notes[event++];
+ //long vel = notes[event++];
+
+ if(note == 2)
+ {
+ bufpos = 0; //resync
+ }
+ else if(note == 1)
+ {
+ if(recreq) recreq = 0; else recreq = 1; //toggle recording
+ }
+ }
+ }
+ notes[0] = EVENTS_DONE; //mark events buffer as done
+}
+
+
+LvzInt32 mdaLooplex::processEvents(LvzEvents* ev)
+{
+ long npos=0;
+
+ for (long i=0; i<ev->numEvents; i++)
+ {
+ if((ev->events[i])->type != kLvzMidiType) continue;
+ LvzMidiEvent* event = (LvzMidiEvent*)ev->events[i];
+ char* midiData = event->midiData;
+
+ switch(midiData[0] & 0xf0) //status byte (all channels)
+ {
+ case 0x80: //note off
+ //notes[npos++] = event->deltaFrames; //delta
+ //notes[npos++] = midiData[1] & 0x7F; //note
+ //notes[npos++] = 0; //vel
+ break;
+
+ case 0x90: //note on
+ //notes[npos++] = event->deltaFrames; //delta
+ //notes[npos++] = midiData[1] & 0x7F; //note
+ //notes[npos++] = midiData[2] & 0x7F; //vel
+ break;
+
+ case 0xB0: //controller
+ switch(midiData[1])
+ {
+ case 0x01: //mod wheel
+ modwhl = 1.0f - 0.007874f * (float)midiData[2];
+ break;
+
+ case 0x40: //sustain
+ if(midiData[2] > 63)
+ {
+ notes[npos++] = event->deltaFrames; //delta
+ notes[npos++] = 1; //note
+ notes[npos++] = 127; //vel
+ }
+ break;
+
+ case 0x42: //soft/sost
+ case 0x43:
+ if(midiData[2] > 63)
+ {
+ notes[npos++] = event->deltaFrames; //delta
+ notes[npos++] = 2; //note
+ notes[npos++] = 127; //vel
+ }
+ break;
+
+ default: //all notes off
+ if(midiData[1]>0x7A)
+ {
+
+ }
+ break;
+ }
+ break;
+
+ default: break;
+ }
+
+ if(npos>EVENTBUFFER) npos -= 3; //discard events if buffer full!!
+ event++;
+ }
+ notes[npos] = EVENTS_DONE;
+ return 1;
+}
+