set route /mix & /draggable
This commit is contained in:
@@ -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.
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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] {
|
||||||
|
|||||||
@@ -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
27
app/pages/mix.vue
Normal 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>
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user