Friki Tiki, NYC

Lighting Designer, Software Engineer

2023 Manhattan, NYC

Friki Tiki is a tiki-themed piano bar in Hell's Kitchen, NYC with a large marquee-style lighting installation that stretches across the ceiling starting from behind the stage. Event staff can control the system's current animation and color palette using a react-native app that lives on the house point-of-sale iPad. External lighting designers are also able to override the system, so long as their sACN priority is greater than the default of 100. This results in a very flexible system that can meet any house-lighting needs or live event requests.

Lights

The lights themselves are sourced from iPixel LED , and are designed custom with the following specs:

  • 6 RGBW leds per bulb, 1.75W per bulb, using the DMX512 protocol

  • 2700k white color temperature

  • 6 leds per aluminum channel, each channel at 1m length

Each aluminum channel needs to be individually programmed for a specific DMX address, so a DMX address writer was used during the install.

Madmapper

LED mapping of shader-based animations is achieved using Madmapper which controls every aspect of the installation from fixture definitions to 3D space calibration, shader parameter control, audio inputs, and DMX interface settings. All of the remote iPad control uses TouchOSC , which Madmapper has built in. Essentially, a large API surface that covers almost every knob, button, cue button, etc is made available at a user-defined IP address/port, and the iPad simply sends formatted GET and POST messages to that address.

Animations

Animations can use a variety of input sources, but vertex shaders were used for this project because of the ability to control animation parameters from the iPad (scale, speed, color palette, etc). Audio-reactivity is also made easily available from Madmapper, and an example of an audio-reactive vertex shader is below:

lava.fs
// lava.fs (cont.) // config omitted and provided in earlier code snippet #include "MadCommon.glsl" #include "MadNoise.glsl" #include "MadSDF.glsl" #define s smoothstep vec4 getColor(vec4 colorRGBA1, vec4 colorRGBA2) { float alpha = 1.0 - ((1.0 - colorRGBA1.a) * (1.0 - colorRGBA2.a) / 1.0); float red = (colorRGBA1.r * (1.0 - colorRGBA2.a) + colorRGBA2.r * colorRGBA2.a) / 1.0; float green = (colorRGBA1.g * (1.0 - colorRGBA2.a) + colorRGBA2.g * colorRGBA2.a) / 1.0; float blue = (colorRGBA1.b * (1.0 - colorRGBA2.a) + colorRGBA2.b * colorRGBA2.a) / 1.0; return vec4(red, green, blue, alpha); } vec4 applyColorMods(vec4 color, float c, float s, float b) { float a = color.a; vec3 output_color = applyContrastSaturationBrightness(color.rgb, c + 1, s + 1, b + 1); return vec4(output_color.r, output_color.g, output_color.b, a); } // https://thebookofshaders.com/11/ // https://thebookofshaders.com/edit.php#11/iching-03.frag vec3 random3(vec3 c) { float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0))); vec3 r; r.z = fract(512.0 * j); j *= .125; r.x = fract(512.0 * j); j *= .125; r.y = fract(512.0 * j); return r - 0.5; } const float F3 = 0.333333; const float G3 = 0.1666667; float snoise(vec3 p) { vec3 s = floor(p + dot(p, vec3(F3))); vec3 x = p - s + dot(s, vec3(G3)); vec3 e = step(vec3(0.0), x - x.yzx); vec3 i1 = e * (1.0 - e.zxy); vec3 i2 = 1.0 - e.zxy * (1.0 - e); vec3 x1 = x - i1 + G3; vec3 x2 = x - i2 + 2.0 * G3; vec3 x3 = x - 1.0 + 3.0 * G3; vec4 w, d; w.x = dot(x, x); w.y = dot(x1, x1); w.z = dot(x2, x2); w.w = dot(x3, x3); w = max(0.6 - w, 0.0); d.x = dot(random3(s), x); d.y = dot(random3(s + i1), x1); d.z = dot(random3(s + i2), x2); d.w = dot(random3(s + 1.0), x3); w *= w; w *= w; d *= w; return dot(d, vec4(52.0)); } vec4 materialColorForPixel(vec2 texCoord) { // get texture coordinates vec2 uv = texCoord; uv.y = 1. - uv.y; // modify uv with material inputs uv += vec2(0.5, 0.); uv *= mat_scale; vec2 m = vec2(mat_trans, mat_gran); vec2 c = vec2(.5, .5); float transitionSize = m.y; float granularity = m.x * 5.; float t = mat_animation_time * m.y; float noiseInX = uv.x * granularity; float noiseInY = uv.y * granularity - t; float n = snoise(vec3(noiseInX, noiseInY, 0)); float pct = 1 - s(0., 1., uv.y + transitionSize * n); vec4 color = vec4(pct); color *= foregroundColor.rgba; color.r *= 1.0 + (1.0 * audio_amplitude_decay * (amplitude * 15)); color.g *= 1.0 + (1.0 * audio_amplitude_decay * (amplitude * 15)); color.b *= 1.0 + (1.0 * audio_amplitude_decay * (amplitude * 15)); color = applyColorMods(color, contrast, saturation, 1); color = mix(color, backgroundColor.rgba, 0.5); color *= vec4(brightness); return vec4(color.rgb, 1); }

Using MadMapper glsl config:

lava.fs config
// lava.fs { "CREDIT": "Drew Andre", "DESCRIPTION": "Lava-style noise", "TAGS": "noise", "VSN": "1.0", "RASTERISATION_SETTINGS": { "DEFAULT_RENDER_TO_TEXTURE": true, "DEFAULT_WIDTH": 1024, "DEFAULT_HEIGHT": 1024, "REQUIRES_LAST_FRAME": true }, "INPUTS": [ { "NAME": "mat_waveform", "TYPE": "audio" }, { "LABEL": "Public/Speed", "NAME": "mat_speed", "TYPE": "float", "MIN": 0, "MAX": 2, "DEFAULT": 1 }, { "LABEL": "Public/Scale", "NAME": "mat_scale", "TYPE": "float", "MIN": 0.1, "MAX": 2, "DEFAULT": 1 }, { "LABEL": "Private/Transition Size", "NAME": "mat_trans", "TYPE": "float", "MIN": 0, "MAX": 1, "DEFAULT": 0.5 }, { "LABEL": "Private/Granularity", "NAME": "mat_gran", "TYPE": "float", "MIN": 0, "MAX": 1, "DEFAULT": 0.5 }, { "LABEL": "Public/Front Color", "NAME": "foregroundColor", "TYPE": "color", "DEFAULT": [ 1, 1, 1, 1 ] }, { "LABEL": "Public/Back Color", "NAME": "backgroundColor", "TYPE": "color", "DEFAULT": [ 1, 1, 1, 1 ] }, { "LABEL": "Public/Brightness", "NAME": "brightness", "TYPE": "float", "MIN": 0, "MAX": 1, "DEFAULT": 0.5 }, { "LABEL": "Public/Saturation", "NAME": "saturation", "TYPE": "float", "MIN": -1, "MAX": 1, "DEFAULT": 0 }, { "LABEL": "Public/Contrast", "NAME": "contrast", "TYPE": "float", "MIN": -1, "MAX": 1, "DEFAULT": 0 }, { "LABEL": "Private/Amplitude", "NAME": "amplitude", "TYPE": "float", "MIN": 0, "MAX": 1, "DEFAULT": 0.2 }, { "LABEL": "Private/Audio Attack", "NAME": "attack", "TYPE": "float", "DEFAULT": 0.1, "MIN": 0, "MAX": 1 }, { "LABEL": "Private/Audio Decay", "NAME": "decay", "TYPE": "float", "DEFAULT": 0.1, "MIN": 0, "MAX": 1 }, { "LABEL": "Private/Audio Release", "NAME": "release", "TYPE": "float", "DEFAULT": 0.7, "MIN": 0, "MAX": 1 } ], "GENERATORS": [ { "NAME": "mat_animation_time", "TYPE": "time_base", "PARAMS": { "speed": "mat_speed", "speed_curve": 1, "bpm_sync": false, "link_speed_to_global_bpm": false } }, { "NAME": "audio_amplitude", "TYPE": "pass_thru", "PARAMS": { "input_value": "/audioin/MadMapper/amplitude" } }, { "NAME": "audio_amplitude_decay", "TYPE": "adsr", "PARAMS": { "input_value": "audio_amplitude", "attack": "attack", "decay": "decay", "release": "release" } } ] }

DMX Interface

A Chauvet Net X II is used to convert the sACN output from the Mac Mini to usable DMX values that the lights expect. DMX cables are run under the stage, and power is injected separately using three 300W 24V power supplies.

Materials: Computer and DMX bulbs in aluminum channels over a painted canvas

Tools: Madmapper, React-Native

Media

Screenshot of MadMapper configuration
Screenshot of MadMapper configuration
Video of lava animation
Rack unit containing a Mac Mini and Chauvet Net X II
Rack unit containing a Mac Mini and Chauvet Net X II
iOS app with color HSL color palette control
iOS app with color HSL color palette control
iOS app with cue control
iOS app with cue control