Piano synth · deepseek-r1-0528
Using the pre-provided tone.min.js (global Tone), build a playable piano keyboard controlled by mouse and computer keys that synthesizes notes with an ADSR envelope and visual key feedback.
This one-shot run produced no runnable artifact.
agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
transcript · 2 messages
Using the pre-provided tone.min.js (global Tone), build a playable piano keyboard controlled by mouse and computer keys that synthesizes notes with an ADSR envelope and visual key feedback.
thinking
We are building a single-page web app in one file: index.html
We are given that tone.min.js is already in the current directory. We will load it with a script tag.
The app must include:
- A piano keyboard (graphical with keys) that can be controlled by mouse and computer keys.
- Each note should be synthesized with an ADSR envelope (using Tone.js).
- Visual feedback when a key is pressed (e.g., change color).
Steps:
1. We'll create an HTML structure for the piano. We can have a container for the keys.
2. For the piano keys, we'll create white and black keys. We can use divs and style them appropriately.
3. We'll map computer keys (like A, S, D, ...) to the piano notes. Also, we'll handle mouse events on the keys.
4. We'll use Tone.js to create a synthesizer with an ADSR envelope.
5. When a key is pressed (by mouse or keyboard), we'll play the note and change the key's appearance.
6. When the key is released, we'll change the appearance back.
Design decisions:
- The piano will have 12 keys (one octave) for simplicity, but we can design it to be scalable.
However, for a single page and simplicity, we'll do one octave starting at C4 (middle C).
- White keys: C, D, E, F, G, A, B
- Black keys: C#, D#, F#, G#, A#
Keyboard mapping for one octave (from left to right, starting at C4):
Computer keys: A (C), W (C#), S (D), E (D#), D (E), F (F), T (F#), G (G), Y (G#), H (A), U (A#), J (B)
Mouse: click on the key.
ADSR parameters (example values, we can adjust):
Attack: 0.1
Decay: 0.2
Sustain: 0.5
Release: 0.8
We'll create a Tone.Synth and set the envelope.
Implementation:
1. HTML structure:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple Tone.js Piano</title>
<style>
... CSS for the piano and keys ...
</style>
</head>
<body>
<div id="piano">
<div class="key white" data-note="C4"></div>
<div class="key black" data-note="C#4"></div>
<div class="key white" data-note="D4"></div>
<div class="key black" data-note="D#4"></div>
... and so on ...
</div>
<script src="tone.min.js"></script>
<script>
... JavaScript for the app ...
</script>
</body>
</html>
2. CSS:
We'll style the keys to look like a piano.
White keys: wider, white, with border.
Black keys: narrower, black, positioned over the white keys.
3. JavaScript:
We'll create a synthesizer.
We'll set up:
- mousedown and mouseup (or touch events) for the keys.
- keydown and keyup for the computer keyboard.
We'll map computer keys to note names.
We'll have an array or object that maps computer key to note.
For example:
const keyToNote = {
'a': 'C4',
'w': 'C#4',
's': 'D4',
'e': 'D#4',
'd': 'E4',
'f': 'F4',
't': 'F#4',
'g': 'G4',
'y': 'G#4',
'h': 'A4',
'u': 'A#4',
'j': 'B4'
};
When a key is pressed (keydown) we play the note and add a class to the corresponding key element to highlight it.
When released (keyup), we trigger the release of the note and remove the class.
Similarly for mouse: on mousedown, play the note and add the class. On mouseup (or when the mouse leaves the key?) we release.
However, note: if the mouse is pressed and then moved off the key, we still want to release when the mouse button is released anywhere. We can listen to mouseup on the document.
4. Tone.js:
We'll create a synth with ADSR:
const synth = new Tone.Synth({
oscillator: {
type: "sine"
},
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8
}
}).toDestination();
Then, when we want to play: synth.triggerAttack(noteName)
When we want to release: synth.triggerRelease()
But note: we have multiple notes and we want to play them independently? However, we have only one synth. We can do:
synth.triggerAttack(note);
... and then when released for that note, triggerRelease.
But the problem: if we press two keys, the first note will be released when we release the second?
Alternatively, we can use a PolySynth to handle multiple notes. However, the requirement says "ADSR envelope", and we are building a simple one-octave piano. For simplicity, we'll use one synth and when we press a new note, we release the previous? But that doesn't allow chords.
Let me see: we can use a PolySynth. But the requirement might be met by a single synth because we only play one note at a time? However, if we press two keys at the same time, we need to play two notes.
So, we'll use a PolySynth with voice count of 12 (max simultaneous notes) and of type Tone.Synth.
However, note: the requirement says "synthesizes notes with an ADSR envelope". A PolySynth of Synth will do that per note.
So:
const synth = new Tone.PolySynth(Tone.Synth).toDestination();
synth.set({
oscillator: { type: "sine" },
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8
}
});
Then, when playing a note: synth.triggerAttack(note)
When releasing: synth.triggerRelease(note)
But note: for the triggerRelease, we have to give the same note. So we have to keep track.
However, we are mapping each note individually.
Alternatively, we can use the release of the voice by the note name.
5. Implementation of events:
We'll attach event listeners:
- For the keys (piano keys): mousedown and mouseup (or touch events).
- For the document: keydown and keyup.
For mouse:
When a key is pressed, we get the note from the data-note attribute.
We'll also store the state (which keys are pressed) so that if the same key is pressed again without release (for keyboard) we don't trigger again.
6. Steps in code:
a) Create the piano keys in HTML. We can generate the 12 keys with an array of objects representing the keys.
const notes = [
{ name: "C4", type: "white", key: "a" },
{ name: "C#4", type: "black", key: "w" },
{ name: "D4", type: "white", key: "s" },
{ name: "D#4", type: "black", key: "e" },
{ name: "E4", type: "white", key: "d" },
{ name: "F4", type: "white", key: "f" },
{ name: "F#4", type: "black", key: "t" },
{ name: "G4", type: "white", key: "g" },
{ name: "G#4", type: "black", key: "y" },
{ name: "A4", type: "white", key: "h" },
{ name: "A#4", type: "black", key: "u" },
{ name: "B4", type: "white", key: "j" }
];
Then loop through and create the divs.
b) We'll create a function to handle key press (for a note) and release.
We'll also handle the case: if the mouse is held down and moved over multiple keys? But that might be complex. We'll stick to the simple: one key at a time with mouse.
We can do:
For each key element:
mousedown: play the note and set the style (add class 'active').
And then we set up a listener for the mouseup event on the whole document to release that note? But we must know which note to release.
However, we want the note to be released when the mouse is released, but we can only release the note we played. We can store the currently pressed note by mouse? But if multiple? We'll release by note only when the mouseup happens.
Alternatively, we can attach the mouseup event to the key itself? But what if the mouse moves off the key?
Better: use the document mouseup to release the note that was pressed by that mouse event.
We can do:
keyElement.addEventListener('mousedown', function() {
const note = this.getAttribute('data-note');
playNote(note);
this.classList.add('active');
// and on document mouseup, we release and remove the class for this note
const release = function() {
releaseNote(note);
thisKeyElement.classList.remove('active');
document.removeEventListener('mouseup', release);
}.bind(this);
document.addEventListener('mouseup', release);
});
This way, when the mouse is released anywhere, the note is released.
c) For keyboard:
document.addEventListener('keydown', function(e) {
const note = keyToNote[e.key];
if (note && !activeKeys[e.key]) { // activeKeys is a map to prevent repeated triggering
activeKeys[e.key] = true;
playNote(note);
// find the corresponding key element and add active
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.add('active');
}
}
});
document.addEventListener('keyup', function(e) {
const note = keyToNote[e.key];
if (note) {
activeKeys[e.key] = false;
releaseNote(note);
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.remove('active');
}
}
});
d) The playNote(note) function:
function playNote(note) {
synth.triggerAttack(note);
}
function releaseNote(note) {
synth.triggerRelease(note);
}
7. We have to ensure that Tone.js is loaded and the context starts on a user gesture. We can require the user to click a button to start the audio context. This is because of autoplay policies.
We can show a "Start Audio" button at the top. When clicked, we start Tone.context and remove the button.
OR: we can start the audio context on the first user interaction (like keydown or mousedown).
We'll do:
let audioStarted = false;
function startAudio() {
if (audioStarted) return;
Tone.start().then(() => {
console.log('Audio started');
audioStarted = true;
});
}
Then, in the mousedown and keydown events, we call startAudio().
8. The code structure:
- HTML: container div with id="piano"
- We'll create the keys with JavaScript? Or with HTML? Since it's only 12 keys, we can write them in HTML. But let me do it in the HTML part to avoid having to run JS to create them.
However, to avoid repetition, we can write the keys in the HTML.
But for now, we can write them manually.
9. CSS for the piano:
.piano {
position: relative;
width: 600px;
height: 200px;
margin: 50px auto;
}
.key {
position: absolute;
cursor: pointer;
}
.white {
background-color: white;
border: 1px solid #ccc;
width: 60px;
height: 180px;
}
.black {
background-color: black;
width: 40px;
height: 120px;
}
.active {
background-color: #ccc; // for white keys when active
}
.black.active {
background-color: #666; // for black keys
}
Now, we have to position the black keys above the white keys.
We'll assign an index for white keys and then position black keys relative to them.
But without JavaScript, we can hardcode the positions.
Let me map:
C4: index 0 -> left: 0
C#4: index 1 -> left: 45px (overlapping at half of white key width? White key width is 60, so black key should be above at 60 - blackWidth/2? Let me do 60-20=40? Then next white key starts at 60? We want black keys to be between the white keys.)
So:
White keys: each 60px wide, so left = i * 60 for the i-th white key? But how many white keys: 7 in one octave (from C to B: C, D, E, F, G, A, B)
Actually, we have 12 keys but white and black? We'll do:
C4 (white): 0
D4 (white): 60
E4 (white): 120
F4 (white): 180
G4 (white): 240
A4 (white): 300
B4 (white): 360
Then black keys:
C#4: halfway between C4 and D4 -> (0+60)/2 = 30, then we adjust to center the black key (width 40) at 30 - 20 + something? Or simply:
left for the black key = (position of the next white) - (40/2) = 60 - 20 = 40? Then the black key would be at 40? Then we do:
Actually, we can position the black keys at the position of the next white key minus 20 pixels? Let me do:
C#4: 60 - 20 = 40? But then it would be at 40? And the black key is 40px wide -> it covers from 40 to 80? While the white key at 0 is 0-60 and the white at 60 is 60-120? So the black key is centered over the gap? But actually, black keys are above the white keys.
We'll position the black keys absolutely at:
C#4: 45 (so that it is 45 from left, and the next white starts at 60, then 5px of the black remains over the D white? Let me try with a common design.
Alternatively, we can do:
The black keys are at 60-25 = 35? Then the black key will be from 35 to 75? So it overlaps the white keys on both sides.
We'll try:
White keys: 7 keys: from 0 to 420 (60*7).
Then black keys:
C#4: between C and D -> left: 60 - 40/2 = 40? (because the next white key is at 60, so the center should be at 30? Then left = 30 - 20 = 10? That centers it at 30? Or we can do: 60*(i) - (blackWidth/2) for the black key that is after the i-th white key?
We can hardcode for 12 keys:
Index 0: C4 -> white, left=0
Index 1: C#4 -> black, left=45px (from 0 to 60: the black is at 60 - 20? no) -> we can do: 0 + 60 - 15 = 45? and width=30 (so 45 to 75, but then the next white is at 60? so the black key will start at 45 which is overlapping the white key at 0? No, white key at 0 is from 0 to 60, so 45 is within)
Instead, let me use a standard of 7 white keys, each 60px. Then each white key is 60px.
Then the black keys should be positioned at:
after the first white key: C# -> left: 60 - 20 (half of black width) ? That would be 60 - 20 = 40, but then the next white key (D) starts at 60, so the black is at 40 to 80, which overlaps the D key? We want the black key to be above the gap? Actually, in a real piano, the black keys are above the white keys.
We'll set:
.piano { position: relative; height: 200px; width: 420px; } for 7 white keys * 60.
The black keys should be above, so we give them a lower z-index? Or higher? The black keys should be above the white keys? Actually, no: we want the white keys to be behind? In the DOM, the black keys are after the white keys? Or they are before? We'll put the white keys first and then the black keys, and then position the black keys with absolute.
But note: the white keys are 180px high, the black are 120px and we position them at top:0, so they will be over the white keys? We can set the black keys to be above by z-index.
However, the white keys are 180px and the black are 120px, so the black keys are shorter? Then we don't need to worry about overlapping? Actually, we want the black keys to be above the white keys.
So:
.white: z-index:1, .black: z-index:2? and also .black is positioned above.
Steps:
let positions = {
"C4": 0,
"C#4": 45,
"D4": 60,
"D#4": 105,
"E4": 120,
"F4": 180,
"F#4": 225,
"G4": 240,
"G#4": 285,
"A4": 300,
"A#4": 345,
"B4": 360
};
How I calculated:
White keys: 0, 60, 120, 180, 240, 300, 360 (for 7 keys)
Then each black key is placed at the midpoint between two white keys, but shifted to the right to be centered over the gap? Actually, the black key between C and D should be at 60 - halfWhite? Let me do:
C4:0, next white at 60 -> then the black key between them: at 60 - (half of black width, 20) ? that would be 60 - 20 = 40? Then for D# (between D and E): D is at 60, E at 120 -> then at 120 - 20 = 100? But we have written 105?
Actually, to avoid the black keys being too close to the next white, we can add some spacing? How about 15px? Then:
After C4 (at 0): the black key C#4 is at 0 + 60 - 15 = 45? and then the next black key after D (at 60) is 60 + 60 - 15 = 105? Then after F (at 180) -> F#: 180+60-15=225? Then after G: 240+60-15=285? After A: 300+60-15=345.
So:
positions = {
"C4": 0,
"C#4": 45,
"D4": 60,
"D#4": 105,
"E4": 120,
"F4": 180,
"F#4": 225,
"G4": 240,
"G#4": 285,
"A4": 300,
"A#4": 345,
"B4": 360
};
This will work.
Then in CSS, each key gets a left position.
We can write:
.key[data-note="C4"] { left: 0px; }
.key[data-note="C#4"] { left: 45px; }
... etc.
But that is tedious. Instead, we can assign in the HTML an inline style? Or we can generate them with JavaScript? But we don't have to, we can write 12 rules? Since it's only 12, we can do it in the CSS.
Alternatively, we can set in the JavaScript after creating the divs? But we are writing the HTML structure by hand.
Or, we can write the positions in the HTML as style attributes? For each div:
<div class="key white" data-note="C4" style="left:0px;"></div>
<div class="key black" data-note="C#4" style="left:45px;"></div>
...
But then we have repetition? But only 12.
10. Let me write the HTML for the piano keys with style and data attributes.
We can create an array of objects:
notes = [
{ note: "C4", type: "white", key: "a", left: 0 },
{ note: "C#4", type: "black", key: "w", left: 45 },
{ note: "D4", type: "white", key: "s", left: 60 },
{ note: "D#4", type: "black", key: "e", left: 105 },
{ note: "E4", type: "white", key: "d", left: 120 },
{ note: "F4", type: "white", key: "f", left: 180 },
{ note: "F#4", type: "black", key: "t", left: 225 },
{ note: "G4", type: "white", key: "g", left: 240 },
{ note: "G#4", type: "black", key: "y", left: 285 },
{ note: "A4", type: "white", key: "h", left: 300 },
{ note: "A#4", type: "black", key: "u", left: 345 },
{ note: "B4", type: "white", key: "j", left: 360 }
]
Then we iterate and create the divs.
But the problem: we are writing the HTML in one file and we don't want to use JavaScript to generate the DOM? We are allowed to write the HTML.
Since the app is small, we'll write the HTML keys as:
<div id="piano">
<div class="key white" data-note="C4" style="left:0px;"></div>
<div class="key black" data-note="C#4" style="left:45px;"></div>
<div class="key white" data-note="D4" style="left:60px;"></div>
<div class="key black" data-note="D#4" style="left:105px;"></div>
<div class="key white" data-note="E4" style="left:120px;"></div>
<div class="key white" data-note="F4" style="left:180px;"></div>
<div class="key black" data-note="F#4" style="left:225px;"></div>
<div class="key white" data-note="G4" style="left:240px;"></div>
<div class="key black" data-note="G#4" style="left:285px;"></div>
<div class="key white" data-note="A4" style="left:300px;"></div>
<div class="key black" data-note="A#4" style="left:345px;"></div>
<div class="key white" data-note="B4" style="left:360px;"></div>
</div>
And then in the CSS, we have the .key with position:absolute, and the .piano with position:relative and width 420px (60*7) and height 200px.
11. Finally, the JavaScript:
We define:
const synth = new Tone.PolySynth(Tone.Synth).toDestination();
synth.set({
oscillator: { type: "sine" },
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8
}
});
// Map computer key to note
const keyToNote = {
'a': 'C4',
'w': 'C#4',
's': 'D4',
'e': 'D#4',
'd': 'E4',
'f': 'F4',
't': 'F#4',
'g': 'G4',
'y': 'G#4',
'h': 'A4',
'u': 'A#4',
'j': 'B4'
};
let activeKeys = {};
function playNote(note) {
synth.triggerAttack(note);
}
function releaseNote(note) {
synth.triggerRelease(note);
}
let audioStarted = false;
function startAudio() {
if (audioStarted) return;
Tone.start().then(() => {
console.log("Audio context started");
audioStarted = true;
});
}
// Hook mouse events for the keys
const keys = document.querySelectorAll('.key');
keys.forEach(key => {
key.addEventListener('mousedown', () => {
startAudio();
const note = key.getAttribute('data-note');
playNote(note);
key.classList.add('active');
const release = (e) => {
releaseNote(note);
key.classList.remove('active');
document.removeEventListener('mouseup', release);
};
document.addEventListener('mouseup', release);
});
});
// Hook keyboard events
document.addEventListener('keydown', (e) => {
startAudio();
if (e.repeat) return; // prevent holding down the key
const note = keyToNote[e.key];
if (note) {
// we prevent triggering the same key if it is already down
if (!activeKeys[e.key]) {
activeKeys[e.key] = true;
playNote(note);
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.add('active');
}
}
}
});
document.addEventListener('keyup', (e) => {
const note = keyToNote[e.key];
if (note) {
activeKeys[e.key] = false;
releaseNote(note);
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.remove('active');
}
}
});
12. We have to consider touch events? We only have mousedown, but we can also add touchstart and touchend? Or for the sake of simplicity, we skip for now.
13. Let me write the entire file.
Note: We must close the HTML tags and include the script for Tone.
Since the filename is tone.min.js, we load:
<script src="tone.min.js"></script>
And the JavaScript will go in a script tag at the bottom.
I think that's all.
Let me write the entire index.html.
We are in the current directory: /tmp/ws. We write index.html.
Let me build the HTML file:
We write:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tone.js Piano</title>
<style>
body {
text-align: center;
}
.piano {
position: relative;
width: 420px;
height: 200px;
margin: 50px auto;
}
.key {
position: absolute;
cursor: pointer;
user-select: none;
}
.white {
background-color: white;
border: 1px solid #ccc;
width: 60px;
height: 180px;
z-index: 1;
}
.black {
background-color: black;
width: 30px;
height: 120px;
z-index: 2;
}
.key.active {
background-color: #ccc;
}
.key.black.active {
background-color: #666;
}
</style>
</head>
<body>
<div class="piano">
<!-- Key elements with inline styles for left -->
<div class="key white" data-note="C4" style="left:0px;"></div>
<div class="key black" data-note="C#4" style="left:45px;"></div>
<div class="key white" data-note="D4" style="left:60px;"></div>
<div class="key black" data-note="D#4" style="left:105px;"></div>
<div class="key white" data-note="E4" style="left:120px;"></div>
<div class="key white" data-note="F4" style="left:180px;"></div>
<div class="key black" data-note="F#4" style="left:225px;"></div>
<div class="key white" data-note="G4" style="left:240px;"></div>
<div class="key black" data-note="G#4" style="left:285px;"></div>
<div class="key white" data-note="A4" style="left:300px;"></div>
<div class="key black" data-note="A#4" style="left:345px;"></div>
<div class="key white" data-note="B4" style="left:360px;"></div>
</div>
<script src="tone.min.js"></script>
<script>
// Our JavaScript code as described above
const synth = new Tone.PolySynth(Tone.Synth).toDestination();
synth.set({
oscillator: { type: "sine" },
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8
}
});
const keyToNote = {
'a': 'C4',
'w': 'C#4',
's': 'D4',
'e': 'D#4',
'd': 'E4',
'f': 'F4',
't': 'F#4',
'g': 'G4',
'y': 'G#4',
'h': 'A4',
'u': 'A#4',
'j': 'B4'
};
let activeKeys = {};
let audioStarted = false;
function startAudio() {
if (audioStarted) return;
// Start the audio context
Tone.start().then(() => {
console.log('Audio started');
audioStarted = true;
}).catch(e => console.error(e));
}
function playNote(note) {
synth.triggerAttack(note);
}
function releaseNote(note) {
synth.triggerRelease(note);
}
// Mouse events
const keys = document.querySelectorAll('.key');
keys.forEach(key => {
key.addEventListener('mousedown', () => {
startAudio();
const note = key.dataset.note;
playNote(note);
key.classList.add('active');
const release = function(e) {
releaseNote(note);
key.classList.remove('active');
document.removeEventListener('mouseup', release);
};
document.addEventListener('mouseup', release);
});
});
// Keyboard events
document.addEventListener('keydown', e => {
startAudio();
if (e.repeat) return; // ignore if key repeat
const note = keyToNote[e.key];
if (note) {
if (!activeKeys[e.key]) {
activeKeys[e.key] = true;
playNote(note);
const keyEl = document.querySelector(`.key[data-note="${note}"]`);
if (keyEl) {
keyEl.classList.add('active');
}
}
}
});
document.addEventListener('keyup', e => {
const note = keyToNote[e.key];
if (note) {
activeKeys[e.key] = false;
releaseNote(note);
const keyEl = document.querySelector(`.key[data-note="${note}"]`);
if (keyEl) {
keyEl.classList.remove('active');
}
}
});
</script>
</body>
</html>
14. We set the black keys to 30px wide? And positioned at 45, 105, etc. with left. And the piano container is 420px (which is 7*60) and that matches.
15. We'll write the file.
Note: We are writing to index.html in the current directory.
I'll create a playable piano keyboard using Tone.js. Here's the complete index.html file with embedded CSS and JavaScript: html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tone.js Piano</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c); font-family: 'Arial', sans-serif; overflow: hidden; padding: 20px; } .piano-container { background: rgba(0, 0, 0, 0.8); border-radius: 20px; box-shadow: 0 15px 30px rgba(0, 0, 0, 0.6); padding: 40px; width: 95%; max-width: 1000px; } h1 { text-align: center; color: white; margin-bottom: 20px; font-size: 2.5rem; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); } .instructions { background: rgba(255, 255, 255, 0.1); border-radius: 10px; color: white; padding: 15px; margin-bottom: 30px; font-size: 1.1rem; } .piano { display: flex; position: relative; height: 200px; margin: 50px auto; touch-action: none; } .key { position: relative; border-radius: 0 0 5px 5px; } .white { height: 100%; width: 60px; background-color: white; border: 1px solid #ccc; z-index: 1; } .black { height: 60%; width: 40px; background-color: black; margin: 0 -20px; z-index: 2; } .white.active { background: linear-gradient(to bottom, #fff, #b3e5fc); box-shadow: inset 0 0 15px #039be5; } .black.active { background: linear-gradient(to bottom, #000, #424242); box-shadow: inset 0 0 15px #4fc3f7; } .key-label { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); color: rgba(0, 0, 0, 0.6); font-weight: bold; } .black .key-label { color: rgba(255, 255, 255, 0.7); bottom: 5px; } .adsr-controls { background: rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 20px; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 15px; margin-top: 30px; } .adsr-label { color: white; font-size: 1.3rem; text-align: center; flex: 1; min-width: 150px; } .slider-value { color: #4fc3f7; font-weight: bold; margin-top: 5px; } input[type="range"] { width: 100%; margin-top: 10px; height: 8px; border-radius: 5px; background: #b3e5fc; outline: none; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #0277bd; cursor: pointer; box-shadow: 0 0 5px rgba(0, 0, 0, 0.6); } .bottom-info { color: rgba(255, 255, 255, 0.7); text-align: center; margin-top: 20px; font-size: 0.9rem; } </style> </head> <body> <div class="piano-container"> <h1>Tone.js Piano</h1> <div class="instructions"> Click the piano keys or use your computer keyboard: A (C), W (C#), S (D), E (D#), D (E), F (F), T (F#), G (G), Y (G#), H (A), U (A#), J (B) </div> <div class="piano"> <!-- Piano keys will be added dynamically --> </div> <div class="adsr-controls"> <!-- ADSR sliders will be added dynamically --> </div> <div class="bottom-info"> Press any key to start | Made with Tone.js </div> </div> <script src="tone.min.js"></script> <script> // Initialize Synth with initial ADSR values const synth = new Tone.PolySynth(Tone.Synth); synth.set({ oscillator: { type: 'sine', }, envelope: { attack: 0.1, decay: 0.3, sustain: 0.5, release: 0.8 } }).toDestination(); // Piano keyboard layout (white and black keys) const keyboard = [ { note: 'C4', type: 'white', key: 'A', label: 'C' }, { note: 'C#4', type: 'black', key: 'W', label: 'C#' }, { note: 'D4', type: 'white', key: 'S', label: 'D' }, { note: 'D#4', type: 'black', key: 'E', label: 'D#' }, { note: 'E4', type: 'white', key: 'D', label: 'E' }, { note: 'F4', type: 'white', key: 'F', label: 'F' }, { note: 'F#4', type: 'black', key: 'T', label: 'F#' }, { note: 'G4', type: 'white', key: 'G', label: 'G' }, { note: 'G#4', type: 'black', key: 'Y', label: 'G#' }, { note: 'A4', type: 'white', key: 'H', label: 'A' }, { note: 'A#4', type: 'black', key: 'U', label: 'A#' }, { note: 'B4', type: 'white', key: 'J', label: 'B' } ]; // Create piano keyboard const piano = document.querySelector('.piano'); keyboard.forEach(key => { const keyEl = document.createElement('div'); keyEl.className = `key ${key.type}`; keyEl.dataset.note = key.note; const label = document.createElement('div'); label.className = 'key-label'; label.textContent = `${key.label} (${key.key})`; keyEl.appendChild(label); piano.appendChild(keyEl); }); // Create ADSR controls const adsrParams = [ { label: 'Attack', type: 'attack', value: 0.1 }, { label: 'Decay', type: 'decay', value: 0.3 }, { label: 'Sustain', type: 'sustain', value: 0.5 }, { label: 'Release', type: 'release', value: 0.8 } ]; const adsrContainer = document.querySelector('.adsr-controls'); adsrParams.forEach(param => { const container = document.createElement('div'); container.className = 'adsr-label'; container.innerHTML = ` ${param.label}: <span class="slider-value" id="${param.type}-value">${param.value}</span> <input type="range" min="0" max="1" step="0.05" value="${param.value}" id="${param.type}-slider" > `; adsrContainer.appendChild(container); // Add event listener to each slider const slider = document.getElementById(`${param.type}-slider`); const valueDisplay = document.getElementById(`${param.type}-value`); slider.addEventListener('input', () => { const value = parseFloat(slider.value); valueDisplay.textContent = value.toFixed(2); if (synth && synth.get().envelope) { synth.set({ envelope: { [param.type]: value } }); } }); }); // Key handling variables const activeKeys = new Set(); let audioStarted = false; // Keyboard event handlers function playNote(note) { if (!audioStarted) { Tone.start().then(() => { audioStarted = true; synth.triggerAttack(note); }); } else { synth.triggerAttack(note); } } function releaseNote(note) { if (audioStarted) { synth.triggerRelease(note); } } // Find key element by note function getKeyElement(note) { return document.querySelector(`.key[data-note="${note}"]`); } // Visual feedback for active keys function activateKey(note) { const key = getKeyElement(note); if (key) key.classList.add('active'); } function deactivateKey(note) { const key = getKeyElement(note); if (key) key.classList.remove('active'); } // Mouse/click events piano.addEventListener('mousedown', e => { if (e.target.classList.contains('key')) { const note = e.target.dataset.note; activeKeys.add(note); playNote(note); activateKey(note); } }); document.addEventListener('mouseup', () => { activeKeys.forEach(note => { releaseNote(note); deactivateKey(note); }); activeKeys.clear(); }); // Computer keyboard events const keyMap = { 'A': 'C4', 'a': 'C4', 'W': 'C#4', 'w': 'C#4', 'S': 'D4', 's': 'D4', 'E': 'D#4', 'e': 'D#4', 'D': 'E4', 'd': 'E4', 'F': 'F4', 'f': 'F4', 'T': 'F#4', 't': 'F#4', 'G': 'G4', 'g': 'G4', 'Y': 'G#4', 'y': 'G#4', 'H': 'A4', 'h': 'A4', 'U': 'A#4', 'u': 'A#4', 'J': 'B4', 'j': 'B4' }; document.addEventListener('keydown', e => { if (keyMap[e.key]) { const note = keyMap[e.key]; if (!activeKeys.has(note)) { activeKeys.add(note); playNote(note); activateKey(note); } } }); document.addEventListener('keyup', e => { if (keyMap[e.key]) { const note = keyMap[e.key]; if (activeKeys.has(note)) { activeKeys.delete(note); releaseNote(note); deactivateKey(note); } } }); </script> </body> </html> This piano has the following features: - Full playable keyboard with white and black keys - Both mouse and keyboard controls (QWERTY keyboard mapping) - Visual feedback highlighting active keys - ADSR envelope controls with real-time adjustment - Responsive design with attractive interface - Self-contained in a single HTML file (no external dependencies other than tone.min.js) - Graceful touch handling via mouse events - Audio context activation on first interaction The piano starts automatically when you press any key (mouse or keyboard) to comply with browser security requirements. The ADSR controls (attack, decay, sustain, release) let you modify the sound envelope in real-time while playing.
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.