No internet connection
  1. Home
  2. How to

How to control the Stream Deck device with the showModal action

By Christian Scheuer @chrscheuer2019-04-01 10:21:52.231Z2019-05-16 07:54:39.178Z

This requires SoundFlow 3.0 which is in beta at the time of writing this.

Sample script:

var sd = sf.devices.streamDeck.firstDevice;

var selectedValue = sd.showModal({
    items: [{
        title: 'Test 1',
        color: { r: 255, g: 100, b: 0 },
        value: 1,
    }, {
        title: 'Test 2',
        color: { r: 100, g: 255, b: 0 },
        value: 2,
    }]
}).selectedItem.value;

log(selectedValue + '');
  • 21 replies

There are 21 replies. Estimated reading time: 23 minutes

  1. Answer is in the question

    1. Dario Ramaglia @dario.ramaglia
        2019-04-14 16:54:58.817Z

        Ok, I just received my StreamDeck and I wanted to try this script. I'm on Stream_Deck_4.2.0.10031.
        At the moment when I run this script SF just stays on the blue icon.
        I'm sure I'm doing something wrong or something really stupid. Any suggestion?
        Thanks in advance,

        Dario

        1. @dario.ramaglia let's get you on board with all the regular commands for the Stream Deck first.

          It can be a little tricky to get this script working right now - @Fokke knows all about that, he might also be able to help.

          Once you wanna get started, you'll need to download the SF-SD plugin anew from here:
          https://forum.soundflow.org/-492/220-preview-19

          You should uninstall your current SF-SD plugin first.

          1. In reply tochrscheuer:

            We need to add some error handling so that SF doesn't stay blue (remember Ctrl+Shift+Escape fixes that). But there are definitely SF bugs related to this feature. The Stream Deck device's API is very complex to interact with (sigh).

            1. Dario Ramaglia @dario.ramaglia
                2019-04-14 17:03:11.017Z

                It seems that when I restart the Stream Deck Software it works. Kinda :)

                1. Yea it's definitely still buggy

                  1. Pushed 2.2 preview 24 with some bugfixes for the Stream Deck action.

        2. In reply tochrscheuer:

          Actually, I just moved this to Alpha/Beta ideas since this is still an alpha/beta feature, so wanna make sure we can share private info in here.

          1. In reply tochrscheuer:

            Here's how to control the Stream Deck with paging:

            
            
            function streamDeckSelectWithPaging(device, items) {
                var result;
                var startIndex = 0;
                while (true) {
                    var pageSize = 15;
                    var hasPrevious = false, hasNext = false;
                    if (startIndex > 0) {
                        pageSize--; //Make room for "Previous"
                        hasPrevious = true;
                    }
                    if (items.length > startIndex + pageSize) {
                        pageSize--; //Make room for "Next"
                        hasNext = true;
                    }
            
                    var pageItems = items.slice(startIndex, startIndex + pageSize);
                    if (hasPrevious) {
                        pageItems.splice(0, 0, {
                            title: '<<',
                            value: '<<'
                        });
                    }
                    if (hasNext) {
                        pageItems.push({
                            title: '>>',
                            value: '>>'
                        });
                    }
            
                    result = device.showModal({
                        items: pageItems,
                    }).selectedItem;
                    if (result.value === '<<') {
                        startIndex = startIndex - 13;
                        if (startIndex === 1) startIndex = 0;
                    } else if (result.value === '>>') {
                        startIndex += pageSize;
                    } else {
                        return result;
                    }
                }
            }
            
            
            var device = sf.devices.streamDeck.firstDevice;
            
            var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35].map(function (i) {
                return {
                    title: '# ' + i,
                    value: 'V' + i,
                    color: {
                        r: i * 255 / 35,
                        g: 100,
                        b: 100,
                    }
                };
            });
            
            var selectedItem = streamDeckSelectWithPaging(device, items);
            
            log(selectedItem.value);
            
            
            1. In reply tochrscheuer:

              The paging function could almost be a build in thing at a point.

              I'm thinking it could be a bit like applescripts display dialog, where you have a couple of "build in" functions to choose/add from.

              Some ideas for extra Modal functions could be:

              • paging (as the above enable it)
              • escape/cancel key (adding a key in the corner that cancels the action)
              • Static (so the modal surface stays after a key press. Obviously this would require that it runs in the background, and also an "escape" key maybe)
              • adding keys with custom name (such as OK). Could be handy if it is used to input more data.
              1. Great ideas!
                You can already make stuff with custom names - just give your button a "title" property. Color is optional by the way. If you don't specify color, it will just have the dimmed SoundFlow icon as the background, like you see in the paging.
                You may also specify imagePath pointing to a png file, and then that will be used :)

              2. S
                In reply tochrscheuer:
                Steve @stevef
                  2019-05-25 19:13:10.734Z

                  OK, I thought I would look in to Soundflow controlling Stream deck, and I am afriad I am not really understadning any of it. I know there's the example script at the top, but I ahve no idea how to even implement that! Any chance of a step-by-step tutorial at some point with a simple example as to what sort of things can be done? I mean, can you actually change what an individual button does and what it looks like based on what window you have open in Cubase? Can you get it to switch profiles or more importantly, can you get it to open a sub page of a profile?

                  One idea I have is to have key switches set up for different Kontakt instruments, and wondered if possible for Soundflow to activate the correct page / profile on the stream deck when a certain instrument is loaded? Although that is probably not a nice simple one to start!

                  So can it do something like changing what a button does when you select some audio, and then do something different when nothing is selected. And maybe something else if a MIDI part is selcted?

                  Any pointers would be appreciated!

                  Thanks.

                  1. cc: @JesperA.

                    Think of the showModal function as a popup window on your Mac with a list of things you can press, and once you've selected one of the things (clicked on a button), the function will return and tell you what was selected. Only it does it on the Stream Deck.

                    This is the first building block to creating more advanced workflows such as the ones you're describing.
                    @JesperA has made some great small apps that can show you how this works. Maybe he can link to some of the videos he created here?

                    I mean, can you actually change what an individual button does and what it looks like based on what window you have open in Cubase?

                    This is a more advanced scenario, but it should be possible, although a little tricky.
                    First you need to have a script trigger when a different window is open in Cubase. You do that by creating a script that loops in the background and constantly checks which Cubase window is active.
                    Then you'd need to sort of "take over" the Stream Deck.. Actually now I'm thinking about it, I'm not sure we're completely ready for this to work still.

                    Can you get it to switch profiles or more importantly, can you get it to open a sub page of a profile?

                    Elgato decided that profile switching is not possible unless we switch to profiles created by SoundFlow. In other words, no you can't tell it to switch to existing profiles that you designed on the Stream Deck.
                    Later we hope to allow you to design profiles directly in SoundFlow which will make this possible.

                    If you do the profiles via the showModal command however, you will be able to do this - you just can't switch to profiles designed in Stream Deck.

                    Again, JesperA has some great examples of this in Cubase with for example color switching.

                    1. Hey Steve.

                      I mean, can you actually change what an individual button does and what it looks like based on what window you have open in Cubase?

                      As Christian says, it's more advanced, and right now it doesn't make sense, because Stream Deck only works properly with the showModal function, which blocks SF for other inputs.

                      I've attached a script for you to check. It basically sets my stream deck with a couple of buttens. As it came from an earlier thing I did with stream deck, it's made with icons, so if you wanna try it I've attached the icons.
                      Basically what you do is put the key command for your export mix down as a trigger for the script. The idea is to have some pre-configured settings. I'm selection specific outputs and if it should be marker export or not with the different keys, so I don't have to think when exporting.
                      If I would write the script today I would probably just have made it with colors coded for the stream deck, rather then icons.

                      if (sf.ui.nuendo.exists || sf.ui.cubase.exists) {
                          var app = sf.ui.nuendo.exists ? sf.ui.nuendo : sf.ui.cubase;
                          var version = app.shortVersionString;
                          var cubendo = app.title.value;
                      }
                      else {
                          cubendo = 'Cubase'
                          version = '10'
                      }
                      
                      if (sf.devices.streamDeck.deviceCount == 1)
                          var device = sf.devices.streamDeck.firstDevice;
                      else
                          var device = sf.devices.streamDeck.secondDevice;
                      
                      
                      function checkThatExportAudioMixdownWindowIsOpen() {
                          if (!app.getWindowWithTitleStartingWith('Export Audio Mixdown').exists)
                              app.menuClick({ menuPath: ['File', 'Export', 'Audio Mixdown...'] })
                          else
                              app.getWindowWithTitleStartingWith('Export Audio Mixdown').elementRaise();
                      }
                      
                      function exportFuntion(channelName, cycleMarker, homeEnd) {
                      
                          var EAMWin = app.getWindowWithTitleStartingWith('Export Audio Mixdown')
                          EAMWin.elementRaise();
                      
                          EAMWin.mouseClickElement({ relativePosition: { x: 40, y: 164 } })
                          if (cycleMarker) EAMWin.mouseClickElement({ relativePosition: { x: 40, y: 300 } })
                          else EAMWin.mouseClickElement({ relativePosition: { x: 40, y: 270 } })
                      
                          EAMWin.mouseClickElement({ relativePosition: { x: 280, y: 164 } })
                          sf.clipboard.setText({ text: channelName });
                          sf.keyboard.press({ keys: 'cmd+v,enter' })
                          EAMWin.elementRaise();
                      }
                      
                      function exportCueStems() {
                          var EAMWin = app.getWindowWithTitleStartingWith('Export Audio Mixdown')
                          EAMWin.elementRaise();
                          EAMWin.mouseClickElement({ relativePosition: { x: 40, y: 195 } })
                          EAMWin.mouseClickElement({ relativePosition: { x: 40, y: 300 } })
                      }
                      
                      function clickEAMWinPos(x, y, cornerX, cornerY) {
                          var EAMWinFrame = app.getWindowWithTitleStartingWith('Export Audio Mixdown').frame;
                          var EAMWinX = EAMWinFrame.x + (EAMWinFrame.w * cornerX);
                          var EAMWinY = EAMWinFrame.y + (EAMWinFrame.h * cornerY);
                          sf.mouse.click({ position: { x: EAMWinX + x, y: EAMWinY + y } });
                      }
                      
                      function pathLocation(folderName) {
                          clickEAMWinPos(-100, 90, true, false)
                          var path = app.getFirstWithTitleContaining(cubendo).getString('AXDocument');
                          var sessionDirectory = decodeURIComponent(path.substring(7).split('/').slice(0, -1).join('/')).replace(/ /g, ' '); // directory of the file open in Cubase
                          var directory = sessionDirectory.split('/')[5] // directory of the parent folder
                      
                          //set your standardized folder structure 
                          sf.clipboard.setText({ text: '/Volumes/HDD/Music/Cubase/' + directory + '/' + folderName });
                          sf.keyboard.press({ keys: 'shift+cmd+g, cmd+v, enter,enter' });
                          sf.wait({ intervalMs: 100 })
                          sf.keyboard.press({ keys: 'enter' })
                          app.getWindowWithTitleStartingWith('Export Audio Mixdown').elementRaise();
                      }
                      
                      function pathProjectAudio() {
                          clickEAMWinPos(-27, 120, true, false)
                          sf.keyboard.press({ keys: 'down,down,enter' });
                      }
                      
                      function openAudioExportWindow() {
                          app.menuClick({ menuPath: ['File', 'Export', 'Audio Mixdown'], looseMatch: true });
                      }
                      
                      function render() {
                          var tempIconNames = ['black', 'black', 'black', 'black', 'mixdown_projectBouncePath', 'black', 'mixdown_selection', 'mixdown_cues', 'mixdown_video', 'black', 'black', 'black', 'mixdown_cuesStems', 'black', 'export'];
                          var names = ['', '', '', '', 'Project Bounce Path', '', 'Export Selection', 'Export Cue', 'Export Video', '', '', '', 'Export Cue & Stems', '', 'Export'];
                          // set the folder of where the icons are: 
                          var newFile = '/Volumes/HDD/Google Drev/5ComputerRelateret/StreamDeck/Flexible Icons/MIDI Selector/'
                      
                      
                          var items = [];
                          var color;
                          for (var i = 0; i < names.length; i++) {
                              color = { r: 0, g: 0, b: 0, a: 0 }
                              items.push({
                                  // title: names[i].replace(/ /g, '\n'),
                                  value: names[i],
                                  //color: color
                                  imagePath: newFile + tempIconNames[i] + '.png'
                              })
                          }
                          return items;
                      }
                      function handleAction(actionValue) {
                          var actionArgs = actionValue.split(/:/g);
                          switch (actionArgs[0]) {
                              case '':
                                  app.getWindowWithTitleStartingWith('Export Audio Mixdown').windowClose();
                                  return false
                              case 'Name':
                                  clickEAMWinPos(-400, 80, true, false);
                                  return true
                              case 'Export Selection':
                                  exportFuntion('MusicMixDown', false, 'home');
                                  return true
                              case 'Export Cue':
                                  exportFuntion('MusicMixDown', true, 'home');
                                  return true
                              case 'Export Cue & Stems':
                                  exportCueStems();
                                  return true
                              case 'Export Video':
                                  exportFuntion('StereoMixDown', false, 'home');
                                  return true
                              case 'Cancel':
                                  clickEAMWinPos(-150, -20, true, true);
                                  return true
                              case 'Path Audio':
                                  pathProjectAudio();
                                  return true
                              case 'Project Bounce Path':
                                  pathLocation('1Bounce');
                                  return true
                              case 'Export': {
                                  clickEAMWinPos(300, 520, false, false);
                                  if (app.focusedWindow.title.value.match('Export Audio Mixdown'))
                                      clickEAMWinPos(300, 690, false, false);
                                  return false
                              }
                          }
                      }
                      
                      function main() {
                          openAudioExportWindow()
                          device.doWithSeizedDevice({
                              action: function () {
                                  while (true) {
                                      var items = render();
                                      var actionValue =
                                          device.showModal({
                                              items: render(),
                                          }).selectedItem.value;
                      
                                      if (!handleAction(actionValue))
                                          break;
                                  }
                              }
                          });
                      }
                      main();
                      

                      export icons.zip (194.39 kB)

                      1. On the store there's a new color app/package. it works really well for stream deck. Still needs a little documentations. But that would more be like a button you press when you wanna color stuff, and in that way works like a sub folder as we already do with stream deck's own app. the difference is thought that what you see on the stream deck is made from the default.xml file in Cubase/Nuendo, so it'll always be up to date with your colors, and there's settings so you can change the behaviour.

                        But I do look forward to seizeDevice for the Stream Deck becomes fully functional, since if you forget to close the Stream Deck after showModal SF will be paralyzed :P

                        1. @stevef as you can see Jesper's doing some pretty advanced things with this, but it still is kinda new ground so it takes a while to get your head wrapped around everything.
                          I think you should try out Jesper's script for selecting colors for tracks and events via the Stream Deck. It's pretty epic. I think it still might need a bit of beta testing (it's hardcoded to use multiple Stream Decks so right now it doesn't work here). Jesper which of your packages is this contained in?

                          1. It's contained in it's own new package called Cubendo Color Tool, which has some beta version of surfaces as well.

                            1. Great! I thought so :) This one wants me to have 3 Stream Decks though haha... Don't work on it now, go to sleep! But we can talk about next week if we can find a way to let the user configure which device to use. Maybe first time the command is run, it pops up and asks the user which device to use.

                              1. SSteve @stevef
                                  2019-05-27 10:34:05.416Z

                                  Thank you both for your input. I'll take a look at these and see if it makes any sense ;)

                          2. In reply toJesperA:
                            SSteve @stevef
                              2019-05-27 11:19:45.752Z

                              @JesperA - This is brilliant and potentially extremely useful! Whilst I don't quite understand the way it is scripted, I do understand what it is doing and actually understand quite a bit of the script. It actually has similarities to Kontakt KSP which I am OK with. For example, I understand the setting up of functions and calling them. Also, the way it deals with the buttons is just like how Kontakt deals with menu's, so I totally get that. You name a button and then you use Case to see which button is pressed and call the function (Kontakt uses Case as well when selecting menu items). So, changing your paths (and adding a black.png icon which was missing), I got this to work. I even managed to add my own buttong which changed from Wav export to mp3! How the script then interacts with the Stream Deck is still beyond me at the moment, but then, having a script like this to work from solves that for now :)

                              I have a question about the EAMwinpos item, but I'll start a new post for that. But thanks very much for this. As you say, it would be nice to swap out the icons for colours. But, I can really see how useful the interaction with stream deck will be.

                              1. arg damn, forgot to include the black icon. Sorry. It's just because I like it a bit cleaner, so adding black ones to the spaces.
                                Sounds to me like you undestand the script pretty well. I think it could probably be scripted a little smoother now, as I did this more then half a year ago for surface and just adapted that to Stream Deck.