anycoder-8fbbe525 / index.html
sirsan69's picture
Upload folder using huggingface_hub
af77333 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PitchLab Pro - DAW-Integrated Audio Pitch Editor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #0f0f0f;
--bg-secondary: #1a1a1a;
--bg-tertiary: #252525;
--bg-hover: #2e2e2e;
--text-primary: #ffffff;
--text-secondary: #a0a0a0;
--accent-blue: #3b82f6;
--accent-purple: #8b5cf6;
--accent-green: #10b981;
--accent-orange: #f59e0b;
--accent-red: #ef4444;
--accent-yellow: #eab308;
--border-color: #333333;
--grid-color: #1e1e1e;
--success-green: #22c55e;
--warning-yellow: #fbbf24;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Header */
.header {
background: linear-gradient(180deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
border-bottom: 1px solid var(--border-color);
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 22px;
font-weight: 600;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--accent-purple), var(--accent-blue));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.header-controls {
display: flex;
gap: 10px;
align-items: center;
}
.header-btn {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-secondary);
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
}
.header-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
/* Toolbar */
.toolbar {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: 10px 20px;
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.tool-group {
display: flex;
gap: 8px;
padding: 0 12px;
border-right: 1px solid var(--border-color);
}
.tool-group:last-child {
border-right: none;
}
.tool-btn {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-secondary);
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
position: relative;
}
.tool-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
transform: translateY(-1px);
}
.tool-btn.active {
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
color: white;
border-color: var(--accent-blue);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
/* Main Content */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* DAW Info Bar */
.daw-info {
background: linear-gradient(90deg, var(--bg-tertiary), var(--bg-secondary));
padding: 8px 20px;
display: flex;
align-items: center;
gap: 30px;
border-bottom: 1px solid var(--border-color);
font-size: 12px;
}
.daw-info-item {
display: flex;
align-items: center;
gap: 8px;
}
.daw-info-label {
color: var(--text-secondary);
}
.daw-info-value {
color: var(--accent-blue);
font-weight: 600;
}
/* Editor Container */
.editor-container {
flex: 1;
position: relative;
background: var(--bg-primary);
overflow: hidden;
}
/* Timeline */
.timeline {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 45px;
background: linear-gradient(180deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
padding: 0 15px;
z-index: 10;
}
.time-ruler {
width: 100%;
height: 30px;
position: relative;
}
.time-mark {
position: absolute;
top: 0;
transform: translateX(-50%);
font-size: 11px;
color: var(--text-secondary);
font-weight: 500;
}
.time-mark::before {
content: '';
position: absolute;
top: 20px;
left: 50%;
width: 1px;
height: 5px;
background: var(--text-secondary);
}
/* Piano Roll */
.piano-roll {
position: absolute;
top: 45px;
left: 0;
width: 60px;
bottom: 0;
background: linear-gradient(90deg, var(--bg-tertiary), var(--bg-secondary));
border-right: 1px solid var(--border-color);
z-index: 5;
}
.piano-key {
height: 20px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: var(--text-secondary);
position: relative;
}
.piano-key.black {
background: #1a1a1a;
color: var(--text-primary);
height: 15px;
}
.piano-key.white {
background: #252525;
}
/* Canvas */
#pitchCanvas {
position: absolute;
top: 45px;
left: 60px;
cursor: crosshair;
}
/* Playback Controls */
.playback-controls {
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
border-top: 1px solid var(--border-color);
padding: 15px 20px;
display: flex;
align-items: center;
gap: 25px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
}
.transport {
display: flex;
gap: 5px;
align-items: center;
}
.transport-btn {
width: 42px;
height: 42px;
border-radius: 50%;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
font-size: 16px;
}
.transport-btn:hover {
background: var(--bg-hover);
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.transport-btn.play {
width: 50px;
height: 50px;
background: linear-gradient(135deg, var(--success-green), #16a34a);
border-color: var(--success-green);
}
.transport-btn.play:hover {
background: linear-gradient(135deg, #16a34a, #15803d);
}
/* Time Display */
.time-display {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 18px;
background: var(--bg-tertiary);
border-radius: 10px;
font-size: 14px;
font-variant-numeric: tabular-nums;
border: 1px solid var(--border-color);
}
.time-separator {
color: var(--text-secondary);
}
/* Pitch Display */
.pitch-display {
display: flex;
align-items: center;
gap: 15px;
margin-left: auto;
}
.pitch-info {
padding: 10px 18px;
background: var(--bg-tertiary);
border-radius: 10px;
font-size: 14px;
border: 1px solid var(--border-color);
}
.pitch-label {
color: var(--text-secondary);
margin-right: 8px;
}
.pitch-value {
color: var(--accent-blue);
font-weight: 600;
}
/* Side Panel */
.side-panel {
position: absolute;
right: 0;
top: 45px;
width: 320px;
height: calc(100% - 45px);
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
border-left: 1px solid var(--border-color);
padding: 25px;
transform: translateX(100%);
transition: transform 0.3s;
z-index: 20;
overflow-y: auto;
box-shadow: -2px 0 10px rgba(0,0,0,0.2);
}
.side-panel.open {
transform: translateX(0);
}
.panel-section {
margin-bottom: 30px;
}
.panel-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.panel-title::before {
content: '';
width: 4px;
height: 16px;
background: linear-gradient(180deg, var(--accent-blue), var(--accent-purple));
border-radius: 2px;
}
.slider-control {
margin-bottom: 20px;
}
.slider-label {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 13px;
color: var(--text-secondary);
}
.slider {
width: 100%;
height: 6px;
background: var(--bg-hover);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.slider::-moz-range-thumb {
width: 18px;
height: 18px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
/* Export Options */
.export-options {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 15px;
}
.export-btn {
background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-hover));
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 12px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.export-btn:hover {
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
/* Loading Overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-overlay.active {
display: flex;
}
.loading-content {
text-align: center;
}
.loading-spinner {
width: 60px;
height: 60px;
border: 3px solid var(--border-color);
border-top-color: var(--accent-blue);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
.loading-text {
color: var(--text-primary);
font-size: 14px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Tooltip */
.tooltip {
position: absolute;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
pointer-events: none;
z-index: 100;
opacity: 0;
transition: opacity 0.2s;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.tooltip.visible {
opacity: 1;
}
/* DAW Integration Modal */
.daw-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.daw-modal.active {
display: flex;
}
.modal-content {
background: var(--bg-secondary);
border-radius: 15px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
}
.modal-header {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: var(--text-primary);
}
.modal-section {
margin-bottom: 25px;
}
.modal-section h3 {
font-size: 16px;
margin-bottom: 10px;
color: var(--accent-blue);
}
.modal-section p {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 10px;
}
.modal-close {
background: var(--accent-red);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
margin-top: 20px;
}
/* Responsive */
@media (max-width: 768px) {
.toolbar {
padding: 8px 12px;
}
.tool-group {
padding: 0 8px;
}
.tool-btn {
padding: 6px 8px;
font-size: 12px;
}
.side-panel {
width: 100%;
}
.header-controls {
gap: 8px;
}
.daw-info {
flex-wrap: wrap;
gap: 15px;
}
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--bg-primary);
}
::-webkit-scrollbar-thumb {
background: var(--bg-hover);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-color);
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="logo">
<div class="logo-icon"></div>
<span>PitchLab Pro</span>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
style="color: var(--text-secondary); font-size: 12px; margin-left: 8px;">Built with anycoder</a>
</div>
<div class="header-controls">
<button class="header-btn" onclick="showDAWIntegration()">
🎹 DAW Integration
</button>
<button class="header-btn" onclick="toggleSidePanel()">
⚙️ Settings
</button>
<button class="header-btn" onclick="exportProject()">
💾 Export Project
</button>
</div>
</header>
<!-- Toolbar -->
<div class="toolbar">
<div class="tool-group">
<button class="tool-btn active" data-tool="select" onclick="selectTool('select')">
↖️ Select
</button>
<button class="tool-btn" data-tool="pitch" onclick="selectTool('pitch')">
🎵 Pitch
</button>
<button class="tool-btn" data-tool="time" onclick="selectTool('time')">
⏱️ Time
</button>
<button class="tool-btn" data-tool="slice" onclick="selectTool('slice')">
✂️ Slice
</button>
</div>
<div class="tool-group">
<button class="tool-btn" onclick="resetView()">
🔄 Reset View
</button>
<button class="tool-btn" onclick="zoomIn()">
🔍+ Zoom In
</button>
<button class="tool-btn" onclick="zoomOut()">
🔍- Zoom Out
</button>
</div>
<div class="tool-group">
<div class="file-input-wrapper">
<button class="tool-btn" onclick="document.getElementById('audioFile').click()">
📁 Load Audio
</button>
<input type="file" id="audioFile" class="file-input" accept="audio/*" onchange="loadAudioFile(event)">
</div>
<button class="tool-btn" onclick="document.getElementById('midiFile').click()">
🎹 Import MIDI
</button>
<input type="file" id="midiFile" class="file-input" accept=".mid,.midi" onchange="loadMIDIFile(event)">
<button class="tool-btn" onclick="clearAll()">
🗑️ Clear
</button>
</div>
</div>
<!-- DAW Info Bar -->
<div class="daw-info">
<div class="daw-info-item">
<span class="daw-info-label">Tempo:</span>
<span class="daw-info-value" id="bpmValue">120 BPM</span>
</div>
<div class="daw-info-item">
<span class="daw-info-label">Key:</span>
<span class="daw-info-value" id="keyValue">C Major</span>
</div>
<div class="daw-info-item">
<span class="daw-info-label">Time Sig:</span>
<span class="daw-info-value" id="timeSigValue">4/4</span>
</div>
<div class="daw-info-item">
<span class="daw-info-label">Sample Rate:</span>
<span class="daw-info-value" id="sampleRateValue">44.1 kHz</span>
</div>
<div class="daw-info-item">
<span class="daw-info-label">Format:</span>
<span class="daw-info-value" id="formatValue">Ready for DAW</span>
</div>
</div>
<!-- Main Editor -->
<div class="main-content">
<div class="editor-container">
<!-- Timeline -->
<div class="timeline">
<div class="time-ruler" id="timeRuler"></div>
</div>
<!-- Piano Roll -->
<div class="piano-roll" id="pianoRoll"></div>
<!-- Pitch Canvas -->
<canvas id="pitchCanvas"></canvas>
<!-- Side Panel -->
<div class="side-panel" id="sidePanel">
<div class="panel-section">
<h3 class="panel-title">DAW Settings</h3>
<div class="slider-control">
<div class="slider-label">
<span>Tempo (BPM)</span>
<span id="bpmSlider">120</span>
</div>
<input type="range" class="slider" min="60" max="200" value="120" oninput="updateBPM(this.value)">
</div>
<div class="slider-control">
<div class="slider-label">
<span>Quantize Strength</span>
<span id="quantizeValue">50%</span>
</div>
<input type="range" class="slider" min="0" max="100" value="50" oninput="updateSlider('quantize', this.value)">
</div>
</div>
<div class="panel-section">
<h3 class="panel-title">Pitch Correction</h3>
<div class="slider-control">
<div class="slider-label">
<span>Snap Strength</span>
<span id="snapValue">50%</span>
</div>
<input type="range" class="slider" min="0" max="100" value="50" oninput="updateSlider('snap', this.value)">
</div>
<div class="slider-control">
<div class="slider-label">
<span>Pitch Range</span>
<span id="rangeValue">±12</span>
</div>
<input type="range" class="slider" min="1" max="24" value="12" oninput="updateSlider('range', this.value)">
</div>
</div>
<div class="panel-section">
<h3 class="panel-title">Export Options</h3>
<div class="export-options">
<button class="export-btn" onclick="exportMIDI()">
🎹 Export as MIDI
</button>
<button class="export-btn" onclick="exportAudioWithPitch()">
🎵 Export Corrected Audio
</button>
<button class="export-btn" onclick="exportMelodyneXML()">
📄 Export Melodyne XML
</button>
<button class="export-btn" onclick="exportAbletonClip()">
🎛️ Export Ableton Clip
</button>
<button class="export-btn" onclick="exportLogicPX()">
🎼 Export Logic Pro PX
</button>
</div>
</div>
<div class="panel-section">
<h3 class="panel-title">Display</h3>
<div class="slider-control">
<div class="slider-label">
<span>Vertical Zoom</span>
<span id="vZoomValue">100%</span>
</div>
<input type="range" class="slider" min="50" max="200" value="100" oninput="updateSlider('vZoom', this.value)">
</div>
</div>
</div>
</div>
</div>
<!-- Playback Controls -->
<div class="playback-controls">
<div class="transport">
<button class="transport-btn" onclick="skipBackward()">⏮️</button>
<button class="transport-btn" onclick="stepBackward()"></button>
<button class="transport-btn play" onclick="togglePlayback()">▶️</button>
<button class="transport-btn" onclick="stepForward()"></button>
<button class="transport-btn" onclick="skipForward()">⏭️</button>
<button class="transport-btn" onclick="loopToggle()">🔁</button>
</div>
<div class="time-display">
<span id="currentTime">00:00.000</span>
<span class="time-separator">/</span>
<span id="totalTime">00:00.000</span>
</div>
<div class="pitch-display">
<div class="pitch-info">
<span class="pitch-label">Current Note:</span>
<span class="pitch-value" id="currentNote">--</span>
</div>
<div class="pitch-info">
<span class="pitch-label">Frequency:</span>
<span class="pitch-value" id="currentFreq">-- Hz</span>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
<div class="loading-spinner"></div>
<div class="loading-text">Processing audio...</div>
</div>
</div>
<!-- DAW Integration Modal -->
<div class="daw-modal" id="dawModal">
<div class="modal-content">
<h2 class="modal-header">🎹 DAW Integration Guide</h2>
<div class="modal-section">
<h3>How to use PitchLab Pro with your DAW:</h3>
<p><strong>1. Export Options:</strong></p>
<p><strong>MIDI Export:</strong> Export your edited melody as a MIDI file that can be dragged into any DAW</p>
<p><strong>Audio Export:</strong> Export the pitch-corrected audio with applied changes</p>
<p><strong>DAW-Specific Formats:</strong> Export in formats compatible with Ableton Live, Logic Pro, and more</p>
</div>
<div class="modal-section">
<h3>Workflow Integration:</h3>
<p><strong>Ableton Live:</strong></p>
<p>• Export as MIDI and drag onto a MIDI track</p>
<p>• Use "Export Ableton Clip" for seamless integration</p>
<p>• Tempo and time signature are automatically matched</p>
<p><strong>Logic Pro:</strong></p>
<p>• Export as Logic PX format for direct import</p>
<p>• Maintains all pitch correction data</p>
<p>• Compatible with Logic's Flex Pitch</p>
<p><strong>FL Studio:</strong></p>
<p>• Export as MIDI file</p>
<p>• Drag into Piano Roll</p>
<p>• Use with Newtone or Pitcher for further processing</p>
</div>
<div class="modal-section">
<h3>Real-time Integration:</h3>
<p>• Use your DAW's ReWire or virtual audio cable to route audio</p>
<p>• Set BPM to match your project tempo</p>
<p>• Export stems for each edited section</p>
</div>
<button class="modal-close" onclick="closeDAWModal()">Got it!</button>
</div>
</div>
<script>
// Global variables
let canvas, ctx;
let audioContext;
let audioBuffer;
let source;
let isPlaying = false;
let startTime = 0;
let pauseTime = 0;
let currentTool = 'select';
let zoomLevel = 1;
let verticalZoom = 1;
let notes = [];
let selectedNote = null;
let isDragging = false;
let dragStartX = 0;
let dragStartY = 0;
let viewOffset = 0;
let playbackPosition = 0;
let projectBPM = 120;
let projectKey = 'C';
let timeSignature = '4/4';
let isLooping = false;
// Note frequencies for C2 to B6
const noteFrequencies = {
'C2': 65.41, 'C#2': 69.30, 'D2': 73.42, 'D#2': 77.78, 'E2': 82.41, 'F2': 87.31,
'F#2': 92.50, 'G2': 98.00, 'G#2': 103.83, 'A2': 110.00, 'A#2': 116.54, 'B2': 123.47,
'C3': 130.81, 'C#3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'E3': 164.81, 'F3': 174.61,
'F#3': 185.00, 'G3': 196.00, 'G#3': 207.65, 'A3': 220.00, 'A#3': 233.08, 'B3': 246.94,
'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'E4': 329.63, 'F4': 349.23,
'F#4': 369.99, 'G4': 392.00, 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88,
'C5': 523.25, 'C#5': 554.37, 'D5': 587.33, 'D#5': 622.25, 'E5': 659.25, 'F5': 698.46,
'F#5': 739.99, 'G5': 783.99, 'G#5': 830.61, 'A5': 880.00, 'A#5': 932.33, 'B5': 987.77,
'C6': 1046.50, 'C#6': 1108.73, 'D6': 1174.66, 'D#6': 1244.51, 'E6': 1318.51, 'F6': 1396.91
};
// Initialize
document.addEventListener('DOMContentLoaded', function() {
initCanvas();
initAudioContext();
initPianoRoll();
generateDemoNotes();
drawInterface();
updateTimeRuler();
startAnimationLoop();
});
function initCanvas() {
canvas = document.getElementById('pitchCanvas');
ctx = canvas.getContext('2d');
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('wheel', handleWheel);
}
function resizeCanvas() {
const container = document.querySelector('.editor-container');
canvas.width = container.clientWidth - 60;
canvas.height = container.clientHeight - 45;
drawInterface();
}
function initPianoRoll() {
const pianoRoll = document.getElementById('pianoRoll');
pianoRoll.innerHTML = '';
const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
for (let octave = 6; octave >= 2; octave--) {
for (let i = noteNames.length - 1; i >= 0; i--) {
const noteName = noteNames[i] + octave;
const key = document.createElement('div');
key.className = 'piano-key';
if (noteNames[i].includes('#')) {
key.classList.add('black');
} else {
key.classList.add('white');
}
key.textContent = noteName;
pianoRoll.appendChild(key);
}
}
}
function initAudioContext() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
function generateDemoNotes() {
const demoNotes = [
{ note: 'C4', start: 0, duration: 0.5, pitch: 261.63, velocity: 80 },
{ note: 'E4', start: 0.5, duration: 0.5, pitch: 329.63, velocity: 75 },
{ note: 'G4', start: 1.0, duration: 0.5, pitch: 392.00, velocity: 70 },
{ note: 'C5', start: 1.5, duration: 0.5, pitch: 523.25, velocity: 85 },
{ note: 'G4', start: 2.0, duration: 0.5, pitch: 392.00, velocity: 70 },
{ note: 'E4', start: 2.5, duration: 0.5, pitch: 329