Go For AI
Type in the search box above for quick search in Pluingtutor.
BECOME A CONTRIBUTOR
Interested in getting your articles published with us ?

RESOURCE PAGE

BAND NAME

AI Band Name Generator

Generate your Band Name

Resources

 

PT Piano Practice

Piano chords are essentially groups of two or more notes played together. The most basic chords that form the building blocks of songs are major and minor chords.

Learning to play chords on piano is the perfect way to get started playing piano. Just use our PT Piano Plugin and have fun creating major and minor chords anywhere on the keyboard!

 

Please wait, PTPiano loading...

PT Bit Rate

Bit rate refers to the amount of data, specifically bits, transmitted or received per second, here is just the tool you need to calculate the audio time with a certain audio data input.

The simple way to calculate a audio file’s bit rate when given any three value in following equation. In fact, as long as you know any three of the following four values, you can calculate the missing value.

Bit rate = (sampling rate) x (number of channels) x (bit depth)

For a recording with a 44.1 kHz sampling rate, 2 channels (stereo) and a 16 bit depth:
44100 x 2 x 16 = 1411200 bits per second, or, 1411.2 kbit/s

Ai Generated Musical Apps

NEON SYNTH

Use this code to add this Synth

<div class="synth-container">

  <div class="synth-title">NEON SYNTH</div>

  <div class="controls-row">
    <div class="control">
      <label>Cutoff</label>
      <input type="range" id="cutoff" min="200" max="8000" value="1800">
    </div>

    <div class="control">
      <label>Resonance</label>
      <input type="range" id="res" min="0.1" max="20" step="0.1" value="1.5">
    </div>

    <div class="control">
      <label>Volume</label>
      <input type="range" id="vol" min="0" max="1" step="0.01" value="0.4">
    </div>
 
	<div class="control">
	  <label>Waveform</label>
	  <select id="waveform">
		<option value="sawtooth">Saw</option>
		<option value="square">Square</option>
		<option value="triangle">Triangle</option>
		<option value="pwm">Pulse Width</option>
	  </select>
	</div>
</div>
 <div id="mnk-keyboard" class="mnk-keyboard"></div>

</div>
<style>
.synth-container {
  padding: 25px;
  width: 550px;
  background: #06080f;
  border-radius: 20px;
  box-shadow: 0 0 25px #00ffea33;
  border: 1px solid #0ff6;
}

.synth-title {
  text-align: center;
  font-size: 1.7rem;
  margin-bottom: 18px;
  letter-spacing: 6px;
  color: #0ff;
  text-shadow: 0 0 12px #00ffee;
}

.controls-row {
  display: flex;
  justify-content: space-between;
  margin-bottom: 25px;
}

.control { text-align: center; width: 30%; }
.control label { font-size: 0.8rem; }

input[type=range] {
  width: 100%;
  margin-top: 8px;
  accent-color: #00f7ff;
}

/* ----- Modern Neon Keyboard ----- */
.mnk-keyboard {
  display: flex;
  gap: 4px;
  justify-content: center;
  flex-wrap: wrap;
}

.mnk-key {
  width: 40px;
  height: 140px;
  border-radius: 8px;
  background: linear-gradient(180deg, #fafafa, #c8c8c8);
  border: 2px solid #444;
  box-shadow: 0 0 10px #00eaff55;
  position: relative;
  cursor: pointer;
  transition: all .08s ease;
}

.mnk-key.black {
  width: 28px;
  height: 90px;
  background: #111;
  color: #0ff;
  z-index: 2;
  margin-left: -18px;
  margin-right: -18px;
  border: 2px solid #000;
  box-shadow: 0 0 12px #00f0ff55;
}

.mnk-key.active {
  background: #0ff;
  box-shadow: 0 0 18px #00ffffbb, 0 0 4px #0ff inset;
  transform: translateY(2px);
}
select {
  width: 100%;
  padding: 4px;
  background: #000;
  border: 1px solid #0ff8;
  color: #0ff;
  border-radius: 6px;
  font-family: "Orbitron", sans-serif;
}

</style>
<script>
const NOTES = [
  "C", "C#", "D", "D#", "E", "F",
  "F#", "G", "G#", "A", "A#", "B", "C2"
];

const FREQ = {
  "C": 261.63, "C#": 277.18, "D": 293.66, "D#": 311.13,
  "E": 329.63, "F": 349.23, "F#": 369.99, "G": 392.0,
  "G#": 415.3, "A": 440.0, "A#": 466.16, "B": 493.88,
  "C2": 523.25
};

let audio = null;
let filter = null;
let volNode = null;

function initAudio() {
  if (audio) return;

  audio = new AudioContext();
  filter = audio.createBiquadFilter();
  filter.type = "lowpass";

  volNode = audio.createGain();
  volNode.gain.value = 0.4;

  filter.connect(volNode);
  volNode.connect(audio.destination);
}
let currentWaveform = "sawtooth";

document.getElementById("waveform").addEventListener("change", e => {
  currentWaveform = e.target.value;
});

function play(note) {
  initAudio();

  const osc = audio.createOscillator();
  const gain = audio.createGain();

  // Envelope
  gain.gain.setValueAtTime(0, audio.currentTime);
  gain.gain.linearRampToValueAtTime(0.6, audio.currentTime + 0.02);
  gain.gain.linearRampToValueAtTime(0.4, audio.currentTime + 0.2);

  // ----- Waveform selection -----
  if (currentWaveform !== "pwm") {
    // Normal oscillator types
    osc.type = currentWaveform;
    osc.frequency.value = FREQ[note];
    osc.connect(gain);
  } else {
    // ----- PWM (Pulse-Width Modulation) -----
    const oscA = audio.createOscillator();
    const oscB = audio.createOscillator();
    const pwmGain = audio.createGain();

    oscA.type = "sawtooth";
    oscB.type = "sawtooth";

    oscA.frequency.value = FREQ[note];
    oscB.frequency.value = FREQ[note];

    // PWM offset detuning
    oscA.detune.value = -15;
    oscB.detune.value = 15;

    // Subtract signals → creates pulse
    pwmGain.gain.value = 0.5;

    oscA.connect(pwmGain);
    oscB.connect(pwmGain);

    pwmGain.connect(gain);

    oscA.start();
    oscB.start();
    oscA.stop(audio.currentTime + 0.35);
    oscB.stop(audio.currentTime + 0.35);

    // connect gain to filter and exit early
    gain.connect(filter);
    return;
  }

  // Connect normal waveforms
  osc.connect(gain);
  gain.connect(filter);

  osc.start();
  osc.stop(audio.currentTime + 0.35);
}


function drawKeyboard() {
  const kb = document.getElementById("mnk-keyboard");

  NOTES.forEach(note => {
    const key = document.createElement("div");
    key.className = "mnk-key" + (note.includes("#") ? " black" : "");
    key.dataset.note = note;

    key.onmousedown = () => {
      key.classList.add("active");
      play(note);
      setTimeout(() => key.classList.remove("active"), 120);
    };

    kb.appendChild(key);
  });
}

drawKeyboard();


/* UI Controls */
document.getElementById("cutoff").addEventListener("input", e => {
  if (filter) filter.frequency.value = e.target.value;
});

document.getElementById("res").addEventListener("input", e => {
  if (filter) filter.Q.value = e.target.value;
});

document.getElementById("vol").addEventListener("input", e => {
  if (volNode) volNode.gain.value = e.target.value;
});
</script>

Osc1

Osc2

Filter

Drive

Env

Arp

Delay

Master

Use this code to add this Synth

<style>
/* container */
.nosck-synth-container {
    width: 50vw;
    min-height: 50vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    box-sizing: border-box;
    padding: 18px 0;
}

/* main panel */
.nosck-synth-panel {
    background: #333;
    border: 2px solid #000;
    border-radius: 7px;
    box-shadow: 0 0 7px #00ff0033;
    padding: 7px 8px 7px 8px;
    width: 100%;
    max-width: 1100px;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    justify-content: center;
    gap: 10px;
    box-sizing: border-box;
    overflow-x: auto;
}

/* module */
.nosck-module {
    background: #444;
    border: 1px solid #222;
    border-radius: 4px;
    padding: 4px 5px 4px 5px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    font-size: 1em;
    min-width: 74px;
    max-width: 88px;
    box-sizing: border-box;
}
.nosck-module h3 {
    color: #00ffff;
    margin: 0 0 4px 0;
    font-size: .8em;
}

/* control */
.nosck-control {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.nosck-control label {
    color: #ccc;
    margin-bottom: 2px;
    font-size: .7em;
    text-align: center;
}

/* range sliders style (sliders = neon green #33ff55) */
.nosck-control input[type=range] {
    width: 79%;
    background: #555;
    color: #33ff55;
    border: 1px solid #33ff55;
    border-radius: 2px;
    padding: 2px;
    margin-bottom: 2px;
    height: 14px;
    font-size: .85em;
}

/* select menus style (selects = neon blue #00e5ff) */
.nosck-control select {
    width: 79%;
    background: #555;
    color: #00e5ff;
    border: 1px solid #00e5ff;
    border-radius: 2px;
    padding: 2px;
    margin-bottom: 2px;
    height: 17px;
    font-size: .85em;
}

/* keyboard area */
.nosck-keyboard-wrapper {
    width: 100%;
    max-width: 1100px;
    display: flex;
    justify-content: center;
    box-sizing: border-box;
}
.nosck-keyboard {
    display: flex;
    justify-content: center;
    margin-top: 9px;
    user-select: none;
    touch-action: none;
    position: relative;
    padding: 0 2px;
    width: 100%;
    max-width: 1100px;
    min-width: 320px;
    box-sizing: border-box;
    overflow-x: auto;
}

/* keys */
.nosck-key {
    width: 24px;
    height: 110px;
    background-color: #eee;
    border: 1px solid #000;
    border-radius: 0 0 3.5px 3.5px;
    margin: 0 1.5px;
    cursor: pointer;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: flex-end;
    padding-bottom: 6px;
    z-index: 0;
    transition: background .12s;
    font-size: .7em;
}

/* state classes (prefixed) */
.nosck-key.nosck-selected {
    background: linear-gradient(0deg, #0f0 12%, #eee 100%);
    box-shadow: 0 0 7px #0f0;
}
.nosck-key.nosck-active:not(.nosck-selected) {
    background-color: #ccc;
}

/* black keys */
.nosck-key.nosck-black {
    background-color: #222;
    color: #fff;
    width: 17px;
    height: 68px;
    position: absolute;
    top: 0;
    z-index: 1;
    border-radius: 0 0 3px 3px;
    left: 0;
    transition: background .03s;
    font-size: .7em;
}
.nosck-key.nosck-black.nosck-selected {
    background: linear-gradient(0deg, #0f0 12%, #222 100%);
    box-shadow: 0 0 7px #0f0;
}
.nosck-key.nosck-black.nosck-active:not(.nosck-selected) {
    background: #444;
}

/* inner keyboard layout */
.nosck-keyboard-inner {
    display: flex;
    position: relative;
    width: 100%;
    max-width: 100%;
    justify-content: space-between;
}
.nosck-keyboard .nosck-key.nosck-white {
    flex: 1 1 auto;
    position: relative;
}

/* absolute positions for black keys */
.nosck-keyboard .nosck-key[data-note="C#4"]{left:calc(1*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="D#4"]{left:calc(2*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="F#4"]{left:calc(4*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="G#4"]{left:calc(5*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="A#4"]{left:calc(6*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="C#5"]{left:calc(8*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="D#5"]{left:calc(9*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="F#5"]{left:calc(11*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="G#5"]{left:calc(12*100%/14 - 10px);}
.nosck-keyboard .nosck-key[data-note="A#5"]{left:calc(13*100%/14 - 10px);}

/* responsive */
@media (max-width: 1200px) {
    .nosck-synth-panel, .nosck-keyboard-wrapper, .nosck-keyboard {
        max-width: 99vw;
    }
}
@media (max-width: 900px) {
    .nosck-synth-panel,
    .nosck-keyboard-wrapper,
    .nosck-keyboard {
        max-width: 99vw;
        width: 99vw;
    }
    .nosck-keyboard {
        min-width: 200px;
    }
}
@media (max-width: 600px) {
    .nosck-synth-panel {
        flex-direction: column;
        align-items: center;
        gap: 4px;
        max-width: 99vw;
        min-width: unset;
        padding: 5px 1vw;
    }
    .nosck-module {
        min-width: 65px;
        max-width: 96vw;
    }
    .nosck-keyboard,
    .nosck-keyboard-wrapper {
        max-width: 99vw;
        min-width: 150px;
    }
    .nosck-synth-container {
        padding: 8px 0;
        gap: 10px;
    }
}
</style>

<div class="nosck-synth-container">
    <div class="nosck-synth-panel">
        <div class="nosck-module"><h3>Osc1</h3>
            <div class="nosck-control"><label for="osc1-detune">Detune</label><input type="range" id="osc1-detune" min="-100" max="100" value="0" step="1"></div>
            <div class="nosck-control"><label for="osc1-gain">Gain</label><input type="range" id="osc1-gain" min="0" max="1" step="0.01" value="0.5"></div>
            <div class="nosck-control"><label for="osc1-wave">Wave</label><select id="osc1-wave"><option value="sawtooth">Saw</option><option value="sine">Sine</option><option value="square">Sqr</option><option value="triangle">Tri</option></select></div>
        </div>
        <div class="nosck-module"><h3>Osc2</h3>
            <div class="nosck-control"><label for="osc2-detune">Detune</label><input type="range" id="osc2-detune" min="-100" max="100" value="0" step="1"></div>
            <div class="nosck-control"><label for="osc2-gain">Gain</label><input type="range" id="osc2-gain" min="0" max="1" step="0.01" value="0.45"></div>
            <div class="nosck-control"><label for="osc2-wave">Wave</label><select id="osc2-wave"><option value="sawtooth">Saw</option><option value="sine">Sine</option><option value="square">Sqr</option><option value="triangle">Tri</option></select></div>
        </div>
        <div class="nosck-module"><h3>Filter</h3>
            <div class="nosck-control"><label for="filter-freq">Cutoff</label><input type="range" id="filter-freq" min="50" max="20000" value="2000"></div>
            <div class="nosck-control"><label for="filter-reso">Q</label><input type="range" id="filter-reso" min="0" max="10" value="1.2" step="0.01"></div>
            <div class="nosck-control"><label for="filter-type">Type</label><select id="filter-type"><option value="lowpass">LP</option><option value="highpass">HP</option><option value="bandpass">BP</option></select></div>
        </div>
        <div class="nosck-module"><h3>Drive</h3>
            <div class="nosck-control"><label for="drive-amount">Amt</label><input type="range" id="drive-amount" min="0" max="1" step="0.01" value="0.3"></div>
        </div>
        <div class="nosck-module"><h3>Env</h3>
            <div class="nosck-control"><label for="env-attack">Atk</label><input type="range" id="env-attack" min="0.001" max="1" step="0.001" value="0.02"></div>
            <div class="nosck-control"><label for="env-decay">Dec</label><input type="range" id="env-decay" min="0.01" max="2" step="0.01" value="0.18"></div>
            <div class="nosck-control"><label for="env-sustain">Sus</label><input type="range" id="env-sustain" min="0" max="1" step="0.01" value="0.7"></div>
            <div class="nosck-control"><label for="env-release">Rel</label><input type="range" id="env-release" min="0.01" max="2" step="0.01" value="0.18"></div>
        </div>
        <div class="nosck-module"><h3>Arp</h3>
            <div class="nosck-control"><label for="arpeggiator-mode">Mode</label>
                <select id="arpeggiator-mode">
                    <option value="off">Off</option>
                    <option value="up">Up</option>
                    <option value="down">Dn</option>
                    <option value="updown">UD</option>
                    <option value="random">Random</option>
                </select>
            </div>
            <div class="nosck-control"><label for="arpeggiator-rate">Rate</label><input type="range" id="arpeggiator-rate" min="50" max="1000" value="150" step="10"></div>
            <div class="nosck-control"><label for="arpeggiator-octaves">Oct</label><input type="range" id="arpeggiator-octaves" min="1" max="3" value="1" step="1"></div>
        </div>
        <div class="nosck-module"><h3>Delay</h3>
            <div class="nosck-control"><label for="delay-time">Time</label><input type="range" id="delay-time" min="10" max="1200" step="1" value="220"></div>
            <div class="nosck-control"><label for="delay-feedback">FB</label><input type="range" id="delay-feedback" min="0" max="0.8" step="0.01" value="0.26"></div>
            <div class="nosck-control"><label for="delay-mix">Mix</label><input type="range" id="delay-mix" min="0" max="1" step="0.01" value="0.35"></div>
        </div>
        <div class="nosck-module"><h3>Master</h3>
            <div class="nosck-control"><label for="master-gain">Vol</label><input type="range" id="master-gain" min="0" max="1" step="0.01" value="0.28"></div>
        </div>
    </div>

    <div class="nosck-keyboard-wrapper">
      <div class="nosck-keyboard">
        <div class="nosck-keyboard-inner">
            <div class="nosck-key nosck-white" data-note="C4"></div>
            <div class="nosck-key nosck-black" data-note="C#4"></div>
            <div class="nosck-key nosck-white" data-note="D4"></div>
            <div class="nosck-key nosck-black" data-note="D#4"></div>
            <div class="nosck-key nosck-white" data-note="E4"></div>
            <div class="nosck-key nosck-white" data-note="F4"></div>
            <div class="nosck-key nosck-black" data-note="F#4"></div>
            <div class="nosck-key nosck-white" data-note="G4"></div>
            <div class="nosck-key nosck-black" data-note="G#4"></div>
            <div class="nosck-key nosck-white" data-note="A4"></div>
            <div class="nosck-key nosck-black" data-note="A#4"></div>
            <div class="nosck-key nosck-white" data-note="B4"></div>
            <div class="nosck-key nosck-white" data-note="C5"></div>
            <div class="nosck-key nosck-black" data-note="C#5"></div>
            <div class="nosck-key nosck-white" data-note="D5"></div>
            <div class="nosck-key nosck-black" data-note="D#5"></div>
            <div class="nosck-key nosck-white" data-note="E5"></div>
            <div class="nosck-key nosck-white" data-note="F5"></div>
            <div class="nosck-key nosck-black" data-note="F#5"></div>
            <div class="nosck-key nosck-white" data-note="G5"></div>
            <div class="nosck-key nosck-black" data-note="G#5"></div>
            <div class="nosck-key nosck-white" data-note="A5"></div>
            <div class="nosck-key nosck-black" data-note="A#5"></div>
            <div class="nosck-key nosck-white" data-note="B5"></div>
        </div>
      </div>
    </div>
</div>

<script>
let audioContext, filterNode, masterGainNode, driveNode, delayNode, delayFeedbackNode, delayWetNode, delayDryNode;
const arpSelectedNotes = new Set(), monoHeldNotes = new Set(), activeNotes = new Map();
let arpeggiatorInterval = null, arpeggioNotes = [], arpeggioIndex = 0, arpeggioDirection = 1;

const noteFrequencies = {
  '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,'G#4':415.3,'A4':440,'A#4':466.16,'B4':493.88,
  'C5':523.25,'C#5':554.37,'D5':587.33,'D#5':622.25,'E5':659.26,'F5':698.46,'F#5':739.99,'G5':783.99,'G#5':830.61,'A5':880,'A#5':932.33,'B5':987.77
};

function makeDriveNode(a){
    let ws = audioContext.createWaveShaper(), n = 1024, curve = new Float32Array(n), k = a*8 + .5;
    for (let i=0;i<n;i++){ let x = i*2/n - 1; curve[i] = ((3+k) * x * 20 * Math.PI/180) / (Math.PI + k*Math.abs(x)); }
    ws.curve = curve;
    ws.oversample = '4x';
    return ws;
}

function initAudio(){
    if(!audioContext){
        audioContext = new (window.AudioContext || window.webkitAudioContext)();
        filterNode = audioContext.createBiquadFilter();
        driveNode = makeDriveNode(parseFloat(document.getElementById('drive-amount').value));
        delayNode = audioContext.createDelay(3.0);
        delayFeedbackNode = audioContext.createGain();
        delayWetNode = audioContext.createGain();
        delayDryNode = audioContext.createGain();
        masterGainNode = audioContext.createGain();

        filterNode.connect(driveNode);
        driveNode.connect(delayDryNode);
        delayDryNode.connect(masterGainNode);
        driveNode.connect(delayNode);
        delayNode.connect(delayWetNode);
        delayWetNode.connect(masterGainNode);
        delayNode.connect(delayFeedbackNode);
        delayFeedbackNode.connect(delayNode);
        masterGainNode.connect(audioContext.destination);

        updateDrive();
        updateDelay();
        updateFilter();
        updateMaster();
    }
}

function getFrequencyForNote(n, d=0, dr=0){
    const f = noteFrequencies[n];
    if(!f) return null;
    return f * Math.pow(2, (d+dr)/1200);
}

function applyEnvelope(g, type='start'){
    if(!audioContext || !g) return;
    const now = audioContext.currentTime;
    const a = parseFloat(document.getElementById('env-attack').value),
          d = parseFloat(document.getElementById('env-decay').value),
          s = parseFloat(document.getElementById('env-sustain').value),
          r = parseFloat(document.getElementById('env-release').value);
    g.gain.cancelScheduledValues(now);
    if(type==='start'){
        g.gain.setTargetAtTime(.0001, now, .01);
        g.gain.linearRampToValueAtTime(1, now + a);
        g.gain.linearRampToValueAtTime(s, now + a + d);
    } else if (type==='stop'){
        g.gain.cancelScheduledValues(now);
        g.gain.setValueAtTime(g.gain.value, now);
        g.gain.linearRampToValueAtTime(.0001, now + r);
    }
}

function playNote(n){
    initAudio();
    let dr1 = (Math.random() - .5) * 6, dr2 = (Math.random() - .5) * 6;
    let freq = getFrequencyForNote(n);
    if(!freq) return;
    if(activeNotes.has(n)) stopNote(n);

    const now = audioContext.currentTime;
    const osc1 = audioContext.createOscillator(), osc2 = audioContext.createOscillator();
    const osc1g = audioContext.createGain(), osc2g = audioContext.createGain();
    const ng = audioContext.createGain();
    const od1 = parseFloat(document.getElementById('osc1-detune').value), od2 = parseFloat(document.getElementById('osc2-detune').value);

    osc1.frequency.setValueAtTime(getFrequencyForNote(n, od1, dr1), now);
    osc1.type = document.getElementById('osc1-wave').value;
    osc1g.gain.setValueAtTime(parseFloat(document.getElementById('osc1-gain').value), now);

    osc2.frequency.setValueAtTime(getFrequencyForNote(n, od2, dr2), now);
    osc2.type = document.getElementById('osc2-wave').value;
    osc2g.gain.setValueAtTime(parseFloat(document.getElementById('osc2-gain').value), now);

    osc1.connect(osc1g);
    osc2.connect(osc2g);
    osc1g.connect(ng);
    osc2g.connect(ng);
    ng.connect(filterNode);

    osc1.start(now);
    osc2.start(now);
    applyEnvelope(ng, 'start');

    activeNotes.set(n, {osc1: osc1, osc2: osc2, noteGain: ng});
}

function stopNote(n){
    if(!audioContext || !activeNotes.has(n)) return;
    const {osc1, osc2, noteGain} = activeNotes.get(n);
    const now = audioContext.currentTime;
    const r = parseFloat(document.getElementById('env-release').value);
    applyEnvelope(noteGain, 'stop');
    const stopTime = now + r + .05;
    osc1.stop(stopTime);
    osc2.stop(stopTime);
    osc1.onended = () => {
        if(activeNotes.has(n) && activeNotes.get(n).osc1 === osc1){
            activeNotes.delete(n);
            try{ osc1.disconnect(); }catch(e){}
            try{ osc2.disconnect(); }catch(e){}
            try{ noteGain.disconnect(); }catch(e){}
        }
    };
}

function updateFilter(){
    if(!audioContext || !filterNode) return;
    filterNode.frequency.setValueAtTime(parseFloat(document.getElementById('filter-freq').value), audioContext.currentTime);
    let maxRes = 9.9, reso = parseFloat(document.getElementById('filter-reso').value);
    if(reso > maxRes) reso = maxRes;
    filterNode.Q.setValueAtTime(reso, audioContext.currentTime);
    filterNode.type = document.getElementById('filter-type').value;
}

function updateMaster(){
    if(!audioContext || !masterGainNode) return;
    masterGainNode.gain.setValueAtTime(parseFloat(document.getElementById('master-gain').value), audioContext.currentTime);
}

function updateDelay(){
    if(!audioContext || !delayNode) return;
    delayNode.delayTime.setValueAtTime(parseFloat(document.getElementById('delay-time').value) / 1000, audioContext.currentTime);
    delayFeedbackNode.gain.setValueAtTime(parseFloat(document.getElementById('delay-feedback').value), audioContext.currentTime);
    delayWetNode.gain.setValueAtTime(parseFloat(document.getElementById('delay-mix').value), audioContext.currentTime);
    delayDryNode.gain.setValueAtTime(1 - parseFloat(document.getElementById('delay-mix').value), audioContext.currentTime);
}

function updateDrive(){
    if(!audioContext || !driveNode) return;
    let a = parseFloat(document.getElementById('drive-amount').value), n = 1024, curve = new Float32Array(n), k = a*8 + .5;
    for(let i=0;i<n;i++){ let x = i*2/n - 1; curve[i] = ((3+k) * x * 20 * Math.PI/180) / (Math.PI + k*Math.abs(x)); }
    driveNode.curve = curve;
}

function startArpeggiator(){
    if(arpeggiatorInterval) stopArpeggiator();
    let sel = Array.from(arpSelectedNotes).sort((a,b) => getFrequencyForNote(a) - getFrequencyForNote(b));
    if(sel.length === 0){ stopArpeggiator(); return; }
    const mode = document.getElementById('arpeggiator-mode').value, rate = parseFloat(document.getElementById('arpeggiator-rate').value), octs = parseInt(document.getElementById('arpeggiator-octaves').value);
    arpeggioNotes = [];
    for(let i=0;i<octs;i++){
        sel.forEach(n => {
            const m = n.match(/([A-G]#?)(\d)/);
            if(m){
                const nn = m[1], o = parseInt(m[2]), no = o + i, nnn = `${nn}${no}`;
                if(noteFrequencies[nnn]) arpeggioNotes.push(nnn);
            }
        });
    }
    arpeggioNotes.sort((a,b) => getFrequencyForNote(a) - getFrequencyForNote(b));
    arpeggioIndex = 0; arpeggioDirection = 1;
    arpeggiatorInterval = setInterval(() => {
        if(arpeggioNotes.length === 0){ stopArpeggiator(); return; }
        activeNotes.forEach((_, n) => stopNote(n));
        let cn;
        if(mode === 'random'){
            cn = arpeggioNotes[Math.floor(Math.random() * arpeggioNotes.length)];
        } else {
            cn = arpeggioNotes[arpeggioIndex];
        }
        playNote(cn);
        if(mode === 'up'){
            arpeggioIndex = (arpeggioIndex + 1) % arpeggioNotes.length;
        } else if(mode === 'down'){
            arpeggioIndex = (arpeggioIndex - 1 + arpeggioNotes.length) % arpeggioNotes.length;
        } else if(mode === 'updown'){
            arpeggioIndex += arpeggioDirection;
            if(arpeggioIndex >= arpeggioNotes.length){
                arpeggioDirection = -1;
                arpeggioIndex = arpeggioNotes.length - 2;
                if(arpeggioIndex < 0) arpeggioIndex = 0;
            } else if(arpeggioIndex < 0){
                arpeggioDirection = 1;
                arpeggioIndex = 1;
                if(arpeggioIndex >= arpeggioNotes.length) arpeggioIndex = arpeggioNotes.length - 1;
            }
        } else if(mode === 'random'){
            // handled above
        }
    }, rate);
}

function stopArpeggiator(){
    clearInterval(arpeggiatorInterval);
    arpeggiatorInterval = null;
    arpeggioNotes = [];
    arpeggioIndex = 0;
    arpeggioDirection = 1;
    activeNotes.forEach((_, n) => stopNote(n));
}

/* class toggles use prefixed state classes */
function setKeySelected(k, v){
    if(v) k.classList.add('nosck-selected'); else k.classList.remove('nosck-selected');
}
function setKeyActive(k, v){
    if(v) k.classList.add('nosck-active'); else k.classList.remove('nosck-active');
}

/* updated DOM queries to use the prefixed class names */
const keyboardKeys = document.querySelectorAll('.nosck-keyboard .nosck-key'), activeTouches = new Map();

function handleKeyToggleSelection(n, k){
    if(arpSelectedNotes.has(n)){ arpSelectedNotes.delete(n); setKeySelected(k, false); }
    else { arpSelectedNotes.add(n); setKeySelected(k, true); }
    if(document.getElementById('arpeggiator-mode').value !== 'off'){
        if(arpSelectedNotes.size > 0) startArpeggiator(); else stopArpeggiator();
    }
}

/* attach events to each key (keeps same behaviour) */
keyboardKeys.forEach(k => {
    const n = k.dataset.note;
    function handleToggle(e){
        e.preventDefault();
        initAudio();
        if(document.getElementById('arpeggiator-mode').value !== 'off'){
            handleKeyToggleSelection(n, k);
        } else {
            if(!monoHeldNotes.has(n)){ monoHeldNotes.add(n); setKeyActive(k, true); playNote(n); }
        }
    }
    k.addEventListener('mousedown', handleToggle);
    k.addEventListener('mouseup', e => {
        if(document.getElementById('arpeggiator-mode').value === 'off'){
            if(monoHeldNotes.has(n)){ monoHeldNotes.delete(n); setKeyActive(k, false); stopNote(n); }
        }
    });
    k.addEventListener('mouseleave', e => {
        if(document.getElementById('arpeggiator-mode').value === 'off'){
            if(monoHeldNotes.has(n)){ monoHeldNotes.delete(n); setKeyActive(k, false); stopNote(n); }
        }
    });

    k.addEventListener('touchstart', e => {
        e.preventDefault();
        initAudio();
        if(document.getElementById('arpeggiator-mode').value !== 'off'){
            handleKeyToggleSelection(n, k);
        } else {
            if(!monoHeldNotes.has(n)){ monoHeldNotes.add(n); setKeyActive(k, true); playNote(n); }
        }
        if(e.changedTouches && e.changedTouches[0]){ activeTouches.set(e.changedTouches[0].identifier, k); }
    }, { passive: false });

    k.addEventListener('touchend', e => {
        e.preventDefault();
        if(document.getElementById('arpeggiator-mode').value === 'off'){
            if(e.changedTouches && e.changedTouches[0]){
                const ke = activeTouches.get(e.changedTouches[0].identifier);
                if(ke && monoHeldNotes.has(n)){ monoHeldNotes.delete(n); setKeyActive(ke, false); stopNote(n); }
                activeTouches.delete(e.changedTouches[0].identifier);
            }
        }
    }, { passive: false });

    k.addEventListener('contextmenu', e => { e.preventDefault(); });
});

/* controls: updated selector to prefixed panel */
document.querySelectorAll('.nosck-synth-panel input[type="range"], .nosck-synth-panel select').forEach(c => {
    if(c.type === 'range'){
        c.addEventListener('input', () => {
            if(!audioContext) return;
            updateFilter(); updateMaster(); updateDelay();
            if(c.id === 'drive-amount') updateDrive();
            if(arpeggiatorInterval){
                const oscC = ['osc1-detune','osc1-gain','osc2-detune','osc2-gain'];
                if(oscC.includes(c.id) || c.id === 'arpeggiator-rate') startArpeggiator();
            }
        });
    } else if(c.tagName === 'SELECT'){
        c.addEventListener('change', () => {
            if(!audioContext) return;
            updateFilter(); updateDelay();
            if(c.id === 'drive-amount') updateDrive();
            if(arpeggiatorInterval){
                const arpC = ['arpeggiator-mode','arpeggiator-octaves'], oscC = ['osc1-wave','osc2-wave'];
                if(arpC.includes(c.id) || oscC.includes(c.id)) startArpeggiator();
            }
            if(c.id === 'arpeggiator-mode'){
                if(c.value === 'off'){
                    arpSelectedNotes.clear();
                    document.querySelectorAll('.nosck-keyboard .nosck-key').forEach(k => setKeySelected(k, false));
                    stopArpeggiator();
                } else {
                    if(arpSelectedNotes.size > 0) startArpeggiator();
                }
            }
        });
    }
});
</script>

logowordpressSelect Option