Skip to content

Commit 28b92e1

Browse files
committed
feat: app DJ (2 decks, crossfader, EQ, pitch)
1 parent 9d83afd commit 28b92e1

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

assets/js/dj-app.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// DJ App V1 (2 decks + crossfader + EQ 3 bandes + pitch)
2+
// IMPORTANT : le son ne sortira que si tu charges de vrais fichiers audio (mp3/ogg/wav) dans assets/music/
3+
4+
const playlist = [
5+
{ name: 'Track 1 (test)', src: 'assets/music/track1.mp3' },
6+
{ name: 'Track 2 (test)', src: 'assets/music/track2.mp3' },
7+
{ name: 'Track 3 (test)', src: 'assets/music/track3.mp3' }
8+
];
9+
10+
const els = {
11+
audioA: document.getElementById('audio-a'),
12+
audioB: document.getElementById('audio-b'),
13+
14+
trackA: document.getElementById('track-a'),
15+
trackB: document.getElementById('track-b'),
16+
17+
playA: document.getElementById('play-a'),
18+
pauseA: document.getElementById('pause-a'),
19+
pitchA: document.getElementById('pitch-a'),
20+
eqLowA: document.getElementById('eq-low-a'),
21+
eqMidA: document.getElementById('eq-mid-a'),
22+
eqHighA: document.getElementById('eq-high-a'),
23+
24+
playB: document.getElementById('play-b'),
25+
pauseB: document.getElementById('pause-b'),
26+
pitchB: document.getElementById('pitch-b'),
27+
eqLowB: document.getElementById('eq-low-b'),
28+
eqMidB: document.getElementById('eq-mid-b'),
29+
eqHighB: document.getElementById('eq-high-b'),
30+
31+
crossfader: document.getElementById('crossfader'),
32+
playlist: document.getElementById('playlist')
33+
};
34+
35+
let audioCtx = null;
36+
let deckA = null;
37+
let deckB = null;
38+
39+
function ensureAudioContext() {
40+
if (audioCtx) return;
41+
42+
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
43+
44+
deckA = createDeck(els.audioA);
45+
deckB = createDeck(els.audioB);
46+
47+
// Crossfader au centre au départ
48+
applyCrossfader(parseFloat(els.crossfader.value));
49+
}
50+
51+
function createDeck(audioEl) {
52+
const source = audioCtx.createMediaElementSource(audioEl);
53+
54+
const low = audioCtx.createBiquadFilter();
55+
low.type = 'lowshelf';
56+
low.frequency.value = 200;
57+
58+
const mid = audioCtx.createBiquadFilter();
59+
mid.type = 'peaking';
60+
mid.frequency.value = 1000;
61+
mid.Q.value = 1;
62+
63+
const high = audioCtx.createBiquadFilter();
64+
high.type = 'highshelf';
65+
high.frequency.value = 3000;
66+
67+
const gain = audioCtx.createGain();
68+
gain.gain.value = 0.5;
69+
70+
source.connect(low);
71+
low.connect(mid);
72+
mid.connect(high);
73+
high.connect(gain);
74+
gain.connect(audioCtx.destination);
75+
76+
return { audioEl, source, low, mid, high, gain };
77+
}
78+
79+
function setEQ(deck, lowVal, midVal, highVal) {
80+
// sliders 0..2 (1 = neutre)
81+
// on mappe sur un gain dB raisonnable (-12dB .. +12dB)
82+
const toDb = (v) => (v - 1) * 12;
83+
84+
deck.low.gain.value = toDb(lowVal);
85+
deck.mid.gain.value = toDb(midVal);
86+
deck.high.gain.value = toDb(highVal);
87+
}
88+
89+
function applyCrossfader(x) {
90+
// x: 0 => deckA full, deckB 0 ; 1 => deckB full, deckA 0
91+
// courbe simple (linéaire) pour V1
92+
if (!deckA || !deckB) return;
93+
94+
deckA.gain.gain.value = 1 - x;
95+
deckB.gain.gain.value = x;
96+
}
97+
98+
function loadTrack(deck, track) {
99+
deck.audioEl.src = track.src;
100+
deck.audioEl.load();
101+
}
102+
103+
function renderPlaylist() {
104+
els.playlist.innerHTML = '';
105+
106+
playlist.forEach((t) => {
107+
const wrap = document.createElement('div');
108+
wrap.style.cssText = 'display:flex; gap:8px; align-items:center; background:#1b1b1b; padding:8px 10px; border-radius:8px;';
109+
110+
const title = document.createElement('div');
111+
title.textContent = t.name;
112+
title.style.cssText = 'min-width:180px;';
113+
114+
const btnA = document.createElement('button');
115+
btnA.textContent = 'Charger A';
116+
btnA.style.cssText = 'background:#4CAF50;color:#fff;border:none;padding:8px 10px;border-radius:6px;cursor:pointer;';
117+
btnA.onclick = () => {
118+
ensureAudioContext();
119+
loadTrack(deckA, t);
120+
els.trackA.textContent = t.name;
121+
};
122+
123+
const btnB = document.createElement('button');
124+
btnB.textContent = 'Charger B';
125+
btnB.style.cssText = 'background:#4CAF50;color:#fff;border:none;padding:8px 10px;border-radius:6px;cursor:pointer;';
126+
btnB.onclick = () => {
127+
ensureAudioContext();
128+
loadTrack(deckB, t);
129+
els.trackB.textContent = t.name;
130+
};
131+
132+
wrap.appendChild(title);
133+
wrap.appendChild(btnA);
134+
wrap.appendChild(btnB);
135+
els.playlist.appendChild(wrap);
136+
});
137+
}
138+
139+
function wireUI() {
140+
els.playA.onclick = async () => {
141+
ensureAudioContext();
142+
await audioCtx.resume();
143+
els.audioA.play();
144+
};
145+
els.pauseA.onclick = () => els.audioA.pause();
146+
els.pitchA.oninput = (e) => (els.audioA.playbackRate = parseFloat(e.target.value));
147+
148+
els.playB.onclick = async () => {
149+
ensureAudioContext();
150+
await audioCtx.resume();
151+
els.audioB.play();
152+
};
153+
els.pauseB.onclick = () => els.audioB.pause();
154+
els.pitchB.oninput = (e) => (els.audioB.playbackRate = parseFloat(e.target.value));
155+
156+
const updateEQA = () => {
157+
if (!deckA) return;
158+
setEQ(deckA,
159+
parseFloat(els.eqLowA.value),
160+
parseFloat(els.eqMidA.value),
161+
parseFloat(els.eqHighA.value)
162+
);
163+
};
164+
const updateEQB = () => {
165+
if (!deckB) return;
166+
setEQ(deckB,
167+
parseFloat(els.eqLowB.value),
168+
parseFloat(els.eqMidB.value),
169+
parseFloat(els.eqHighB.value)
170+
);
171+
};
172+
173+
els.eqLowA.oninput = updateEQA;
174+
els.eqMidA.oninput = updateEQA;
175+
els.eqHighA.oninput = updateEQA;
176+
177+
els.eqLowB.oninput = updateEQB;
178+
els.eqMidB.oninput = updateEQB;
179+
els.eqHighB.oninput = updateEQB;
180+
181+
els.crossfader.oninput = (e) => applyCrossfader(parseFloat(e.target.value));
182+
}
183+
184+
window.addEventListener('DOMContentLoaded', () => {
185+
// Si la section DJ n'est pas présente sur la page, on ne fait rien.
186+
if (!els.audioA || !els.audioB || !els.playlist) return;
187+
188+
renderPlaylist();
189+
wireUI();
190+
});

0 commit comments

Comments
 (0)