Skip to content

Commit bc52f50

Browse files
author
Nathan Ho
committed
add AnalogEcho ugen
1 parent 583caa0 commit bc52f50

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

03-AnalogEcho/AnalogEcho.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#include "SC_PlugIn.h"
2+
3+
static InterfaceTable *ft;
4+
5+
struct AnalogEcho : public Unit {
6+
// Max delay in seconds.
7+
float maxdelay;
8+
9+
// Size of the buffer in samples, always a power of 2
10+
int bufsize;
11+
// bufsize - 1, so the modulo function can be replaced with a faster bitwise and
12+
int mask;
13+
// The buffer itself. This is an internal buffer, and is not connected with any Buffer instance.
14+
// It must be allocated in Ctor and freed in Dtor.
15+
float* buf;
16+
17+
// Position of the write head.
18+
int writephase;
19+
20+
// State of the one-pole lowpass filter.
21+
float s1;
22+
};
23+
24+
25+
static void AnalogEcho_next(AnalogEcho *unit, int inNumSamples);
26+
static void AnalogEcho_Ctor(AnalogEcho* unit);
27+
static void AnalogEcho_Dtor(AnalogEcho* unit);
28+
29+
30+
void AnalogEcho_Ctor(AnalogEcho* unit) {
31+
SETCALC(AnalogEcho_next);
32+
33+
unit->maxdelay = IN0(2);
34+
35+
// To get the buffer size in samples, take the sample rate times the length in seconds.
36+
// The buffer size doesn't NEED to be a power of two, but if you're doing a lot of moduloing then it's faster that way.
37+
unit->bufsize = NEXTPOWEROFTWO((float)SAMPLERATE * unit->maxdelay);
38+
unit->mask = unit->bufsize - 1;
39+
40+
unit->writephase = 0;
41+
unit->s1 = 0;
42+
43+
// Allocate the buffer. Do NOT use malloc!
44+
// SuperCollider provides special real-time-safe allocation and freeing functions.
45+
unit->buf = (float*)RTAlloc(unit->mWorld, unit->bufsize * sizeof(float));
46+
if (unit->buf == NULL) {
47+
SETCALC(ft->fClearUnitOutputs);
48+
ClearUnitOutputs(unit, 1);
49+
50+
if(unit->mWorld->mVerbosity > -2) {
51+
Print("Failed to allocate memory for AnalogEcho ugen.\n");
52+
}
53+
}
54+
// Fill the buffer with zeros.
55+
memset(unit->buf, 0, unit->bufsize * sizeof(float));
56+
57+
AnalogEcho_next(unit, 1);
58+
}
59+
60+
// this must be named PluginName_Dtor.
61+
void AnalogEcho_Dtor(AnalogEcho* unit) {
62+
// Free the memory.
63+
RTFree(unit->mWorld, unit->buf);
64+
}
65+
66+
void AnalogEcho_next(AnalogEcho *unit, int inNumSamples)
67+
{
68+
// audio-rate input signal
69+
float *in = IN(0);
70+
// audio-rate output signal
71+
float *out = OUT(0);
72+
// control-rate delay
73+
float delay = IN0(1);
74+
// control-rate feedback coefficient
75+
float fb = IN0(3);
76+
// control-rate filter coefficient
77+
float coeff = IN0(4);
78+
79+
float* buf = unit->buf;
80+
int mask = unit->mask;
81+
int writephase = unit->writephase;
82+
float s1 = unit->s1;
83+
84+
// Cap the delay at maxdelay
85+
if (delay > unit->maxdelay) {
86+
delay = unit->maxdelay;
87+
}
88+
89+
// Compute the delay in samples and the integer and fractional parts of this delay.
90+
float delay_samples = (float)SAMPLERATE * unit->maxdelay;
91+
int offset = delay_samples;
92+
float frac = delay_samples - offset;
93+
94+
// Precompute a filter coefficient.
95+
float a = 1 - std::abs(coeff);
96+
97+
for (int i = 0; i < inNumSamples; i++) {
98+
99+
// Four integer phases into the buffer
100+
int phase1 = writephase - offset;
101+
int phase2 = phase1 - 1;
102+
int phase3 = phase1 - 2;
103+
int phase0 = phase1 + 1;
104+
float d0 = buf[phase0 & mask];
105+
float d1 = buf[phase1 & mask];
106+
float d2 = buf[phase2 & mask];
107+
float d3 = buf[phase3 & mask];
108+
// Use cubic interpolation with the fractional part of the delay in samples
109+
float delayed = cubicinterp(frac, d0, d1, d2, d3);
110+
111+
// Apply lowpass filter and store the state of the filter.
112+
float lowpassed = a * delayed + coeff * s1;
113+
s1 = lowpassed;
114+
115+
// Multiply by feedback coefficient and add to input signal.
116+
// zapgremlins gets rid of Bad Things like denormals, explosions, etc.
117+
out[i] = zapgremlins(in[i] + fb * lowpassed);
118+
buf[writephase] = out[i];
119+
120+
// Here's why the buffer size is a power of two -- otherwise this becomes a much more
121+
// expensive modulo operation.
122+
writephase = (writephase + 1) & mask;
123+
}
124+
125+
// These two variables were updated and need to be stored back into the state of the UGen.
126+
unit->writephase = writephase;
127+
unit->s1 = s1;
128+
}
129+
130+
131+
PluginLoad(AnalogEcho)
132+
{
133+
ft = inTable;
134+
// ATTENTION! This has changed!
135+
// In the previous examples this was DefineSimpleUnit.
136+
DefineDtorUnit(AnalogEcho);
137+
}

03-AnalogEcho/AnalogEcho.sc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// without mul and add.
2+
AnalogEcho : UGen {
3+
*ar { arg in = 0.0, delay = 0.3, maxdelay = 0.3, fb = 0.9, coeff = 0.95;
4+
^this.multiNew('audio', in, delay, maxdelay, fb, coeff);
5+
}
6+
}

03-AnalogEcho/CMakeLists.txt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
cmake_minimum_required (VERSION 2.8)
2+
set(PROJECT "AnalogEcho")
3+
project (${PROJECT})
4+
5+
include_directories(${SC_PATH}/include/plugin_interface)
6+
include_directories(${SC_PATH}/include/common)
7+
include_directories(${SC_PATH}/common)
8+
9+
10+
set(CMAKE_SHARED_MODULE_PREFIX "")
11+
if(APPLE OR WIN32)
12+
set(CMAKE_SHARED_MODULE_SUFFIX ".scx")
13+
endif()
14+
15+
option(CPP11 "Build with c++11." ON)
16+
17+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG)
18+
add_definitions(-fvisibility=hidden)
19+
20+
include (CheckCCompilerFlag)
21+
include (CheckCXXCompilerFlag)
22+
23+
CHECK_C_COMPILER_FLAG(-msse HAS_SSE)
24+
CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE)
25+
26+
if (HAS_SSE)
27+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse")
28+
endif()
29+
if (HAS_CXX_SSE)
30+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse")
31+
endif()
32+
33+
CHECK_C_COMPILER_FLAG(-msse2 HAS_SSE2)
34+
CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2)
35+
36+
if (HAS_SSE2)
37+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2")
38+
endif()
39+
if (HAS_CXX_SSE2)
40+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2")
41+
endif()
42+
43+
CHECK_C_COMPILER_FLAG(-mfpmath=sse HAS_FPMATH_SSE)
44+
CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE)
45+
46+
if (HAS_FPMATH_SSE)
47+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpmath=sse")
48+
endif()
49+
if (HAS_CXX_FPMATH_SSE)
50+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse")
51+
endif()
52+
53+
if(NATIVE)
54+
add_definitions(-march=native)
55+
endif()
56+
57+
if(CPP11)
58+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
59+
if(CMAKE_COMPILER_IS_CLANG)
60+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
61+
endif()
62+
endif()
63+
endif()
64+
if(MINGW)
65+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mstackrealign")
66+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mstackrealign")
67+
endif()
68+
69+
add_library(${PROJECT} MODULE AnalogEcho.cpp)

0 commit comments

Comments
 (0)