Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The following method converts midi files in the attachment compression package to die and does not return results #1308

Open
Titans1001 opened this issue Jan 16, 2025 · 0 comments

Comments

@Titans1001
Copy link

The following method converts midi files in the attachment compression package to die and does not return results

`async function midiToPCM(midiFile, config = {}) {
try {
// 默认渲染配置
const defaultConfig = {
batchSize: 100,
minNoteDuration: 0.1,
maxPolyphony: 10,
oscillator: { type: "sawtooth" },
envelope: { attack: 0.1, decay: 0.2, sustain: 0.8, release: 0.3 },
};
const renderConfig = { ...defaultConfig, ...config };

// 1. 解析 MIDI 文件
const midiData = midiFile instanceof File ? await midiFile.arrayBuffer() : midiFile;
console.log("🍎🍎🍎🍎MIDI 数据:", midiData);

const midi = new Midi(midiData);
const channels = midi.tracks.length;
let timer = null;
// 2. 使用 Tone.js 离线渲染 MIDI 数据
let audioBuffer = null

audioBuffer = await Tone.Offline(async (context) => {
  // 设置渲染参数
  const synth = new Tone.PolySynth(Tone.Synth, {
    maxPolyphony: renderConfig.maxPolyphony,
    // oscillator: renderConfig.oscillator,
    // envelope: renderConfig.envelope,
  }).toDestination();

  for (let track of midi.tracks) {
    console.log(`Rendering track: ${track.name || "Unnamed"}`);
    for (let i = 0; i < track.notes.length; i += 1) {
      const note = track.notes[i];
      if (note.duration > renderConfig.minNoteDuration) {
        console.log(`🍎🍎🍎🍎🍎🍎Playing note: ${note.name} at time: ${note.time} in duration: ${note.duration}`);
        synth.triggerAttackRelease(note.name, note.duration, note.time);
        // 小间隔,避免长时间阻塞
        await new Promise(resolve => setTimeout(resolve, 0));
      }
      if (i == track.notes.length - 1) {
        console.log("🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎", context);
        timer = setTimeout(async () => {
          if (audioBuffer == null) {
            context.transport.stop(); // 停止播放
            context.dispose(); // 释放资源
            console.log("渲染超时,提前终止");
            throw new Error("渲染超时,提前终止");
          } else {
            console.log("渲染超时,提前终止");
            throw new Error("渲染超时,提前终止");
          }
        }, 15 * 1000)
      }
    }
  }
}, midi.duration, channels, 44100);

clearTimeout(timer);
// 3. 提取 PCM 数据
const pcmData = [];
for (let i = 0; i < channels; i++) {
  pcmData.push(audioBuffer.getChannelData(i).slice());
}

return {
  pcmData,
  channels,
  sampleRate: audioBuffer.sampleRate,
  duration: audioBuffer.duration,
};

} catch (error) {
clearTimeout(timer);
console.error("MIDI 转 PCM 失败:", error.message);
throw new Error(MIDI 转 PCM 失败: ${error.message});
}
}

EveningWalk.mid.zip
function convertPCMToMP3(pcmData, sampleRate = 44100, channels = 2, bitrate = 128) {
return new Promise((resolve, reject) => {
try {
// 初始化 MP3 编码器
const mp3Encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitrate);

  // 创建一个新的 MP3 编码数据
  const mp3Data = [];

  // 合并所有通道的 PCM 数据
  const interleavedPCMData = new Float32Array(pcmData[0].length * channels);
  // 
  let offset = 0;
  for (let i = 0; i < pcmData[0].length; i++) {
    for (let channel = 0; channel < channels; channel++) {
      interleavedPCMData[offset++] = pcmData[channel][i];
    }
  }

  // 将 PCM 数据转成 16-bit 格式
  const pcmData16Bit = new Int16Array(interleavedPCMData.length);
  for (let i = 0; i < interleavedPCMData.length; i++) {
    pcmData16Bit[i] = Math.max(-32768, Math.min(32767, interleavedPCMData[i] * 32767)); // 防止溢出
  }

  // 编码 PCM 数据
  const mp3Buffer = mp3Encoder.encodeBuffer(pcmData16Bit);
  if (mp3Buffer.length > 0) {
    mp3Data.push(new Int8Array(mp3Buffer));
  }

  // 完成 MP3 编码并生成文件
  const finalBuffer = mp3Encoder.flush();
  if (finalBuffer.length > 0) {
    mp3Data.push(new Int8Array(finalBuffer));
  }

  // 将 MP3 数据转为 Blob 对象
  const blob = new Blob(mp3Data, { type: 'audio/mp3' });
  resolve({
    blob,
    file: new File([blob], 'output.mp3', { type: 'audio/mp3' }),
    blobUrl: URL.createObjectURL(blob),
  });
} catch (error) {

  reject(error);
}

});
}
const convertMidiFileToMp3 = (midiFile) => {
return new Promise(async (resolve, reject) => {
try {
const midiToPCMData = await midiToPCM(midiFile);
const mp3Result = await convertPCMToMP3(midiToPCMData.pcmData, midiToPCMData.sampleRate, midiToPCMData.channels);
resolve(mp3Result);
} catch (error) {
reject(error);
}
})
}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant