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

Revision:
0:87aab40d5806
Child:
1:f9fca21102e0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sat Mar 10 19:26:44 2012 +0000
@@ -0,0 +1,429 @@
+// *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 - TODO: EDID upload from USB mass storage
+// v17 - TODO: EDID creation from resolution
+
+// !! AIN on own thread doing some kind of low pass / median filter? ie. take average of two most similar values from last three
+
+#include "mbed.h"
+
+#include "spk_tvone_mbed.h"
+#include "spk_utils.h"
+#include "spk_mRotaryEncoder.h"
+#include "spk_oled_ssd1305.h"
+
+#include <sstream>
+
+#define kMenuLine1 4
+#define kMenuLine2 5
+#define kStatusLine 7
+
+//// 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(p19);    
+AnalogIn fadeUpAIN(p20);
+DigitalIn tapLeftDIN(p24);
+DigitalIn tapRightDIN(p21);
+
+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(p28, p27, LED3, LED4, debug);
+//SPKTVOne tvOne(p28, p27, LED3, LED4);
+
+// 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;
+
+// Fade logic constants + variables
+const float xFadeTolerance = 0.05;
+const float fadeUpTolerance = 0.05;
+float xFade = 0;
+float fadeUp = 1;
+
+// 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
+    // ...
+};
+
+
+
+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");
+    }
+    
+    // Splash screen
+    screen.imageToBuffer();
+    screen.textToBuffer("SPK:D-Fuser",0);
+    screen.textToBuffer("SW beta.15",1);
+    screen.sendBuffer();
+
+    
+    // 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);
+
+    mainMenu.title = "Main Menu";
+    mainMenu.addMenuItem(&mixModeMenu);
+    mainMenu.addMenuItem(&resolutionMenu);
+    
+    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(kStatusLine*pixInPage - 1);
+    screen.clearBufferRow(kStatusLine);
+    screen.textToBuffer(sendOK, 7);
+    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) {
+
+        //// 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);
+            screen.sendBuffer();
+            
+            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);
+                screen.sendBuffer();
+                
+                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);
+                screen.sendBuffer();
+                
+                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(kStatusLine);
+                screen.textToBuffer(sentOK + sentMSG.str(), kStatusLine);
+                screen.sendBuffer();
+                
+                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(kStatusLine);
+                screen.textToBuffer(sentOK + sentMSG.str(), kStatusLine);
+                screen.sendBuffer();
+                
+                if (debug) { debug->printf("Changing resolution"); }
+            }
+            else
+            {
+                if (debug) { debug->printf("Warning: No action identified"); }
+            }
+
+        }
+        
+
+        
+        //// MIX 
+
+        bool updateFade = false;
+
+        // 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);
+
+        // 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;
+    }
+}