2

I basically have some source-code(not my own) in python that I would like to understand. It's an anti-aliased xor audio oscillator. I don't know python at all - but it's quite readable except for a few things:

Firstly - the full code:

f0 = 500.
fs = 44100.
T0 = f0/fs
P0 = fs/f0

t = arange(0,3*fs)
L = len(t)
imax = 2**16


# =============================================================================
# SIGNALS
# =============================================================================

# -----------------------------------------------------------------------------
#
def trivial_xor():
    s = zeros(L)
    sd1 = zeros(L)
    sd2 = zeros(L)
    s = zeros(L)
    w = 0.5
    p = 0
    for n in range(0,L):
        d1 = 2*p - 1
        if p < w:   d2 = 0
        else:           d2 = -0.5
        x1 = int(d1 * imax) & 0xFFFF
        x2 = int(d2 * imax) & 0xFFFF
        y = (x1 ^ x2) / float(imax)
        s[n] = 2*y - 1
        sd1[n] = d1
        sd2[n] = d2
        p += T0
        if p > 1: p -= 1
    return s

# -----------------------------------------------------------------------------
#
def trivial_xor_withSources():
    s = zeros(L)
    sd1 = zeros(L)
    sd2 = zeros(L)
    s = zeros(L)
    w = 0.5
    p = 0
    for n in range(0,L):
        d1 = 2*p - 1
        if p < w:   d2 = 0
        else:           d2 = -0.5
        x1 = int(d1 * imax) & 0xFFFF
        x2 = int(d2 * imax) & 0xFFFF
        y = (x1 ^ x2) / float(imax)
        s[n] = 2*y - 1
        sd1[n] = d1
        sd2[n] = d2
        p += T0
        if p > 1: p -= 1
    return s,sd1,sd2

# -----------------------------------------------------------------------------
#
def PTR1_xor():
    s = trivial_xor() - 2*T0
    #
    T1 = 2*T0
    P1 = 1/T1
    cdc = 1 + T1
    p0 = p1 = 0
    #
    for n in range(0,L):
        if p0 < 0.5:
            h = 0.5
            if p1 < T1:
                s[n] = p1*(2 - 2*h*P1) + 2*h - cdc
        elif p0 < 0.75:
            h = 0.5
            if p1 < T1:
                s[n] = p1*(2 - 2*h*P1) + 2*h - cdc + 1
        else:
            h = 1
            pp = p1 - 0.5
            if pp < T1:
                s[n] = pp*(2 - 2*h*P1) + 2*h - cdc
        #
        p0 += T0
        p1 += T1
        if p0 > 1:  p0 -= 1
        if p1 > 1:  p1 -= 1
    return s

It all seems pretty straight forward - except for what I assume to be the buffers, all I need to know is what is these function(s) in c++?

////////////////////////////////

t = arange(0,3*fs)
L = len(t)
imax = 2**16

////////////////////////////////

 def trivial_xor_withSources():
    s = zeros(L)
    sd1 = zeros(L)
    sd2 = zeros(L)
    s = zeros(L)
    w = 0.5
    p = 0
    for n in range(0,L):

I'm planning on using this in real time. The rest just look like simple math. Any help greatly appreciated!

Andrew

ichad.c
  • 131
  • 2
  • 8
  • you haven't even shown that you're importing `arange` from `numpy`. you repeatedly state how straightforward and readable your code is, but you're clearly missing lines (like the import) and one can only infer what you're trying to do with your functions from the function names. some explanation would be great. – abcd Oct 12 '15 at 22:23

3 Answers3

6

If you're trying to convert the code to C++, you can easily implement a (roughly) equivalent arange function:

#include <vector>

template<typename T>
std::vector<T> arange(T start, T stop, T step = 1) {
    std::vector<T> values;
    for (T value = start; value < stop; value += step)
        values.push_back(value);
    return values;
}

You could then use it like this:

auto t = arange<double>(0, 3*fs);
auto L = t.length();

The ** is exponentiation. You could call the pow function:

#include <cmath>
const double imax = pow(2., 16.);

But since you are dealing with constants anyway, you would be better off with:

const double imax = 65536.;

If you want to retain the expressive power of 2**16 and you don't want to incur the run-time cost of calling pow (perhaps you want to be able to change the exponent without having to manually recalculate the constant), you can use a constexpr function:

constexpr unsigned long power(unsigned long base, unsigned long exponent) {
    return exponent == 0 ? 1 : base * pow(base, exponent - 1);
}

const unsigned long imax = power(2, 16);
Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • I get it now - arange() can be ommited if used in real-time (it has to do with writing to a file). Your c++ arange() might come in handy somewhere down the line. Thanks! Think I understand everything now! – ichad.c Jan 19 '14 at 14:54
  • @ichad.c - while you could, technically, use `arange` directly in a range-based for loop (i.e. `for (int i : arange(0, 100)`), you would incur the unnecessary overhead of creating a temporary vector. In that case you would be better off with a traditional C(++) for loop (`for (int i = 0; i < 100; ++i)`). – Ferruccio Jan 19 '14 at 15:51
  • that was my conclusion too. I just had to figure out what arange() was doing. – ichad.c Jan 19 '14 at 17:09
  • i'm getting `error: ‘t_array’ does not name a type` on my line `auto t_array = arange(0, 40, dt);` -- any idea what i've done wrong? as far as i can tell, i've adapted the code in your answer faithfully. – abcd Oct 12 '15 at 23:48
  • i think maybe the `template` needs to be in a different file from the code that uses `arange`? i don't know -- i'm new to c++. – abcd Oct 12 '15 at 23:52
4

Here is an explanation for all the non-trivials lines you outlined:

  • len(t) means "length of t", that is to say, the number of elements in array t.

  • 2**16 is "two to the power of 16" (1 << 16 in your C++ code).

  • for n in range(0,L) is equivalent to for (int n = 0; i < L; ++i)

  • arange and zeros are likely Numpy functions. You can find reference for them here and here.

Regarding the last point, probably there is some import statement you omitted from the code.

Quoting from the docs:

arange

Return evenly spaced values within a given interval.

Values are generated within the half-open interval [start, stop) (in other words, the interval including start but excluding stop).

The default step is 1, so t will be an array containing numbers [0, 1, 2, 3, ..., 3 * 44100 - 1] .

zeros

Return a new array of given shape and type, filled with zeros.

Default type for zeros is float, so s, sd1 and sd2 are initialized as arrays filled of 0.0, each having L elements.

Community
  • 1
  • 1
Stefano Sanfilippo
  • 32,265
  • 7
  • 79
  • 80
  • Yeah, I ommited the import def by accident. Thanks for the explenations! Everything makes sense now - except for arange() - but if I understand it correctly - it has to do with the example actually being on offline process/example, so hopefully I can modify it for real-time. I have used this type of algo before, for a simple Saw - but this is pretty insane. – ichad.c Jan 19 '14 at 13:46
  • Done, actually everybody everybody's answer helped a bit. I did not need a python -> c++ translation, I just needed to understand the python syntax. Thanks to all! – ichad.c Jan 19 '14 at 17:07
1

Python: t = arange(0,3*fs)

C++: double t[] = {0.0,1.0,...,3*fs}; // will not compile of course

Python: L = len(t)

C++: int L = sizeof(t)/sizeof(*t); // where t is an array in the current scope

Python: s = zeros(L)

C++: double s[L] = {0}; // where L is a constant

Python: for n in range(0,L)

C++: for (int i=0; i<L; i++)

barak manos
  • 29,648
  • 10
  • 62
  • 114