diff --git a/orx-kinect-v1-demo/build.gradle b/orx-kinect-v1-demo/build.gradle
new file mode 100644
index 00000000..9ef36ea1
--- /dev/null
+++ b/orx-kinect-v1-demo/build.gradle
@@ -0,0 +1,13 @@
+def os = org.gradle.internal.os.OperatingSystem.current()
+def openrndrOs
+if (os.windows) { openrndrOs = "windows" }
+else if (os.macOsX) { openrndrOs = "macos" }
+else if (os.linux) { openrndrOs = "linux-x64" }
+
+dependencies {
+ compile project(":orx-kinect-v1")
+ runtime project(":orx-kinect-v1-natives-$openrndrOs")
+ runtime "org.openrndr:openrndr-gl3:$openrndrVersion"
+ runtime "org.openrndr:openrndr-gl3-natives-$openrndrOs:$openrndrVersion"
+ runtime "ch.qos.logback:logback-classic:1.2.3"
+}
diff --git a/orx-kinect-v1-demo/src/main/kotlin/BasicUseCaseDemo.kt b/orx-kinect-v1-demo/src/main/kotlin/BasicUseCaseDemo.kt
new file mode 100644
index 00000000..8cca5feb
--- /dev/null
+++ b/orx-kinect-v1-demo/src/main/kotlin/BasicUseCaseDemo.kt
@@ -0,0 +1,26 @@
+package org.openrndr.extra.kinect.v1.demo
+
+import org.openrndr.application
+import org.openrndr.extra.kinect.v1.getKinectsV1
+
+/**
+ * Basic kinect use case showing continuous stream from the depth camera.
+ *
+ * Note: kinect depth map is stored only on the RED color channel to save
+ * space. Therefore depth map is displayed only in the red tones.
+ */
+fun main() = application {
+ configure { // default resolution of the Kinect v1 depth camera
+ width = 640
+ height = 480
+ }
+ program {
+ val kinects = getKinectsV1(this)
+ val kinect = kinects.startDevice()
+ kinect.depthCamera.enabled = true
+ kinect.depthCamera.mirror = true
+ extend {
+ drawer.image(kinect.depthCamera.currentFrame)
+ }
+ }
+}
diff --git a/orx-kinect-v1-demo/src/main/kotlin/DepthToColorMapsDemo.kt b/orx-kinect-v1-demo/src/main/kotlin/DepthToColorMapsDemo.kt
new file mode 100644
index 00000000..150491e5
--- /dev/null
+++ b/orx-kinect-v1-demo/src/main/kotlin/DepthToColorMapsDemo.kt
@@ -0,0 +1,73 @@
+package org.openrndr.extra.kinect.v1.demo
+
+import org.openrndr.application
+import org.openrndr.draw.ColorBuffer
+import org.openrndr.draw.ColorFormat
+import org.openrndr.draw.colorBuffer
+import org.openrndr.extra.kinect.*
+import org.openrndr.extra.kinect.v1.getKinectsV1
+
+/**
+ * Shows 4 different representations of the depth map.
+ *
+ * - the original depth map stored as RED channel values
+ * - the same values expressed as gray tones
+ * -
+ * color map according to natural light dispersion as described
+ * by Alan Zucconi in the
+ * Improving the Rainbow
+ * article.
+ *
+ * -
+ * color map according to
+ *
+ * Turbo, An Improved Rainbow Colormap for Visualization
+ *
+ * by Google.
+ *
+ *
+ *
+ * @see DepthToGrayscaleMapper
+ * @see DepthToColorsZucconi6Mapper
+ * @see DepthToColorsTurboMapper
+ */
+fun main() = application {
+ configure {
+ width = 2 * 640
+ height = 2 * 480
+ }
+ program {
+ val kinects = getKinectsV1(this)
+ val kinect = kinects.startDevice()
+ kinect.depthCamera.enabled = true
+ kinect.depthCamera.mirror = true
+ val camera = kinect.depthCamera
+ val grayscaleFilter = DepthToGrayscaleMapper()
+ val zucconiFilter = DepthToColorsZucconi6Mapper()
+ val turboFilter = DepthToColorsTurboMapper()
+ val grayscaleBuffer = kinectColorBuffer(camera)
+ val zucconiBuffer = kinectColorBuffer(camera)
+ val turboBuffer = kinectColorBuffer(camera)
+ extend {
+ /*
+ * Note: getting the latest frame this way will guarantee
+ * that filters are being applied only if the actual new frame
+ * from kinect was received. Kinect has different refresh rate
+ * than usual screen (30 fps).
+ */
+ kinect.depthCamera.getLatestFrame()?.let { frame ->
+ grayscaleFilter.apply(frame, grayscaleBuffer)
+ zucconiFilter.apply(frame, zucconiBuffer)
+ turboFilter.apply(frame, turboBuffer)
+ }
+ drawer.image(camera.currentFrame)
+ drawer.image(grayscaleBuffer, camera.width.toDouble(), 0.0)
+ drawer.image(turboBuffer, 0.0, camera.height.toDouble())
+ drawer.image(zucconiBuffer, camera.width.toDouble(), camera.height.toDouble())
+ }
+ }
+}
+
+private fun kinectColorBuffer(camera: KinectCamera): ColorBuffer {
+ return colorBuffer(camera.width, camera.height, format = ColorFormat.RGB)
+}
\ No newline at end of file
diff --git a/orx-kinect-v1-demo/src/main/kotlin/MultipleKinectsDemo.kt b/orx-kinect-v1-demo/src/main/kotlin/MultipleKinectsDemo.kt
new file mode 100644
index 00000000..2d4a0657
--- /dev/null
+++ b/orx-kinect-v1-demo/src/main/kotlin/MultipleKinectsDemo.kt
@@ -0,0 +1,27 @@
+package org.openrndr.extra.kinect.v1.demo
+
+import org.openrndr.application
+import org.openrndr.extra.kinect.v1.getKinectsV1
+
+/**
+ * Stream from 2 kinects side by side.
+ */
+fun main() = application {
+ configure {
+ width = 640 * 2
+ height = 480
+ }
+ program {
+ val kinects = getKinectsV1(this)
+ val depthCamera1 = kinects.startDevice(0).depthCamera
+ val depthCamera2 = kinects.startDevice(1).depthCamera
+ depthCamera1.enabled = true
+ depthCamera1.mirror = true
+ depthCamera2.enabled = true
+ depthCamera2.mirror = true
+ extend {
+ drawer.image(depthCamera1.currentFrame)
+ drawer.image(depthCamera2.currentFrame, depthCamera1.width.toDouble(), 0.0)
+ }
+ }
+}
diff --git a/orx-kinect-v1-demo/src/main/kotlin/NativeFreenectCommandsDemo.kt b/orx-kinect-v1-demo/src/main/kotlin/NativeFreenectCommandsDemo.kt
new file mode 100644
index 00000000..720435fc
--- /dev/null
+++ b/orx-kinect-v1-demo/src/main/kotlin/NativeFreenectCommandsDemo.kt
@@ -0,0 +1,39 @@
+package org.openrndr.extra.kinect.v1.demo
+
+import org.bytedeco.libfreenect.global.freenect
+import org.bytedeco.libfreenect.global.freenect.*
+import org.openrndr.application
+import org.openrndr.extra.kinect.v1.getKinectsV1
+
+/**
+ * Even though this library is abstracting freenect access, it is still
+ * possible to call any low level kinect API through execute methods.
+ * The calls are executed in separate kinect runner thread but they will
+ * block the calling thread until the result is returned.
+ */
+fun main() = application {
+ program {
+ val kinects = getKinectsV1(this)
+ kinects.execute { ctx ->
+ freenect_set_log_level(ctx.fnCtx, freenect.FREENECT_LOG_FLOOD) // lots of logs
+ }
+ kinects.execute { ctx ->
+ // extra FREENECT_DEVICE_MOTOR gives control over tilt and LEDs
+ freenect_select_subdevices(ctx.fnCtx, FREENECT_DEVICE_CAMERA xor FREENECT_DEVICE_MOTOR)
+ }
+ val kinect = kinects.startDevice()
+ var tilt = 90.0
+ extend {
+ kinect.execute { ctx ->
+ freenect_set_led(ctx.fnDev, (seconds * 10).toInt() % 7) // disco
+ }
+ val currentTilt = if ((seconds % 10) < 5) -90.0 else 90.0
+ if (currentTilt != tilt) {
+ kinect.execute { ctx ->
+ freenect_set_tilt_degs(ctx.fnDev, currentTilt)
+ }
+ tilt = currentTilt
+ }
+ }
+ }
+}
diff --git a/orx-kinect-v1-demo/src/main/resources/logback.xml b/orx-kinect-v1-demo/src/main/resources/logback.xml
new file mode 100644
index 00000000..3d7044e6
--- /dev/null
+++ b/orx-kinect-v1-demo/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+ true
+
+
+
+ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 420d5f80..01422ade 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,22 +1,23 @@
rootProject.name = 'orx'
include 'orx-camera',
- 'orx-compositor',
- 'orx-easing',
- 'orx-file-watcher',
- 'orx-filter-extension',
- 'orx-integral-image',
- 'orx-interval-tree',
- 'orx-jumpflood',
- 'orx-kdtree',
- 'orx-mesh-generators',
- 'orx-midi',
- 'orx-no-clear',
- 'orx-noise',
- 'orx-obj-loader',
- 'orx-olive',
- 'orx-kinect-common',
- 'orx-kinect-v1',
- 'orx-kinect-v1-natives-linux-x64',
- 'orx-kinect-v1-natives-macos'
- 'orx-kinect-v1-natives-windows'
+ 'orx-compositor',
+ 'orx-easing',
+ 'orx-file-watcher',
+ 'orx-filter-extension',
+ 'orx-integral-image',
+ 'orx-interval-tree',
+ 'orx-jumpflood',
+ 'orx-kdtree',
+ 'orx-mesh-generators',
+ 'orx-midi',
+ 'orx-no-clear',
+ 'orx-noise',
+ 'orx-obj-loader',
+ 'orx-olive',
+ 'orx-kinect-common',
+ 'orx-kinect-v1',
+ 'orx-kinect-v1-natives-linux-x64',
+ 'orx-kinect-v1-natives-macos',
+ 'orx-kinect-v1-natives-windows',
+ 'orx-kinect-v1-demo'