ADD: LED Ring & LED Keys in preview
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
23
src/components/device/DeviceKeys.vue
Normal file
23
src/components/device/DeviceKeys.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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
75
src/data/mapping.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user