![](/media/cache/profiles/89a26a363b87a87ac6a94277e841b7f4.50x50_q85.jpg)
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-10-26
- Revision:
- 23:909928cafb95
- Parent:
- 22:90054fe6d86c
- Child:
- 24:49c6624119ae
File content as of revision 23:909928cafb95:
/* *SPARK D-FUSER * A project by Toby Harris * * 'DJ' controller styke RS232 Control for TV-One products * Good for 1T-C2-750, others will need some extra work * * www.sparkav.co.uk/dvimixer */ /* Copyright (c) 2011 Toby Harris, MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* ROADMAP / HISTORY * 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, ArtNet - April'12 * v17 - RJ45 - May'12 * v18 - DMX - July'12 * v19 - TVOne mixing comms further optimised - August'12 * vxx - TODO: Keying values load from USB mass storage * vxx - TODO: Set keying values from controller, requires a guided, step-through process for user * vxx - TODO: Defaults load/save from USB mass storage * 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 "spk_settings.h" #include "EthernetNetIf.h" #include "mbedOSC.h" #include "DmxArtNet.h" #include "DMX.h" #include "filter.h" #include <sstream> #define kSPKDFSoftwareVersion "beta.19" // MBED PINS #define kMBED_AIN_XFADE p20 #define kMBED_AIN_FADEUP p19 #define kMBED_DIN_TAP_L p24 #define kMBED_DIN_TAP_R p23 #define kMBED_ENC_SW p15 #define kMBED_ENC_A p16 #define kMBED_ENC_B p17 #define kMBED_RS232_TTLTX p13 #define kMBED_RS232_TTLRX p14 #define kMBED_OLED_MOSI p5 #define kMBED_OLED_SCK p7 #define kMBED_OLED_CS p8 #define kMBED_OLED_RES p9 #define kMBED_OLED_DC p10 #define kMBED_DIN_ETHLO_DMXHI p30 #define kMBED_DOUT_RS485_TXHI_RXLO p29 #define kMBED_RS485_TTLTX p28 #define kMBED_RS485_TTLRX p27 // DISPLAY #define kMenuLine1 3 #define kMenuLine2 4 #define kCommsStatusLine 6 #define kTVOneStatusLine 7 // NETWORKING #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 #define kDMXInChannelXFade 0 #define kDMXInChannelFadeUp 1 #define kDMXOutChannelXFade 0 #define kDMXOutChannelFadeUp 1 // 8.3 format filename only, no subdirs #define kSPKDFSettingsFilename "SPKDF.ini" //// 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) //// SOFT RESET extern "C" void mbed_reset(); //// mBED PIN ASSIGNMENTS // Inputs AnalogIn xFadeAIN(kMBED_AIN_XFADE); AnalogIn fadeUpAIN(kMBED_AIN_FADEUP); DigitalIn tapLeftDIN(kMBED_DIN_TAP_L); DigitalIn tapRightDIN(kMBED_DIN_TAP_R); medianFilter xFadeFilter(9); medianFilter fadeUpFilter(9); SPKRotaryEncoder menuEnc(kMBED_ENC_A, kMBED_ENC_B, kMBED_ENC_SW); DigitalIn rj45ModeDIN(kMBED_DIN_ETHLO_DMXHI); // Outputs PwmOut fadeAPO(LED1); PwmOut fadeBPO(LED2); DigitalOut dmxDirectionDOUT(kMBED_DOUT_RS485_TXHI_RXLO); // SPKTVOne(PinName txPin, PinName rxPin, PinName signWritePin, PinName signErrorPin, Serial *debugSerial) SPKTVOne tvOne(kMBED_RS232_TTLTX, kMBED_RS232_TTLRX, LED3, LED4, debug); // SPKDisplay(PinName mosi, PinName clk, PinName cs, PinName dc, PinName res, Serial *debugSerial = NULL); SPKDisplay screen(kMBED_OLED_MOSI, kMBED_OLED_SCK, kMBED_OLED_CS, kMBED_OLED_DC, kMBED_OLED_RES, debug); // Saved Settings SPKSettings settings; // Menu SPKMenu *selectedMenu; SPKMenu *lastSelectedMenu; SPKMenu mainMenu; SPKMenu resolutionMenu; SPKMenu mixModeMenu; enum { mixBlend, mixAdditive, mixKey }; // additive will require custom TVOne firmware. int mixMode = mixBlend; float fadeCurve = 0; // 0 = "X", 1 = "/\" <-- pictograms! SPKMenu commsMenu; enum { commsNone, commsOSC, commsArtNet, commsDMXIn, commsDMXOut}; int commsMode = commsNone; SPKMenu advancedMenu; enum { advancedHDCPOn, advancedHDCPOff, advancedConformProcessor, advancedLoadDefaults, advancedSelfTest, advancedSetResolutions }; // RJ45 Comms enum { rj45Ethernet = 0, rj45DMX = 1}; // These values from circuit int rj45Mode = -1; EthernetNetIf *ethernet = NULL; OSCClass *osc = NULL; OSCMessage recMessage; DmxArtNet *artNet = NULL; DMX *dmx = 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; int oldFadeAPercent = 0; int oldFadeBPercent = 0; // Tap button states bool tapLeftWasFirstPressed = false; // Key mode parameters int keyerParamsSet = -1; // last keyParams index uploaded to unit 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"); } void processDMXIn(float &xFade, float &fadeUp) { std::stringstream statusMessage; int xFadeDMX = dmx->get(kDMXInChannelXFade); int fadeUpDMX = dmx->get(kDMXInChannelFadeUp); xFade = (float)xFadeDMX/255; fadeUp = (float)fadeUpDMX/255; screen.clearBufferRow(kCommsStatusLine); statusMessage << "DMX In: xF " << xFadeDMX << " fUp " << fadeUpDMX; screen.textToBuffer(statusMessage.str(), kCommsStatusLine); screen.sendBuffer(); if (debug) debug->printf(statusMessage.str().c_str()); } void processDMXOut(float &xFade, float &fadeUp) { std::stringstream statusMessage; int xFadeDMX = xFade*255; int fadeUpDMX = fadeUp*255; dmx->put(kDMXOutChannelXFade, xFadeDMX); dmx->put(kDMXOutChannelFadeUp, fadeUpDMX); screen.clearBufferRow(kCommsStatusLine); statusMessage << "DMX Out: xF " << xFadeDMX << " fUp " << fadeUpDMX; screen.textToBuffer(statusMessage.str(), kCommsStatusLine); screen.sendBuffer(); if (debug) debug->printf(statusMessage.str().c_str()); } 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; if (index != keyerParamsSet) { ok = tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinY, settings.keyerParamSet(index)[0]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxY, settings.keyerParamSet(index)[1]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinU, settings.keyerParamSet(index)[2]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxU, settings.keyerParamSet(index)[3]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMinV, settings.keyerParamSet(index)[4]); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerMaxV, settings.keyerParamSet(index)[5]); keyerParamsSet = index; } else { ok = true; } return ok; } bool conformProcessor() { bool ok = true; int32_t on = 1; int32_t off = 0; // Independent output ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsOutputEnable, on); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsLockMethod, off); // Make sure our windows exist ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsEnable, on); ok = ok && tvOne.command(0, kTV1WindowIDB, kTV1FunctionAdjustWindowsEnable, on); ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsLayerPriority, 0); ok = ok && tvOne.command(0, kTV1WindowIDB, kTV1FunctionAdjustWindowsLayerPriority, 1); // Assign inputs to windows, so that left on the crossfader is left on the processor viewed from front ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsWindowSource, kTV1SourceRGB2); ok = ok && tvOne.command(0, kTV1WindowIDB, kTV1FunctionAdjustWindowsWindowSource, kTV1SourceRGB1); // Set scaling to fit source within output, maintaining aspect ratio ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustWindowsZoomLevel, 100); ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDB, kTV1FunctionAdjustWindowsZoomLevel, 100); ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustWindowsShrinkEnable, off); ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDB, kTV1FunctionAdjustWindowsShrinkEnable, off); int32_t fit = 1; ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustSourceAspectCorrect, fit); ok = ok && tvOne.command(kTV1SourceRGB2, kTV1WindowIDA, kTV1FunctionAdjustSourceAspectCorrect, fit); // On source loss, hold on the last frame received. int32_t freeze = 1; ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustSourceOnSourceLoss, freeze); ok = ok && tvOne.command(kTV1SourceRGB2, kTV1WindowIDA, kTV1FunctionAdjustSourceOnSourceLoss, freeze); // Finally, autoset to sources? //int32_t start = 1; //ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustSourceAutoSet, start); //ok = ok && tvOne.command(kTV1SourceRGB2, kTV1WindowIDA, kTV1FunctionAdjustSourceAutoSet, start); return ok; } void selfTest() { /* SELF TEST - Pixels * Clicking �self-test� menu will display a solid lit screen. Check all pixels lit. * Verified: Display */ screen.imageToBuffer(spkDisplayAllPixelsOn); screen.sendBuffer(); while(!menuEnc.hasPressed()) { // do nothing, wait for press } /* SELF TEST - Mixing Controls * Clicking again will prompt to check crossfader, fade to black and tap buttons. Check movement of physical controls against 0.0-1.0 values on- screen. * Verified: Mixing controls. */ screen.clearBuffer(); screen.textToBuffer("Self test - Mixing Controls", 0); while(!menuEnc.hasPressed()) { stringstream xFadeReadOut; stringstream fadeToBlackReadOut; stringstream tapsReadOut; xFadeReadOut.precision(2); fadeToBlackReadOut.precision(2); tapsReadOut.precision(1); xFadeReadOut << "Crossfade: " << xFadeAIN.read(); fadeToBlackReadOut << "Fade to black: " << fadeUpAIN.read(); tapsReadOut << "Tap left: " << tapLeftDIN.read() << " right: " << tapRightDIN.read(); screen.clearBufferRow(1); screen.clearBufferRow(2); screen.clearBufferRow(3); screen.textToBuffer(xFadeReadOut.str(), 1); screen.textToBuffer(fadeToBlackReadOut.str(), 2); screen.textToBuffer(tapsReadOut.str(), 3); screen.sendBuffer(); } /* SELF TEST - RS232 * Click the controller menu control. Should see �RS232 test� prompt and test message. Ensure PC is displaying the test message. * Verified: RS232 connection. */ screen.clearBuffer(); screen.textToBuffer("Self test - RS232", 0); screen.sendBuffer(); while(!menuEnc.hasPressed()) { screen.textToBuffer("TODO!", 1); screen.sendBuffer(); } /* SELF TEST - DMX * Click the controller menu control. Should see �DMX test� prompt and test message. Ensure PC is displaying the test message. * Verified: RS485 connection and DMX library. */ screen.clearBuffer(); screen.textToBuffer("Self test - DMX", 0); screen.sendBuffer(); while(!menuEnc.hasPressed()) { screen.textToBuffer("TODO!", 1); screen.sendBuffer(); } /* SELF TEST - OSC * Click the controller menu control. Should see �OSC test� prompt and test message. Ensure PC is displaying the test message. * Verified: Ethernet connection and OSC library. */ screen.clearBuffer(); screen.textToBuffer("Self test - DMX", 0); screen.sendBuffer(); while(!menuEnc.hasPressed()) { screen.textToBuffer("TODO!", 1); screen.sendBuffer(); } /* SELF TEST - Exit! * To do this, we could just do nothing but we'd need to recreate screen and comms as they were. * Instead, lets just restart the mbed */ screen.clearBuffer(); screen.textToBuffer("Self test complete", 0); screen.textToBuffer("Press to restart controller", 1); screen.sendBuffer(); while(!menuEnc.hasPressed()) {} mbed_reset(); } 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(string("SW ") + kSPKDFSoftwareVersion,1); screen.sendBuffer(); // Load saved settings bool settingsAreCustom = false; /* CRAZY, see note in spk_settings.h settingsAreCustom = settings.load(kSPKDFSettingsFilename); if (settingsAreCustom) {screen.textToBuffer("Settings Read",2); screen.sendBuffer();} else {screen.textToBuffer("Settings NOT Read",2); screen.sendBuffer();} */ // Set menu structure mixModeMenu.title = "Mix Mode"; mixModeMenu.addMenuItem(SPKMenuItem("Blend", mixBlend)); if (true) mixModeMenu.addMenuItem(SPKMenuItem("Additive", mixAdditive)); // TODO: Detect whether SPKDF custom firmware // TODO: Set fadecurve parameter menuitem for (int i=0; i < settings.keyerSetCount(); i++) { mixModeMenu.addMenuItem(SPKMenuItem(settings.keyerParamName(i), mixKey+i)); } mixModeMenu.addMenuItem(SPKMenuItem("< Main Menu", &mainMenu)); resolutionMenu.title = "Resolution"; for (int i=0; i < settings.resolutionsCount(); i++) { resolutionMenu.addMenuItem(SPKMenuItem(settings.resolutionName(i), settings.resolutionIndex(i), settings.resolutionEDIDIndex(i))); } resolutionMenu.addMenuItem(SPKMenuItem("< Main Menu", &mainMenu)); commsMenu.title = "Network Mode"; // commsMenu is built in mixer's run loop, depending on RJ45 mode. advancedMenu.title = "Troubleshooting"; advancedMenu.addMenuItem(SPKMenuItem("HDCP Off", advancedHDCPOff)); advancedMenu.addMenuItem(SPKMenuItem("HDCP On", advancedHDCPOn)); advancedMenu.addMenuItem(SPKMenuItem("Conform Processor", advancedConformProcessor)); if (settingsAreCustom) advancedMenu.addMenuItem(SPKMenuItem("Revert to defaults", advancedLoadDefaults)); advancedMenu.addMenuItem(SPKMenuItem("Start Self-Test", advancedSelfTest)); advancedMenu.addMenuItem(SPKMenuItem("< Main Menu", &mainMenu)); mainMenu.title = "Main Menu"; mainMenu.addMenuItem(SPKMenuItem("> " + mixModeMenu.title, &mixModeMenu)); mainMenu.addMenuItem(SPKMenuItem("> " + resolutionMenu.title, &resolutionMenu)); mainMenu.addMenuItem(SPKMenuItem("> " + commsMenu.title, &commsMenu)); mainMenu.addMenuItem(SPKMenuItem("> " + advancedMenu.title, &advancedMenu)); selectedMenu = &mainMenu; // Misc I/O stuff fadeAPO.period(0.001); fadeBPO.period(0.001); // Test for TV One connectivity and determine unit type int32_t testConnectionPayload = 0; // kTV1FunctionReadSoftwareVersion // kTV1FunctionReadProductType // kTV1FunctionReadBoardType bool ok = tvOne.readCommand(0, kTV1WindowIDA, kTV1FunctionReadSoftwareVersion, testConnectionPayload); string tvOneDetectString = ok ? "TVOne link ok" : "TVOne link failed"; // TODO: Use software version to select resolution slots etc? // TODO: Use product / board type to select TVOne conform type? // 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(kTVOneStatusLine); screen.textToBuffer(tvOneDetectString, kTVOneStatusLine); screen.sendBuffer(); debug->printf("this far?"); //// 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 (ethernet && rj45Mode == rj45Ethernet) { if (debug) debug->printf("net poll"); Net::poll(); } //// RJ45 SWITCH if (rj45ModeDIN != rj45Mode) { if (debug) debug->printf("Handling RJ45 mode change\r\n"); // update state rj45Mode = rj45ModeDIN; if (rj45Mode == rj45Ethernet) { commsMenu.title = "Network Mode [Ethernet]"; commsMenu.clearMenuItems(); commsMenu.addMenuItem(SPKMenuItem("None", commsNone)); commsMenu.addMenuItem(SPKMenuItem("OSC", commsOSC)); commsMenu.addMenuItem(SPKMenuItem("ArtNet", commsArtNet)); commsMenu.addMenuItem(SPKMenuItem("< Main Menu", &mainMenu)); } if (rj45Mode == rj45DMX) { commsMenu.title = "Network Mode [DMX]"; commsMenu.clearMenuItems(); commsMenu.addMenuItem(SPKMenuItem("None", commsNone)); commsMenu.addMenuItem(SPKMenuItem("DMX In", commsDMXIn)); commsMenu.addMenuItem(SPKMenuItem("DMX Out", commsDMXOut)); commsMenu.addMenuItem(SPKMenuItem("< Main Menu", &mainMenu)); } // cancel old comms commsMode = commsNone; commsMenu = commsMode; // refresh display if (selectedMenu == &commsMenu) { screen.textToBuffer(selectedMenu->title, kMenuLine1); screen.textToBuffer(selectedMenu->selectedString(), kMenuLine2); } if (rj45Mode == rj45Ethernet) screen.textToBuffer("RJ45: Ethernet Engaged", kCommsStatusLine); if (rj45Mode == rj45DMX) screen.textToBuffer("RJ45: DMX Engaged", kCommsStatusLine); } //// 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->selectedItem().type == SPKMenuItem::changesToMenu) { // point selected menu to the new menu selectedMenu = selectedMenu->selectedItem().payload.menu; // 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()); } } // With that out of the way, we should be actioning a specific menu's payload? else if (selectedMenu == &mixModeMenu) { mixMode = mixModeMenu.selectedItem().payload.command[0]; bool ok = true; std::string sentOK; std::stringstream sentMSG; // Set Keyer if (mixMode < mixKey) { ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerEnable, false); sentMSG << "Keyer Off"; } else { ok = ok && tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustKeyerEnable, true); sentMSG << "Keyer On"; int index = mixMode - mixKey; ok = ok && setKeyParamsTo(index); sentMSG << " with " << index; } 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 = true; ok = tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsOutputResolution, resolutionMenu.selectedItem().payload.command[0]); ok = ok && tvOne.command(kTV1SourceRGB1, kTV1WindowIDA, kTV1FunctionAdjustSourceEDID, resolutionMenu.selectedItem().payload.command[1]); ok = ok && tvOne.command(kTV1SourceRGB2, kTV1WindowIDA, kTV1FunctionAdjustSourceEDID, resolutionMenu.selectedItem().payload.command[1]); std::string sentOK; if (ok) sentOK = "Sent: "; else sentOK = "Send Error: "; std::stringstream sentMSG; sentMSG << "Res " << resolutionMenu.selectedItem().payload.command[0] << ", EDID " << resolutionMenu.selectedItem().payload.command[1]; screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sentOK + sentMSG.str(), kTVOneStatusLine); if (debug) { debug->printf("Changing resolution"); } } else if (selectedMenu == &commsMenu) { std::string commsTypeString = "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 (dmx) {delete dmx; dmx = NULL;} // Ensure we can't change to comms modes the hardware isn't switched to if (rj45Mode == rj45DMX && (commsMenu.selectedItem().payload.command[0] == commsOSC || commsMenu.selectedItem().payload.command[0] == commsArtNet)) { commsTypeString = "RJ45 not in Ethernet mode"; } else if (rj45Mode == rj45Ethernet && (commsMenu.selectedItem().payload.command[0] == commsDMXIn || commsMenu.selectedItem().payload.command[0] == commsDMXOut)) { commsTypeString = "RJ45 not in DMX mode"; } // Action! else if (commsMenu.selectedItem().payload.command[0] == commsOSC) { commsMode = commsOSC; commsTypeString = "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; // 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.selectedItem().payload.command[0] == commsArtNet) { commsMode = commsArtNet; commsTypeString = "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.selectedItem().payload.command[0] == commsDMXIn) { commsMode = commsDMXIn; commsTypeString = "DMX In: "; dmxDirectionDOUT = 0; dmx = new DMX(kMBED_RS485_TTLTX, kMBED_RS485_TTLRX); } else if (commsMenu.selectedItem().payload.command[0] == commsDMXOut) { commsMode = commsDMXOut; commsTypeString = "DMX Out: "; dmxDirectionDOUT = 1; dmx = new DMX(kMBED_RS485_TTLTX, kMBED_RS485_TTLRX); } screen.clearBufferRow(kCommsStatusLine); screen.textToBuffer(commsTypeString + commsStatus.str(), kCommsStatusLine); } else if (selectedMenu == &advancedMenu) { if (advancedMenu.selectedItem().payload.command[0] == advancedHDCPOff) { bool ok = false; ok = tvOne.setHDCPOn(false); std::string sendOK = ok ? "Sent: HDCP Off" : "Send Error: HDCP Off"; screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sendOK, kTVOneStatusLine); } else if (advancedMenu.selectedItem().payload.command[0] == advancedHDCPOn) { bool ok = false; ok = tvOne.setHDCPOn(true); std::string sendOK = ok ? "Sent: HDCP On" : "Send Error: HDCP On"; screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sendOK, kTVOneStatusLine); } else if (advancedMenu.selectedItem().payload.command[0] == advancedConformProcessor) { screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer("Conforming...", kTVOneStatusLine); screen.sendBuffer(); bool ok = conformProcessor(); std::string sendOK = ok ? "Conform success" : "Send Error: Conform"; screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(sendOK, kTVOneStatusLine); } else if (advancedMenu.selectedItem().payload.command[0] == advancedLoadDefaults) { settings.loadDefaults(); screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer("Controller reverted", kTVOneStatusLine); } else if (advancedMenu.selectedItem().payload.command[0] == advancedSelfTest) { selfTest(); } else if (advancedMenu.selectedItem().payload.command[0] == advancedSetResolutions) { bool ok; ok = tvOne.setCustomResolutions(); screen.clearBufferRow(kTVOneStatusLine); screen.textToBuffer(ok ? "Resolutions set" : "Res' could not be set", kTVOneStatusLine); } } 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 MIX MIX MIX MIXMIX MIX MIXMIX MIX MIX MIX 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; const bool tapRight = !tapRightDIN; // We're taking a further median of the AINs on top of mbed libs v29. // This takes some values from last passes and most from now. With debug off, seem to need median size > 5 xFadeFilter.process(xFadeAIN.read()); fadeUpFilter.process(fadeUpAIN.read()); xFadeFilter.process(xFadeAIN.read()); fadeUpFilter.process(fadeUpAIN.read()); xFadeFilter.process(xFadeAIN.read()); fadeUpFilter.process(fadeUpAIN.read()); xFadeFilter.process(xFadeAIN.read()); fadeUpFilter.process(fadeUpAIN.read()); const float xFadeAINCached = xFadeFilter.process(xFadeAIN.read()); const float fadeUpAINCached = fadeUpFilter.process(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, take to the one that is new, ie. not the first pressed. if (tapLeft && tapRight) { xFade = tapLeftWasFirstPressed ? 1 : 0; } // If just one is pressed, take to that and remember which is pressed else if (tapLeft) { xFade = 0; tapLeftWasFirstPressed = 1; } else if (tapRight) { xFade = 1; tapLeftWasFirstPressed = 0; } } else xFade = 1.0 - fadeCalc(xFadeAINCached, xFadeTolerance); fadeUp = 1.0 - fadeCalc(fadeUpAINCached, fadeUpTolerance); //// TASK: Process Network Comms if (commsMode == commsOSC) { if (osc->newMessage) { osc->newMessage = false; // fixme! processOSC(xFade, fadeUp); } } if (commsMode == commsArtNet) { if (artNet->Work()) processArtNet(xFade, fadeUp); } if (commsMode == commsDMXIn) { processDMXIn(xFade, fadeUp); } if (commsMode == commsDMXOut) { processDMXOut(xFade, fadeUp); } // Calculate new A&B fade percents int newFadeAPercent = 0; int newFadeBPercent = 0; if (mixMode == mixBlend) { if (fadeUp < 1.0) // TODO: OR if a source is not valid { // we need to set fade level of both windows as there is no way AFAIK to implement fade to black as a further window on top of A&B newFadeAPercent = (1.0-xFade) * fadeUp * 100.0; newFadeBPercent = xFade * fadeUp * 100.0; } else { // we can optimise and just fade A in and out over a fully up B, doubling the rate of fadeA commands sent. newFadeAPercent = (1.0-xFade) * 100.0; newFadeBPercent = 100.0; } } else if (mixMode == mixAdditive) { // we need to set fade level of both windows according to the fade curve profile float newFadeA = (1.0-xFade) * (1.0 + fadeCurve); float newFadeB = xFade * (1 + fadeCurve); if (newFadeA > 1.0) newFadeA = 1.0; if (newFadeB > 1.0) newFadeB = 1.0; newFadeAPercent = newFadeA * fadeUp * 100.0; newFadeBPercent = newFadeB * fadeUp * 100.0; } else if (mixMode >= mixKey) { newFadeAPercent = (1.0-xFade) * fadeUp * 100.0; newFadeBPercent = fadeUp * 100.0; } //// TASK: Send to TVOne if percents have changed // No amount of median filtering is stopping flipflopping between two adjacent percents, so... bool fadeAPercentHasChanged; bool fadeBPercentHasChanged; if (oldFadeAPercent == newFadeAPercent && (newFadeAPercent == fadeAPercent - 1 || newFadeAPercent == fadeAPercent + 1)) fadeAPercentHasChanged = false; else fadeAPercentHasChanged = newFadeAPercent != fadeAPercent; if (oldFadeBPercent == newFadeBPercent && (newFadeBPercent == fadeBPercent - 1 || newFadeBPercent == fadeBPercent + 1)) fadeBPercentHasChanged = false; else fadeBPercentHasChanged = newFadeBPercent != fadeBPercent; // We want to send the higher first, otherwise black flashes can happen on taps if (fadeAPercentHasChanged && newFadeAPercent >= newFadeBPercent) { oldFadeAPercent = fadeAPercent; fadeAPercent = newFadeAPercent; updateFade = true; fadeAPO = fadeAPercent / 100.0; tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsMaxFadeLevel, fadeAPercent); } if (fadeBPercentHasChanged) { oldFadeBPercent = fadeBPercent; fadeBPercent = newFadeBPercent; updateFade = true; fadeBPO = fadeBPercent / 100.0; tvOne.command(0, kTV1WindowIDB, kTV1FunctionAdjustWindowsMaxFadeLevel, fadeBPercent); } if (fadeAPercentHasChanged && newFadeAPercent < newFadeBPercent) { oldFadeAPercent = fadeAPercent; fadeAPercent = newFadeAPercent; updateFade = true; fadeAPO = fadeAPercent / 100.0; tvOne.command(0, kTV1WindowIDA, kTV1FunctionAdjustWindowsMaxFadeLevel, fadeAPercent); } if (updateFade && debug) { //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); debug->printf("\r\n"); } } }