l@272: /***** Scope.cpp *****/
l@272: #include <Scope.h>
l@272: 
l@272: Scope::Scope():connected(0), triggerPrimed(false), started(false){}
l@272: 
l@272: // static aux task functions
l@272: void Scope::triggerTask(void *ptr){
l@272:     Scope *instance = (Scope*)ptr;
l@272:     if (instance->started)
l@272:         instance->doTrigger();
l@272: }
l@272: void Scope::sendBufferTask(void *ptr){
l@272:     Scope *instance = (Scope*)ptr;
l@272:     instance->sendBuffer();
l@272: }
l@272: 
l@272: void Scope::setup(unsigned int _numChannels, float _sampleRate){
l@272:     
l@272:     numChannels = _numChannels;
l@272:     sampleRate = _sampleRate;
l@272:     
l@272:     // setup the OSC server && client
l@272:     // used for sending / recieving settings
l@272:     // oscClient has it's send task turned off, as we only need to send one handshake message
l@272:     oscServer.setup(OSC_RECIEVE_PORT);
l@272:     oscClient.setup(OSC_SEND_PORT, "127.0.0.1", false);
l@272: 
l@272:     // setup the udp socket
l@272:     // used for sending raw frame data
l@272:     socket.setServer("127.0.0.1");
l@272: 	socket.setPort(SCOPE_UDP_PORT);
l@272: 
l@272: 	// setup the auxiliary tasks
andrewm@303: 	scopeTriggerTask = Bela_createAuxiliaryTask(Scope::triggerTask, BELA_AUDIO_PRIORITY-2, "scopeTriggerTask", this, true);
andrewm@303: 	scopeSendBufferTask = Bela_createAuxiliaryTask(Scope::sendBufferTask, BELA_AUDIO_PRIORITY-1, "scopeSendBufferTask", this);
l@272: 
l@272:     // send an OSC message to address /scope-setup
l@272:     // then wait 1 second for a reply on /scope-setup-reply 
l@272:     bool handshakeRecieved = false;
l@272:     oscClient.sendMessageNow(oscClient.newMessage.to("/scope-setup").add((int)numChannels).add(sampleRate).end());
l@272:     oscServer.recieveMessageNow(1000);
l@272:     while (oscServer.messageWaiting()){
l@272:         if (handshakeRecieved){
l@272:             parseMessage(oscServer.popMessage());
l@272:         } else if (oscServer.popMessage().match("/scope-setup-reply")){
l@272:             handshakeRecieved = true;
l@272:         }
l@272:     }
l@272:     
l@272:     if (handshakeRecieved && connected)
l@272:         start();
l@272:     
l@272: }
l@272: 
l@272: void Scope::start(){
l@272: 
l@272:     // resize the buffers
l@272:     channelWidth = frameWidth * FRAMES_STORED;
l@272:     buffer.resize(numChannels*channelWidth);
l@272:     outBuffer.resize(numChannels*frameWidth);
l@272: 
l@272:     // reset the pointers
l@272:     writePointer = 0;
l@272:     readPointer = 0;
l@272:     triggerPointer = 0;
l@272: 
l@272:     // reset the trigger
l@272:     triggerPrimed = true;
l@272:     triggerCollecting = false;
l@272:     triggerWaiting = false;
l@272:     triggerCount = 0;
l@272:     downSampleCount = 1;
l@272:     autoTriggerCount = 0;
l@272:     customTriggered = false;
l@272: 
l@272:     started = true;
l@272: }
l@272: 
l@272: void Scope::stop(){
l@272:     started = false;
l@272: }
l@272: 
l@272: void Scope::log(float chn1, ...){
l@272: 
l@272:     // check for any recieved OSC messages
l@272:     while (oscServer.messageWaiting()){
l@272:         parseMessage(oscServer.popMessage());
l@272:     }
l@272:     
l@272:     if (!started) return;
l@272:     
l@272:     if (downSampling > 1){
l@272:         if (downSampleCount < downSampling){
l@272:             downSampleCount++;
l@272:             return;
l@272:         }
l@272:         downSampleCount = 1;
l@272:     }
l@272:     
l@272:     va_list args;
l@272:     va_start (args, chn1);
l@272:     
l@272:     int startingWritePointer = writePointer;
l@272:   
l@272:     // save the logged samples into the buffer
l@272:     buffer[writePointer] = chn1;
l@272: 
l@272:     for (int i=1; i<numChannels; i++) {
l@272:         // iterate over the function arguments, store them in the relevant part of the buffer
l@272:         // channels are stored sequentially in the buffer i.e [[channel1], [channel2], etc...]
l@272:         buffer[i*channelWidth + writePointer] = (float)va_arg(args, double);
l@272:     }
l@272: 
l@272:     writePointer = (writePointer+1)%channelWidth;
l@272:     
l@272:     // if upSampling > 1, save repeated samples into the buffer
l@272:     for (int j=1; j<upSampling; j++){
l@272:         
l@272:         buffer[writePointer] = buffer[startingWritePointer];
l@272:     
l@272:         for (int i=1; i<numChannels; i++) {
l@272:             buffer[i*channelWidth + writePointer] = buffer[i*channelWidth + startingWritePointer];
l@272:         }
l@272:     
l@272:         writePointer = (writePointer+1)%channelWidth;
l@272:         
l@272:     }
l@272:     
l@272:     va_end (args);
l@272:     
l@272: }
l@272: 
l@272: bool Scope::trigger(){
l@272:     if (triggerMode == 2 && !customTriggered && triggerPrimed && started){
l@272:         customTriggerPointer = (writePointer-xOffset+channelWidth)%channelWidth;
l@272:         customTriggered = true;
l@272:         return true;
l@272:     }
l@272:     return false;
l@272: }
l@272: 
l@272: void Scope::scheduleSendBufferTask(){
giuliomoro@301:     Bela_scheduleAuxiliaryTask(scopeSendBufferTask);
l@272: }
l@272: 
l@272: bool Scope::triggered(){
l@272:     if (triggerMode == 0 || triggerMode == 1){  // normal or auto trigger
l@272:         return (!triggerDir && buffer[channelWidth*triggerChannel+((readPointer-1+channelWidth)%channelWidth)] < triggerLevel // positive trigger direction
l@272:                 && buffer[channelWidth*triggerChannel+readPointer] >= triggerLevel) || 
l@272:                 (triggerDir && buffer[channelWidth*triggerChannel+((readPointer-1+channelWidth)%channelWidth)] > triggerLevel // negative trigger direciton
l@272:                 && buffer[channelWidth*triggerChannel+readPointer] <= triggerLevel);
l@272:     } else if (triggerMode == 2){   // custom trigger
l@272:         return (customTriggered && readPointer == customTriggerPointer);
l@272:     }
l@272:     return false;
l@272: }
l@272: 
l@272: void Scope::doTrigger(){
l@272:     // iterate over the samples between the read and write pointers and check for / deal with triggers
l@272:     while (readPointer != writePointer){
l@272:         
l@272:         // if we are currently listening for a trigger
l@272:         if (triggerPrimed){
l@272:             
l@272:             // if we crossed the trigger threshold
l@272:             if (triggered()){
l@272:                 
l@272:                 // stop listening for a trigger
l@272:                 triggerPrimed = false;
l@272:                 triggerCollecting = true;
l@272:                 
l@272:                 // save the readpointer at the trigger point
l@272:                 triggerPointer = (readPointer-xOffset+channelWidth)%channelWidth;
l@272:                 
l@272:                 triggerCount = frameWidth/2 - xOffset;
l@272:                 autoTriggerCount = 0;
l@272:                 
l@272:             } else {
l@272:                 // auto triggering
l@272:                 if (triggerMode == 0 && (autoTriggerCount++ > (frameWidth+holdOff))){
l@272:                     // it's been a whole frameWidth since we've found a trigger, so auto-trigger anyway
l@272:                     triggerPrimed = false;
l@272:                     triggerCollecting = true;
l@272:                     
l@272:                     // save the readpointer at the trigger point
l@272:                     triggerPointer = (readPointer-xOffset+channelWidth)%channelWidth;
l@272:                     
l@272:                     triggerCount = frameWidth/2 - xOffset;
l@272:                     autoTriggerCount = 0;
l@272:                 }
l@272:             }
l@272:             
l@272:         } else if (triggerCollecting){
l@272:             
l@272:             // a trigger has been detected, and we are collecting the second half of the triggered frame
l@272:             if (--triggerCount > 0){
l@272:                 
l@272:             } else {
l@272:                 triggerCollecting = false;
l@272:                 triggerWaiting = true;
l@272:                 triggerCount = frameWidth/2 + holdOffSamples;
l@272:                 
l@272:                 // copy the previous to next frameWidth/2 samples into the outBuffer
l@272:                 int startptr = (triggerPointer-frameWidth/2 + channelWidth)%channelWidth;
l@272:                 int endptr = (triggerPointer+frameWidth/2 + channelWidth)%channelWidth;
l@272:                 
l@272:                 if (endptr > startptr){
l@272:                     for (int i=0; i<numChannels; i++){
l@272:                         std::copy(&buffer[channelWidth*i+startptr], &buffer[channelWidth*i+endptr], outBuffer.begin()+(i*frameWidth));
l@272:                     }
l@272:                 } else {
l@272:                     for (int i=0; i<numChannels; i++){
l@272:                         std::copy(&buffer[channelWidth*i+startptr], &buffer[channelWidth*(i+1)], outBuffer.begin()+(i*frameWidth));
l@272:                         std::copy(&buffer[channelWidth*i], &buffer[channelWidth*i+endptr], outBuffer.begin()+((i+1)*frameWidth-endptr));
l@272:                     }
l@272:                 }
l@272:                 
l@272:                 // the whole frame has been saved in outBuffer, so send it
l@272:                 scheduleSendBufferTask();
l@272:                 
l@272:             }
l@272:             
l@272:         } else if (triggerWaiting){
l@272:             
l@272:             // a trigger has completed, so wait half a framewidth before looking for another
l@272:             if (--triggerCount > 0){
l@272:                 
l@272:             } else {
l@272:                 triggerWaiting = false;
l@272:                 triggerPrimed = true;
l@272:                 customTriggered = false;
l@272:             }
l@272:             
l@272:         }
l@272:         
l@272:         // increment the read pointer
l@272:         readPointer = (readPointer+1)%channelWidth;
l@272:     }
l@272: 
l@272: }
l@272: 
l@272: void Scope::sendBuffer(){
l@272:     socket.send(&(outBuffer[0]), outBuffer.size()*sizeof(float));
l@272: }
l@272: 
l@272: void Scope::parseMessage(oscpkt::Message msg){
l@272:     if (msg.partialMatch("/scope-settings/")){
l@272:         int intArg;
l@272:         float floatArg;
l@272:         if (msg.match("/scope-settings/connected").popInt32(intArg).isOkNoMoreArgs()){
l@272:             if (connected == 0 && intArg == 1){
l@272:                 start();
l@272:             } else if (connected == 1 && intArg == 0){
l@272:                 stop();
l@272:             }
l@272:             connected = intArg;
l@272:         } else if (msg.match("/scope-settings/frameWidth").popInt32(intArg).isOkNoMoreArgs()){
l@272:             stop();
l@272:             frameWidth = intArg;
l@272:             start();
l@272:         } else if (msg.match("/scope-settings/triggerMode").popInt32(intArg).isOkNoMoreArgs()){
l@272:             triggerMode = intArg;
l@272:         } else if (msg.match("/scope-settings/triggerChannel").popInt32(intArg).isOkNoMoreArgs()){
l@272:             triggerChannel = intArg;
l@272:         } else if (msg.match("/scope-settings/triggerDir").popInt32(intArg).isOkNoMoreArgs()){
l@272:             triggerDir = intArg;
l@272:         } else if (msg.match("/scope-settings/triggerLevel").popFloat(floatArg).isOkNoMoreArgs()){
l@272:             triggerLevel = floatArg;
l@272:         } else if (msg.match("/scope-settings/xOffset").popInt32(intArg).isOkNoMoreArgs()){
l@272:             xOffset = intArg;
l@272:         } else if (msg.match("/scope-settings/upSampling").popInt32(intArg).isOkNoMoreArgs()){
l@272:             upSampling = intArg;
l@272:             holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
l@272:         } else if (msg.match("/scope-settings/downSampling").popInt32(intArg).isOkNoMoreArgs()){
l@272:             downSampling = intArg;
l@272:             holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
l@272:         } else if (msg.match("/scope-settings/holdOff").popFloat(floatArg).isOkNoMoreArgs()){
l@272:             holdOff = floatArg;
l@272:             holdOffSamples = (int)(sampleRate*0.001*holdOff*upSampling/downSampling);
l@272:         }
l@272:     }
l@272: }