internal SoundRecorderDialog(ILogger logger, ModuleSchema schema, RolandMidiClient midiClient) : this() { this.logger = logger; this.schema = schema; this.midiClient = midiClient; kitNumber.PreviewTextInput += TextConversions.CheckDigits; userSamples.PreviewTextInput += TextConversions.CheckDigits; cancellationTokenSource = new CancellationTokenSource(); kitNumber.Text = TextConversions.Format(schema.KitRoots.Count); // Capture the input device names, and attempt to guess a reasonable default. var allInputDevices = AudioDevices.GetInputDeviceNames(); var midiName = midiClient.OutputName; var expectedInputDevices = new[] { $"MASTER ({midiName})", $"IN ({midiName})", $"KICK ({midiName})" }; inputDevice.ItemsSource = allInputDevices; inputDevice.SelectedIndex = allInputDevices.FindIndex(inputName => expectedInputDevices.Contains(inputName)); foreach (var group in schema.InstrumentGroups) { instrumentGroupSelector.Items.Add(group.Description); } }
internal InstrumentAudioExplorer(ILogger logger, ModuleAudio audio, string fileName) : this() { this.audio = audio; this.logger = logger; Title = $"{Title} - {fileName}"; capturesByGroup = audio.Captures.ToLookup(c => c.Instrument.Group); var allOutputDeviceNames = AudioDevices.GetOutputDeviceNames(); outputDevice.ItemsSource = allOutputDeviceNames; // Assume that device 0 is the default. That will usually be the case. if (allOutputDeviceNames.Count > 0) { outputDevice.SelectedIndex = 0; } moduleId.Content = audio.Schema.Identifier.Name; userSamples.Content = TextConversions.Format(capturesByGroup[null].Count()); var format = audio.Format; audioFormat.Content = $"Channels: {format.Channels}; Bits: {format.Bits}; Frequency: {format.Frequency}"; timePerInstrument.Content = TextConversions.Format(audio.DurationPerInstrument.TotalSeconds); var groups = capturesByGroup.Select(c => new InstrumentGroupOrUserSample(c.Key)).Distinct(); treeView.ItemsSource = groups; instrumentsGroupBox.Visibility = Visibility.Collapsed; }
internal SoundRecorderDialog(ILogger logger, ModuleSchema schema, RolandMidiClient midiClient) : this() { this.logger = logger; this.schema = schema; this.midiClient = midiClient; kitNumber.PreviewTextInput += TextConversions.CheckDigits; userSamples.PreviewTextInput += TextConversions.CheckDigits; cancellationTokenSource = new CancellationTokenSource(); inputDevice.ItemsSource = AudioDevices.GetInputDeviceNames(); kitNumber.Text = TextConversions.Format(schema.KitRoots.Count); }
private Config TryCreateConfig() { if (inputDevice.SelectedIndex == -1) { return(null); } string deviceName = (string)inputDevice.Items[inputDevice.SelectedIndex]; int? deviceId = AudioDevices.GetAudioInputDeviceId(deviceName); if (deviceId == null) { return(null); } int midiChannel = int.Parse(midiChannelSelector.Text); if (!TextConversions.TryParseDecimal(recordingTime.Text, out var recordingSeconds)) { return(null); } TimeSpan recordingDuration = TimeSpan.FromSeconds((double)recordingSeconds); if (!TextConversions.TryGetKitRoot(kitNumber.Text, schema, logger, out var kit)) { return(null); } if (outputFile == null) { return(null); } if (!TextConversions.TryParseInt32(userSamples.Text, out int parsedUserSamples)) { return(null); } if (parsedUserSamples < 0 || parsedUserSamples > schema.UserSampleInstruments.Count) { return(null); } return(new Config { KitRoot = kit, RecordingDuration = recordingDuration, OutputFile = outputFile, AudioDeviceId = deviceId.Value, InstrumentGroup = instrumentGroupSelector.SelectedIndex - 1, UserSamples = parsedUserSamples, MidiChannel = midiChannel, Attack = (int)attackSlider.Value, }); }
private async void PlaySample(object sender, RoutedEventArgs args) { try { InstrumentAudio capture = (InstrumentAudio)((Button)sender).Tag; int? deviceId = AudioDevices.GetAudioOutputDeviceId(outputDevice.Text); if (deviceId == null) { return; } // TODO: Allow audio playback cancellation. Seems unlikely we'll actually need it. await AudioDevices.PlayAudio(deviceId.Value, audio.Format, capture.Audio, CancellationToken.None); } catch (Exception e) { logger.Log("Error playing sample", e); } }
internal InstrumentAudioExplorer(ILogger logger, ModuleAudio audio) : this() { this.audio = audio; this.logger = logger; capturesByGroup = audio.Captures.ToLookup(c => c.Instrument.Group); outputDevice.ItemsSource = AudioDevices.GetOutputDeviceNames(); moduleId.Content = audio.Schema.Identifier.Name; userSamples.Content = TextConversions.Format(capturesByGroup[null].Count()); var format = audio.Format; audioFormat.Content = $"Channels: {format.Channels}; Bits: {format.Bits}; Frequency: {format.Frequency}"; timePerInstrument.Content = TextConversions.Format(audio.DurationPerInstrument.TotalSeconds); var groups = capturesByGroup.Select(c => new InstrumentGroupOrUserSample(c.Key)).Distinct(); treeView.ItemsSource = groups; instrumentsGroupBox.Visibility = Visibility.Collapsed; }
private async void StartRecording(object sender, RoutedEventArgs args) { var config = TryCreateConfig(); // Shouldn't really happen, as the button shouldn't be enabled. if (config == null) { return; } // Load all the details var instrumentRoot = config.KitRoot.DescendantNodesAndSelf().FirstOrDefault(node => node.InstrumentNumber == 1); if (instrumentRoot == null) { logger.LogError($"No instrument root available. Please email a bug report to [email protected]"); return; } var data = new ModuleData(); var midiNoteChain = instrumentRoot.MidiNoteField; if (midiNoteChain == null) { logger.LogError($"No midi field available. Please email a bug report to [email protected]"); return; } logger.LogInformation($"Starting recording process"); var midiNoteContext = midiNoteChain.GetFinalContext(instrumentRoot.Context); logger.LogInformation($"Loading existing data to restore after recording"); List <FixedContainer> instrumentContainers; try { await LoadContainerAsync(data, midiNoteContext); instrumentContainers = instrumentRoot.DescendantNodesAndSelf() .SelectMany(node => node.Details) .Select(detail => detail.Container) .Where(fc => fc != null) .Distinct() .ToList(); foreach (var container in instrumentContainers) { await LoadContainerAsync(data, container); } } catch (Exception e) { logger.LogError($"Error loading data for recording", e); return; } data.Snapshot(); var(instrumentFieldContext, instrumentField) = (from ct in instrumentContainers orderby ct.Address from field in ct.Container.Fields where field is InstrumentField select(ct, (InstrumentField)field)).FirstOrDefault(); if (instrumentFieldContext == null) { logger.LogError($"No instrument field available. Please email a bug report to [email protected]"); return; } var midiNote = midiNoteChain.FinalField.GetMidiNote(midiNoteContext, data); if (midiNote == null) { logger.LogError($"No midi note for instrument 1. Please email a bug report to [email protected]"); } var presetInstrumentsToRecord = schema.PresetInstruments .Where(ins => config.InstrumentGroup == -1 || ins.Group.Index == config.InstrumentGroup) .ToList(); progress.Maximum = presetInstrumentsToRecord.Count + config.UserSamples; logger.LogInformation($"Starting recording process"); try { var captures = new List <InstrumentAudio>(); progress.Value = 0; foreach (var instrument in presetInstrumentsToRecord) { var instrumentAudio = await RecordInstrument(instrument); captures.Add(instrumentAudio); } for (int i = 0; i < config.UserSamples; i++) { var instrumentAudio = await RecordInstrument(schema.UserSampleInstruments[i]); captures.Add(instrumentAudio); } var moduleAudio = new ModuleAudio(schema, AudioDevices.AudioFormat, config.RecordingDuration, captures.AsReadOnly()); using (var output = File.Create(config.OutputFile)) { moduleAudio.Save(output); } logger.LogInformation($"Saved instrument sounds to {config.OutputFile}."); } catch (OperationCanceledException) { logger.LogWarning("Cancelled recording"); } catch (Exception e) { logger.LogError($"Error recording data", e); } finally { data.RevertSnapshot(); await RestoreData(data); } Close(); async Task <InstrumentAudio> RecordInstrument(Instrument instrument) { progress.Value++; progressLabel.Content = $"Recording {instrument.Name}"; foreach (var container in instrumentContainers) { container.Container.Reset(container, data); } // Note: setting the instrument resets VEdit data to defaults instrumentField.SetInstrument(instrumentFieldContext, data, instrument); foreach (var container in instrumentContainers) { var segment = data.GetSegment(container.Address); midiClient.SendData(segment.Start.Value, segment.CopyData()); await Task.Delay(40, CancellationToken); } midiClient.Silence(config.MidiChannel); await Task.Delay(40); var recordingTask = AudioDevices.RecordAudio(config.AudioDeviceId, config.RecordingDuration, CancellationToken); midiClient.PlayNote(config.MidiChannel, midiNote.Value, config.Attack); var audio = await recordingTask; return(new InstrumentAudio(instrument, audio)); } }