set route /mix & /draggable
All checks were successful
Deploy App / build (push) Successful in 3m13s
Deploy App / deploy (push) Successful in 18s

This commit is contained in:
valere
2025-12-17 19:34:25 +01:00
parent 65aaa71a3d
commit dc2cba500c
6 changed files with 87 additions and 119 deletions

View File

@@ -1,6 +0,0 @@
---
trigger: always_on
---
j'aimerai que tu te comporte avec moi comme un prof socratique.
Tu ne me donnes pas la réponse directement mais tu me guides petit à petit vers la réponse pour que je puisse apprendre par moi même.

View File

@@ -11,6 +11,10 @@
<deckPlaylist :box="box" class="box-page" v-if="box.type === 'playlist'" @click.stop="" /> <deckPlaylist :box="box" class="box-page" v-if="box.type === 'playlist'" @click.stop="" />
</template> </template>
</box> </box>
<nav class="fixed top-0">
<a class="button m-4 bg-esyellow" href="/draggable">Draggable</a>
<a class="button m-4 bg-esyellow" href="/mix">Mix</a>
</nav>
</div> </div>
</template> </template>

View File

@@ -1,16 +1,11 @@
<template> <template>
<div class="layout player fixed z-40" <div class="layout fixed z-40">
:class="playerStore.currentTrack ? '-bottom-1/4 opacity-100' : '-bottom-1/2 opacity-0'">
<div class="disc bg-slate-900" id="disc"> <div class="disc bg-slate-900" id="disc">
<div class="disc__que"> <div class="absolute top-1/2 right-8 size-5 rounded-full bg-esyellow">
</div> </div>
<div class="disc__label rounded-full bg-cover bg-center" :style="{ <div class="disc-label rounded-full bg-cover bg-center">
backgroundImage: playerStore.currentTrack?.coverId
? `url(${playerStore.currentTrack.coverId})`
: 'none'
}">
</div> </div>
<div class="disc__middle"> <div class="disc-middle">
</div> </div>
</div> </div>
</div> </div>
@@ -24,9 +19,6 @@
import Disc from '@/platine-tools/disc'; import Disc from '@/platine-tools/disc';
import Sampler from '@/platine-tools/sampler'; import Sampler from '@/platine-tools/sampler';
import Controls from '@/platine-tools/controls'; import Controls from '@/platine-tools/controls';
import { usePlayerStore } from '~/store/player'
const playerStore = usePlayerStore()
onMounted(async () => { onMounted(async () => {
const disc = new Disc(document.querySelector('#disc')!) const disc = new Disc(document.querySelector('#disc')!)
@@ -37,7 +29,7 @@ onMounted(async () => {
rewindButton: document.querySelector('#rewind') as HTMLButtonElement, rewindButton: document.querySelector('#rewind') as HTMLButtonElement,
}) })
await sampler.loadTrack('/jet.mp3') await sampler.loadTrack('https://files.erudi.fr/music/2004011815__connie_francis__siboney.mp3')
controls.isDisabled = false controls.isDisabled = false
@@ -75,10 +67,6 @@ onMounted(async () => {
</script> </script>
<style> <style>
.player {
transition: all 1s ease-in-out;
}
.layout { .layout {
width: 100vw; width: 100vw;
height: 100vw; height: 100vw;
@@ -89,30 +77,12 @@ onMounted(async () => {
align-items: center; align-items: center;
} }
.disc-container {
width: 100%;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
border: 2px solid #000;
background: linear-gradient(45deg, #f0f0f0, #424242);
}
.disc { .disc {
position: relative; position: relative;
aspect-ratio: 1; aspect-ratio: 1;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
border-radius: 50%; border-radius: 50%;
cursor: grab; cursor: grab;
} }
@@ -120,56 +90,32 @@ onMounted(async () => {
cursor: grabbing; cursor: grabbing;
} }
.disc__que { .disc-label {
--dim: 20px;
position: absolute; position: absolute;
top: 50%;
right: 30px;
width: var(--dim);
height: var(--dim);
background: var(--color-theme);
border-radius: 50%;
}
.disc__label {
position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
background-size: cover; background-size: cover;
width: 45%; width: 45%;
aspect-ratio: 1/1; aspect-ratio: 1/1;
background: no-repeat url(/logo.svg) center center;
/* background: no-repeat url(/logo.svg) center center; */
background-size: cover; background-size: cover;
border-radius: 50%; border-radius: 50%;
pointer-events: none; pointer-events: none;
} }
.disc__middle { .disc-middle {
--dim: 10px; --dim: 10px;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 10px;
width: var(--dim); height: 10px;
height: var(--dim);
background: rgb(26, 26, 26); background: rgb(26, 26, 26);
border-radius: 50%; border-radius: 50%;
} }
@@ -182,13 +128,7 @@ onMounted(async () => {
padding: 0.4rem; padding: 0.4rem;
color: #fff; color: #fff;
line-height: 1.3; line-height: 1.3;
cursor: pointer; cursor: pointer;
box-shadow:
1px 1px 0px 1px rgb(0 0 0),
0px 0px 0px 0px var(--color-theme);
will-change: box-shadow; will-change: box-shadow;
transition: transition:
box-shadow 0.2s ease-out, box-shadow 0.2s ease-out,
@@ -197,7 +137,6 @@ onMounted(async () => {
.button.is-active { .button.is-active {
transform: translate(1px, 2px); transform: translate(1px, 2px);
box-shadow: 0px 0px 5px 1px var(--color-theme);
} }
.button[disabled] { .button[disabled] {

View File

@@ -1,5 +1,9 @@
<template> <template>
<div class="deck"> <div class="deck">
<button class="px-4 py-2 text-black hover:text-black bg-esyellow transition-colors relative z-30"
@click="cardStore.revealAllCards(tracks)">
reveal
</button>
<draggable v-model="tracks" item-key="id" class="draggable-container" @start="drag = true" @end="onDragEnd"> <draggable v-model="tracks" item-key="id" class="draggable-container" @start="drag = true" @end="onDragEnd">
<template #item="{ element: track }"> <template #item="{ element: track }">
<card :key="track.id" :track="track" tabindex="0" :is-face-up="track.isFaceUp" class="draggable-item" <card :key="track.id" :track="track" tabindex="0" :is-face-up="track.isFaceUp" class="draggable-item"
@@ -12,7 +16,9 @@
<script setup> <script setup>
import { useDataStore } from '~/store/data' import { useDataStore } from '~/store/data'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { useCardStore } from '~/store/card'
const cardStore = useCardStore()
const drag = ref(false) const drag = ref(false)
const tracks = ref([]) const tracks = ref([])
// Configuration du layout // Configuration du layout

27
app/pages/mix.vue Normal file
View File

@@ -0,0 +1,27 @@
<template>
<Platine />
</template>
<script setup>
import { useUiStore } from '~/store/ui'
import { useDataStore } from '~/store/data'
// Configuration du layout
definePageMeta({
layout: 'default'
})
const uiStore = useUiStore()
onMounted(async () => {
const dataStore = useDataStore()
await dataStore.loadData()
uiStore.listBoxes()
})
</script>
<style>
.logo {
filter: drop-shadow(3px 3px 0 rgb(0 0 0 / 0.7));
}
</style>

View File

@@ -1,97 +1,95 @@
class Sampler { class Sampler {
public audioContext: AudioContext = new AudioContext(); public audioContext: AudioContext = new AudioContext()
public gainNode: GainNode = new GainNode(this.audioContext); public gainNode: GainNode = new GainNode(this.audioContext)
public audioBuffer: AudioBuffer | null = null; public audioBuffer: AudioBuffer | null = null
public audioBufferReversed: AudioBuffer | null = null; public audioBufferReversed: AudioBuffer | null = null
public audioSource: AudioBufferSourceNode | null = null; public audioSource: AudioBufferSourceNode | null = null
public duration: number = 0; public duration: number = 0
public isReversed: boolean = false; public isReversed: boolean = false
constructor() { constructor() {
this.gainNode.connect(this.audioContext.destination); this.gainNode.connect(this.audioContext.destination)
} }
async getAudioBuffer(audioUrl: string) { async getAudioBuffer(audioUrl: string) {
const response = await fetch(audioUrl); const response = await fetch(audioUrl)
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer()
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer); const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)
return audioBuffer; return audioBuffer
} }
async loadTrack(audioUrl: string) { async loadTrack(audioUrl: string) {
this.audioBuffer = await this.getAudioBuffer(audioUrl); this.audioBuffer = await this.getAudioBuffer(audioUrl)
this.audioBufferReversed = this.getReversedAudioBuffer(this.audioBuffer); this.audioBufferReversed = this.getReversedAudioBuffer(this.audioBuffer)
this.duration = this.audioBuffer.duration; this.duration = this.audioBuffer.duration
} }
getReversedAudioBuffer(audioBuffer: AudioBuffer) { getReversedAudioBuffer(audioBuffer: AudioBuffer) {
const bufferArray = audioBuffer.getChannelData(0).slice().reverse(); const bufferArray = audioBuffer.getChannelData(0).slice().reverse()
const audioBufferReversed = this.audioContext.createBuffer( const audioBufferReversed = this.audioContext.createBuffer(
1, 1,
audioBuffer.length, audioBuffer.length,
audioBuffer.sampleRate, audioBuffer.sampleRate
); )
audioBufferReversed.getChannelData(0).set(bufferArray); audioBufferReversed.getChannelData(0).set(bufferArray)
return audioBufferReversed; return audioBufferReversed
} }
changeDirection(isReversed: boolean, secondsPlayed: number) { changeDirection(isReversed: boolean, secondsPlayed: number) {
this.isReversed = isReversed; this.isReversed = isReversed
this.play(secondsPlayed); this.play(secondsPlayed)
} }
play(offset = 0) { play(offset = 0) {
this.pause(); this.pause()
const buffer = this.isReversed const buffer = this.isReversed ? this.audioBufferReversed : this.audioBuffer
? this.audioBufferReversed
: this.audioBuffer;
const cueTime = this.isReversed ? this.duration - offset : offset; const cueTime = this.isReversed ? this.duration - offset : offset
this.audioSource = this.audioContext.createBufferSource(); this.audioSource = this.audioContext.createBufferSource()
this.audioSource.buffer = buffer; this.audioSource.buffer = buffer
this.audioSource.loop = false; this.audioSource.loop = false
this.audioSource.connect(this.gainNode); this.audioSource.connect(this.gainNode)
this.audioSource.start(0, cueTime); this.audioSource.start(0, cueTime)
} }
updateSpeed(speed: number, isReversed: boolean, secondsPlayed: number) { updateSpeed(speed: number, isReversed: boolean, secondsPlayed: number) {
if (!this.audioSource) { if (!this.audioSource) {
return; return
} }
if (isReversed !== this.isReversed) { if (isReversed !== this.isReversed) {
this.changeDirection(isReversed, secondsPlayed); this.changeDirection(isReversed, secondsPlayed)
} }
const { currentTime } = this.audioContext; const { currentTime } = this.audioContext
const speedAbsolute = Math.abs(speed); const speedAbsolute = Math.abs(speed)
this.audioSource.playbackRate.cancelScheduledValues(currentTime); this.audioSource.playbackRate.cancelScheduledValues(currentTime)
this.audioSource.playbackRate.linearRampToValueAtTime( this.audioSource.playbackRate.linearRampToValueAtTime(
Math.max(0.001, speedAbsolute), Math.max(0.001, speedAbsolute),
currentTime, currentTime
); )
} }
pause() { pause() {
if (!this.audioSource) { if (!this.audioSource) {
return; return
} }
this.audioSource.stop(); this.audioSource.stop()
} }
} }
export default Sampler; export default Sampler