1use std::sync::Arc;
8
9use crossbeam_channel::{Receiver, Sender};
10use phosphor_midi::message::MidiMessage;
11use phosphor_plugin::{MidiEvent, Plugin};
12
13use crate::clip::{ClipEvent, ClipSnapshot, MidiClip, RecordBuffer};
14use crate::engine::VuLevels;
15use crate::metronome::Metronome;
16use crate::project::{TrackHandle, TrackKind};
17use crate::transport::Transport;
18
19pub enum MixerCommand {
22 AddTrack {
23 kind: TrackKind,
24 handle: Arc<TrackHandle>,
25 },
26 SetInstrument {
27 track_id: usize,
28 instrument: Box<dyn Plugin + Send>,
29 },
30 RemoveTrack {
31 track_id: usize,
32 },
33 SetParameter {
34 track_id: usize,
35 param_index: usize,
36 value: f32,
37 },
38 CreateClip {
40 track_id: usize,
41 start_tick: i64,
42 length_ticks: i64,
43 },
44 UpdateClip {
46 track_id: usize,
47 clip_index: usize,
48 events: Vec<ClipEvent>,
49 },
50}
51
52pub struct AudioTrack {
55 pub id: usize,
56 pub kind: TrackKind,
57 pub handle: Arc<TrackHandle>,
58 pub instrument: Option<Box<dyn Plugin>>,
59 pub clips: Vec<MidiClip>,
61 record_buf: RecordBuffer,
63 was_recording: bool,
65 last_record_tick: i64,
67 buf_l: Vec<f32>,
68 buf_r: Vec<f32>,
69 plugin_events: Vec<MidiEvent>,
70}
71
72impl AudioTrack {
73 pub fn new(handle: Arc<TrackHandle>, max_buffer_size: usize) -> Self {
74 Self {
75 id: handle.id,
76 kind: handle.kind,
77 handle,
78 instrument: None,
79 clips: Vec::new(),
80 record_buf: RecordBuffer::new(),
81 was_recording: false,
82 last_record_tick: -1,
83 buf_l: vec![0.0; max_buffer_size],
84 buf_r: vec![0.0; max_buffer_size],
85 plugin_events: Vec::with_capacity(256),
86 }
87 }
88}
89
90pub struct Mixer {
93 tracks: Vec<AudioTrack>,
94 master_vu: Arc<VuLevels>,
95 command_rx: Receiver<MixerCommand>,
96 clip_tx: Sender<ClipSnapshot>,
97 metronome: Metronome,
98 sample_rate: u32,
99 max_buffer_size: usize,
100 scratch_l: Vec<f32>,
102 scratch_r: Vec<f32>,
103 live_events: Vec<MidiEvent>,
105}
106
107impl Mixer {
108 pub fn new(
109 command_rx: Receiver<MixerCommand>,
110 master_vu: Arc<VuLevels>,
111 clip_tx: Sender<ClipSnapshot>,
112 sample_rate: u32,
113 max_buffer_size: usize,
114 ) -> Self {
115 Self {
116 tracks: Vec::new(),
117 master_vu,
118 command_rx,
119 clip_tx,
120 metronome: Metronome::new(sample_rate as f64),
121 sample_rate,
122 max_buffer_size,
123 scratch_l: vec![0.0; max_buffer_size],
124 scratch_r: vec![0.0; max_buffer_size],
125 live_events: Vec::with_capacity(256),
126 }
127 }
128
129 pub fn process(&mut self, output: &mut [f32], midi_messages: &[MidiMessage], transport: &Transport) {
131 self.drain_commands();
132
133 let num_frames = output.len() / 2;
134 let playing = transport.is_playing();
135 let recording = transport.is_recording();
136 let looping = transport.is_looping();
137 let current_tick = transport.position_ticks();
138 let bpm = transport.tempo_bpm();
139 let ticks_per_sample = (bpm * Transport::PPQ as f64) / (60.0 * self.sample_rate as f64);
140 let buffer_ticks = (num_frames as f64 * ticks_per_sample) as i64;
141 let loop_end = transport.loop_end();
142
143 self.live_events.clear();
145 for msg in midi_messages {
146 if let Some(ev) = midi_to_plugin_event(msg) {
147 self.live_events.push(ev);
148 }
149 }
150
151 let any_solo = self.tracks.iter().any(|t| t.handle.config.is_soloed());
152
153 let mut master_l = std::mem::take(&mut self.scratch_l);
156 let mut master_r = std::mem::take(&mut self.scratch_r);
157 let live_events = std::mem::take(&mut self.live_events);
158 if master_l.len() < num_frames {
159 master_l.resize(num_frames, 0.0);
160 master_r.resize(num_frames, 0.0);
161 }
162 master_l[..num_frames].fill(0.0);
163 master_r[..num_frames].fill(0.0);
164
165 let clip_tx = &self.clip_tx;
166
167 for track in &mut self.tracks {
168 if track.buf_l.len() < num_frames {
169 track.buf_l.resize(num_frames, 0.0);
170 track.buf_r.resize(num_frames, 0.0);
171 }
172 track.buf_l[..num_frames].fill(0.0);
173 track.buf_r[..num_frames].fill(0.0);
174 track.plugin_events.clear();
175
176 let is_midi_active = track.kind == TrackKind::Instrument
177 && track.handle.config.is_midi_active();
178 let is_armed = track.handle.config.is_armed();
179 let should_record = playing && recording && is_armed && is_midi_active;
180
181 if should_record && !track.was_recording {
183 let rec_start = if looping { transport.loop_start() } else { current_tick };
186 track.record_buf.start(rec_start);
187 tracing::debug!("rec start track={} tick={}", track.id, current_tick);
188 }
189
190 if should_record && track.was_recording && looping
192 && track.record_buf.is_active() && track.last_record_tick >= 0
193 && current_tick < track.last_record_tick
194 {
195 commit_recording(track, loop_end, clip_tx);
196 track.record_buf.start(current_tick);
197 }
198 if should_record {
199 track.last_record_tick = current_tick;
200 }
201
202 if !should_record && track.was_recording {
204 commit_recording(track, current_tick, clip_tx);
205 }
206 track.was_recording = should_record;
207
208 if is_midi_active {
210 for ev in &live_events {
211 track.plugin_events.push(*ev);
212 if should_record {
213 let event_tick = current_tick
214 + (ev.sample_offset as f64 * ticks_per_sample) as i64;
215 track.record_buf.record(event_tick, ev.status, ev.data1, ev.data2);
216 }
217 }
218 }
219
220 if playing && !track.clips.is_empty() {
222 let from = current_tick;
223 let to = current_tick + buffer_ticks;
224
225 let just_wrapped = looping && track.last_record_tick >= 0
228 && current_tick < track.last_record_tick;
229
230 if just_wrapped {
231 let wrap_start = transport.loop_start();
233 for clip in &track.clips {
234 for (tick_offset, event) in clip.events_in_range(wrap_start, to) {
235 let sample_offset = (tick_offset as f64 / ticks_per_sample) as u32;
236 track.plugin_events.push(MidiEvent {
237 sample_offset: sample_offset.min(num_frames as u32 - 1),
238 status: event.status,
239 data1: event.data1,
240 data2: event.data2,
241 });
242 }
243 }
244 } else {
245 for clip in &track.clips {
246 for (tick_offset, event) in clip.events_in_range(from, to) {
247 let sample_offset = (tick_offset as f64 / ticks_per_sample) as u32;
248 track.plugin_events.push(MidiEvent {
249 sample_offset: sample_offset.min(num_frames as u32 - 1),
250 status: event.status,
251 data1: event.data1,
252 data2: event.data2,
253 });
254 }
255 }
256 }
257 track.plugin_events.sort_by_key(|e| e.sample_offset);
258 }
259
260 if playing {
262 track.last_record_tick = current_tick;
263 }
264
265 if let Some(ref mut instrument) = track.instrument {
267 let out_l = &mut track.buf_l[..num_frames];
268 let out_r = &mut track.buf_r[..num_frames];
269 let mut out_slices: [&mut [f32]; 2] = [out_l, out_r];
270 instrument.process(&[], &mut out_slices, &track.plugin_events);
271 }
272
273 let muted = track.handle.config.is_muted();
275 let soloed = track.handle.config.is_soloed();
276 let audible = !muted && (!any_solo || soloed);
277 let volume = track.handle.config.get_volume();
278
279 let mut peak_l = 0.0f32;
280 let mut peak_r = 0.0f32;
281 for i in 0..num_frames {
282 peak_l = peak_l.max(track.buf_l[i].abs());
283 peak_r = peak_r.max(track.buf_r[i].abs());
284 }
285
286 let (old_l, old_r) = track.handle.vu.get();
287 let decay = 0.85f32;
288 track.handle.vu.set(
289 if peak_l > old_l { peak_l } else { old_l * decay },
290 if peak_r > old_r { peak_r } else { old_r * decay },
291 );
292
293 if audible {
294 for i in 0..num_frames {
295 master_l[i] += track.buf_l[i] * volume;
296 master_r[i] += track.buf_r[i] * volume;
297 }
298 }
299 }
300
301 for i in 0..num_frames {
303 output[i * 2] = master_l[i];
304 output[i * 2 + 1] = master_r[i];
305 }
306
307 self.scratch_l = master_l;
309 self.scratch_r = master_r;
310 self.live_events = live_events;
311
312 self.metronome.process(output, transport);
314
315 let mut mp_l = 0.0f32;
317 let mut mp_r = 0.0f32;
318 for i in 0..num_frames {
319 mp_l = mp_l.max(output[i * 2].abs());
320 mp_r = mp_r.max(output[i * 2 + 1].abs());
321 }
322
323 let (old_l, old_r) = self.master_vu.get();
324 let decay = 0.85f32;
325 self.master_vu.set(
326 if mp_l > old_l { mp_l } else { old_l * decay },
327 if mp_r > old_r { mp_r } else { old_r * decay },
328 );
329 }
330
331 pub fn reset_all(&mut self) {
332 for track in &mut self.tracks {
333 if let Some(ref mut inst) = track.instrument {
334 inst.reset();
335 }
336 track.handle.vu.set(0.0, 0.0);
337 if track.record_buf.is_active() {
338 track.record_buf.discard();
339 }
340 track.was_recording = false;
341 }
342 self.metronome.reset();
343 }
344
345 fn drain_commands(&mut self) {
346 while let Ok(cmd) = self.command_rx.try_recv() {
347 match cmd {
348 MixerCommand::AddTrack { kind: _, handle } => {
349 let track = AudioTrack::new(handle, self.max_buffer_size);
350 self.tracks.push(track);
351 }
352 MixerCommand::SetInstrument { track_id, mut instrument } => {
353 if let Some(track) = self.tracks.iter_mut().find(|t| t.id == track_id) {
354 instrument.init(self.sample_rate as f64, self.max_buffer_size);
355 track.instrument = Some(instrument);
356 }
357 }
358 MixerCommand::RemoveTrack { track_id } => {
359 self.tracks.retain(|t| t.id != track_id);
360 }
361 MixerCommand::SetParameter { track_id, param_index, value } => {
362 if let Some(track) = self.tracks.iter_mut().find(|t| t.id == track_id) {
363 if let Some(ref mut inst) = track.instrument {
364 inst.set_parameter(param_index, value);
365 }
366 }
367 }
368 MixerCommand::CreateClip { track_id, start_tick, length_ticks } => {
369 if let Some(track) = self.tracks.iter_mut().find(|t| t.id == track_id) {
370 track.clips.push(MidiClip::new(start_tick, length_ticks, Vec::new()));
371 }
372 }
373 MixerCommand::UpdateClip { track_id, clip_index, events } => {
374 if let Some(track) = self.tracks.iter_mut().find(|t| t.id == track_id) {
375 if let Some(clip) = track.clips.get_mut(clip_index) {
376 clip.events = events;
377 clip.events.sort_by_key(|e| e.tick);
378 }
379 }
380 }
381 }
382 }
383 }
384}
385
386fn commit_recording(track: &mut AudioTrack, end_tick: i64, clip_tx: &Sender<ClipSnapshot>) {
388 if let Some(clip) = track.record_buf.commit(end_tick) {
389 let idx = track.clips.len();
390 tracing::debug!(
391 "rec commit track={}: {} events, ticks {}..{}",
392 track.id, clip.events.len(), clip.start_tick, clip.end_tick()
393 );
394 let snapshot = ClipSnapshot::from_clip(track.id, idx, &clip);
395 track.clips.push(clip);
396 let _ = clip_tx.send(snapshot);
397 }
398}
399
400fn midi_to_plugin_event(msg: &MidiMessage) -> Option<MidiEvent> {
401 use phosphor_midi::message::MidiMessageType;
402 match msg.message_type {
403 MidiMessageType::NoteOn { .. }
404 | MidiMessageType::NoteOff { .. }
405 | MidiMessageType::ControlChange { .. }
406 | MidiMessageType::PitchBend { .. } => Some(MidiEvent {
407 sample_offset: 0,
408 status: msg.raw[0],
409 data1: msg.raw[1],
410 data2: msg.raw[2],
411 }),
412 _ => None,
413 }
414}
415
416pub fn mixer_command_channel() -> (Sender<MixerCommand>, Receiver<MixerCommand>) {
417 crossbeam_channel::unbounded()
418}
419
420pub fn clip_snapshot_channel() -> (Sender<ClipSnapshot>, Receiver<ClipSnapshot>) {
422 crossbeam_channel::unbounded()
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use phosphor_dsp::synth::PhosphorSynth;
429 use phosphor_midi::message::{MidiMessage, MidiMessageType};
430
431 fn make_note_on(note: u8, vel: u8) -> MidiMessage {
432 MidiMessage {
433 timestamp: Some(0),
434 message_type: MidiMessageType::NoteOn { channel: 0, note, velocity: vel },
435 raw: [0x90, note, vel],
436 len: 3,
437 }
438 }
439
440 fn make_note_off(note: u8) -> MidiMessage {
441 MidiMessage {
442 timestamp: Some(0),
443 message_type: MidiMessageType::NoteOff { channel: 0, note, velocity: 0 },
444 raw: [0x80, note, 0],
445 len: 3,
446 }
447 }
448
449 fn setup_mixer() -> (Mixer, Sender<MixerCommand>, Receiver<ClipSnapshot>, Arc<Transport>) {
450 let (tx, rx) = mixer_command_channel();
451 let (clip_tx, clip_rx) = clip_snapshot_channel();
452 let master_vu = Arc::new(VuLevels::new());
453 let transport = Arc::new(Transport::new(120.0));
454 let mixer = Mixer::new(rx, master_vu, clip_tx, 44100, 256);
455 (mixer, tx, clip_rx, transport)
456 }
457
458 fn add_armed_synth(tx: &Sender<MixerCommand>, id: usize) -> Arc<TrackHandle> {
459 let handle = Arc::new(TrackHandle::new(id, TrackKind::Instrument));
460 handle.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
461 handle.config.armed.store(true, std::sync::atomic::Ordering::Relaxed);
462 tx.send(MixerCommand::AddTrack { kind: TrackKind::Instrument, handle: handle.clone() }).unwrap();
463 tx.send(MixerCommand::SetInstrument { track_id: id, instrument: Box::new(PhosphorSynth::new()) }).unwrap();
464 handle
465 }
466
467 #[test]
468 fn mixer_empty_output() {
469 let (mut mixer, _tx, _clip_rx, transport) = setup_mixer();
470 let mut output = vec![0.0f32; 128];
471 mixer.process(&mut output, &[], &transport);
472 assert!(output.iter().all(|&s| s == 0.0));
473 }
474
475 #[test]
476 fn mixer_live_midi_produces_sound() {
477 let (mut mixer, tx, _clip_rx, transport) = setup_mixer();
478 let _handle = add_armed_synth(&tx, 0);
479 transport.play();
480
481 let midi = vec![make_note_on(60, 100)];
482 let mut output = vec![0.0f32; 512];
483 mixer.process(&mut output, &midi, &transport);
484
485 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
486 assert!(peak > 0.01, "Should produce sound, peak={peak}");
487 }
488
489 #[test]
490 fn mixer_records_midi_clip() {
491 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
492 let _handle = add_armed_synth(&tx, 0);
493 transport.play();
494 transport.toggle_record();
495
496 let midi = vec![make_note_on(60, 100)];
498 let mut output = vec![0.0f32; 512];
499 mixer.process(&mut output, &midi, &transport);
500
501 let midi = vec![make_note_off(60)];
503 mixer.process(&mut output, &midi, &transport);
504
505 transport.toggle_record();
507 mixer.process(&mut output, &[], &transport);
508
509 let snap = clip_rx.try_recv().expect("Should receive clip snapshot");
511 assert_eq!(snap.track_id, 0);
512 assert!(snap.event_count >= 2, "Should have note on + off, got {}", snap.event_count);
513 assert!(!snap.notes.is_empty(), "Should have parsed notes");
514 }
515
516 #[test]
517 fn mixer_plays_back_recorded_clip() {
518 let (mut mixer, tx, _clip_rx, transport) = setup_mixer();
519 let _handle = add_armed_synth(&tx, 0);
520 transport.play();
521 transport.toggle_record();
522
523 let midi = vec![make_note_on(60, 100)];
525 let mut output = vec![0.0f32; 512];
526 mixer.process(&mut output, &midi, &transport);
527
528 let midi = vec![make_note_off(60)];
529 mixer.process(&mut output, &midi, &transport);
530
531 transport.toggle_record();
533 mixer.process(&mut output, &[], &transport);
534
535 transport.stop();
537
538 transport.play();
540 output.fill(0.0);
541 mixer.process(&mut output, &[], &transport);
542
543 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
544 assert!(peak > 0.01, "Playback should produce sound, peak={peak}");
545 }
546
547 #[test]
548 fn mixer_mute_silences() {
549 let (mut mixer, tx, _clip_rx, transport) = setup_mixer();
550 let handle = add_armed_synth(&tx, 0);
551 handle.config.muted.store(true, std::sync::atomic::Ordering::Relaxed);
552 transport.play();
553
554 let midi = vec![make_note_on(60, 100)];
555 let mut output = vec![0.0f32; 512];
556 mixer.process(&mut output, &midi, &transport);
557
558 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
559 assert!(peak == 0.0, "Muted track should be silent, peak={peak}");
560 }
561
562 #[test]
563 fn mixer_no_record_when_not_armed() {
564 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
565 let handle = add_armed_synth(&tx, 0);
566 handle.config.armed.store(false, std::sync::atomic::Ordering::Relaxed);
567 transport.play();
568 transport.toggle_record();
569
570 let midi = vec![make_note_on(60, 100)];
571 let mut output = vec![0.0f32; 512];
572 mixer.process(&mut output, &midi, &transport);
573
574 transport.toggle_record();
575 mixer.process(&mut output, &[], &transport);
576
577 assert!(clip_rx.try_recv().is_err(), "Should not record when not armed");
578 }
579
580 #[test]
581 fn mixer_reset_discards_recording() {
582 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
583 let _handle = add_armed_synth(&tx, 0);
584 transport.play();
585 transport.toggle_record();
586
587 let midi = vec![make_note_on(60, 100)];
588 let mut output = vec![0.0f32; 512];
589 mixer.process(&mut output, &midi, &transport);
590
591 mixer.reset_all();
592
593 transport.toggle_record();
594 mixer.process(&mut output, &[], &transport);
595
596 assert!(clip_rx.try_recv().is_err(), "Reset should discard active recording");
597 }
598
599 #[test]
600 fn end_to_end_record_and_playback() {
601 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
604 let _handle = add_armed_synth(&tx, 0);
605 let sr = 44100u32;
606 let buf_frames = 256;
607 let buf_samples = buf_frames * 2; transport.toggle_record();
611 transport.play();
612
613 let mut output = vec![0.0f32; buf_samples];
615 for _ in 0..4 {
616 mixer.process(&mut output, &[], &transport);
617 transport.advance(buf_frames as u32, sr);
618 }
619
620 let midi = vec![make_note_on(60, 100)];
622 mixer.process(&mut output, &midi, &transport);
623 let peak_during = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
624 assert!(peak_during > 0.01, "Should hear note during recording (monitoring)");
625 transport.advance(buf_frames as u32, sr);
626
627 for _ in 0..8 {
629 output.fill(0.0);
630 mixer.process(&mut output, &[], &transport);
631 transport.advance(buf_frames as u32, sr);
632 }
633
634 let midi = vec![make_note_off(60)];
636 mixer.process(&mut output, &midi, &transport);
637 transport.advance(buf_frames as u32, sr);
638
639 for _ in 0..4 {
641 output.fill(0.0);
642 mixer.process(&mut output, &[], &transport);
643 transport.advance(buf_frames as u32, sr);
644 }
645
646 transport.toggle_record();
648 mixer.process(&mut output, &[], &transport);
649 transport.advance(buf_frames as u32, sr);
650
651 let snap = clip_rx.try_recv().expect("Should receive clip snapshot after stopping record");
653 assert!(snap.event_count >= 2, "Clip should have note on + off");
654 assert!(!snap.notes.is_empty(), "Clip should have parsed notes");
655
656 transport.stop();
658
659 transport.play();
661
662 for _ in 0..4 {
665 output.fill(0.0);
666 mixer.process(&mut output, &[], &transport);
667 transport.advance(buf_frames as u32, sr);
668 }
669
670 output.fill(0.0);
672 mixer.process(&mut output, &[], &transport);
673 let peak_playback = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
674 assert!(peak_playback > 0.001, "Playback should produce sound at the recorded position, peak={peak_playback}");
675 }
676
677 #[test]
678 fn loop_record_commits_on_wrap() {
679 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
680 let _handle = add_armed_synth(&tx, 0);
681 let sr = 44100u32;
682 let buf_frames = 256u32;
683
684 transport.set_loop_bars(1, 1);
686 transport.start_loop_record();
687
688 let mut output = vec![0.0f32; buf_frames as usize * 2];
689
690 let midi = vec![make_note_on(60, 100)];
692 mixer.process(&mut output, &midi, &transport);
693 transport.advance(buf_frames, sr);
694
695 for _ in 0..5 {
697 mixer.process(&mut output, &[], &transport);
698 transport.advance(buf_frames, sr);
699 }
700 let midi = vec![make_note_off(60)];
701 mixer.process(&mut output, &midi, &transport);
702 transport.advance(buf_frames, sr);
703
704 for _ in 0..400 {
707 mixer.process(&mut output, &[], &transport);
708 transport.advance(buf_frames, sr);
709
710 if let Ok(snap) = clip_rx.try_recv() {
711 assert!(snap.event_count >= 2, "Clip should have events, got {}", snap.event_count);
712 assert!(!snap.notes.is_empty(), "Clip should have notes");
713 transport.stop_loop_record();
715 return;
716 }
717 }
718
719 panic!("Recording should have committed when the loop wrapped");
720 }
721
722 #[test]
723 fn loop_playback_after_record() {
724 let (mut mixer, tx, clip_rx, transport) = setup_mixer();
725 let _handle = add_armed_synth(&tx, 0);
726 let sr = 44100u32;
727 let bf = 256u32;
728
729 transport.set_loop_bars(1, 1);
731 transport.start_loop_record();
732
733 let mut output = vec![0.0f32; bf as usize * 2];
734
735 mixer.process(&mut output, &[make_note_on(60, 100)], &transport);
737 transport.advance(bf, sr);
738 for _ in 0..3 {
739 mixer.process(&mut output, &[], &transport);
740 transport.advance(bf, sr);
741 }
742 mixer.process(&mut output, &[make_note_off(60)], &transport);
743 transport.advance(bf, sr);
744
745 for _ in 0..200 {
747 mixer.process(&mut output, &[], &transport);
748 transport.advance(bf, sr);
749 if clip_rx.try_recv().is_ok() { break; }
750 }
751
752 transport.stop_loop_record();
754 transport.set_position(0);
755
756 transport.toggle_loop(); transport.play();
759
760 output.fill(0.0);
761 mixer.process(&mut output, &[], &transport);
762 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
763 assert!(peak > 0.001, "Should hear playback, peak={peak}");
764 }
765}