l@272
|
1 /***** Scope.cpp *****/
|
l@272
|
2 #include <Scope.h>
|
l@272
|
3
|
l@272
|
4 Scope::Scope():connected(0), triggerPrimed(false), started(false){}
|
l@272
|
5
|
l@272
|
6 // static aux task functions
|
l@272
|
7 void Scope::triggerTask(void *ptr){
|
l@272
|
8 Scope *instance = (Scope*)ptr;
|
l@272
|
9 if (instance->started)
|
l@272
|
10 instance->doTrigger();
|
l@272
|
11 }
|
l@272
|
12 void Scope::sendBufferTask(void *ptr){
|
l@272
|
13 Scope *instance = (Scope*)ptr;
|
l@272
|
14 instance->sendBuffer();
|
l@272
|
15 }
|
l@272
|
16
|
l@272
|
17 void Scope::setup(unsigned int _numChannels, float _sampleRate){
|
l@272
|
18
|
l@272
|
19 numChannels = _numChannels;
|
l@272
|
20 sampleRate = _sampleRate;
|
l@272
|
21
|
l@272
|
22 // setup the OSC server && client
|
giuliomoro@474
|
23 // used for sending / receiving settings
|
l@272
|
24 // oscClient has it's send task turned off, as we only need to send one handshake message
|
giuliomoro@474
|
25 oscServer.setup(OSC_RECEIVE_PORT);
|
l@272
|
26 oscClient.setup(OSC_SEND_PORT, "127.0.0.1", false);
|
l@272
|
27
|
l@272
|
28 // setup the udp socket
|
l@272
|
29 // used for sending raw frame data
|
l@272
|
30 socket.setServer("127.0.0.1");
|
l@272
|
31 socket.setPort(SCOPE_UDP_PORT);
|
l@272
|
32
|
l@272
|
33 // setup the auxiliary tasks
|
andrewm@303
|
34 scopeTriggerTask = Bela_createAuxiliaryTask(Scope::triggerTask, BELA_AUDIO_PRIORITY-2, "scopeTriggerTask", this, true);
|
andrewm@303
|
35 scopeSendBufferTask = Bela_createAuxiliaryTask(Scope::sendBufferTask, BELA_AUDIO_PRIORITY-1, "scopeSendBufferTask", this);
|
l@272
|
36
|
l@272
|
37 // send an OSC message to address /scope-setup
|
l@272
|
38 // then wait 1 second for a reply on /scope-setup-reply
|
giuliomoro@474
|
39 bool handshakeReceived = false;
|
l@272
|
40 oscClient.sendMessageNow(oscClient.newMessage.to("/scope-setup").add((int)numChannels).add(sampleRate).end());
|
giuliomoro@474
|
41 oscServer.receiveMessageNow(1000);
|
l@272
|
42 while (oscServer.messageWaiting()){
|
giuliomoro@474
|
43 if (handshakeReceived){
|
l@272
|
44 parseMessage(oscServer.popMessage());
|
l@272
|
45 } else if (oscServer.popMessage().match("/scope-setup-reply")){
|
giuliomoro@474
|
46 handshakeReceived = true;
|
l@272
|
47 }
|
l@272
|
48 }
|
l@272
|
49
|
giuliomoro@474
|
50 if (handshakeReceived && connected)
|
l@272
|
51 start();
|
l@272
|
52
|
l@272
|
53 }
|
l@272
|
54
|
l@272
|
55 void Scope::start(){
|
l@272
|
56
|
l@272
|
57 // resize the buffers
|
l@272
|
58 channelWidth = frameWidth * FRAMES_STORED;
|
l@272
|
59 buffer.resize(numChannels*channelWidth);
|
l@272
|
60 outBuffer.resize(numChannels*frameWidth);
|
l@272
|
61
|
l@272
|
62 // reset the pointers
|
l@272
|
63 writePointer = 0;
|
l@272
|
64 readPointer = 0;
|
l@272
|
65 triggerPointer = 0;
|
l@272
|
66
|
l@272
|
67 // reset the trigger
|
l@272
|
68 triggerPrimed = true;
|
l@272
|
69 triggerCollecting = false;
|
l@272
|
70 triggerWaiting = false;
|
l@272
|
71 triggerCount = 0;
|
l@272
|
72 downSampleCount = 1;
|
l@272
|
73 autoTriggerCount = 0;
|
l@272
|
74 customTriggered = false;
|
l@272
|
75
|
l@272
|
76 started = true;
|
l@272
|
77 }
|
l@272
|
78
|
l@272
|
79 void Scope::stop(){
|
l@272
|
80 started = false;
|
l@272
|
81 }
|
l@272
|
82
|
giuliomoro@485
|
83 void Scope::log(float* values){
|
giuliomoro@485
|
84 //TODO: contains lots of duplicated code from log(float,...).
|
giuliomoro@485
|
85 //TODO: needs refactoring
|
giuliomoro@485
|
86 // check for any received OSC messages
|
giuliomoro@485
|
87 while (oscServer.messageWaiting()){
|
giuliomoro@485
|
88 parseMessage(oscServer.popMessage());
|
giuliomoro@485
|
89 }
|
giuliomoro@485
|
90
|
giuliomoro@485
|
91 if (!started) return;
|
giuliomoro@485
|
92
|
giuliomoro@485
|
93 if (downSampling > 1){
|
giuliomoro@485
|
94 if (downSampleCount < downSampling){
|
giuliomoro@485
|
95 downSampleCount++;
|
giuliomoro@485
|
96 return;
|
giuliomoro@485
|
97 }
|
giuliomoro@485
|
98 downSampleCount = 1;
|
giuliomoro@485
|
99 }
|
giuliomoro@485
|
100
|
giuliomoro@485
|
101 int startingWritePointer = writePointer;
|
giuliomoro@485
|
102
|
giuliomoro@485
|
103 // save the logged samples into the buffer
|
giuliomoro@485
|
104 for (int i=0; i<numChannels; i++) {
|
giuliomoro@485
|
105 buffer[i*channelWidth + writePointer] = values[i];
|
giuliomoro@485
|
106 }
|
giuliomoro@485
|
107
|
giuliomoro@485
|
108 writePointer = (writePointer+1)%channelWidth;
|
giuliomoro@485
|
109
|
giuliomoro@485
|
110 // if upSampling > 1, save repeated samples into the buffer
|
giuliomoro@485
|
111 for (int j=1; j<upSampling; j++){
|
giuliomoro@485
|
112
|
giuliomoro@485
|
113 buffer[writePointer] = buffer[startingWritePointer];
|
giuliomoro@485
|
114
|
giuliomoro@485
|
115 for (int i=1; i<numChannels; i++) {
|
giuliomoro@485
|
116 buffer[i*channelWidth + writePointer] = buffer[i*channelWidth + startingWritePointer];
|
giuliomoro@485
|
117 }
|
giuliomoro@485
|
118
|
giuliomoro@485
|
119 writePointer = (writePointer+1)%channelWidth;
|
giuliomoro@485
|
120
|
giuliomoro@485
|
121 }
|
giuliomoro@485
|
122
|
giuliomoro@485
|
123 }
|
l@272
|
124 void Scope::log(float chn1, ...){
|
l@272
|
125
|
giuliomoro@474
|
126 // check for any received OSC messages
|
l@272
|
127 while (oscServer.messageWaiting()){
|
l@272
|
128 parseMessage(oscServer.popMessage());
|
l@272
|
129 }
|
l@272
|
130
|
l@272
|
131 if (!started) return;
|
l@272
|
132
|
l@272
|
133 if (downSampling > 1){
|
l@272
|
134 if (downSampleCount < downSampling){
|
l@272
|
135 downSampleCount++;
|
l@272
|
136 return;
|
l@272
|
137 }
|
l@272
|
138 downSampleCount = 1;
|
l@272
|
139 }
|
l@272
|
140
|
l@272
|
141 va_list args;
|
l@272
|
142 va_start (args, chn1);
|
l@272
|
143
|
l@272
|
144 int startingWritePointer = writePointer;
|
l@272
|
145
|
l@272
|
146 // save the logged samples into the buffer
|
l@272
|
147 buffer[writePointer] = chn1;
|
l@272
|
148
|
l@272
|
149 for (int i=1; i<numChannels; i++) {
|
l@272
|
150 // iterate over the function arguments, store them in the relevant part of the buffer
|
l@272
|
151 // channels are stored sequentially in the buffer i.e [[channel1], [channel2], etc...]
|
l@272
|
152 buffer[i*channelWidth + writePointer] = (float)va_arg(args, double);
|
l@272
|
153 }
|
l@272
|
154
|
l@272
|
155 writePointer = (writePointer+1)%channelWidth;
|
l@272
|
156
|
l@272
|
157 // if upSampling > 1, save repeated samples into the buffer
|
l@272
|
158 for (int j=1; j<upSampling; j++){
|
l@272
|
159
|
l@272
|
160 buffer[writePointer] = buffer[startingWritePointer];
|
l@272
|
161
|
l@272
|
162 for (int i=1; i<numChannels; i++) {
|
l@272
|
163 buffer[i*channelWidth + writePointer] = buffer[i*channelWidth + startingWritePointer];
|
l@272
|
164 }
|
l@272
|
165
|
l@272
|
166 writePointer = (writePointer+1)%channelWidth;
|
l@272
|
167
|
l@272
|
168 }
|
l@272
|
169
|
l@272
|
170 va_end (args);
|
l@272
|
171
|
l@272
|
172 }
|
l@272
|
173
|
l@272
|
174 bool Scope::trigger(){
|
l@272
|
175 if (triggerMode == 2 && !customTriggered && triggerPrimed && started){
|
l@272
|
176 customTriggerPointer = (writePointer-xOffset+channelWidth)%channelWidth;
|
l@272
|
177 customTriggered = true;
|
l@272
|
178 return true;
|
l@272
|
179 }
|
l@272
|
180 return false;
|
l@272
|
181 }
|
l@272
|
182
|
l@272
|
183 void Scope::scheduleSendBufferTask(){
|
giuliomoro@301
|
184 Bela_scheduleAuxiliaryTask(scopeSendBufferTask);
|
l@272
|
185 }
|
l@272
|
186
|
l@272
|
187 bool Scope::triggered(){
|
l@272
|
188 if (triggerMode == 0 || triggerMode == 1){ // normal or auto trigger
|
l@272
|
189 return (!triggerDir && buffer[channelWidth*triggerChannel+((readPointer-1+channelWidth)%channelWidth)] < triggerLevel // positive trigger direction
|
l@272
|
190 && buffer[channelWidth*triggerChannel+readPointer] >= triggerLevel) ||
|
l@272
|
191 (triggerDir && buffer[channelWidth*triggerChannel+((readPointer-1+channelWidth)%channelWidth)] > triggerLevel // negative trigger direciton
|
l@272
|
192 && buffer[channelWidth*triggerChannel+readPointer] <= triggerLevel);
|
l@272
|
193 } else if (triggerMode == 2){ // custom trigger
|
l@272
|
194 return (customTriggered && readPointer == customTriggerPointer);
|
l@272
|
195 }
|
l@272
|
196 return false;
|
l@272
|
197 }
|
l@272
|
198
|
l@272
|
199 void Scope::doTrigger(){
|
l@272
|
200 // iterate over the samples between the read and write pointers and check for / deal with triggers
|
l@272
|
201 while (readPointer != writePointer){
|
l@272
|
202
|
l@272
|
203 // if we are currently listening for a trigger
|
l@272
|
204 if (triggerPrimed){
|
l@272
|
205
|
l@272
|
206 // if we crossed the trigger threshold
|
l@272
|
207 if (triggered()){
|
l@272
|
208
|
l@272
|
209 // stop listening for a trigger
|
l@272
|
210 triggerPrimed = false;
|
l@272
|
211 triggerCollecting = true;
|
l@272
|
212
|
l@272
|
213 // save the readpointer at the trigger point
|
l@272
|
214 triggerPointer = (readPointer-xOffset+channelWidth)%channelWidth;
|
l@272
|
215
|
l@272
|
216 triggerCount = frameWidth/2 - xOffset;
|
l@272
|
217 autoTriggerCount = 0;
|
l@272
|
218
|
l@272
|
219 } else {
|
l@272
|
220 // auto triggering
|
l@272
|
221 if (triggerMode == 0 && (autoTriggerCount++ > (frameWidth+holdOff))){
|
l@272
|
222 // it's been a whole frameWidth since we've found a trigger, so auto-trigger anyway
|
l@272
|
223 triggerPrimed = false;
|
l@272
|
224 triggerCollecting = true;
|
l@272
|
225
|
l@272
|
226 // save the readpointer at the trigger point
|
l@272
|
227 triggerPointer = (readPointer-xOffset+channelWidth)%channelWidth;
|
l@272
|
228
|
l@272
|
229 triggerCount = frameWidth/2 - xOffset;
|
l@272
|
230 autoTriggerCount = 0;
|
l@272
|
231 }
|
l@272
|
232 }
|
l@272
|
233
|
l@272
|
234 } else if (triggerCollecting){
|
l@272
|
235
|
l@272
|
236 // a trigger has been detected, and we are collecting the second half of the triggered frame
|
l@272
|
237 if (--triggerCount > 0){
|
l@272
|
238
|
l@272
|
239 } else {
|
l@272
|
240 triggerCollecting = false;
|
l@272
|
241 triggerWaiting = true;
|
l@272
|
242 triggerCount = frameWidth/2 + holdOffSamples;
|
l@272
|
243
|
l@272
|
244 // copy the previous to next frameWidth/2 samples into the outBuffer
|
l@272
|
245 int startptr = (triggerPointer-frameWidth/2 + channelWidth)%channelWidth;
|
l@272
|
246 int endptr = (triggerPointer+frameWidth/2 + channelWidth)%channelWidth;
|
l@272
|
247
|
l@272
|
248 if (endptr > startptr){
|
l@272
|
249 for (int i=0; i<numChannels; i++){
|
l@272
|
250 std::copy(&buffer[channelWidth*i+startptr], &buffer[channelWidth*i+endptr], outBuffer.begin()+(i*frameWidth));
|
l@272
|
251 }
|
l@272
|
252 } else {
|
l@272
|
253 for (int i=0; i<numChannels; i++){
|
l@272
|
254 std::copy(&buffer[channelWidth*i+startptr], &buffer[channelWidth*(i+1)], outBuffer.begin()+(i*frameWidth));
|
l@272
|
255 std::copy(&buffer[channelWidth*i], &buffer[channelWidth*i+endptr], outBuffer.begin()+((i+1)*frameWidth-endptr));
|
l@272
|
256 }
|
l@272
|
257 }
|
l@272
|
258
|
l@272
|
259 // the whole frame has been saved in outBuffer, so send it
|
l@272
|
260 scheduleSendBufferTask();
|
l@272
|
261
|
l@272
|
262 }
|
l@272
|
263
|
l@272
|
264 } else if (triggerWaiting){
|
l@272
|
265
|
l@272
|
266 // a trigger has completed, so wait half a framewidth before looking for another
|
l@272
|
267 if (--triggerCount > 0){
|
l@272
|
268
|
l@272
|
269 } else {
|
l@272
|
270 triggerWaiting = false;
|
l@272
|
271 triggerPrimed = true;
|
l@272
|
272 customTriggered = false;
|
l@272
|
273 }
|
l@272
|
274
|
l@272
|
275 }
|
l@272
|
276
|
l@272
|
277 // increment the read pointer
|
l@272
|
278 readPointer = (readPointer+1)%channelWidth;
|
l@272
|
279 }
|
l@272
|
280
|
l@272
|
281 }
|
l@272
|
282
|
l@272
|
283 void Scope::sendBuffer(){
|
l@272
|
284 socket.send(&(outBuffer[0]), outBuffer.size()*sizeof(float));
|
l@272
|
285 }
|
l@272
|
286
|
l@272
|
287 void Scope::parseMessage(oscpkt::Message msg){
|
l@272
|
288 if (msg.partialMatch("/scope-settings/")){
|
l@272
|
289 int intArg;
|
l@272
|
290 float floatArg;
|
l@272
|
291 if (msg.match("/scope-settings/connected").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
292 if (connected == 0 && intArg == 1){
|
l@272
|
293 start();
|
l@272
|
294 } else if (connected == 1 && intArg == 0){
|
l@272
|
295 stop();
|
l@272
|
296 }
|
l@272
|
297 connected = intArg;
|
l@272
|
298 } else if (msg.match("/scope-settings/frameWidth").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
299 stop();
|
l@272
|
300 frameWidth = intArg;
|
l@272
|
301 start();
|
l@272
|
302 } else if (msg.match("/scope-settings/triggerMode").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
303 triggerMode = intArg;
|
l@272
|
304 } else if (msg.match("/scope-settings/triggerChannel").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
305 triggerChannel = intArg;
|
l@272
|
306 } else if (msg.match("/scope-settings/triggerDir").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
307 triggerDir = intArg;
|
l@272
|
308 } else if (msg.match("/scope-settings/triggerLevel").popFloat(floatArg).isOkNoMoreArgs()){
|
l@272
|
309 triggerLevel = floatArg;
|
l@272
|
310 } else if (msg.match("/scope-settings/xOffset").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
311 xOffset = intArg;
|
l@272
|
312 } else if (msg.match("/scope-settings/upSampling").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
313 upSampling = intArg;
|
l@272
|
314 holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
|
l@272
|
315 } else if (msg.match("/scope-settings/downSampling").popInt32(intArg).isOkNoMoreArgs()){
|
l@272
|
316 downSampling = intArg;
|
l@272
|
317 holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
|
l@272
|
318 } else if (msg.match("/scope-settings/holdOff").popFloat(floatArg).isOkNoMoreArgs()){
|
l@272
|
319 holdOff = floatArg;
|
l@272
|
320 holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
|
l@272
|
321 }
|
l@272
|
322 }
|
l@272
|
323 }
|