using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.ComponentModel; using System.Collections.ObjectModel; using System.IO; using Microsoft.Win32; using BuzzGUI.Interfaces; using BuzzGUI.Common; using System.Windows.Threading; using libsndfile; using System.Threading.Tasks; using System.Threading; using System.Collections.Concurrent; using BuzzGUI.Common.DSP; using PropertyChanged; namespace BuzzGUI.MachineView.HDRecorder { /// /// Interaction logic for HDRecorderWindow.xaml /// /// [DoNotNotify] public partial class HDRecorderWindow : Window, INotifyPropertyChanged { public enum States { Stopped, WaitingForStart, RecordingLoop, Recording }; IBuzz buzz; IMachineGraph machineGraph; public IMachineGraph MachineGraph { get { return machineGraph; } set { if (machineGraph != null) { Global.GeneralSettings.PropertyChanged -= new PropertyChangedEventHandler(GeneralSettings_PropertyChanged); buzz.PropertyChanged -= buzz_PropertyChanged; } machineGraph = value; if (machineGraph != null) { Global.GeneralSettings.PropertyChanged += new PropertyChangedEventHandler(GeneralSettings_PropertyChanged); buzz = machineGraph.Buzz; buzz.PropertyChanged += buzz_PropertyChanged; } } } void GeneralSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "WPFIdealFontMetrics": PropertyChanged.Raise(this, "TextFormattingMode"); break; } } void buzz_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Playing") { if (!buzz.Playing && IsRecording && driveTask != null) Stop(); } } public SimpleCommand SaveAsCommand { get; private set; } public SimpleCommand RecordCommand { get; private set; } public SimpleCommand RecordLoopCommand { get; private set; } public SimpleCommand RenderLoopCommand { get; private set; } public SimpleCommand RenderLoop2Command { get; private set; } public SimpleCommand StopCommand { get; private set; } bool isRecording; public bool IsRecording { get { return isRecording; } set { isRecording = value; PropertyChanged.Raise(this, "IsRecording"); PropertyChanged.Raise(this, "IsNotRecording"); } } public bool IsNotRecording { get { return !IsRecording; } } public string OutputPath { get; set; } public int BitDepthIndex { get; set; } libsndfile.Format Format; States state; CancellationTokenSource cts; BlockingCollection bufferQueue; Task saveTask; Task driveTask; int prepareCount; string baseName; int fileCount = 0; string NextFilename { get { string path; do { fileCount++; path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(baseName), System.IO.Path.GetFileNameWithoutExtension(baseName)); path += "-" + fileCount.ToString("D4") + System.IO.Path.GetExtension(baseName); } while (File.Exists(path)); return path; } } public HDRecorderWindow() { baseName = System.IO.Path.Combine( RegistryEx.Read("HDRecorderPath", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)), "Buzz.wav"); OutputPath = NextFilename; Format = libsndfile.Format.SF_FORMAT_WAV; BitDepthIndex = 0; SaveAsCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => { var dlg = new SaveFileDialog(); dlg.InitialDirectory = System.IO.Path.GetDirectoryName(OutputPath); dlg.FileName = System.IO.Path.GetFileName(OutputPath); dlg.Filter = "Microsoft PCM wave|*.wav|Apple/SGI AIFF|*.aif|Sun/NeXT AU format|*.au|RAW PCM|*.raw|FLAC lossless|*.flac|Ogg Vorbis|*.ogg"; dlg.DefaultExt = ".wav"; if ((bool)dlg.ShowDialog()) { baseName = OutputPath = dlg.FileName; fileCount = 0; PropertyChanged.Raise(this, "OutputPath"); RegistryEx.Write("HDRecorderPath", System.IO.Path.GetDirectoryName(baseName)); switch (dlg.FilterIndex) { case 2: Format = libsndfile.Format.SF_FORMAT_AIFF; break; case 3: Format = libsndfile.Format.SF_FORMAT_AU; break; case 4: Format = libsndfile.Format.SF_FORMAT_RAW; break; case 5: Format = libsndfile.Format.SF_FORMAT_FLAC; break; case 6: Format = libsndfile.Format.SF_FORMAT_VORBIS | libsndfile.Format.SF_FORMAT_OGG; break; default: Format = libsndfile.Format.SF_FORMAT_WAV; break; } } } }; RecordCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => Record() }; RecordLoopCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => RecordLoop(false, 0) }; RenderLoopCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => RecordLoop(true, 0) }; RenderLoop2Command = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => RecordLoop(true, 1) }; StopCommand = new SimpleCommand { CanExecuteDelegate = x => true, ExecuteDelegate = x => Stop() }; DataContext = this; InitializeComponent(); this.Closing += (sender, e) => { Stop(); }; } void CreateFile() { var bitdepths = new[] { Format.SF_FORMAT_PCM_16, Format.SF_FORMAT_PCM_24, Format.SF_FORMAT_PCM_32, Format.SF_FORMAT_FLOAT }; var soundFile = SoundFile.Create(OutputPath, buzz.SelectedAudioDriverSampleRate, 2, Format | bitdepths[BitDepthIndex]); soundFile.Clipping = true; bufferQueue = new BlockingCollection(); saveTask = Task.Factory.StartNew(() => { while (!bufferQueue.IsCompleted) { try { var samples = bufferQueue.Take(); DSP.Scale(samples, 1.0f / 32768.0f); soundFile.WriteFloat(samples, 0, samples.Length / 2); } catch (InvalidOperationException) { } } soundFile.Close(); }); } void Record() { try { CreateFile(); } catch (Exception e) { MessageBox.Show(e.Message); return; } progress.IsEnabled = true; progress.IsIndeterminate = true; prepareCount = 0; state = States.Recording; buzz.MasterTap += Buzz_MasterTap; IsRecording = true; } void RecordLoop(bool driveaudio, int prepcount) { try { CreateFile(); } catch (Exception e) { MessageBox.Show(e.Message); return; } buzz.Playing = false; if (driveaudio) buzz.OverrideAudioDriver = true; buzz.Song.PlayPosition = buzz.Song.LoopStart; progress.Minimum = buzz.Song.LoopStart; progress.Maximum = buzz.Song.LoopEnd; progress.IsEnabled = true; prepareCount = prepcount; state = States.WaitingForStart; buzz.MasterTap += Buzz_MasterTap; buzz.Playing = true; IsRecording = true; if (driveaudio) { cts = new CancellationTokenSource(); driveTask = Task.Factory.StartNew(() => { var buffer = new float[2 * 256]; while (!cts.Token.IsCancellationRequested) { System.Threading.Thread.Sleep(0); for (int i = 0; i < 10; i++) buzz.RenderAudio(buffer, 256, buzz.SelectedAudioDriverSampleRate); } buzz.OverrideAudioDriver = false; }); } } void Buzz_MasterTap(float[] samples, bool stereo, SongTime songtime) { bool juststarted = false; if (state == States.WaitingForStart) { if (songtime.PosInTick == 0 && songtime.CurrentTick == buzz.Song.LoopStart) { if (prepareCount > 0) { prepareCount--; return; } else { state = States.RecordingLoop; juststarted = true; } } } if (state == States.RecordingLoop) { if (!juststarted && songtime.PosInTick == 0 && (songtime.CurrentTick == buzz.Song.LoopStart || songtime.CurrentTick == buzz.Song.LoopEnd)) { state = States.Stopped; Stop(); return; } bufferQueue.Add(samples); progress.Value = songtime.CurrentTick + (songtime.SubTicksPerTick > 0 ? (double)songtime.CurrentSubTick / songtime.SubTicksPerTick : 0); } else if (state == States.Recording) { bufferQueue.Add(samples); } } void Stop() { if (!IsRecording) return; IsRecording = false; buzz.MasterTap -= Buzz_MasterTap; if (buzz.Playing) buzz.Playing = false; progress.Value = 0; progress.IsEnabled = false; progress.IsIndeterminate = false; if (cts != null) cts.Cancel(); bufferQueue.CompleteAdding(); OutputPath = NextFilename; PropertyChanged.Raise(this, "OutputPath"); if (driveTask != null) Task.WaitAll(driveTask, saveTask); else Task.WaitAll(saveTask); driveTask = saveTask = null; } public TextFormattingMode TextFormattingMode { get { return Global.GeneralSettings.WPFIdealFontMetrics ? TextFormattingMode.Ideal : TextFormattingMode.Display; } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } }