The codebase to run the *spark d-fuser controller www.sparkav.co.uk/dvimixer
Dependencies: SPK-TVOne DMX DmxArtNet NetServicesMin OSC PinDetect mRotaryEncoder iniparser mbed spk_oled_ssd1305 filter
main.cpp
- Committer:
- tobyspark
- Date:
- 2012-04-23
- Revision:
- 4:d5ff91b66357
- Parent:
- 3:033d2b7768f3
- Child:
- 5:f8b285ca41ba
File content as of revision 4:d5ff91b66357:
// *SPARK D-FUSER // A project by *spark audio-visual // // 'DJ' controller styke RS232 Control for TV-One products // Good for 1T-C2-750, others will need some extra work // // Copyright *spark audio-visual 2009-2011 // // v10 - Port to mBed, keying redux - Apr'11 // v11 - Sign callbacks, code clean-up - Apr'11 // v12 - TVOne header split into two: defines and mbed class. v002 header updates pulled down. Removed sign callbacks, rewrite of debug and signing. - Apr'11 // v13 - Menu system for Resolution + Keying implemented, it writing to debug, it sending TVOne commands - Apr'11 // v14 - Fixes for new PCB - Oct'11 // v15 - TBZ PCB, OLED - Mar'12 // v16 - Comms menu, OSC. There in theory: lots of trouble from EthernetNetIf. NetServices better. But still silently crashes on creation of EthernetNetIf, despite (now) ample memory and code tested elsewhere (inc OSC + spkOLED). // vxx - TODO: EDID upload from USB mass storage // vxx - TODO: EDID creation from resolution #include "mbed.h" #include "spk_tvone_mbed.h" #include "spk_utils.h" #include "spk_mRotaryEncoder.h" #include "spk_oled_ssd1305.h" #include "spk_oled_gfx.h" #include "EthernetNetIf.h" #include "mbedOSC.h" #include "DmxArtNet.h" #include <sstream> #define kMenuLine1 3 #define kMenuLine2 4 #define kCommsStatusLine 6 #define kTVOneStatusLine 7 #define kOSCMbedPort 10000 #define kOSCMbedIPAddress 10,0,0,2 #define kOSCMbedSubnetMask 255,255,255,0 #define kOSCMbedGateway 10,0,0,1 #define kOSCMbedDNS 10,0,0,1 #define kArtNetBindIPAddress 2,0,0,100 #define kArtNetBroadcastAddress 2,255,255,255 //// DEBUG // Comment out one or the other... Serial *debug = new Serial(USBTX, USBRX); // For debugging via USB serial // Serial *debug = NULL; // For release (no debugging) //// mBED PIN ASSIGNMENTS // Inputs AnalogIn xFadeAIN(p20); AnalogIn fadeUpAIN(p19); DigitalIn tapLeftDIN(p24); DigitalIn tapRightDIN(p23); SPKRotaryEncoder menuEnc(p17, p16, p15); // Outputs PwmOut fadeAPO(LED1); PwmOut fadeBPO(LED2); // SPKTVOne(PinName txPin, PinName rxPin, PinName signWritePin, PinName signErrorPin, Serial *debugSerial) SPKTVOne tvOne(p13, p14, LED3, LED4, debug); // SPKDisplay(PinName mosi, PinName clk, PinName cs, PinName dc, PinName res, Serial *debugSerial = NULL); SPKDisplay screen(p5, p7, p8, p10, p9, debug); // Menu SPKMenu *selectedMenu; SPKMenu *lastSelectedMenu; SPKMenuOfMenus mainMenu; SPKMenuPayload resolutionMenu; SPKMenuPayload mixModeMenu; SPKMenuPayload commsMenu; // Comms Objects EthernetNetIf *ethernet = NULL; OSCClass *osc = NULL; OSCMessage recMessage; DmxArtNet *artNet = NULL; // Fade logic constants const float xFadeTolerance = 0.05; const float fadeUpTolerance = 0.05; // A&B Fade as resolved percent int fadeAPercent = 0; int fadeBPercent = 0; // Tap button states bool tapLeftPrevious = false; bool tapRightPrevious = false; // Key mode parameters int keyerParamsSet = -1; // last keyParams index uploaded to unit // {lumakey, chroma on blue [, to be extended as needed] } // {minY, maxY, minU, maxU, minV, maxV } int keyerParams[2][6] = { {0, 18, 128, 129, 128, 129}, // lumakey {41, 42, 240, 241, 109, 110} // chroma on blue // ... }; void processOSC(float &xFade, float &fadeUp) { std::stringstream statusMessage; statusMessage.setf(ios::fixed,ios::floatfield); statusMessage.precision(2); if (!strcmp( recMessage.getTopAddress() , "dvimxr" )) { statusMessage << "OSC: /dvimxr"; if (!strcmp( recMessage.getSubAddress() , "xFade" )) if (recMessage.getArgNum() == 1) if (recMessage.getTypeTag(0) == 'f') { double newXFade = recMessage.getArgFloat(0); statusMessage << "/xFade " << newXFade; xFade = newXFade; } else if (!strcmp( recMessage.getSubAddress() , "fadeUp" )) if (recMessage.getArgNum() == 1) if (recMessage.getTypeTag(0) == 'f') { double newFadeUp = recMessage.getArgFloat(0); statusMessage << "/fadeUp " << newFadeUp; xFade = newFadeUp; } else statusMessage << recMessage.getSubAddress() << " - Ignoring"; } else { statusMessage << "OSC: " << recMessage.getTopAddress() << " - Ignoring"; } screen.clearBufferRow(kCommsStatusLine); screen.textToBuffer(statusMessage.str(), kCommsStatusLine); screen.sendBuffer(); if (debug) debug->printf("%s \r\n", statusMessage.str().c_str()); } void processArtNet(float &xFade, float &fadeUp) { screen.clearBufferRow(kCommsStatusLine); screen.textToBuffer("ArtNet activity", kCommsStatusLine); screen.sendBuffer(); if (debug) debug->printf("ArtNet activity"); } inline float fadeCalc (const float AIN, const float tolerance) { float pos ; if (AIN < tolerance) pos = 0; else if (AIN > 1.0 - tolerance) pos = 1; else pos = (AIN - tolerance) / (1 - 2*tolerance); if (debug && false) debug->printf("fadeCalc in: %f out: %f \r\n", AIN, pos); return pos; } bool setKeyParamsTo(int index) { // Only spend the time uploading six parameters if we need to // Might want to bounds check here bool ok = false; if (index != keyerParamsSet) { ok = tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinY, keyerParams[index][0]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxY, keyerParams[index][1]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinU, keyerParams[index][2]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxU, keyerParams[index][3]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinV, keyerParams[index][4]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxV, keyerParams[index][5]); keyerParamsSet = index; } return ok; } int main() { if (debug) { debug->printf("\r\n\r\n"); debug->printf("*spark d-fuser -----------\r\n"); debug->printf(" debug channel\r\n"); } // Set display font screen.fontStartCharacter = &characterBytesStartChar; screen.fontEndCharacter = &characterBytesEndChar; screen.fontCharacters = characterBytes; // Splash screen screen.imageToBuffer(spkDisplayLogo); screen.textToBuffer("SPK:D-Fuser",0); screen.textToBuffer("SW beta.15",1); // Set menu structure mixModeMenu.title = "Mix Mode"; enum { blend, additive, lumaKey, chromaKey1, chromaKey2, chromaKey3 }; // additive will require custom TVOne firmware. mixModeMenu.addMenuItem("Blend", blend, 0); mixModeMenu.addMenuItem("LumaKey", lumaKey, 0); mixModeMenu.addMenuItem("ChromaKey - Blue", chromaKey1, 0); resolutionMenu.title = "Resolution"; resolutionMenu.addMenuItem(kTV1ResolutionDescriptionVGA, kTV1ResolutionVGA, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionSVGA, kTV1ResolutionSVGA, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionXGAp60, kTV1ResolutionXGAp60, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionWSXGAPLUSp60, kTV1ResolutionWSXGAPLUSp60, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionWUXGAp60, kTV1ResolutionWUXGAp60, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescription720p60, kTV1Resolution720p60, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescription1080p60, kTV1Resolution1080p60, 5); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionDualHeadSVGAp60, kTV1ResolutionDualHeadSVGAp60, 0); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionDualHeadXGAp60, kTV1ResolutionDualHeadXGAp60, 0); resolutionMenu.addMenuItem(kTV1ResolutionDescriptionTripleHeadVGAp60, kTV1ResolutionTripleHeadVGAp60, 0); commsMenu.title = "Network Mode"; enum { commsNone, commsOSC, commsArtNet, commsDMX}; commsMenu.addMenuItem("None", commsNone, 0); commsMenu.addMenuItem("OSC", commsOSC, 0); commsMenu.addMenuItem("ArtNet", commsArtNet, 0); commsMenu.addMenuItem("DMX", commsDMX, 0); mainMenu.title = "Main Menu"; mainMenu.addMenuItem(&mixModeMenu); mainMenu.addMenuItem(&resolutionMenu); mainMenu.addMenuItem(&commsMenu); selectedMenu = &mainMenu; lastSelectedMenu = &mainMenu; // Misc I/O stuff fadeAPO.period(0.001); fadeBPO.period(0.001); // TVOne setup bool ok = false; // horrid, horrid HDCP ok = tvOne.setHDCPOff(); std::string sendOK = ok ? "Sent: HDCP Off" : "Send Error: HDCP Off"; // display menu and framing lines screen.horizLineToBuffer(kMenuLine1*pixInPage - 1); screen.clearBufferRow(kMenuLine1); screen.textToBuffer(selectedMenu->title, kMenuLine1); screen.clearBufferRow(kMenuLine2); screen.textToBuffer(selectedMenu->selectedString(), kMenuLine2); screen.horizLineToBuffer(kMenuLine2*pixInPage + pixInPage); screen.horizLineToBuffer(kCommsStatusLine*pixInPage - 1); screen.clearBufferRow(kCommsStatusLine); screen.textToBuffer(commsMenu.selectedString(), kCommsStatusLine); screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sendOK, kTVOneStatusLine); screen.sendBuffer(); //// CONTROLS TEST while (0) { if (debug) debug->printf("xFade: %f, fadeOut: %f, tapLeft %i, tapRight: %i encPos: %i encChange:%i encHasPressed:%i \r\n" , xFadeAIN.read(), fadeUpAIN.read(), tapLeftDIN.read(), tapRightDIN.read(), menuEnc.getPos(), menuEnc.getChange(), menuEnc.hasPressed()); } //// MIXER RUN while (1) { //// Task background things if (commsMenu.selectedPayload1() == commsOSC || commsMenu.selectedPayload1() == commsArtNet) { Net::poll(); } //// MENU int menuChange = menuEnc.getChange(); // Update GUI if (menuChange != 0) { if (debug) debug->printf("Menu changed by %i\r\n", menuChange); *selectedMenu = selectedMenu->selectedIndex() + menuChange; // update OLED line 2 here screen.clearBufferRow(kMenuLine2); screen.textToBuffer(selectedMenu->selectedString(), kMenuLine2); if (debug) debug->printf("%s \r\n", selectedMenu->selectedString().c_str()); } // Action menu item if (menuEnc.hasPressed()) { if (debug) debug->printf("Action Menu Item!\r\n"); // Are we changing menus? if (selectedMenu->type() == menuOfMenus) { // point selected menu to the new menu // FIXME. Make this function abstract virtual of base class or get dynamic_cast working. BTW: C++ sucks / Obj-c rocks / Right now. if (selectedMenu == &mainMenu) selectedMenu = mainMenu.selectedMenu(); else if (debug) debug->printf("FIXME: You've missed a SPKMenuOfMenus"); // reset the selection within that menu to the first position (*selectedMenu) = 0; // update OLED lines 1&2 screen.clearBufferRow(kMenuLine1); screen.clearBufferRow(kMenuLine2); screen.textToBuffer(selectedMenu->title, kMenuLine1); screen.textToBuffer(selectedMenu->selectedString(), kMenuLine2); if (debug) { debug->printf("\r\n"); debug->printf("%s \r\n", selectedMenu->title.c_str()); debug->printf("%s \r\n", selectedMenu->selectedString().c_str()); } } // Are we cancelling? else if (selectedMenu->type() == payload && selectedMenu->selectedIndex() == 0) { selectedMenu = lastSelectedMenu; // update OLED lines 1&2 screen.clearBufferRow(kMenuLine1); screen.clearBufferRow(kMenuLine2); screen.textToBuffer(selectedMenu->title, kMenuLine1); screen.textToBuffer(selectedMenu->selectedString(), kMenuLine2); if (debug) { debug->printf("\r\n"); debug->printf("%s \r\n", selectedMenu->title.c_str()); debug->printf("%s \r\n", selectedMenu->selectedString().c_str()); } } // With that out of the way, we should be actioning a specific menu's payload? else if (selectedMenu == &mixModeMenu) { bool ok = false; std::string sentOK; std::stringstream sentMSG; // Set keying parameters switch (mixModeMenu.selectedPayload1()) { case lumaKey: ok = setKeyParamsTo(0); sentMSG << "Keyer Params 0, "; break; case chromaKey1: ok = setKeyParamsTo(1); sentMSG << "Keyer Params 1, "; break; } // Set keying on or off switch (mixModeMenu.selectedPayload1()) { case blend: case additive: ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerEnable, false); sentMSG << "Keyer Off"; break; case lumaKey: case chromaKey1: case chromaKey2: case chromaKey3: ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerEnable, true); sentMSG << "Keyer On"; break; } if (ok) sentOK = "Sent:"; else sentOK = "Send Error:"; screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sentOK + sentMSG.str(), kTVOneStatusLine); if (debug) { debug->printf("Changing mix mode"); } } else if (selectedMenu == &resolutionMenu) { bool ok = false; ok = tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsOutputResolution, resolutionMenu.selectedPayload1()); ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustSourceEDID, resolutionMenu.selectedPayload2()); ok = ok && tvOne.command(kTV1SourceRGB2, kTV1WindowIDA, kTV1FunctionAdjustSourceEDID, resolutionMenu.selectedPayload2()); std::string sentOK; if (ok) sentOK = "Sent: "; else sentOK = "Send Error: "; std::stringstream sentMSG; sentMSG << "Res " << resolutionMenu.selectedPayload1() << ", EDID " << resolutionMenu.selectedPayload2(); screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sentOK + sentMSG.str(), kTVOneStatusLine); if (debug) { debug->printf("Changing resolution"); } } else if (selectedMenu == &commsMenu) { std::string commsType = "Network: --"; std::stringstream commsStatus; // Tear down any existing comms // This is the action of commsNone // And also clears the way for other comms actions if (osc) {delete osc; osc = NULL;} if (ethernet) {delete ethernet; ethernet = NULL;} if (artNet) {delete artNet; artNet = NULL;} if (commsMenu.selectedPayload1() == commsOSC) { commsType = "OSC: "; ethernet = new EthernetNetIf( IpAddr(kOSCMbedIPAddress), IpAddr(kOSCMbedSubnetMask), IpAddr(kOSCMbedGateway), IpAddr(kOSCMbedDNS) ); EthernetErr ethError = ethernet->setup(); if(ethError) { if (debug) debug->printf("Ethernet setup error, %d", ethError); commsStatus << "Ethernet setup failed"; // commsMenu = commsNone; //FIXME: this should set the selected menu item to none, but errors. wtf? // break out of here. this setup should be a function that returns a boolean } osc = new OSCClass(); osc->setReceiveMessage(&recMessage); osc->begin(kOSCMbedPort); commsStatus << "Listening on " << kOSCMbedPort; } else if (commsMenu.selectedPayload1() == commsArtNet) { commsType = "ArtNet: "; artNet = new DmxArtNet(); artNet->BindIpAddress = IpAddr(kArtNetBindIPAddress); artNet->BCastAddress = IpAddr(kArtNetBroadcastAddress); artNet->InitArtPollReplyDefaults(); artNet->ArtPollReply.PortType[0] = 128; // output artNet->ArtPollReply.PortType[2] = 64; // input artNet->ArtPollReply.GoodInput[2] = 4; artNet->Init(); artNet->SendArtPollReply(); // announce to art-net nodes commsStatus << "Listening"; } else if (commsMenu.selectedPayload1() == commsDMX) { } screen.clearBufferRow(kCommsStatusLine); screen.textToBuffer(commsType + commsStatus.str(), kCommsStatusLine); } else { if (debug) { debug->printf("Warning: No action identified"); } } } // Send any updates to the display screen.sendBuffer(); //// MIX MIX MIX MIX MIX MIX MIX MIX MIXMIX MIX MIXMIX MIX MIXMIX MIX MIXMIX MIX MIXMIX MIX MIX bool updateFade = false; float xFade = 0; float fadeUp = 1; //// TASK: Process control surface // Get new states of tap buttons, remembering at end of loop() assign these current values to the previous variables const bool tapLeft = (tapLeftDIN) ? false : true; const bool tapRight = (tapRightDIN) ? false : true; // We're going to cache the analog in reads, as have seen wierdness otherwise const float xFadeAINCached = xFadeAIN.read(); const float fadeUpAINCached = fadeUpAIN.read(); // When a tap is depressed, we can ignore any move of the crossfader but not fade to black if (tapLeft || tapRight) { // If both are pressed, which was not pressed in the last loop? if (tapLeft && tapRight) { if (!tapLeftPrevious) xFade = 0; if (!tapRightPrevious) xFade = 1; } // If just one is pressed, is this it going high or the other going low? else if (tapLeft && (!tapLeftPrevious || tapRightPrevious)) xFade = 0; else if (tapRight && (!tapRightPrevious || tapLeftPrevious)) xFade = 1; } else xFade = fadeCalc(xFadeAINCached, xFadeTolerance); fadeUp = 1.0 - fadeCalc(fadeUpAINCached, fadeUpTolerance); //// TASK: Process Network Comms if (commsMenu.selectedPayload1() == commsOSC) { if (osc->newMessage) { osc->newMessage = false; // fixme! processOSC(xFade, fadeUp); } } if (commsMenu.selectedPayload1() == commsArtNet) { if (artNet->Work()) processArtNet(xFade, fadeUp); } // WISH: Really, we should have B at 100% and A fading in over that, with fade to black implemented as a fade in black layer on top of that correct mix. // There is no way to implement that though, and the alphas get messy, so this is the only way (afaik). // Calculate new A&B fade percents int newFadeAPercent = 0; int newFadeBPercent = 0; switch (mixModeMenu.selectedPayload1()) { case blend: case additive: newFadeAPercent = (1.0-xFade) * fadeUp * 100.0; newFadeBPercent = xFade * fadeUp * 100.0; break; case lumaKey: case chromaKey1: case chromaKey2: case chromaKey3: newFadeAPercent = (1.0-xFade) * fadeUp * 100.0; newFadeBPercent = fadeUp * 100.0; break; } // Send to TVOne if percents have changed if (newFadeAPercent != fadeAPercent) { fadeAPercent = newFadeAPercent; updateFade = true; fadeAPO = fadeAPercent / 100.0; tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsMaxFadeLevel, fadeAPercent); } if (newFadeBPercent != fadeBPercent) { fadeBPercent = newFadeBPercent; updateFade = true; fadeBPO = fadeBPercent / 100.0; tvOne.command(0, kTV1WindowIDB, kTV1FunctionAdjustWindowsMaxFadeLevel, fadeBPercent); } if (updateFade && debug) { debug->printf("\r\n"); //debug->printf("xFade = %3f fadeUp = %3f \r\n", xFadeAIN.read(), fadeUpAIN.read()); debug->printf("xFade = %3f fadeUp = %3f \r\n", xFadeAINCached, fadeUpAINCached); debug->printf("xFade = %3f fadeUp = %3f fadeA% = %i fadeB% = %i \r\n", xFade, fadeUp, fadeAPercent, fadeBPercent); } // END OF LOOP - Reset tapLeftPrevious = tapLeft; tapRightPrevious = tapRight; } }