ค้นหาเว็บไซต์

javascript - Tone.js หยุดเสียงทั้งหมดที่กำลังเล่นอย่างสมบูรณ์


โดยสรุป เมื่อกดปุ่ม ผมอยากเล่นโน้ตไม่กี่ตัวโดยใช้ PolySynth และ Sequence หากผู้ใช้กดปุ่มซ้ำ ๆ ผมอยากให้สิ่งที่กำลังเล่นอยู่ถูกหยุด และเริ่มเล่นใหม่อีกครั้ง

ปัญหา: ไม่ว่าจะพยายามอย่างไร ฉันก็ไม่สามารถยกเลิก/ทำให้โน้ตที่เล่นก่อนหน้านี้เงียบสนิทได้ในกรณีที่เริ่มลำดับใหม่ (กดปุ่มอีกครั้ง) ซึ่งสาเหตุส่วนใหญ่น่าจะมาจากการลดทอน/การคงเสียงของ envelope

ซินธ์ของฉัน:

import { PolySynth } from 'tone'

const synth = new PolySynth(Synth, {
  oscillator: {
    type: 'sine4',
    volume: -6,
  },
  envelope: {
    attack: 0.01,
    decay: 0.5,
    sustain: 0.1,
    release: 1,
  },
}).toDestination()
synth.maxPolyphony = 4 // max notes playing at a time, not sure if necessary

ลำดับของฉัน:

import { Sequence } from 'tone'

// Play the 2 notes individually then play them together
const notes = [
  { note: 'C4', duration: '8n' },
  { note: 'G4', duration: '8n' },
  { note: ['C4', 'G4'], duration: '4n' }
]

// The sequence that should play the notes after one another
const sequence = new Sequence({
  subdivision: '8n',
  loop: false,
  events: notes,
  callback: (time, note) => synth.triggerAttackRelease(note.note, note.duration, time),
})

ในแบบที่ฉันเล่น มันคือผู้จัดการเหตุการณ์:

import { start, Transport } from 'tone'

// Event handler simply attached to a button's onClick
function onButtonClicked() {
  // Call whatever this start is, doc says it can only happen in an event handler
  start()
  
  // Try everything to kill current sound
  Transport.cancel()
  Transport.stop()

  // Start it again
  Transport.start()
  sequence.start()
}

ฉันจะทำให้เสียงทั้งหมดเงียบสนิทได้อย่างไร (ถ้ามีเสียง) ก่อนที่จะเริ่มเล่นมัน?

คำตอบ #1

คำตอบสั้น

คิดเกี่ยวกับเรื่องนี้ค่อนข้างมาก ถ้าฉันเข้าใจคุณถูกต้อง นี่จริง ๆ แล้วเป็นพฤติกรรมตามที่ตั้งใจไว้ คุณกำลังกระตุ้นโน้ตบนซินธ์ (ซึ่งโดยพื้นฐานคือ AudioWorkletNode) ดังนั้นทันทีที่โน้ตถูกกระตุ้น ซินธ์ก็จะเล่นโน้ตนั้นจบ วิธีเดียวที่จะหยุดโน้ตนั้นจากการเล่นคือการปิดเสียงซินธ์เอง

คำตอบยาว

ในคอมเมนต์คุณกล่าวว่าคุณอาจพลาดบางสิ่งในเชิงแนวคิด และฉันคิดว่าคุณกำลังอยู่บนเส้นทางที่ถูกต้องในเรื่องนั้น

ลองมาคิดกันว่ามีการสร้างเสียงด้วย MIDI อย่างไร

  1. คุณกำลังเชื่อมต่อซินธ์ (ซึ่งรับโน้ต MIDI และสร้างเสียง) ไปยังเอาต์พุต
  2. คุณกำลังกำหนดโน้ต MIDI บางส่วนบนตัวส่ง
  3. คุณเริ่มการขนส่ง
  4. ทันทีที่การขนส่งถึงเวลาที่กำหนดสำหรับโน้ต ค่ามิดีไอนนั้นจะถูกส่งไปยังซินธิไซเซอร์
  5. เนื่องจาก Synth โดยพื้นฐานแล้วเป็น AudioWorkletNode ที่มี Envelope Generator, Synth จะรับโน้ต MIDI นั้นและกระตุ้นการสร้างเสียงภายใน (ผ่าน envelope) ดังนั้นโน้ต MIDI ในช่วงเวลาที่เฉพาะเจาะจงจะกระตุ้นการสร้างเสียงยาวในช่วงเวลาที่เฉพาะเจาะจง (ซึ่งจะเป็นส่วน ADS) แม้ว่าโน้ต MIDI จะมีระยะเวลาเพียง 1 มิลลิวินาทีในตัวอย่างของคุณ, การสร้างเสียงจะคงอยู่ไม่น้อยกว่า 1.001 วินาที (Release บวกกับระยะเวลาโน้ต MIDI 1 มิลลิวินาที) มาลองแยกเรื่องนี้ออกมาเพิ่มเติม:

    • โน้ต MIDI มีจุดเริ่มต้นและจุดสิ้นสุดบนเส้นเวลาขนส่งสมมติ
    • เริ่มต้นทำให้ส่วน ADS ของซองสัญญาณทำงาน
    • End กระตุ้นส่วน R ของซองเสียง
    • เมื่อโน้ต MIDI ของคุณกระตุ้นซองเสียง เสียงจะถูกสร้างขึ้น

ดังนั้นเมื่อคุณหยุดการขนส่งของคุณหรือแม้แต่ลำดับนั้น มันจะทำอะไร? หากโน้ต MIDI ได้กระตุ้น Envelope แล้ว Envelope จะได้รับทริกเกอร์สิ้นสุด MIDI และกระตุ้น Envelope แบบ Release

ดังนั้นจะมีเสียงต่อเนื่องของซินธิไซเซอร์ของคุณเสมอ เพราะว่าโน้ต MIDI ไม่ได้กำหนดจุดเริ่มต้นและจุดสิ้นสุดของซินธิไซเซอร์ของคุณ แต่เป็นตัวกระตุ้นบางส่วนของ Envelope ของคุณ ดังนั้นจริง ๆ แล้วซินธิไซเซอร์ของคุณสร้างเสียงขึ้นมา และสิ่งนั้นไม่ได้ผูกติดกับการควบคุมการเล่นเพลง และก็ไม่สามารถผูกติดได้เช่นกัน

หวังว่าคำอธิบายนั้นช่วยคุณได้บ้าง หากฉันเข้าใจคุณผิด ฉันยินดีที่จะแก้ไข

คำตอบ #2

โน้ตยังคงเล่นต่อไปเพราะการขนส่งหยุดหลังจากที่โน้ตถูกกระตุ้นและก่อนที่จะถูกปล่อย ดังนั้นวิธีแก้ปัญหาคือการกระตุ้นการปล่อยของโน้ตทั้งหมดเมื่อคุณกดปุ่มหยุด ฉันประสบปัญหาแบบเดียวกันขณะใช้ tambien/piano (อิงจาก Tonejs)

    Tone.Transport.toggle()
    if (Tone.Transport.state === 'stopped') {
      for (let i=9; i<97; i++) {
        piano.keyUp({midi: i}, '+0')
      }
    }

บทความที่เกี่ยวข้อง