Visual feedback using matrix effect

Explaining a new MM module called the matrix effect allowing visual feedback on the mirror while alexa is listening.

Now the mirror can detect when Alexa is listening to our voice input, it’s time to provide a visual feedback on the mirror.  We could just do that using an alert or an extra module that displays a text.  Instead I want to do something fancier.

It’s time for a small side project in the form of a new magic mirror module displaying a female head in a “matrix” effect, just like in the movie with a bunch of green falling letters hinting a silhouette.  I’m saying green because I’m kind of preconditioned, but it can be any color anyway.

The idea behind this module is to create a JavaScript routine that would generate the the letters on a canvas.  But I want it to be a bit more generic, so other magic mirror owners would be able to download the module and display something else instead of a human head.   So the solution to that would be to make a configuration file, and store all the settings in the configuration file, and refer to it in the mirror’s config.js file.  So I went ahead and created the js file, the node helper and a subfolder for configuration files in json format :

Schermafbeelding 2017-01-29 om 23.18.43.png

Now for the module itself.  Below you can see an extract of the code of the javascript file.  most important is the start function here.

Schermafbeelding 2017-01-29 om 23.17.22.png

The first line in this function is crucial.  The setting for this effect are stored in the “alexa.json” file in the “effects” sub folder.  In order to be able to use the settings, the json file needs to be read.  However, access to the file system is not something you can do in JavaScript for the obvious security reasons.  That’s something that needs to be done with some server side scripting.  That’s where the node helper comes in.  Essentially, we need to tell the node helper to get the settings from a certain configuration file, which I called an animationFile in the code.  I deliberately called it an animation file because – in due time – I want to be able to animate it in multiple frames.  But for now, a single frame will do.

The name of the animation file is stored in the magic mirror’s configuration file :

Schermafbeelding 2017-01-29 om 23.22.42.png

Specifying it in the config tag means that it can be addressed from the JavaScript file using the “config” member.  This piece of information is passed on as a payload in the “MATRIX_ANIMATION_CONNECT” notification.  This notification is caught in the node helper like this :

Schermafbeelding 2017-01-29 om 23.19.02.png

Notice how, in the code extract above, the animation file is loaded using the loadAnimationFile method.  This called whenever the MATRIX_ANIMATION_CONNECT notification is received.

Never mind the test code in the screenshot.  I used it to know what the relative path to the animation file needed to be.  I’ll remove it in the actual code.  Once loaded it is being sent back using a notification using the “SEND_MATRIX_ANIMATION” constant.  This is received in the JavaScript file like this :

Schermafbeelding 2017-01-29 om 23.17.29.png

Notice how a local variable is assigned to the payload.

Another thing you can see in the JavaScript file is the initialization of an array called yPositions . We will by using this array in our JavaScript method that displays the matrix effect on the mirror.  It is initialized with a number of zeroes.  The size of the array is a calculation.  It divides the width of the canvas by the size of the font to calculate the exact number of columns there need to be in the matrix effect.  One last thing worth mentioning here is that I’ve created a custom .css file that contains some properties of the canvas.  It is referenced in the “getStyles” function, and looks like this :

Schermafbeelding 2017-01-29 om 23.18.51.png

In order to start and stop the matrix effect, the module relies on another module telling it to start and stop.  In practice this will of course be the Alexa interface module doing this.  It listens to 2 different notifications : START_MATRIXEFFECT en STOP_MATRIXEFFECT.  The first one hides all of the modules except for the matrix effect module, shows the module itself, and starts the matrix using the animation definition and the yPositions array.  The second one does the exact opposite.

Notice how “notificationReceived” is something else as “SocketNotificationReceived”.  The first one is used from communication between modules, the second one is use for communication between the JavaScript file and the node helper.

Schermafbeelding 2017-01-29 om 23.17.39.png

The screenshot below shows a very simple piece of code overriding the getDom method by creating a HTML canvas, and getting the context from it.  These objects are used to draw something on a HTML page.  You can find more information on this here.

Schermafbeelding 2017-01-29 om 23.17.47.png

The code extract below is the bulk of the module and requires some detailed explanation.

Schermafbeelding 2017-01-29 om 23.18.30.png

The runMatrix function actually starts an interval.  This is a piece of code (executed as a callback function in the call to the “setInterval” method, which is executed every x milliseconds.  These milliseconds are also a parameter in the same method, and as a matter of fact it is controlled by the “updateSpeed” parameter of the animation definition.

The function starts by getting the canvas and the context and filling the screen with a black background.  I haven’t made this variable yet, but I might do so in the future.  Inside the callback function the canvas and the context are set again because the scope of the callback function is different.  A matrix variable is created, containing the different column definitions in the animation file.  Next, the random() function is called.  It returns a random number between 0 and 1.  I use it to determine if and when an almost transparent overlay is to be put over the existing area.  Almost transparent, as you can see, the “alpha” of the rgba function component is 0.01.  This takes care of the fading letters in the matrix effect.

Next the font is set to the primary color (secondary colours are not supported yet) and the font is determined to be a certain font name with a certain width, depending on the animation file.  The next function is very important.  The map function loops through the yPositions matrix and executes a callback function for every element.  Where y is a coördinate on the y axis of the canvas and index is the index of a certain column.  The for loop determines if a letter is to be printed because it is in between the “start” and “end” tag of a section.  If it is, the text is printed on the y coördinate passed by the map function and the x coördinate based on a formula on the index.  Lastly, the yPositions array is updated so the letters drop to the bottom.  If they reach a certain depth, the yPositions array at that index is just rest.

The stopMatrix function just clears the interval allowing the loop to stop.

All this is configured in the animation definition.  It looks like this :

Schermafbeelding 2017-01-29 om 23.22.53.png

The configuration file is “prepared” to do some more exotic things like working with different colors and different animation frames already.  The code doesn’t use these properties yet.  Notice all of the settings used in the code like canvasWidth and canvasHeight, textPoints, etc.  The animation Matrix is a matrix in 3 dimensions :

  • Dimension 1 is the animation frame.  There’s only one in this example
  • Dimension 2 is the matrix contain all columns
  • Dimension 3 is an array within the column telling the mirror to either draw or not draw a letter depending on the start and stop number of pixels.  Remember, that the counting of the pixels starts from the top to the bottom.  Even though there is a “frame” attribute and a “column” attribute, the JavaScript code expects both the frames and columns to be in the correct order.  The “frame” and “column” attributes are ignored in the code, but serve as a pointer to the person creating the configuration file.  Specifying a start and end of -1 will make sure nothing is displayed in that particular column.

When running this module, the end result looks like this :

matrix-effect.gif

Granted, it still needs some work finetuning this.  And I’ll probably need to make a little tool to be able to create the animation definition file more efficient, but that’s for a later phase.

When this module is in a more finished phase I will upload it as a magic mirror module.

 

 

2 thoughts on “Visual feedback using matrix effect”

  1. Hi,

    I really like the idea of this MM module. Even if the code isn’t bulletproof yet I was able to make it work for me….
    …only the silouette looks like ranomly placed matrix code…..
    Would it be possible for you to provide the json file to get a little more structure in this?

    I’m looking forward to see this in the MM module library soon 😉

    -AB

    Like

    1. Hi AB,

      thank you for the feedback!
      I’ve copy pasted my JSON file contents below. It’s not perfect yet of course, and the code could use some extra features. If you’ve got some addendums or some json files of your own, please share them back. I’m currently in the middle of doing some other stuff, but I’ll be continuing my work soon and hope to be finishing up this MM module too. Let me know how it goes.

      cheers

      Bart

      {
      “canvasWidth”: 500,
      “canvasHeight”: 500,
      “textPoints”: 10,
      “fontName”: “Courier New”,
      “primaryColor”: “#0D0”,
      “alternateColors”: [{
      “name”: “highlight”,
      “color”: “#0F0”
      }],
      “updateSpeed”: 5,
      “animationMatrix”: [{
      “frame”: 1,
      “matrix”: [{
      “column”: 1,
      “sections”: [{
      “start”: -1,
      “end”: -1
      }]
      }, {
      “column”: 2,
      “sections”: [{
      “start”: 480,
      “end”: 500
      }]
      }, {
      “column”: 3,
      “sections”: [{
      “start”: 480,
      “end”: 500
      }]
      }, {
      “column”: 4,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 5,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 6,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 7,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 8,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 9,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 10,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 11,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 12,
      “sections”: [{
      “start”: 220,
      “end”: 280
      }, {
      “start”: 440,
      “end”: 500
      }]
      }, {
      “column”: 13,
      “sections”: [{
      “start”: 80,
      “end”: 300
      }, {
      “start”: 440,
      “end”: 500
      }]
      }, {
      “column”: 14,
      “sections”: [{
      “start”: 85,
      “end”: 300
      }, {
      “start”: 440,
      “end”: 500
      }]
      }, {
      “column”: 15,
      “sections”: [{
      “start”: 70,
      “end”: 310
      }, {
      “start”: 440,
      “end”: 500
      }]
      }, {
      “column”: 16,
      “sections”: [{
      “start”: 60,
      “end”: 315
      }, {
      “start”: 440,
      “end”: 500
      }]
      }, {
      “column”: 17,
      “sections”: [{
      “start”: 60,
      “end”: 318
      }, {
      “start”: 430,
      “end”: 500
      }]
      }, {
      “column”: 18,
      “sections”: [{
      “start”: 60,
      “end”: 320
      }, {
      “start”: 420,
      “end”: 500
      }]
      }, {
      “column”: 19,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 20,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 21,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 22,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 23,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 24,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 25,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 26,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 27,
      “sections”: [{
      “start”: 20,
      “end”: 500
      }]
      }, {
      “column”: 28,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 29,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 30,
      “sections”: [{
      “start”: 40,
      “end”: 500
      }]
      }, {
      “column”: 31,
      “sections”: [{
      “start”: 60,
      “end”: 500
      }]
      }, {
      “column”: 32,
      “sections”: [{
      “start”: 60,
      “end”: 500
      }]
      }, {
      “column”: 33,
      “sections”: [{
      “start”: 60,
      “end”: 500
      }]
      }, {
      “column”: 34,
      “sections”: [{
      “start”: 70,
      “end”: 330
      }, {
      “start”: 430,
      “end”: 500
      }]
      }, {
      “column”: 35,
      “sections”: [{
      “start”: 70,
      “end”: 330
      }, {
      “start”: 430,
      “end”: 500
      }]
      }, {
      “column”: 36,
      “sections”: [{
      “start”: 70,
      “end”: 330
      }, {
      “start”: 430,
      “end”: 500
      }]
      }, {
      “column”: 37,
      “sections”: [{
      “start”: 90,
      “end”: 310
      }, {
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 38,
      “sections”: [{
      “start”: 90,
      “end”: 300
      }, {
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 39,
      “sections”: [{
      “start”: 90,
      “end”: 290
      }, {
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 40,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 41,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 42,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 43,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 44,
      “sections”: [{
      “start”: 450,
      “end”: 500
      }]
      }, {
      “column”: 45,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 46,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 47,
      “sections”: [{
      “start”: 470,
      “end”: 500
      }]
      }, {
      “column”: 48,
      “sections”: [{
      “start”: 480,
      “end”: 500
      }]
      }, {
      “column”: 49,
      “sections”: [{
      “start”: 480,
      “end”: 500
      }]
      }, {
      “column”: 50,
      “sections”: [{
      “start”: 0,
      “end”: 500
      }]
      }]
      }]

      }

      Like

Leave a comment