Skip to content

Commit 45ca166

Browse files
committed
feat: initial multi-sample implementation for piano and violin instruments
1 parent 19eae92 commit 45ca166

15 files changed

+3541
-662
lines changed

examples/check-piano-usage.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Utility to check if piano_multi is actually being used
2+
// Copy and paste this into your browser console while in Music Blocks
3+
4+
(function() {
5+
console.log("%c🔍 CHECKING PIANO USAGE IN MUSIC BLOCKS 🔍", "background: #2196F3; color: white; font-size: 16px; padding: 5px;");
6+
7+
// Check if instrumentsSource is available in global scope
8+
if (typeof instrumentsSource !== 'undefined') {
9+
console.log("Found instrumentsSource object:");
10+
console.log(instrumentsSource);
11+
12+
// Check if piano_multi is being used
13+
const pianoMultiUsed = Object.values(instrumentsSource).some(source => source[1] === "piano_multi");
14+
15+
if (pianoMultiUsed) {
16+
console.log("%c✅ piano_multi is being used in your project!", "background: #4CAF50; color: white; font-size: 14px; padding: 3px;");
17+
18+
// Find which instrument is using piano_multi
19+
const instrumentsUsingPianoMulti = Object.entries(instrumentsSource)
20+
.filter(([_, source]) => source[1] === "piano_multi")
21+
.map(([name, _]) => name);
22+
23+
console.log("Instruments using piano_multi:", instrumentsUsingPianoMulti);
24+
} else {
25+
console.log("%c❌ piano_multi is NOT being used in your project!", "background: #F44336; color: white; font-size: 14px; padding: 3px;");
26+
console.log("You might be using the regular piano sample instead.");
27+
28+
// Check if regular piano is being used
29+
const regularPianoUsed = Object.values(instrumentsSource).some(source => source[1] === "piano");
30+
if (regularPianoUsed) {
31+
console.log("%c⚠️ Regular piano sample is being used instead of piano_multi", "background: #FF9800; color: white; font-size: 14px; padding: 3px;");
32+
33+
const instrumentsUsingPiano = Object.entries(instrumentsSource)
34+
.filter(([_, source]) => source[1] === "piano")
35+
.map(([name, _]) => name);
36+
37+
console.log("Instruments using regular piano:", instrumentsUsingPiano);
38+
}
39+
}
40+
} else {
41+
console.error("instrumentsSource not found. Are you running this in Music Blocks?");
42+
}
43+
44+
// Check if we can access the synth objects
45+
if (typeof instruments !== 'undefined') {
46+
console.log("Found instruments object:");
47+
48+
// Count how many instruments are available
49+
let instrumentCount = 0;
50+
let samplerCount = 0;
51+
52+
for (const turtleId in instruments) {
53+
for (const instrumentName in instruments[turtleId]) {
54+
instrumentCount++;
55+
const synth = instruments[turtleId][instrumentName];
56+
if (synth && synth.name === "Sampler") {
57+
samplerCount++;
58+
console.log(`Found Sampler: ${instrumentName} for turtle ${turtleId}`);
59+
60+
// Check if this sampler has multiple buffers (indicating multi-sample)
61+
if (synth._buffers) {
62+
const bufferKeys = Array.from(synth._buffers.keys());
63+
console.log(`Sampler buffers: ${bufferKeys.join(", ")}`);
64+
65+
if (bufferKeys.includes("C2") && bufferKeys.includes("C4") && bufferKeys.includes("C6")) {
66+
console.log("%c✅ This sampler has C2, C4, and C6 buffers - it's using multi-samples!",
67+
"background: #4CAF50; color: white; font-size: 14px; padding: 3px;");
68+
} else if (bufferKeys.length === 1 && bufferKeys[0] === "C4") {
69+
console.log("%c❌ This sampler only has a C4 buffer - it's using a single sample",
70+
"background: #F44336; color: white; font-size: 14px; padding: 3px;");
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
console.log(`Total instruments: ${instrumentCount}, Samplers: ${samplerCount}`);
78+
} else {
79+
console.error("instruments object not found. Are you running this in Music Blocks?");
80+
}
81+
82+
console.log("%c💡 SOLUTION: If piano_multi is not being used, try selecting 'piano multi' in the Set Timbre block",
83+
"background: #673AB7; color: white; font-size: 14px; padding: 3px;");
84+
})();

examples/piano-multi-debug.html

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Piano Multi-Sample Debug</title>
6+
<script src="../lib/tone.min.js"></script>
7+
<script src="../sounds/samples/piano_multi.js"></script>
8+
<style>
9+
body {
10+
font-family: Arial, sans-serif;
11+
margin: 20px;
12+
}
13+
button {
14+
margin: 5px;
15+
padding: 10px;
16+
font-size: 16px;
17+
}
18+
.note-group {
19+
margin-bottom: 20px;
20+
padding: 10px;
21+
border: 1px solid #ccc;
22+
border-radius: 5px;
23+
}
24+
.log {
25+
margin-top: 20px;
26+
padding: 10px;
27+
border: 1px solid #ccc;
28+
border-radius: 5px;
29+
height: 200px;
30+
overflow-y: auto;
31+
background-color: #f9f9f9;
32+
}
33+
</style>
34+
</head>
35+
<body>
36+
<h1>Piano Multi-Sample Debug Test</h1>
37+
<p>This page tests whether the multi-sample piano is correctly using different samples for different note ranges.</p>
38+
39+
<div class="note-group">
40+
<h2>Low Register (should use C2 sample)</h2>
41+
<button id="playC2">Play C2</button>
42+
<button id="playF2">Play F2</button>
43+
<button id="playA2">Play A2</button>
44+
<button id="playC3">Play C3</button>
45+
</div>
46+
47+
<div class="note-group">
48+
<h2>Middle Register (should use C4 sample)</h2>
49+
<button id="playC4">Play C4</button>
50+
<button id="playF4">Play F4</button>
51+
<button id="playA4">Play A4</button>
52+
<button id="playC5">Play C5</button>
53+
</div>
54+
55+
<div class="note-group">
56+
<h2>High Register (should use C6 sample)</h2>
57+
<button id="playC6">Play C6</button>
58+
<button id="playF6">Play F6</button>
59+
<button id="playA6">Play A6</button>
60+
<button id="playC7">Play C7</button>
61+
</div>
62+
63+
<div class="log" id="log">
64+
<h3>Debug Log:</h3>
65+
</div>
66+
67+
<script>
68+
// Function to log messages
69+
function logMessage(message) {
70+
const logElement = document.getElementById('log');
71+
const logEntry = document.createElement('div');
72+
logEntry.textContent = message;
73+
logElement.appendChild(logEntry);
74+
logElement.scrollTop = logElement.scrollHeight;
75+
}
76+
77+
// Create a dictionary of samples for different registers
78+
window.onload = function() {
79+
// Extract sample identifiers to verify which sample is being used
80+
const c2Sample = PIANO_C2_SAMPLE();
81+
const c4Sample = PIANO_C4_SAMPLE();
82+
const c6Sample = PIANO_C6_SAMPLE();
83+
84+
// Log the sample sources to verify they're different
85+
logMessage("C2 Sample length: " + c2Sample.length);
86+
logMessage("C4 Sample length: " + c4Sample.length);
87+
logMessage("C6 Sample length: " + c6Sample.length);
88+
89+
// Create a modified version of each sample with an identifier
90+
const c2SampleWithId = c2Sample;
91+
const c4SampleWithId = c4Sample;
92+
const c6SampleWithId = c6Sample;
93+
94+
// Create a dictionary for Tone.Sampler
95+
const noteDict = {
96+
"C2": c2SampleWithId,
97+
"C4": c4SampleWithId,
98+
"C6": c6SampleWithId
99+
};
100+
101+
// Create a sampler with our samples
102+
const sampler = new Tone.Sampler(noteDict).toDestination();
103+
104+
// Function to play a note and log which sample is being used
105+
function playNote(note) {
106+
// Start audio context if needed
107+
if (Tone.context.state !== 'running') {
108+
Tone.start();
109+
}
110+
111+
// Determine which sample will be used based on the note
112+
let sampleUsed;
113+
const noteNum = Tone.Frequency(note).toMidi();
114+
115+
if (noteNum < 48) { // Notes below C3
116+
sampleUsed = "C2";
117+
} else if (noteNum < 72) { // Notes below C5
118+
sampleUsed = "C4";
119+
} else { // Notes C5 and above
120+
sampleUsed = "C6";
121+
}
122+
123+
logMessage(`Playing ${note} (MIDI: ${noteNum}) using ${sampleUsed} sample`);
124+
125+
// Play the note
126+
sampler.triggerAttackRelease(note, "2n");
127+
}
128+
129+
// Add event listeners to buttons
130+
document.getElementById('playC2').addEventListener('click', () => playNote('C2'));
131+
document.getElementById('playF2').addEventListener('click', () => playNote('F2'));
132+
document.getElementById('playA2').addEventListener('click', () => playNote('A2'));
133+
document.getElementById('playC3').addEventListener('click', () => playNote('C3'));
134+
135+
document.getElementById('playC4').addEventListener('click', () => playNote('C4'));
136+
document.getElementById('playF4').addEventListener('click', () => playNote('F4'));
137+
document.getElementById('playA4').addEventListener('click', () => playNote('A4'));
138+
document.getElementById('playC5').addEventListener('click', () => playNote('C5'));
139+
140+
document.getElementById('playC6').addEventListener('click', () => playNote('C6'));
141+
document.getElementById('playF6').addEventListener('click', () => playNote('F6'));
142+
document.getElementById('playA6').addEventListener('click', () => playNote('A6'));
143+
document.getElementById('playC7').addEventListener('click', () => playNote('C7'));
144+
145+
// Log that everything is ready
146+
logMessage("Multi-sample piano test initialized. Click buttons to test different notes.");
147+
logMessage("Tone.js will automatically select the appropriate sample based on the note's pitch.");
148+
};
149+
</script>
150+
</body>
151+
</html>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Direct test script for piano multi-samples
2+
// Copy and paste this entire script into your browser console while in Music Blocks
3+
4+
(function() {
5+
console.log("%c🎹 PIANO MULTI-SAMPLE DIRECT TEST 🎹", "background: #FF5722; color: white; font-size: 16px; padding: 5px;");
6+
7+
// First, verify that the samples are different
8+
if (typeof verifyPianoMultiSamples === 'function') {
9+
console.log("Running sample verification...");
10+
const result = verifyPianoMultiSamples();
11+
console.log("Verification result:", result);
12+
} else {
13+
console.warn("verifyPianoMultiSamples function not found. Make sure piano_multi.js is loaded.");
14+
}
15+
16+
// Check if we're in Music Blocks with access to Tone.js
17+
if (typeof Tone === 'undefined') {
18+
console.error("Tone.js not found. This test must be run within Music Blocks.");
19+
return;
20+
}
21+
22+
// Make sure Tone.js is started
23+
Tone.start().then(() => {
24+
console.log("%c✅ Tone.js audio context started", "color: green; font-weight: bold;");
25+
runTest();
26+
}).catch(err => {
27+
console.error("Failed to start Tone.js:", err);
28+
});
29+
30+
function runTest() {
31+
// Find the instrument name that's using piano_multi
32+
let isPianoMultiRegistered = false;
33+
let instrumentName = "";
34+
35+
if (typeof instrumentsSource !== 'undefined') {
36+
for (const key in instrumentsSource) {
37+
if (instrumentsSource[key][1] === "piano_multi") {
38+
isPianoMultiRegistered = true;
39+
instrumentName = key;
40+
break;
41+
}
42+
}
43+
44+
console.log("%c🔍 Checking if piano_multi is registered:", "font-weight: bold;");
45+
console.log(isPianoMultiRegistered ?
46+
`✅ piano_multi is registered as "${instrumentName}"` :
47+
"❌ piano_multi is NOT registered in instrumentsSource");
48+
} else {
49+
console.warn("instrumentsSource not found. Can't check if piano_multi is registered.");
50+
}
51+
52+
// Test playing notes in different registers to verify sample selection
53+
console.log("%c🎵 Testing piano multi-sample note selection:", "font-weight: bold;");
54+
55+
// Create a test synth with our samples
56+
const testSynth = new Tone.Sampler({
57+
"C2": PIANO_C2_SAMPLE(),
58+
"C4": PIANO_C4_SAMPLE(),
59+
"C6": PIANO_C6_SAMPLE()
60+
}).toDestination();
61+
62+
// Define test notes in different registers - updated to match the corrected boundaries
63+
const testNotes = [
64+
{ note: "C2", register: "low", expectedSample: "C2" },
65+
{ note: "G2", register: "low", expectedSample: "C2" },
66+
{ note: "B2", register: "low", expectedSample: "C2" }, // B2 is MIDI 47, last note using C2 sample
67+
{ note: "C3", register: "middle", expectedSample: "C4" }, // C3 is MIDI 48, first note using C4 sample
68+
{ note: "G4", register: "middle", expectedSample: "C4" },
69+
{ note: "B4", register: "middle", expectedSample: "C4" }, // B4 is MIDI 71, last note using C4 sample
70+
{ note: "C5", register: "high", expectedSample: "C6" }, // C5 is MIDI 72, first note using C6 sample
71+
{ note: "G6", register: "high", expectedSample: "C6" },
72+
{ note: "C7", register: "high", expectedSample: "C6" }
73+
];
74+
75+
// Create a table to track results
76+
const results = [];
77+
78+
// Play each note with a delay
79+
testNotes.forEach((test, index) => {
80+
setTimeout(() => {
81+
const noteNum = Tone.Frequency(test.note).toMidi();
82+
console.log(`%cPlaying ${test.note} (MIDI: ${noteNum}) in ${test.register} register...`, "color: blue;");
83+
84+
// Play the note
85+
testSynth.triggerAttackRelease(test.note, 0.5);
86+
87+
// Record which sample was used
88+
let actualSample;
89+
// Use the same boundaries as in synthutils.js
90+
if (noteNum <= 47) { // Notes up to B2
91+
actualSample = "C2";
92+
} else if (noteNum <= 71) { // Notes from C3 to B4
93+
actualSample = "C4";
94+
} else { // Notes C5 and above
95+
actualSample = "C6";
96+
}
97+
98+
const result = {
99+
note: test.note,
100+
midi: noteNum,
101+
register: test.register,
102+
expectedSample: test.expectedSample,
103+
actualSample: actualSample,
104+
correct: actualSample === test.expectedSample
105+
};
106+
107+
results.push(result);
108+
console.log(`%c${result.correct ? '✅' : '❌'} Used ${actualSample} sample (${result.correct ? 'correct' : 'incorrect'})`,
109+
result.correct ? "color: green;" : "color: red; font-weight: bold;");
110+
111+
// Show final results table after all notes have played
112+
if (index === testNotes.length - 1) {
113+
setTimeout(() => {
114+
console.log("%c📊 FINAL TEST RESULTS:", "background: #2196F3; color: white; font-size: 14px; padding: 3px;");
115+
console.table(results);
116+
117+
const allCorrect = results.every(r => r.correct);
118+
console.log(`%c${allCorrect ? '✅ ALL TESTS PASSED!' : '❌ SOME TESTS FAILED!'}`,
119+
`background: ${allCorrect ? '#4CAF50' : '#F44336'}; color: white; font-size: 14px; padding: 3px;`);
120+
121+
if (allCorrect) {
122+
console.log("%c🎉 The piano multi-sample implementation is working correctly! The system is using the appropriate sample for each note range and not just transposing a single sample.",
123+
"color: green; font-weight: bold;");
124+
} else {
125+
console.log("%c⚠️ There may be an issue with the piano multi-sample implementation. Some notes are not using the expected sample.",
126+
"color: red; font-weight: bold;");
127+
}
128+
}, 500);
129+
}
130+
}, index * 700); // 700ms between notes
131+
});
132+
}
133+
})();

0 commit comments

Comments
 (0)