MP3 波形トリミングツール

ここにMP3ファイルをドラッグ&ドロップ


view source

JavaScript

let wavesurfer = WaveSurfer.create({
  container: '#waveform',
  waveColor: '#ccc',
  progressColor: '#3b82f6',
  backend: 'WebAudio',
  plugins: [
    WaveSurfer.regions.create()
  ]
});

let audioBuffer, context = new (window.AudioContext || window.webkitAudioContext)();
let fileName = 'trimmed.wav';
let currentRegion = null;

const dropzone = document.getElementById('dropzone');

dropzone.addEventListener('dragover', e => {
  e.preventDefault();
  dropzone.style.background = '#eef';
});

dropzone.addEventListener('dragleave', () => {
  dropzone.style.background = '#f9f9f9';
});

dropzone.addEventListener('drop', e => {
  e.preventDefault();
  dropzone.style.background = '#f9f9f9';

  const file = e.dataTransfer.files[0];
  if (!file || !file.type.startsWith('audio')) return alert('音声ファイルをドロップしてください。');

  fileName = file.name.replace(/\.[^/.]+$/, "") + "_trimmed.wav";

  const reader = new FileReader();
  reader.onload = function(evt) {
    // WaveSurferの読み込み
    wavesurfer.loadBlob(file);

    // AudioBufferの読み込み
    context.decodeAudioData(evt.target.result).then(decoded => {
      audioBuffer = decoded;
    });

    // 波形が準備できたらregion表示(毎回登録しない!)
    wavesurfer.once('ready', () => {
      const duration = wavesurfer.getDuration();

      if (currentRegion) currentRegion.remove();

      currentRegion = wavesurfer.addRegion({
        start: 0,
        end: Math.min(5, duration),
        color: 'rgba(59, 130, 246, 0.3)',
        drag: true,
        resize: true
      });
    });
  };
  reader.readAsArrayBuffer(file);
});




async function exportBufferToMP3(buffer) {
  const samples = buffer.getChannelData(0); // モノラル前提(ステレオ対応も可)
  const sampleRate = buffer.sampleRate;

  const mp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 128);
  const mp3Data = [];
  const chunkSize = 1152;

  for (let i = 0; i < samples.length; i += chunkSize) {
    const sampleChunk = samples.subarray(i, i + chunkSize);
    const int16Chunk = new Int16Array(sampleChunk.length);

    for (let j = 0; j < sampleChunk.length; j++) {
      int16Chunk[j] = Math.max(-32768, Math.min(32767, sampleChunk[j] * 32767));
    }

    const mp3buf = mp3Encoder.encodeBuffer(int16Chunk);
    if (mp3buf.length > 0) mp3Data.push(mp3buf);
  }

  const mp3End = mp3Encoder.flush();
  if (mp3End.length > 0) mp3Data.push(mp3End);

  return new Blob(mp3Data, { type: 'audio/mp3' });
}




wavesurfer.on('region-updated', region => {
  currentRegion = region;
});

document.getElementById('trimBtn').addEventListener('click', () => {
  if (!audioBuffer || !currentRegion) return alert("ファイルを読み込んでください。");

  const start = currentRegion.start;
  const end = currentRegion.end;
  const duration = end - start;

  const trimmedBuffer = context.createBuffer(
    audioBuffer.numberOfChannels,
    context.sampleRate * duration,
    context.sampleRate
  );

  for (let i = 0; i < audioBuffer.numberOfChannels; i++) {
    trimmedBuffer.copyToChannel(
      audioBuffer.getChannelData(i).slice(start * context.sampleRate, end * context.sampleRate),
      i
    );
  }

  const source = context.createBufferSource();
  source.buffer = trimmedBuffer;
  source.connect(context.destination);
  source.start();
/*
  exportBuffer(trimmedBuffer).then(blob => {
    const url = URL.createObjectURL(blob);
    document.getElementById('audioPlayer').src = url;

    document.getElementById('downloadBtn').onclick = () => {
      const a = document.createElement('a');
      a.href = url;
      a.download = fileName;
      a.click();
    };
  });
*/

  exportBufferToMP3(trimmedBuffer).then(blob => {
    const url = URL.createObjectURL(blob);
    document.getElementById('audioPlayer').src = url;

    document.getElementById('downloadBtn').onclick = () => {
      const a = document.createElement('a');
      a.href = url;
      a.download = fileName.replace(".wav", ".mp3");
      a.click();
    };
  });
});

async function exportBuffer(buffer) {
  const offlineCtx = new OfflineAudioContext(
    buffer.numberOfChannels,
    buffer.length,
    buffer.sampleRate
  );
  const source = offlineCtx.createBufferSource();
  source.buffer = buffer;
  source.connect(offlineCtx.destination);
  source.start();
  const rendered = await offlineCtx.startRendering();
  return bufferToWavBlob(rendered);
}

function bufferToWavBlob(buffer) {
  const numChannels = buffer.numberOfChannels;
  const sampleRate = buffer.sampleRate;
  const length = buffer.length * numChannels * 2 + 44;
  const arrayBuffer = new ArrayBuffer(length);
  const view = new DataView(arrayBuffer);

  function writeString(view, offset, string) {
    for (let i = 0; i < string.length; i++) {
      view.setUint8(offset + i, string.charCodeAt(i));
    }
  }

  let offset = 0;
  writeString(view, offset, 'RIFF'); offset += 4;
  view.setUint32(offset, 36 + buffer.length * numChannels * 2, true); offset += 4;
  writeString(view, offset, 'WAVE'); offset += 4;
  writeString(view, offset, 'fmt '); offset += 4;
  view.setUint32(offset, 16, true); offset += 4;
  view.setUint16(offset, 1, true); offset += 2;
  view.setUint16(offset, numChannels, true); offset += 2;
  view.setUint32(offset, sampleRate, true); offset += 4;
  view.setUint32(offset, sampleRate * numChannels * 2, true); offset += 4;
  view.setUint16(offset, numChannels * 2, true); offset += 2;
  view.setUint16(offset, 16, true); offset += 2;
  writeString(view, offset, 'data'); offset += 4;
  view.setUint32(offset, buffer.length * numChannels * 2, true); offset += 4;

  for (let i = 0; i < buffer.length; i++) {
    for (let ch = 0; ch < numChannels; ch++) {
      let sample = buffer.getChannelData(ch)[i] * 0x7FFF;
      sample = Math.max(-32768, Math.min(32767, sample));
      view.setInt16(offset, sample, true);
      offset += 2;
    }
  }

  return new Blob([view], { type: 'audio/wav' });
}

CSS

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/demo/-js/js-mp3-trim/

ABOUT

hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。

途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。