~pbatch/sndkit

sndkit/phsclk.org -rw-r--r-- 3.1 KiB
2a4fda97Paul Batchelor update keys2db to use tabs instead of semicolons 11 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#+TITLE: phsclk
* Overview
=phsclk= is a utility that converts an incoming phasor
signal into a clock signal that will tick N times in one
cycle.

Using a phasor signal to generate a clock signal has the
advantage of arbitrarily subdiving a beat. If the phasor is
the master clock, parallel sequences can be subidvided in
different ways while also remaining relatively in sync.
There is no chance of accumulative drift.

This algorithm is minimally stateful, only requiring memory
of the previous sample to work.

A tick is registered when the phasor crosses a certain
threshold. Both the previous and current phasor signals are
scaled by the subdivision amount, then floored. If they are
different, a tick is registered.
* Generated Files
=phsclk.c= and =phsclk.h= are the generated files.

#+NAME: phsclk.c
#+BEGIN_SRC c :tangle phsclk.c
#include <math.h>
#define SK_PHSCLK_PRIV
#include "phsclk.h"
<<funcs>>
#+END_SRC

#+NAME: phsclk.h
#+BEGIN_SRC c :tangle phsclk.h
#ifndef PHSCLK_H
#define PHSCLK_H

#ifndef SKFLT
#define SKFLT float
#endif

<<typedefs>>
<<funcdefs>>

#ifdef SK_PHSCLK_PRIV
<<structs>>
#endif

#endif
#+END_SRC
* Structs
The state data is encapsulated in a struct called
=sk_phsclk=.

#+NAME: typedefs
#+BEGIN_SRC c
typedef struct sk_phsclk sk_phsclk;
#+END_SRC

#+NAME: structs
#+BEGIN_SRC c
struct sk_phsclk {
    <<sk_phsclk>>
};
#+END_SRC

#+NAME: sk_phsclk
#+BEGIN_SRC c
SKFLT prev;
#+END_SRC

#+NAME: init
#+BEGIN_SRC c
pc->prev = -1;
#+END_SRC
* Init
phsclk is initialized with =sk_phsclk_init=.

#+NAME: funcdefs
#+BEGIN_SRC c
void sk_phsclk_init(sk_phsclk *pc);
#+END_SRC

#+NAME: funcs
#+BEGIN_SRC c
void sk_phsclk_init(sk_phsclk *pc)
{
    <<init>>
}
#+END_SRC
* Setting Number of Ticks
The number of ticks is set with the function
=sk_phsclk_nticks=.

#+NAME: funcdefs
#+BEGIN_SRC c
void sk_phsclk_nticks(sk_phsclk *pc, SKFLT nticks);
#+END_SRC

#+NAME: funcs
#+BEGIN_SRC c
void sk_phsclk_nticks(sk_phsclk *pc, SKFLT nticks)
{
    pc->nticks = nticks;
}
#+END_SRC

#+NAME: sk_phsclk
#+BEGIN_SRC c
SKFLT nticks;
#+END_SRC

4 is a sensible starting value. Western music loves
multiples of 4 and 8.

#+NAME: init
#+BEGIN_SRC c
sk_phsclk_nticks(pc, 4);
#+END_SRC
* Computation
The function =sk_phsclk_tick= computes a single sample of
audio from an input signal =in=.

#+NAME: funcdefs
#+BEGIN_SRC c
SKFLT sk_phsclk_tick(sk_phsclk *pc, SKFLT in);
#+END_SRC

The algorithm for phsclk is quite simple: scale and floor
the previous and current input phasor signals, and if
there is a difference, make a tick.

=floor= will almost always truncate the decimal, and will
almost always ensure that the value is between 0 and
=nticks - 1=. The exception to this is when =in= is exactly
1. This can somes cause extra ticks to happen, so a
conditional is added to avoid this.

#+NAME: funcs
#+BEGIN_SRC c
SKFLT sk_phsclk_tick(sk_phsclk *pc, SKFLT in)
{
    SKFLT out;
    out = 0;

    if (in < 1) {
        int i, pi;
        SKFLT s, ps;
        s = in * pc->nticks;
        ps = pc->prev * pc->nticks;

        i = floor(s);
        pi = floor(ps);

        if (i != pi) out = 1;
    }

    pc->prev = in;

    return out;
}
#+END_SRC