Skip to content

Commit 3e00c78

Browse files
committed
feat(code): allow play and run the code
1 parent 8afc34a commit 3e00c78

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

src/pages/index.astro

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import '../styles/global.css';
2525
<span class="logo-tagline">code → music</span>
2626
</div>
2727
<div class="header-actions">
28+
<button id="play-code" class="icon-button" aria-label="Play code as music" title="Play code as music">
29+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
30+
<polygon points="5 3 19 12 5 21 5 3"></polygon>
31+
</svg>
32+
</button>
2833
<div id="audio-indicator" class="audio-indicator" title="Audio inactive - start typing to activate">
2934
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3035
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
@@ -232,9 +237,17 @@ import '../styles/global.css';
232237
</div>
233238

234239
<script>
235-
import { setTheme, setLanguage, toggleVim, setVolume, getVolume, themeRegistry, languageRegistry } from '../scripts/editor';
240+
import { setTheme, setLanguage, toggleVim, setVolume, getVolume, themeRegistry, languageRegistry, playCurrentCode } from '../scripts/editor';
236241
import { getThemesByGroup } from '../scripts/themes/index';
237242

243+
// Play code button
244+
const playCodeButton = document.getElementById('play-code');
245+
playCodeButton?.addEventListener('click', async () => {
246+
playCodeButton.classList.add('playing');
247+
await playCurrentCode();
248+
setTimeout(() => playCodeButton.classList.remove('playing'), 2000);
249+
});
250+
238251
// Settings panel toggle
239252
const settingsToggle = document.getElementById('settings-toggle');
240253
const settingsPanel = document.getElementById('settings-panel');

src/scripts/editor.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
playBackspace,
3737
playMelodyNote,
3838
playPatternSound,
39+
playCode,
3940
isAudioInitialized,
4041
setVolume,
4142
getVolume,
@@ -353,4 +354,22 @@ function setupVimModeListener(): void {
353354
});
354355
}
355356

357+
export async function playCurrentCode(): Promise<void> {
358+
if (!editorView) return;
359+
360+
if (!isAudioInitialized()) {
361+
await initAudio();
362+
setMusicConfig(currentTheme.music);
363+
const indicator = document.getElementById("audio-indicator");
364+
if (indicator) {
365+
indicator.classList.add("active");
366+
indicator.title = "Audio active";
367+
}
368+
}
369+
370+
const code = editorView.state.doc.toString();
371+
const patterns = detectPattern(code, currentLanguage);
372+
await playCode(code, patterns);
373+
}
374+
356375
export { themeRegistry, languageRegistry, setVolume, getVolume };

src/scripts/music.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,54 @@ export function setVolume(volume: number): void {
381381
export function getVolume(): number {
382382
return masterVolume;
383383
}
384+
385+
export async function playCode(
386+
code: string,
387+
patterns: {
388+
hasFunction: boolean;
389+
hasLoop: boolean;
390+
hasConditional: boolean;
391+
hasString: boolean;
392+
hasComment: boolean;
393+
},
394+
): Promise<void> {
395+
if (!isInitialized) {
396+
await initAudio();
397+
}
398+
399+
if (!currentConfig || !melodySynth || !padSynth) return;
400+
401+
const now = Tone.now();
402+
const lines = code.split("\n").filter((l) => l.trim().length > 0);
403+
const scale = currentConfig.scale;
404+
405+
// Play a melody based on code structure
406+
let time = now;
407+
const noteDelay = 0.15;
408+
409+
// Play melody notes for each non-empty line (up to 16 notes)
410+
const maxNotes = Math.min(lines.length, 16);
411+
for (let i = 0; i < maxNotes; i++) {
412+
const note = scale[i % scale.length];
413+
melodySynth.triggerAttackRelease(note, "16n", time);
414+
time += noteDelay;
415+
}
416+
417+
// Play pattern chords after the melody
418+
const chordTime = time + 0.2;
419+
420+
if (patterns.hasFunction) {
421+
const chord = [scale[0], scale[2], scale[4]];
422+
padSynth.triggerAttackRelease(chord, "2n", chordTime);
423+
}
424+
425+
if (patterns.hasLoop) {
426+
const chord = [scale[0], scale[3], scale[4]];
427+
padSynth.triggerAttackRelease(chord, "2n", chordTime + 0.5);
428+
}
429+
430+
if (patterns.hasConditional) {
431+
const chord = [scale[0], scale[2], scale[5]];
432+
padSynth.triggerAttackRelease(chord, "2n", chordTime + 1);
433+
}
434+
}

src/styles/global.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,22 @@ body {
152152
background-color: var(--bg-primary);
153153
}
154154

155+
.icon-button.playing {
156+
color: var(--accent);
157+
border-color: var(--accent);
158+
animation: playPulse 0.5s ease-in-out infinite;
159+
}
160+
161+
@keyframes playPulse {
162+
0%,
163+
100% {
164+
transform: scale(1);
165+
}
166+
50% {
167+
transform: scale(1.05);
168+
}
169+
}
170+
155171
/* Editor Wrapper */
156172
.editor-wrapper {
157173
flex: 1;

0 commit comments

Comments
 (0)