Piano synth · deepseek-r1
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 going to build a single-page web app in one file: index.html.
We are provided with tone.min.js in the current directory. We'll include it via <script src="tone.min.js"></script>.
Requirements:
- Playable piano keyboard (both mouse and computer keys)
- Synthesize notes using Tone.js with an ADSR envelope
- Visual key feedback (when a key is pressed, it should visually change)
Steps:
1. Create the HTML structure for the piano. We can have white and black keys.
2. Style the piano keys appropriately (CSS).
3. Use Tone.js to create a synthesizer with an ADSR envelope.
4. Map mouse events (click, mousedown, mouseup) to play notes.
5. Map keyboard events (keydown, keyup) to play corresponding notes (using computer keyboard keys as piano keys).
6. Provide visual feedback by changing the style of the key when pressed.
Important:
- We must not use any external resources (all in one file).
- The piano should have at least one octave (for simplicity, we can do 1-2 octaves). Let's do two octaves (C3 to C5).
- We'll map computer keys to piano keys. For example, the home row (ASDFGHJ) for white keys and WERTYU for black keys?
But note: we have two octaves so we need more keys. Let's plan:
We'll have two octaves: C3 to C5 (24 keys: 14 white, 10 black).
We can map:
White keys: A, S, D, F, G, H, J, K, L, ;, ', etc. (but we need 14)
Black keys: W, E, R, T, Y, U, I, O, P, etc. (but we need 10)
Alternatively, we can use two rows:
First row: Z to M for white keys?
Second row: Q to P for black keys?
Let's do:
White keys: A to L (12 keys) and then Z to M? That doesn't give 14.
We can do two sets:
Lower octave: white keys: Z, X, C, V, B, N, M (7 keys)
Upper octave: white keys: Q, W, E, R, T, Y, U (7 keys) -> total 14
Black keys:
Lower: S, D, F, G, H, J (6 keys? but we have 5 black keys per octave? Actually, between ZX: S, XC: D, VB: none? -> no, standard is 5 per octave)
Instead, let's do a standard mapping for two octaves:
White keys (C, D, E, F, G, A, B) for two octaves:
Octave 1: C3, D3, E3, F3, G3, A3, B3 -> keys: A, S, D, F, G, H, J
Octave 2: C4, D4, E4, F4, G4, A4, B4 -> keys: Q, W, E, R, T, Y, U
Black keys (C#, D#, F#, G#, A#) for two octaves:
Octave 1: C#3, D#3, F#3, G#3, A#3 -> keys: W, E, T, Y, U (but wait, we are using W for white in octave2? conflict)
Alternatively, we can use:
White keys for octave1: Z, X, C, V, B, N, M (C3 to B3)
Black keys for octave1: S, D, F, G, H (for C#3, D#3, F#3, G#3, A#3) -> note: skip E because between E and F there is no black key? Actually, between B and C next octave also none.
White keys for octave2: Q, W, E, R, T, Y, U (C4 to B4)
Black keys for octave2: 2,3,5,6,7 (on the top row) or we can use the same row: 1,2,3,4,5?
But note: we want to avoid conflicts and make it playable.
Let me adjust:
We'll do two octaves starting at C3 (white keys: C3, D3, E3, F3, G3, A3, B3, C4, D4, E4, F4, G4, A4, B4) -> 14 white keys.
Black keys: C#3, D#3, F#3, G#3, A#3, C#4, D#4, F#4, G#4, A#4 -> 10 black keys.
Mapping:
White keys (14):
C3 -> 'A'
D3 -> 'S'
E3 -> 'D'
F3 -> 'F'
G3 -> 'G'
A3 -> 'H'
B3 -> 'J'
C4 -> 'K'
D4 -> 'L'
E4 -> ';'
F4 -> '\''
G4 -> (we run out of keys on the home row) -> let's use the row above: Q, W, E, R, T, Y, U for the second octave?
Actually, let's map the entire two octaves on the keyboard from A to the end of the home row and then the top row?
We can do:
Octave1 white: Z, X, C, V, B, N, M (C3 to B3)
Octave2 white: Q, W, E, R, T, Y, U (C4 to B4)
Black keys:
Octave1: S (C#3), D (D#3), G (F#3? wait, between F and G is F#? Actually, between V and B is F#? -> no, V is F3? Let me clarify:
We have:
Z: C3
S: C#3
X: D3
D: D#3
C: E3
V: F3
G: F#3
B: G3
H: G#3
N: A3
J: A#3
M: B3
Then octave2:
Q: C4
2: C#4
W: D4
3: D#4
E: E4
R: F4
5: F#4
T: G4
6: G#4
Y: A4
7: A#4
U: B4
But we don't have enough keys without using numbers? And numbers are shifted?
Alternatively, we can use a different mapping for black keys: for octave1: use the row above? But we are using Q for C4.
Let me propose a simpler mapping for two octaves:
White keys:
C3 -> 'a'
D3 -> 's'
E3 -> 'd'
F3 -> 'f'
G3 -> 'g'
A3 -> 'h'
B3 -> 'j'
C4 -> 'k'
D4 -> 'l'
E4 -> ';'
F4 -> '\''
G4 -> 'z' (but then we run out, so let's use the top row for the second octave? Actually, we can use the same row as the first octave? We have 14 white keys, so we can use: a,s,d,f,g,h,j,k,l,;,', then for the next two: q,w? But that's 12, we need 14 -> so add e,r?
This becomes messy.
Let me instead map the white keys to the first two rows:
First row (letters):
a -> C3
w -> C#3
s -> D3
e -> D#3
d -> E3
f -> F3
t -> F#3
g -> G3
y -> G#3
h -> A3
u -> A#3
j -> B3
Second row (same pattern starting at C4):
z -> C4
x -> C#4
c -> D4
v -> D#4
b -> E4
n -> F4
m -> F#4? but we don't have a key between n and ,?
This is getting too complicated.
Instead, we'll map the white keys to the first 14 letters of the keyboard (a to n) and the black keys to the top row (q to p) but then we have 10 black keys? We can use q to i (10 keys) but that's 10 keys: q,w,e,r,t,y,u,i,o,p -> we only need 10? But then we have to map them appropriately.
How about:
White keys:
a: C3
s: D3
d: E3
f: F3
g: G3
h: A3
j: B3
k: C4
l: D4
;: E4
': F4
z: G4 (we are extending to the bottom row? but we have only 11 so far) -> we need 14.
We can use: a,s,d,f,g,h,j,k,l,;,', then for the next octave: q,w,e,r,t,y? -> but q is already used?
Given the complexity, let's do one octave for simplicity? But the requirement says two octaves.
Alternatively, we can use the following mapping (inspired by online piano):
White keys: A to K (for the first octave) and then Q to U for the second? But then we have:
First octave white: A, S, D, F, G, H, J (7 keys)
Second octave white: Q, W, E, R, T, Y, U (7 keys) -> total 14
Black keys:
First octave: W (for A#? but we are using W for second octave white) -> conflict.
We can shift the black keys to the top row:
First octave black: 2 (C#), 3 (D#), 5 (F#), 6 (G#), 7 (A#)
Second octave black: Q, W, E, R, T? -> but Q is used for white?
This is difficult.
Let me decide:
We'll map the white keys to:
First octave: Z, X, C, V, B, N, M (C3 to B3) -> 7 keys
Second octave: Q, W, E, R, T, Y, U (C4 to B4) -> 7 keys
Black keys:
First octave: S (C#3), D (D#3), G (F#3), H (G#3), J (A#3) -> 5 keys
Second octave: 2 (C#4), 3 (D#4), 5 (F#4), 6 (G#4), 7 (A#4) -> 5 keys
This avoids conflict and uses two rows.
Now, when the user presses a key, we need to know:
- Which note to play?
- And whether it's a white or black key?
We'll create an object mapping key (from keyboard event) to note.
Example mapping:
White keys (octave1):
Z: 'C3'
X: 'D3'
C: 'E3'
V: 'F3'
B: 'G3'
N: 'A3'
M: 'B3'
Black keys (octave1):
S: 'C#3'
D: 'D#3'
G: 'F#3'
H: 'G#3'
J: 'A#3'
White keys (octave2):
Q: 'C4'
W: 'D4'
E: 'E4'
R: 'F4'
T: 'G4'
Y: 'A4'
U: 'B4'
Black keys (octave2):
'2': 'C#4'
'3': 'D#4'
'5': 'F#4'
'6': 'G#4'
'7': 'A#4'
Note: The numbers are on the top row. We have to note that shift might be pressed? But we don't want to require shift. We'll use the key as it comes (without shift, so '2' is the key for 2).
6. Visual feedback: when a key (mouse or keyboard) is pressed, we add a class to the corresponding key element to change its appearance.
Implementation:
HTML structure:
We'll create a container for the piano.
Each key will be a div. White keys and black keys will have different classes.
We'll create the keys in order (from left to right: C3, C#3, D3, D#3, ...). But note: black keys are positioned absolutely between white keys.
CSS:
Position the piano container relatively.
White keys: display inline-block, with width and height, border, background white.
Black keys: position absolute, smaller, background black, and placed between white keys.
JavaScript:
Use Tone.js to create a synth with an ADSR envelope.
Steps for synth:
const synth = new Tone.Synth({
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8,
}
}).toDestination();
Then, when a key is pressed (mouse or keyboard), we trigger the note.
We'll create a mapping from note name to frequency? Actually, Tone.Synth's triggerAttackRelease can take a note name (like "C3").
We'll also create a mapping from keyboard key to note name (as above).
For mouse events: we can attach an event listener to each key element and store the note as a data attribute.
For keyboard events: we listen on the document for keydown and keyup.
We must prevent default behavior for the keys we use to avoid scrolling, etc.
Also, we must handle both keydown and keyup to trigger the attack and release.
For mouse: on mousedown we triggerAttack, on mouseup (or mouseleave) we triggerRelease.
For keyboard: on keydown triggerAttack, on keyup triggerRelease.
Visual feedback: add a class (e.g., 'active') to the key element when pressed, remove on release.
We must be cautious: if the same note is triggered twice without release? We should avoid that.
We'll keep a set of currently pressed keys (by note) and ignore repeated keydown for the same note.
Alternatively, Tone.js Synth can handle multiple notes at once.
However, for simplicity, we can triggerAttack on keydown and triggerRelease on keyup for that note.
We'll also handle the case when the mouse leaves the key while pressed: we should release the note.
Steps:
Create the piano keys in HTML:
We'll generate the keys for two octaves: C3 to B4.
For each note, we create a div with class "key" and also "white" or "black", and set a data-note attribute.
For black keys, we position them absolutely relative to the container.
We'll have a container with position: relative.
Each white key: width 60px, height 200px.
Each black key: width 36px, height 120px, and positioned at an offset from the left of the preceding white key.
How to position black keys:
We'll have a white key for C3 (Z), then a black key for C#3 (S) positioned at the right half of the white key? Actually, we position it between C3 and D3.
We can do:
For a black key that is between two white keys, we set left: (index * whiteKeyWidth) - (blackKeyWidth/2)
But note: the black keys are not evenly spaced? Actually, between E and F there is no black key? So we skip.
We'll create an array of notes in order:
[ 'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3', 'B3', 'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4' ]
Then, for each note:
if note includes '#' -> black key, else white key.
But note: we have 24 keys? Actually, two octaves: 12*2=24.
We'll loop and create:
For white keys: they take space and are in the flow.
For black keys: absolute positioned.
How to calculate the left position:
Let whiteKeyWidth = 60, blackKeyWidth = 36.
We'll keep a counter for the white keys index (starting at 0).
For black keys, we place them at: whiteKeyIndex * whiteKeyWidth - blackKeyWidth/2
But note: after a white key that has a black key on its right, we increment the white key index by 1.
Actually, we can do:
We traverse the notes in order. We'll have a variable `currentLeft` for the next white key position.
Steps:
currentLeft = 0
for each note in the array:
if note is white:
create white key at currentLeft, then currentLeft += whiteKeyWidth
if note is black:
create black key at currentLeft - blackKeyWidth/2? But then the next white key is still at the same currentLeft?
Alternatively, we can note that the black keys are placed at the same horizontal position as the white key that precedes them?
Actually, we can do:
We'll create a container for the white keys and then overlay the black keys.
We'll create all white keys first (for every note that is natural) and then create black keys for the sharps.
How many white keys? 14 (from C3 to B4: two octaves). But note: the array of notes in order has 24, but we only have 14 white keys?
Actually, the white keys are: C3, D3, E3, F3, G3, A3, B3, C4, D4, E4, F4, G4, A4, B4 -> 14.
The black keys are the rest: 10.
We can generate:
First, create a div for each white key, in order, and assign a data-note and set left = index * whiteKeyWidth.
Then, for black keys:
We know the positions: between C3 and D3 -> after the first white key (C3) at position 0, we put a black key at 60 - 18 = 42? But then the next white key (D3) is at 60.
So for a black key between two white keys at positions i and i+1, we put the black key at (i * whiteKeyWidth) + whiteKeyWidth - blackKeyWidth/2? Actually, centered between the two?
Specifically:
left = (i * whiteKeyWidth) + whiteKeyWidth - (blackKeyWidth/2)
But wait, we want the black key to be between C3 and D3:
C3 at 0, D3 at 60 -> the center is at 30. We want the black key centered at 30, so left = 30 - (blackKeyWidth/2) = 30 - 18 = 12.
Formula:
left = (i * whiteKeyWidth) + (whiteKeyWidth/2) - (blackKeyWidth/2)
So we can:
For each black key, we know between which two white keys it falls. We assign an index j (the index of the preceding white key) and then:
left = j * whiteKeyWidth + whiteKeyWidth/2 - blackKeyWidth/2
We'll create an array of white keys and then for each black key, we know its position by the preceding white key index.
Example:
White keys:
index0: C3 -> j=0
index1: D3 -> j=1
index2: E3 -> j=2
index3: F3 -> j=3
index4: G3 -> j=4
index5: A3 -> j=5
index6: B3 -> j=6
index7: C4 -> j=7
... etc.
Black keys:
C#3: between C3 and D3 -> j=0
D#3: between D3 and E3 -> j=1
F#3: between F3 and G3 -> j=3
G#3: between G3 and A3 -> j=4
A#3: between A3 and B3 -> j=5
Then for the next octave: same pattern.
So we can create:
whiteKeys = ['C3','D3','E3','F3','G3','A3','B3','C4','D4','E4','F4','G4','A4','B4']
blackKeys = [
{ note: 'C#3', afterWhiteIndex: 0 },
{ note: 'D#3', afterWhiteIndex: 1 },
{ note: 'F#3', afterWhiteIndex: 3 },
{ note: 'G#3', afterWhiteIndex: 4 },
{ note: 'A#3', afterWhiteIndex: 5 },
{ note: 'C#4', afterWhiteIndex: 7 },
{ note: 'D#4', afterWhiteIndex: 8 },
{ note: 'F#4', afterWhiteIndex: 10 },
{ note: 'G#4', afterWhiteIndex: 11 },
{ note: 'A#4', afterWhiteIndex: 12 }
]
Then when creating:
White keys: for i in [0,13]: left = i * 60
Black keys: for each black key: left = (blackKey.afterWhiteIndex * 60) + 30 - 18 = (blackKey.afterWhiteIndex * 60) + 12
We'll generate the HTML for the keys.
7. We'll write the entire HTML, CSS, and JS in one file.
Let's code:
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Piano with Tone.js</title>
<style>
/* CSS for piano */
.piano {
position: relative;
width: 840px; /* 14 white keys * 60px */
height: 200px;
}
.key {
position: absolute;
bottom: 0;
}
.white {
width: 60px;
height: 200px;
background-color: white;
border: 1px solid #000;
z-index: 1;
}
.black {
width: 36px;
height: 120px;
background-color: black;
z-index: 2;
}
.active {
background-color: #8cf; /* light blue for white keys when active */
}
.black.active {
background-color: #48f; /* darker blue for black keys */
}
</style>
</head>
<body>
<div class="piano" id="piano"></div>
<script src="tone.min.js"></script>
<script>
// We'll generate the piano keys and set up the synth and events.
// White keys: 14 notes
const whiteKeys = ['C3','D3','E3','F3','G3','A3','B3','C4','D4','E4','F4','G4','A4','B4'];
// Black keys: with note and the index of the white key after which they are placed (the preceding white key index)
const blackKeys = [
{ note: 'C#3', afterWhiteIndex: 0 },
{ note: 'D#3', afterWhiteIndex: 1 },
{ note: 'F#3', afterWhiteIndex: 3 },
{ note: 'G#3', afterWhiteIndex: 4 },
{ note: 'A#3', afterWhiteIndex: 5 },
{ note: 'C#4', afterWhiteIndex: 7 },
{ note: 'D#4', afterWhiteIndex: 8 },
{ note: 'F#4', afterWhiteIndex: 10 },
{ note: 'G#4', afterWhiteIndex: 11 },
{ note: 'A#4', afterWhiteIndex: 12 }
];
// Create the piano container
const piano = document.getElementById('piano');
// Create white keys
whiteKeys.forEach((note, index) => {
const key = document.createElement('div');
key.className = 'key white';
key.dataset.note = note;
key.style.left = `${index * 60}px`;
piano.appendChild(key);
});
// Create black keys
blackKeys.forEach(bk => {
const key = document.createElement('div');
key.className = 'key black';
key.dataset.note = bk.note;
// Calculate left: afterWhiteIndex * 60 + 30 - 18 = afterWhiteIndex * 60 + 12
key.style.left = `${bk.afterWhiteIndex * 60 + 12}px`;
piano.appendChild(key);
});
// Create the synth
const synth = new Tone.Synth({
envelope: {
attack: 0.1,
decay: 0.2,
sustain: 0.5,
release: 0.8
}
}).toDestination();
// Mapping from keyboard key to note
const keyToNote = {
// First octave white: Z, X, C, V, B, N, M
'z': 'C3',
'x': 'D3',
'c': 'E3',
'v': 'F3',
'b': 'G3',
'n': 'A3',
'm': 'B3',
// First octave black: S, D, G, H, J
's': 'C#3',
'd': 'D#3',
'g': 'F#3',
'h': 'G#3',
'j': 'A#3',
// Second octave white: Q, W, E, R, T, Y, U
'q': 'C4',
'w': 'D4',
'e': 'E4',
'r': 'F4',
't': 'G4',
'y': 'A4',
'u': 'B4',
// Second octave black: 2, 3, 5, 6, 7
'2': 'C#4',
'3': 'D#4',
'5': 'F#4',
'6': 'G#4',
'7': 'A#4'
};
// We'll keep track of currently pressed keys (to avoid repeated triggerAttack and for cleanup)
const activeKeys = new Set();
// Function to play a note
function playNote(note) {
// If already playing, ignore? Or retrigger? We'll retrigger.
synth.triggerAttackRelease(note, "8n"); // we use a fixed duration? But for keyboard we want to hold until release.
// Actually, we want to triggerAttack on keydown and triggerRelease on keyup.
// So we change: we'll do triggerAttack on keydown and triggerRelease on keyup.
// But for now, we are using triggerAttackRelease for mouse?
// We'll change: we separate attack and release.
}
// Function to handle note start
function startNote(note) {
if (!activeKeys.has(note)) {
activeKeys.add(note);
synth.triggerAttack(note);
// Highlight the key
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.add('active');
}
}
}
// Function to handle note end
function endNote(note) {
if (activeKeys.has(note)) {
activeKeys.delete(note);
synth.triggerRelease(note);
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
keyElement.classList.remove('active');
}
}
}
// Mouse events on keys
piano.addEventListener('mousedown', (e) => {
const key = e.target;
if (key.classList.contains('key')) {
const note = key.dataset.note;
startNote(note);
// We'll also listen for mouseup on the document to release even if outside the key?
const release = (event) => {
endNote(note);
document.removeEventListener('mouseup', release);
};
document.addEventListener('mouseup', release);
}
});
// Keyboard events
document.addEventListener('keydown', (e) => {
// Ignore if repeating
if (e.repeat) return;
const key = e.key.toLowerCase();
const note = keyToNote[key];
if (note) {
startNote(note);
e.preventDefault(); // prevent scrolling
}
});
document.addEventListener('keyup', (e) => {
const key = e.key.toLowerCase();
const note = keyToNote[key];
if (note) {
endNote(note);
e.preventDefault();
}
});
</script>
</body>
</html>
8. Note: We are using Tone.Synth which is monophonic by default? Actually, it's polyphonic? We can play multiple notes at once.
We are using triggerAttack and triggerRelease per note. This should work for polyphony.
9. We'll write this entire content to index.html.
Let's write the file.
I'll create a self-contained piano app using Tone.js. Here's the complete solution: ```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> <script src="tone.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; user-select: none; } 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; } .container { background: rgba(0, 0, 0, 0.7); border-radius: 20px; padding: 30px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5); text-align: center; max-width: 900px; width: 95%; } h1 { color: #fff; margin-bottom: 20px; font-size: 2.5rem; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); } .piano-container { position: relative; width: 100%; height: 200px; margin: 40px 0; } .piano { display: flex; position: relative; height: 100%; border: 2px solid #333; border-radius: 10px; background: linear-gradient(to bottom, #444, #222); box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.8); } .key { position: relative; flex-grow: 1; border-right: 1px solid #555; } .white { height: 100%; background: linear-gradient(to bottom, #fff, #f0f0f0); border-radius: 0 0 5px 5px; z-index: 1; } .white.active { background: linear-gradient(to bottom, #ffcc00, #ff9900); box-shadow: inset 0 0 15px rgba(255, 100, 0, 0.8); } .black { position: absolute; top: 0; width: 7%; height: 65%; background: linear-gradient(to bottom, #000, #333); border-radius: 0 0 4px 4px; z-index: 2; } .black.active { background: linear-gradient(to bottom, #ff6600, #cc3300); box-shadow: inset 0 0 10px rgba(255, 50, 0, 0.8); } .controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; margin-top: 20px; } .control-group { background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; min-width: 200px; } h2 { color: #fff; margin-bottom: 15px; font-size: 1.3rem; } .slider-container { margin: 10px 0; } label { display: block; color: #ddd; margin-bottom: 5px; } input[type="range"] { width: 100%; height: 10px; background: #444; border-radius: 5px; outline: none; } .keyboard-hint { color: #fff; margin-top: 25px; padding: 15px; background: rgba(0, 0, 0, 0.4); border-radius: 10px; font-size: 1rem; } .keyboard-hint kbd { display: inline-block; padding: 3px 8px; margin: 0 5px; background: #333; border-radius: 4px; border: 1px solid #555; box-shadow: 0 2px 0 #111; } </style> </head> <body> <div class="container"> <h1>Tone.js Piano</h1> <div class="piano-container"> <div class="piano" id="piano"></div> </div> <div class="controls"> <div class="control-group"> <h2>ADSR Envelope</h2> <div class="slider-container"> <label for="attack">Attack: <span id="attack-value">0.1</span>s</label> <input type="range" id="attack" min="0" max="1" step="0.01" value="0.1"> </div> <div class="slider-container"> <label for="decay">Decay: <span id="decay-value">0.2</span>s</label> <input type="range" id="decay" min="0" max="1" step="0.01" value="0.2"> </div> <div class="slider-container"> <label for="sustain">Sustain: <span id="sustain-value">0.6</span></label> <input type="range" id="sustain" min="0" max="1" step="0.01" value="0.6"> </div> <div class="slider-container"> <label for="release">Release: <span id="release-value">0.5</span>s</label> <input type="range" id="release" min="0" max="2" step="0.01" value="0.5"> </div> </div> <div class="control-group"> <h2>Keyboard Mapping</h2> <div class="slider-container"> <label for="octave">Octave: <span id="octave-value">4</span></label> <input type="range" id="octave" min="2" max="6" step="1" value="4"> </div> <div class="slider-container"> <label for="volume">Volume: <span id="volume-value">-6</span>dB</label> <input type="range" id="volume" min="-40" max="0" step="1" value="-6"> </div> </div> </div> <div class="keyboard-hint"> Play with computer keys: <kbd>A</kbd><kbd>S</kbd><kbd>D</kbd><kbd>F</kbd><kbd>G</kbd><kbd>H</kbd><kbd>J</kbd> (white keys) | <kbd>W</kbd><kbd>E</kbd><kbd>T</kbd><kbd>Y</kbd><kbd>U</kbd> (black keys) </div> </div> <script> // Initialize Tone.js components const synth = new Tone.PolySynth(Tone.Synth).toDestination(); const reverb = new Tone.Reverb(2).toDestination(); synth.connect(reverb); // Piano state let currentOctave = 4; const keyMap = { 'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#', 'd': 'E', 'f': 'F', 't': 'F#', 'g': 'G', 'y': 'G#', 'h': 'A', 'u': 'A#', 'j': 'B' }; // Create piano keys function createPiano() { const piano = document.getElementById('piano'); piano.innerHTML = ''; const whiteKeys = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; const blackKeys = ['C#', 'D#', '', 'F#', 'G#', 'A#', '']; // Create white keys whiteKeys.forEach((note, i) => { const key = document.createElement('div'); key.className = 'key white'; key.dataset.note = note; key.dataset.index = i; piano.appendChild(key); }); // Create black keys blackKeys.forEach((note, i) => { if (!note) return; const key = document.createElement('div'); key.className = 'key black'; key.dataset.note = note; key.dataset.index = i; key.style.left = `${(i + 0.7) * (100 / whiteKeys.length)}%`; piano.appendChild(key); }); // Add event listeners document.querySelectorAll('.key').forEach(key => { key.addEventListener('mousedown', () => playNote(key.dataset.note)); key.addEventListener('mouseenter', (e) => { if (e.buttons === 1) playNote(key.dataset.note); }); }); } // Play a note function playNote(note) { const fullNote = `${note}${currentOctave}`; synth.triggerAttack(fullNote); // Visual feedback const keyElement = document.querySelector(`.key[data-note="${note}"]`); if (keyElement) { keyElement.classList.add('active'); setTimeout(() => keyElement.classList.remove('active'), 300); } } // Release a note function releaseNote(note) { const fullNote = `${note}${currentOctave}`; synth.triggerRelease(fullNote); } // Update ADSR parameters function updateEnvelope() { synth.set({ envelope: { attack: parseFloat(attack.value), decay: parseFloat(decay.value), sustain: parseFloat(sustain.value), release: parseFloat(release.value) } }); // Update value displays document.getElementById('attack-value').textContent = attack.value; document.getElementById('decay-value').textContent = decay.value; document.getElementById('sustain-value').textContent = sustain.value; document.getElementById('release-value').textContent = release.value; } // Update volume function updateVolume() { synth.volume.value = volume.value; document.getElementById('volume-value').textContent = volume.value; } // Update octave function updateOctave() { currentOctave = parseInt(octave.value); document.getElementById('octave-value').textContent = currentOctave; } // Keyboard event handling document.addEventListener('keydown', (e) => { if (keyMap[e.key.toLowerCase()]) { playNote(keyMap[e.key.toLowerCase()]); } }); document.addEventListener('keyup', (e) => { if (keyMap[e.key.toLowerCase()]) { releaseNote(keyMap[e.key.toLowerCase()]); } }); // Initialize UI window.addEventListener('load', () => { createPiano(); updateEnvelope(); updateVolume(); updateOctave(); // Add event listeners to controls attack.addEventListener('input', updateEnvelope); decay.addEventListener('input', updateEnvelope); sustain.addEventListener('input', updateEnvelope); release.addEventListener('input', updateEnvelope); volume.addEventListener('input', updateVolume); octave.addEventListener('input', updateOctave); }); </script> </body> </html> ``` This piano app includes: 1. **Interactive Piano Keyboard**: - 7 white keys and 5 black keys per octave - Visual feedback with color changes when keys are pressed - Mouse and keyboard controls 2. **Tone.js Synthesis**: - Polyphonic synthesizer with ADSR envelope - Reverb effect for richer sound - Real-time parameter adjustments 3. **ADSR Controls**: - Attack time (0-1s) - Decay time (0-1s) - Sustain level (0-1) - Release time (0-2s) 4. **Additional Controls**: - Octave selection (2-6) - Volume control (-40dB to 0dB) 5. **Keyboard Mapping**: - White keys: A, S, D, F, G, H, J - Black keys: W, E, T, Y, U The interface features a modern gradient design with responsive controls and visual feedback. All code is contained in a single HTML file that loads the local tone.min.js library.
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.