-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPepper.h
More file actions
167 lines (139 loc) · 5.34 KB
/
Copy pathPepper.h
File metadata and controls
167 lines (139 loc) · 5.34 KB
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
161
162
163
164
165
166
167
#pragma once
#include <Bela.h>
#include <Utilities.h>
#include <libraries/Scope/Scope.h>
struct PepperConfig {
int cvInPin[8] = {0, 1, 2, 3, 4, 5, 6, 7}; // CVin/Pot 1..8 -> analog input channel
int cvOutPin[8] = {0, 1, 2, 3, 4, 5, 6, 7}; // CVout 1..8 -> analog output channel
int ledPin[10] = {6, 7, 10, 2, 3, 0, 1, 4, 5, 8}; // LED 1..10 -> digital channel
int buttonPin[4] = {15, 14, 13, 12}; // Button 1..4 -> digital channel
float cvInVoltMax = 10.f;
float cvOutVoltMax = 5.f;
int buttonDebounceFrames = 2;
int scopeChannels = 0; // >0 enables the Bela Scope with this many channels (control rate)
};
template <class D>
class Pepper {
public:
explicit Pepper(const PepperConfig& cfg = PepperConfig{}) : cfg_(cfg) {}
bool _setup(BelaContext* c) {
ctx_ = c;
audioRate_ = c->audioSampleRate;
controlRate_ = c->analogSampleRate;
for (int i = 0; i < 8; ++i)
if ((unsigned)cfg_.cvInPin[i] >= c->analogInChannels) return false;
for (int i = 0; i < 8; ++i)
if ((unsigned)cfg_.cvOutPin[i] >= c->analogOutChannels) return false;
for (int i = 0; i < 10; ++i)
if ((unsigned)cfg_.ledPin[i] >= c->digitalChannels) return false;
for (int i = 0; i < 4; ++i)
if ((unsigned)cfg_.buttonPin[i] >= c->digitalChannels) return false;
for (int i = 0; i < 10; ++i) pinMode(c, 0, cfg_.ledPin[i], OUTPUT);
for (int i = 0; i < 4; ++i) pinMode(c, 0, cfg_.buttonPin[i], INPUT);
for (int i = 0; i < 4; ++i) {
btnLevel_[i] = btnRose_[i] = btnFell_[i] = false;
btnCount_[i] = 0;
}
for (int i = 0; i < 10; ++i) ledState_[i] = false;
if (cfg_.scopeChannels > 0) {
scope_.setup(cfg_.scopeChannels, controlRate_);
}
return self().init();
}
void _render(BelaContext* c) {
ctx_ = c;
latchButtons();
for (cf_ = 0; cf_ < c->analogFrames; ++cf_) self().control();
applyLeds();
for (af_ = 0; af_ < c->audioFrames; ++af_) self().audio();
}
void _cleanup(BelaContext* c) {
(void)c;
self().onCleanup();
}
protected:
void control() {}
void audio() {}
bool init() { return true; }
void onCleanup() {}
float cvIn(int ch) noexcept {
if (ch < 1 || ch > 8 || !ctx_) return 0.f;
return analogRead(ctx_, cf_, cfg_.cvInPin[ch - 1]);
}
float cvInV(int ch) noexcept { return cvIn(ch) * cfg_.cvInVoltMax; }
void cvOut(int ch, float v) noexcept {
if (ch < 1 || ch > 8 || !ctx_) return;
analogWrite(ctx_, cf_, cfg_.cvOutPin[ch - 1], constrain(v, 0.f, 1.f));
}
void cvOutV(int ch, float volts) noexcept {
cvOut(ch, volts / cfg_.cvOutVoltMax);
}
// Log values to the Bela Scope (viewable in the IDE scope). Call from control()
// with exactly PepperConfig::scopeChannels arguments. No-op when the scope is
// disabled (scopeChannels == 0). The scope runs at control/analog rate.
template <class... Fs>
void scope(Fs... vs) noexcept {
if (cfg_.scopeChannels <= 0) return;
float buf[sizeof...(vs)] = {static_cast<float>(vs)...};
scope_.log(buf);
}
float pot(int ch) noexcept { return cvIn(ch); } // pot attenuates the CV jack: same channel
float audioIn(int ch) noexcept {
if (ch < 1 || ch > 2 || !ctx_) return 0.f;
return audioRead(ctx_, af_, ch - 1);
}
void audioOut(int ch, float v) noexcept {
if (ch < 1 || ch > 2 || !ctx_) return;
audioWrite(ctx_, af_, ch - 1, constrain(v, -1.f, 1.f));
}
void led(int i, bool on) noexcept {
if (i < 1 || i > 10) return;
ledState_[i - 1] = on;
}
void ledLevel(int i, float b) noexcept { led(i, b >= 0.5f); } // v1: threshold; PWM is future
void applyLeds() noexcept {
if (!ctx_) return;
for (unsigned f = 0; f < ctx_->digitalFrames; ++f)
for (int i = 0; i < 10; ++i)
digitalWrite(ctx_, f, cfg_.ledPin[i], ledState_[i] ? 1.f : 0.f);
}
bool button(int i) noexcept { return (i < 1 || i > 4) ? false : btnLevel_[i - 1]; }
bool buttonRose(int i) noexcept { return (i < 1 || i > 4) ? false : btnRose_[i - 1]; }
bool buttonFell(int i) noexcept { return (i < 1 || i > 4) ? false : btnFell_[i - 1]; }
void latchButtons() noexcept {
if (!ctx_) return;
for (int i = 0; i < 4; ++i) {
btnRose_[i] = false;
btnFell_[i] = false;
bool raw = digitalRead(ctx_, 0, cfg_.buttonPin[i]) > 0.5f;
if (raw == btnLevel_[i]) {
btnCount_[i] = 0;
} else if (++btnCount_[i] >= cfg_.buttonDebounceFrames) {
btnLevel_[i] = raw;
btnCount_[i] = 0;
btnRose_[i] = raw;
btnFell_[i] = !raw;
}
}
}
float audioRate() const noexcept { return audioRate_; }
float controlRate() const noexcept { return controlRate_; }
PepperConfig cfg_;
BelaContext* ctx_ = nullptr;
unsigned int cf_ = 0;
unsigned int af_ = 0;
float audioRate_ = 0.f, controlRate_ = 0.f;
bool ledState_[10] = {};
bool btnLevel_[4] = {};
bool btnRose_[4] = {};
bool btnFell_[4] = {};
int btnCount_[4] = {};
Scope scope_;
private:
D& self() noexcept { return static_cast<D&>(*this); }
};
#define PEPPER_MAIN(T) \
static T gPepperInstance; \
bool setup(BelaContext* context, void*) { return gPepperInstance._setup(context); } \
void render(BelaContext* context, void*) { gPepperInstance._render(context); } \
void cleanup(BelaContext* context, void*) { gPepperInstance._cleanup(context); }