ADD: LED Ring & LED Keys in preview

This commit is contained in:
Robert Kossessa
2024-02-03 19:32:52 +01:00
parent bbeae3399e
commit 332984ba07
6 changed files with 166 additions and 40 deletions

11
package-lock.json generated
View File

@@ -20,6 +20,7 @@
"color": "^4.2.3",
"concurrently": "^8.2.2",
"electron-squirrel-startup": "^1.0.0",
"gsap": "^3.12.5",
"lucide-vue-next": "^0.309.0",
"pinia": "^2.1.7",
"radix-vue": "^1.3.0",
@@ -6818,6 +6819,11 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"node_modules/gsap": {
"version": "3.12.5",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -16130,6 +16136,11 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"gsap": {
"version": "3.12.5",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",

View File

@@ -27,6 +27,7 @@
"color": "^4.2.3",
"concurrently": "^8.2.2",
"electron-squirrel-startup": "^1.0.0",
"gsap": "^3.12.5",
"lucide-vue-next": "^0.309.0",
"pinia": "^2.1.7",
"radix-vue": "^1.3.0",

View File

@@ -0,0 +1,23 @@
<template>
<div class="flex">
<div
v-for="(key, index) in keys" :key="key" class="aspect-square flex-1 rounded-[2px] flex items-center justify-center"
:style="`box-shadow: 0 0 20px 0 hsl(${-hue + index * 20},100%,50%)`">
<div class="font-heading text-2xl text-black opacity-30">{{ key}}</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import gsap from 'gsap'
const keys = ref(['a', 'b', 'c', 'd'])
const hue = ref(0)
onMounted(() => {
gsap.to(hue, { duration: 10, value: 360, repeat: -1, ease: 'none' })
})
</script>

View File

@@ -1,37 +1,62 @@
<template>
<svg :width="size" :height="size" filter="url(#blur)" class="mix-blend-screen">
<svg :viewBox="`0 0 ${size} ${size}`" filter="url(#blur)">
<filter id="blur" color-interpolation-filters="sRGB">
<feGaussianBlur
v-for="index in blurSteps" :key="index" in="SourceGraphic" :stdDeviation="blur*index"
:result="index" />
<feMerge result="blurMerge">
<feMergeNode v-for="index in blurSteps" :key="index" :in="index" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<circle
v-for="index in ledCount" :key="index"
:r="radius"
:transform="`rotate(${index/ledCount*360} ${size/2} ${size/2})`"
:r="ledRadius"
:cx="size/2"
:cy="size/2"
fill="none"
:stroke-width="ledWidth"
:stroke-dashoffset="index/ledCount*ledArcSize"
:stroke-dasharray="`${ledArcSize/ledCount} ${ledArcSize}`"
:stroke="`rgb(${index/ledCount*255} ${index/ledCount*255-128} ${0})`" />
:cy="padding + ledRadius"
:fill="leds[index-1]?.hex()" />
</svg>
</template>
<script setup>
import { computed, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import Color from 'color'
const radius = ref(120)
const ledWidth = ref(5)
const props = defineProps({
value: {
type: Number,
default: 0,
},
})
const leds = ref(new Array(60).fill(Color()))
const radius = ref(100)
const ledRadius = ref(3)
const ledCount = ref(60)
const blur = ref(10)
const blurSteps = ref(3)
const padding = ref(20)
const blur = ref(2)
const blurSteps = ref(5)
const padding = ref(40)
const size = computed(() => (radius.value + padding.value) * 2 + ledWidth.value)
const ledArcSize = computed(() => 2 * Math.PI * radius.value)
const size = computed(() => (radius.value + ledRadius.value + padding.value) * 2)
let interval = null
onMounted(() => {
interval = setInterval(() => {
const valueIndex = Math.floor(props.value / 100 * ledCount.value)
leds.value.forEach((color, index) => {
const distance = Math.abs(index - valueIndex) % ledCount.value
if (distance < 1) {
leds.value[index] = Color.hsl(40, 100, 100)
} else if (distance < 2) {
leds.value[index] = Color.hsl(40, 100, 60)
} else {
leds.value[index] = color.mix(Color.hsl(310, 100, 20), 0.03)
}
})
})
onUnmounted(() => {
clearInterval(interval)
})
})
</script>

View File

@@ -9,12 +9,13 @@
<ScrambleText :text="store.selectedProfile.name" />
</div>
</div>
<DeviceLEDRing :value="barValue" class="absolute h-[66%] top-[12.5%] left-0 right-0 mx-auto" />
<div
class="rounded-full aspect-square absolute h-[30%] top-[30.5%] left-0 right-0 mx-auto flex flex-col justify-center items-center overflow-hidden"
class="rounded-full aspect-square absolute h-[30%] top-[30.5%] left-0 right-0 mx-auto flex flex-col justify-center items-center overflow-hidden"
style="background: linear-gradient(45deg, black 30%, #252525 50%, #232323 60%, black)">
<div class="flex flex-col items-center text-center pb-2 mix-blend-screen">
<img :src="LogoMidi" alt="midi-logo" class="opacity-50 h-4">
<h2 class="font-pixellg text-5xl">{{ value }}</h2>
<h2 class="font-pixellg text-5xl">{{ parseInt(value) }}</h2>
<div class="font-pixelsm text-md">HIGH PASS</div>
<DeviceBar v-model="barValue" :count="30" :width="120" />
<span
@@ -23,6 +24,7 @@
</span>
</div>
</div>
<DeviceKeys class="absolute w-[72.7%] top-[77.2%] gap-[2.8%] left-0 right-0 mx-auto" />
</div>
</div>
</template>
@@ -33,6 +35,9 @@ import DeviceBar from '@/components/device/DeviceBar.vue'
import { useStore } from '@/store'
import ScrambleText from '@/components/effects/ScrambleText.vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import DeviceLEDRing from '@/components/device/DeviceLEDRing.vue'
import gsap from 'gsap'
import DeviceKeys from '@/components/device/DeviceKeys.vue'
const value = ref(69)
@@ -40,28 +45,14 @@ const barValue = computed(() => value.value / 127 * 100)
const store = useStore()
let anim = null
let step = null
const targetValue = ref(69)
const animateValue = () => {
targetValue.value = Math.floor(Math.random() * 127)
gsap.to(value, { duration: 1, value: targetValue.value, ease: 'power2.inOut' })
setTimeout(animateValue, 1500 + Math.random() * 2000)
}
onMounted(() => {
anim = setInterval(() => {
clearInterval(step)
const target = Math.floor(Math.random() * 127)
step = setInterval(() => {
const intVal = Math.floor(value.value)
if (intVal < target) {
value.value = intVal + 1
} else if (intVal > target) {
value.value = intVal - 1
} else {
clearInterval(step)
}
}, 20)
}, 2000)
})
onUnmounted(() => {
clearInterval(anim)
clearInterval(step)
animateValue()
})
</script>

75
src/data/mapping.json Normal file
View File

@@ -0,0 +1,75 @@
{
"actions": {
"unique-id-12313": {
"trigger": "rotation",
// "rotation", "keyDown", "keyUp", "keyHold"
"key": "a",
"duration": 250,
// ms (only for keyHold)
"direction": "clockwise",
// "counterClockwise" (only for rotation),
"condition": {
"type": "key",
"key": "a"
},
"events": [
{
"type": "keyDown",
// "keyUp"
"key": "ctrl"
},
{
"type": "serialWrite",
"data": "Hello, World!"
},
{
"type": "keyCombination",
"keys": [
"ctrl",
"a"
]
},
{
"type": "delay",
"time": 100
},
{
"type": "mouseMove",
"x": 100,
"y": 20,
"smooth": true
},
{
"type": "midiCC",
"cc": 64,
"value": 127
},
{
"type": "midiNote",
"note": 60,
"velocity": 127
},
{
"type": "midiProgramChange",
"program": 1
},
{
"type": "action",
// watch out for infinite loops
"action": "unique-id-42069"
},
{
"type": "profileChange",
"profile": "+1",
// "profile-name"
"tag": null
// "tag-name"
},
{
"type": "repeat",
"times": 3
}
]
}
}
}