protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, DeviceController device) { var kitNumber = context.ParseResult.ValueForOption <int>("kit"); var file = context.ParseResult.ValueForOption <string>("file"); try { Stopwatch sw = Stopwatch.StartNew(); // Allow up to 30 seconds in total. var token = new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token; var kit = await device.LoadKitAsync(kitNumber, null, token); console.WriteLine($"Finished loading in {(int) sw.Elapsed.TotalSeconds} seconds"); using (var stream = File.Create(file)) { kit.Save(stream); } console.WriteLine($"Saved kit to {file}"); } catch (OperationCanceledException) { console.WriteLine("Data loading from device was cancelled"); return(1); } catch (Exception ex) { console.WriteLine($"Error loading data from device: {ex}"); return(1); } return(0); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { var schema = ModuleSchema.KnownSchemas[ModuleIdentifier.AE10].Value; var instrumentField = schema.PhysicalRoot.ResolveField("TemporaryStudioSet/Part[1]/Instrument"); var instrumentDataField = new EnumDataField((EnumField)instrumentField); // Presets client.SendData(0x01_00_00_05, new byte[] { 64 }); for (int i = 1; i <= 128; i++) { await ShowStudioSet('P', i); } // Preset 129 is in a different bank... client.SendData(0x01_00_00_05, new byte[] { 65 }); for (int i = 129; i <= 129; i++) { await ShowStudioSet('P', i); } // User sets client.SendData(0x01_00_00_05, new byte[] { 0 }); for (int i = 1; i <= 100; i++) { await ShowStudioSet('U', i); } async Task ShowStudioSet(char prefix, int index) { client.SendData(0x01_00_00_06, new byte[] { (byte)((index - 1) & 0x7f) }); await Task.Delay(50); var common = await client.RequestDataAsync(0x18_00_00_00, 16, CancellationToken.None); string name = Encoding.UTF8.GetString(common); console.WriteLine($"{prefix}:{index:D3}: {name}"); for (int partIndex = 0; partIndex < 4; partIndex++) { var part = await client.RequestDataAsync(0x18_00_20_00 + partIndex * 0x1_00, 9, CancellationToken.None); if (part[1] != 1) { continue; } // The address doesn't matter, as only the offset is passed. var segment = new DataSegment(ModuleAddress.FromDisplayValue(0), part); instrumentDataField.Load(segment); console.WriteLine($"Part {partIndex + 1}: {instrumentDataField.Value}"); } console.WriteLine(); } return(0); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { var address = ModuleAddress.FromDisplayValue(HexInt32.Parse(context.ParseResult.ValueForOption <string>("address")).Value); var size = HexInt32.Parse(context.ParseResult.ValueForOption <string>("size")).Value; var timeout = context.ParseResult.ValueForOption <int>("timeout"); // Wait up to 10 seconds to receive all the requested data... int sizeReceived = 0; var delayTask = Task.Delay(TimeSpan.FromSeconds(timeout)); var completeTaskCts = new TaskCompletionSource <int>(); client.DataSetMessageReceived += DumpMessage; client.SendDataRequestMessage(address.DisplayValue, size); await Task.WhenAny(delayTask, completeTaskCts.Task); return(0); void DumpMessage(object sender, DataSetMessage message) { ModuleAddress address = ModuleAddress.FromDisplayValue(message.Address); console.WriteLine($"Address: {address} Length: {message.Length:x4}"); int index = 0; while (index < message.Length) { var builder = new StringBuilder(); var textBuilder = new StringBuilder(); builder.Append(address); builder.Append(" "); for (int i = 0; i < 16 && index < message.Length; i++) { byte b = message.Data[index]; textBuilder.Append(b >= 32 && b < 127 ? (char)b : ' '); builder.Append(b.ToString("x2")); builder.Append(" "); index++; } string text = builder.ToString().PadRight(9 + 16 * 3) + textBuilder; console.WriteLine(text); address = address.PlusLogicalOffset(16); } console.WriteLine(); if (Interlocked.Add(ref sizeReceived, message.Length) >= size) { completeTaskCts.SetResult(0); } } }
internal static async Task <(RolandMidiClient client, ModuleSchema schema)> DetectDeviceAsync(IStandardStreamWriter console) { var inputDevices = MidiDevices.ListInputDevices(); var outputDevices = MidiDevices.ListOutputDevices(); var commonNames = inputDevices.Select(input => input.Name) .Intersect(outputDevices.Select(output => output.Name)) .OrderBy(x => x) .ToList(); if (commonNames.Count != 1) { console.WriteLine("Error: No input and output MIDI ports with the same name detected."); return(null, null); } string name = commonNames[0]; var matchedInputs = inputDevices.Where(input => input.Name == name).ToList(); var matchedOutputs = outputDevices.Where(output => output.Name == name).ToList(); if (matchedInputs.Count != 1 || matchedOutputs.Count != 1) { console.WriteLine($"Error: Name {name} matches multiple input or output MIDI ports."); return(null, null); } var identities = await MidiDevices.ListDeviceIdentities(matchedInputs[0], matchedOutputs[0], TimeSpan.FromSeconds(1)); if (identities.Count != 1) { console.WriteLine($"Error: {(identities.Count == 0 ? "No" : "Multiple")} devices detected for MIDI port {name}."); return(null, null); } var schemaKeys = SchemaRegistry.KnownSchemas.Keys; var identity = identities[0]; var matchingKeys = schemaKeys.Where(sk => sk.FamilyCode == identity.FamilyCode && sk.FamilyNumberCode == identity.FamilyNumberCode).ToList(); if (matchingKeys.Count != 1) { console.WriteLine($"Error: {(matchingKeys.Count == 0 ? "No" : "Multiple")} schemas detected for MIDI device."); return(null, null); } var schema = SchemaRegistry.KnownSchemas[matchingKeys[0]]; var moduleIdentifier = schema.Value.Identifier; var client = await MidiDevices.CreateRolandMidiClientAsync(matchedInputs[0], matchedOutputs[0], identity, moduleIdentifier.ModelId); return(client, schema.Value); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { var schema = ModuleSchema.KnownSchemas[ModuleIdentifier.AE10].Value; var instrumentField = schema.PhysicalRoot.ResolveField("TemporaryStudioSet/Part[1]/Instrument"); var instrumentDataField = new EnumDataField((EnumField)instrumentField); var deviceController = new DeviceController(client, new ConsoleLogger(console)); var temporaryKitRoot = schema.LogicalRoot.ResolveNode("TemporaryStudioSet"); // First 100 presets client.SendData(0x01_00_00_05, new byte[] { 64 }); for (int i = 1; i <= 100; i++) { console.WriteLine($"Copying studio set {i}"); client.SendData(0x01_00_00_06, new byte[] { (byte)((i - 1) & 0x7f) }); await Task.Delay(40); var data = ModuleData.FromLogicalRootNode(temporaryKitRoot); await deviceController.LoadDescendants(data.LogicalRoot, null, progressHandler : null, cancellationToken : default); await deviceController.SaveDescendants(data.LogicalRoot, schema.GetKitRoot(i).Container.Address, progressHandler : null, cancellationToken : default); } return(0); }
public void SetErrorInstruction(string message, NotificationErrorType notificationErrorType) { IStandardStreamWriter errorWriter = _console.Error; switch (notificationErrorType) { case NotificationErrorType.Error: Environment.ExitCode = CLIConsts.ExistErrorCode; _console.ForegroundColor = ConsoleColor.DarkRed; break; case NotificationErrorType.Attention: _console.ForegroundColor = ConsoleColor.DarkBlue; break; case NotificationErrorType.None: default: throw new Exception($"Invalid NotificationErrorType '{notificationErrorType}'"); } errorWriter.WriteLine(message); _console.ForegroundColor = ConsoleColor.White; }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { using (var device = new DeviceController(client)) { var schema = device.Schema; var channel = context.ParseResult.ValueForOption <int>("channel"); var keys = context.ParseResult.ValueForOption <string>("keys"); var targetKit = context.ParseResult.ValueForOption <int>("kit"); if (targetKit < 1 || targetKit + 1 > schema.Kits) { console.WriteLine($"Kit {targetKit} is out of range for {schema.Identifier.Name} for this command."); console.WriteLine("Note that one extra kit is required after the specified one."); return(1); } // Detect the current kit var currentKit = await device.GetCurrentKitAsync(CancellationToken.None); // Copy current kit to target kit and target kit + 1 var kit = await device.LoadKitAsync(currentKit, progressHandler : null, CreateCancellationToken()); var dataNode = new DataTreeNode(kit.Data, kit.KitRoot); await device.SaveDescendants(dataNode, schema.GetKitRoot(targetKit).Container.Address, progressHandler : null, CreateCancellationToken()); await device.SaveDescendants(dataNode, schema.GetKitRoot(targetKit + 1).Container.Address, progressHandler : null, CreateCancellationToken()); await device.SetCurrentKitAsync(targetKit, CancellationToken.None); var programChangeCommand = (byte)(0xc0 | (channel - 1)); // Now listen for the foot switch... client.MessageReceived += async(sender, message) => { if (message.Data.Length == 2 && message.Data[0] == programChangeCommand) { console.WriteLine("Turning the page..."); SendKeysUtilities.SendWait(keys); await device.SetCurrentKitAsync(targetKit, CancellationToken.None); } }; console.WriteLine("Listening for foot switch"); await Task.Delay(TimeSpan.FromHours(1)); } return(0); CancellationToken CreateCancellationToken() => new CancellationTokenSource(10000).Token; }
public void Write(string value) { foreach (var console in _consoles) { IStandardStreamWriter writer = _error ? console.Error : console.Out; writer.WriteLine(value.TrimEnd('\r', '\n')); } }
public static void VerboseWriteLine(this IStandardStreamWriter writer, string value = "") { if (!Globals.Verbose) { return; } writer.WriteLine(value); }
public void SetErrorMessage(string message) { Environment.ExitCode = CLIConsts.ExistErrorCode; IStandardStreamWriter errorWriter = _console.Error; _console.ForegroundColor = ConsoleColor.Red; errorWriter.WriteLine(message); _console.ForegroundColor = ConsoleColor.White; }
private static void LogException(IStandardStreamWriter output, Logger logger, Exception error) { logger.Error(error.Message); output.WriteLine(error.Message); if (error.StackTrace != null) { logger.Error(error.StackTrace); output.WriteLine(error.StackTrace); } if (error.InnerException == null) { return; } logger.Error("==Inner Exception=="); output.WriteLine("==Inner Exception=="); LogException(output, logger, error.InnerException); logger.Error("==End Inner Exception=="); output.WriteLine("==End Inner Exception=="); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, DeviceController device) { var schema = device.Schema; for (int i = 1; i <= schema.Kits; i++) { var name = await device.LoadKitNameAsync(i, CancellationToken.None); console.WriteLine($"Kit {i}: {name}"); } return(0); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { var channel = context.ParseResult.ValueForOption <int>("channel"); var keys = context.ParseResult.ValueForOption <string>("keys"); var midiNote = context.ParseResult.ValueForOption <int>("note"); var noteOn = (byte)(0x90 | (channel - 1)); // Now listen for the foot switch... client.MessageReceived += (sender, message) => { if (message.Data.Length == 3 && message.Data[0] == noteOn && message.Data[1] == midiNote) { console.WriteLine("Turning the page..."); SendKeysUtilities.SendWait(keys); } }; console.WriteLine("Listening for MIDI note"); await Task.Delay(TimeSpan.FromHours(1)); return(0); }
/// <summary> /// Writes a kit to a device. /// </summary> internal static async Task WriteKit(RolandMidiClient client, Kit kit, int kitNumber, IStandardStreamWriter console) { var targetKitRoot = kit.Schema.KitRoots[kitNumber]; var clonedData = kit.KitRoot.Context.CloneData(kit.Data, targetKitRoot.Context.Address); var segments = clonedData.GetSegments(); console.WriteLine($"Writing {segments.Count} containers to device {kit.Schema.Identifier.Name} kit {kitNumber}"); foreach (var segment in segments) { client.SendData(segment.Start.Value, segment.CopyData()); await Task.Delay(40); } }
private static void OnException(Exception ex, InvocationContext context) { IStandardStreamWriter err = context.Console.Error; switch (ex) { case AggregateException aex: aex.Handle(x => { err.WriteLine($"fatal: {x.Message}"); return(true); }); break; case Win32Exception wex: err.WriteLine($"fatal: {wex.Message} [0x{wex.NativeErrorCode:x}]"); break; default: err.WriteLine($"fatal: {ex.Message}"); break; } }
private static async Task PopulateSegment(RolandMidiClient client, ModuleData data, AnnotatedContainer annotatedContainer, CancellationToken token, IStandardStreamWriter console) { var timerToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token; var effectiveToken = CancellationTokenSource.CreateLinkedTokenSource(token, timerToken).Token; try { var segment = await client.RequestDataAsync(annotatedContainer.Context.Address.Value, annotatedContainer.Container.Size, effectiveToken); data.Populate(annotatedContainer.Context.Address, segment); } catch (OperationCanceledException) when(timerToken.IsCancellationRequested) { console.WriteLine($"Device didn't respond for container {annotatedContainer.Path}; skipping."); } }
private static void WriteLine(this IConsole console, IStandardStreamWriter writer, string message, ConsoleColor?color) { lock (_writeLock) { if (color.HasValue) { console.SetTerminalForeground(color.Value); } writer.WriteLine(message); if (color.HasValue) { console.ResetColor(); } } }
private static void WriteMessage(this IConsole console, IStandardStreamWriter writer, ConsoleColor color, string messsage) { var terminal = console.GetTerminal(preferVirtualTerminal: false); var originalForegroundColor = terminal?.ForegroundColor ?? Console.ForegroundColor; if (terminal is not null) { terminal.ForegroundColor = color; } writer.WriteLine(messsage); if (terminal is not null) { terminal.ForegroundColor = originalForegroundColor; } }
/// <summary> /// Reads a kit from a device. /// </summary> internal static async Task <Kit> ReadKit(ModuleSchema schema, RolandMidiClient client, int kitNumber, IStandardStreamWriter console) { // Allow up to 30 seconds in total, and 1 second per container. var overallToken = new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token; var moduleData = new ModuleData(); var kitRoot = schema.KitRoots[kitNumber]; var containers = kitRoot.Context.AnnotateDescendantsAndSelf().Where(c => c.Container.Loadable).ToList(); console.WriteLine($"Reading {containers.Count} containers from device {schema.Identifier.Name} kit {kitNumber}"); foreach (var container in containers) { await PopulateSegment(client, moduleData, container, overallToken, console); } var clonedData = kitRoot.Context.CloneData(moduleData, schema.KitRoots[1].Context.Address); return(new Kit(schema, clonedData, kitNumber)); }
public void WriteLine(string text) => _writer.WriteLine($"{_indent}{text}");
internal static void WriteLine(this IStandardStreamWriter streamWriter, string message) { streamWriter.Write(message); streamWriter.WriteLine(); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, DeviceController device) { var path = context.ParseResult.ValueForOption <string>("path"); var switchFieldName = context.ParseResult.ValueForOption <string>("switch"); var parametersFieldName = context.ParseResult.ValueForOption <string>("parameters"); var mfxNode = device.Schema.LogicalRoot.ResolveNode(path); var mfxContainer = mfxNode.Container; var switchField = (EnumField)mfxContainer.ResolveField(switchFieldName); var parametersField = (OverlayField)mfxContainer.ResolveField(parametersFieldName); var deviceData = ModuleData.FromLogicalRootNode(mfxNode); var deviceParametersField = (OverlayDataField)deviceData.GetDataField(parametersField); console.WriteLine($"Loading original MFX data"); await device.LoadDescendants(deviceData.LogicalRoot, null, null, default); var originalSnapshot = deviceData.CreateSnapshot(); var modelData = ModuleData.FromLogicalRootNode(mfxNode); var modelTypeField = (EnumDataField)modelData.GetDataField(switchField); var modelParametersField = (OverlayDataField)modelData.GetDataField(parametersField); try { // Set it to the max value so that we'll be resetting it. await SetDeviceMfx(switchField.Max); for (int mfxType = switchField.Min; mfxType <= switchField.Max; mfxType++) { // Make the change on the device... await SetDeviceMfx(mfxType); await device.LoadDescendants(deviceData.LogicalRoot, null, null, default); // Make the change in the model... modelTypeField.RawValue = mfxType; var modelFields = modelParametersField.CurrentFieldList; var deviceFields = deviceParametersField.CurrentFieldList; if (modelFields.Description != deviceFields.Description) { console.WriteLine($"Mismatch in description: '{modelFields.Description}' != '{deviceFields.Description}'. Skipping."); continue; } console.WriteLine($"Comparing fields for {modelFields.Description}"); foreach (var(modelField, deviceField) in AsNumericFields(modelFields.Fields).Zip(AsNumericFields(deviceFields.Fields))) { if (modelField.RawValue != deviceField.RawValue) { console.WriteLine($"{modelField.SchemaField.Name}: Device={deviceField.RawValue}; Model={modelField.RawValue}"); } } console.WriteLine(); } } finally { // Restore the original data console.WriteLine($"Restoring original kit data"); deviceData.LoadSnapshot(originalSnapshot, NullLogger.Instance); await device.SaveDescendants(deviceData.LogicalRoot, targetAddress : null, progressHandler : null, CancellationToken.None); } return(0); async Task SetDeviceMfx(int type) { var segment = new DataSegment(mfxContainer.Address + switchField.Offset, new[] { (byte)type }); await device.SaveSegment(segment, CancellationToken.None); } IEnumerable <NumericDataFieldBase> AsNumericFields(IEnumerable <IDataField> fields) { foreach (var field in fields) { switch (field) { case NumericDataFieldBase numeric: yield return(numeric); break; case TempoDataField tempo: yield return(tempo.SwitchDataField); yield return(tempo.NumericDataField); yield return(tempo.MusicalNoteDataField); break; default: throw new InvalidOperationException($"Can't convert {field.GetType()} into a numeric field"); } } } }
public static void WriteLine(this IStandardStreamWriter writer, string?value) { writer.WriteLine(value, avoidExtraNewLine: false); }
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, RolandMidiClient client) { using var writer = File.CreateText(context.ParseResult.ValueForOption <string>("file")); int chunkSize = context.ParseResult.ValueForOption <int>("chunkSize"); TimeSpan timeout = TimeSpan.FromMilliseconds(context.ParseResult.ValueForOption <int>("timeout")); var address = ModuleAddress.FromDisplayValue(0); int messagesReceived = 0; client.DataSetMessageReceived += DumpMessage; // TODO: Make this tighter. But we're unlikely to really get here. while (address.DisplayValue < 0x74_00_00_00) { int receivedAtStartOfChunk = messagesReceived; client.SendDataRequestMessage(address.DisplayValue, chunkSize); int receivedAtStartOfDelay; do { receivedAtStartOfDelay = messagesReceived; await Task.Delay(timeout); } while (messagesReceived > receivedAtStartOfDelay); int receivedForChunk = messagesReceived - receivedAtStartOfChunk; console.WriteLine($"Received {receivedForChunk} messages in chunk at {address}"); writer.Flush(); address = address.PlusLogicalOffset(chunkSize); } return(0); void DumpMessage(object sender, DataSetMessage message) { ModuleAddress address = ModuleAddress.FromDisplayValue(message.Address); writer.WriteLine($"Address: {address} Length: {message.Length:x4}"); int index = 0; while (index < message.Length) { var builder = new StringBuilder(); var textBuilder = new StringBuilder(); builder.Append(address); builder.Append(" "); for (int i = 0; i < 16 && index < message.Length; i++) { byte b = message.Data[index]; textBuilder.Append(b >= 32 && b < 127 ? (char)b : ' '); builder.Append(b.ToString("x2")); builder.Append(" "); index++; } string text = builder.ToString().PadRight(9 + 16 * 3) + textBuilder; writer.WriteLine(text); address = address.PlusLogicalOffset(16); } writer.WriteLine(); Interlocked.Increment(ref messagesReceived); } }
public static void WriteLineIndented( this IStandardStreamWriter writer, string text, int level = 1, int indent = DefaultIndent) => WriteIndented(s => writer.WriteLine(s), text, level, indent);
protected override async Task <int> InvokeAsync(InvocationContext context, IStandardStreamWriter console, DeviceController device) { var kit = context.ParseResult.ValueForOption <int>("kit"); var triggerRoot = device.Schema.GetTriggerRoot(kit, trigger: 1); var deviceData = ModuleData.FromLogicalRootNode(triggerRoot); var modelData = ModuleData.FromLogicalRootNode(triggerRoot); var defaultValuesSnapshot = modelData.CreateSnapshot(); var deviceDataRoot = new DataTreeNode(deviceData, triggerRoot); await device.LoadDescendants(deviceDataRoot, targetAddress : null, progressHandler : null, CancellationToken.None); var originalSnapshot = deviceData.CreateSnapshot(); var instrumentField = device.Schema.GetMainInstrumentField(kit, trigger: 1); var modelInstrumentField = (InstrumentDataField)modelData.GetDataField(instrumentField); var instrumentContainers = triggerRoot.DescendantFieldContainers(); var differences = new List <Difference>(); var logger = new ConsoleLogger(console); try { // Reset the device to an empty snapshot deviceData.LoadSnapshot(defaultValuesSnapshot, logger); await device.SaveDescendants(deviceDataRoot, targetAddress : null, progressHandler : null, CancellationToken.None); foreach (var instrument in device.Schema.PresetInstruments) { // Make the change on the real module and load the data. // Assumption: the segment containing the instrument itself (e.g. KitPadInst) doesn't // have any implicit model changes to worry about. await device.SetInstrumentAsync(kit, trigger : 1, instrument, CancellationToken.None); await device.LoadDescendants(deviceDataRoot, targetAddress : null, progressHandler : null, CancellationToken.None); // Make the change in the model. modelData.LoadSnapshot(defaultValuesSnapshot, logger); modelInstrumentField.Instrument = instrument; // Compare the two. bool anyDifferences = false; foreach (var container in instrumentContainers) { // We won't compare InstrumentDataField, TempoDataField or StringDataField this way, but that's okay. var realFields = deviceData.GetDataFields(container).SelectMany(ExpandOverlays).OfType <NumericDataFieldBase>().ToList(); var modelFields = modelData.GetDataFields(container).SelectMany(ExpandOverlays).OfType <NumericDataFieldBase>().ToList(); if (realFields.Count != modelFields.Count) { console.WriteLine($"Major failure: for instrument {instrument.Id} ({instrument.Group} / {instrument.Name}), found {realFields.Count} real fields and {modelFields.Count} model fields in container {container.Path}"); return(1); } foreach (var pair in realFields.Zip(modelFields)) { var real = pair.First; var model = pair.Second; if (real.SchemaField != model.SchemaField) { console.WriteLine($"Major failure: for instrument {instrument.Id} ({instrument.Group} / {instrument.Name}), mismatched schema field for {container.Path}: {real.SchemaField.Name} != {model.SchemaField.Name}"); return(1); } var realValue = real.RawValue; var predictedValue = model.RawValue; if (realValue != predictedValue) { anyDifferences = true; differences.Add(new Difference(instrument, container, real.SchemaField, realValue, predictedValue)); } } } console.Write(anyDifferences ? "!" : "."); } } finally { // Restore the original data deviceData.LoadSnapshot(originalSnapshot, logger); await device.SaveDescendants(deviceDataRoot, targetAddress : null, progressHandler : null, CancellationToken.None); } console.WriteLine(); foreach (var difference in differences) { console.WriteLine(difference.ToString()); } console.WriteLine($"Total differences: {differences.Count}"); return(0); IEnumerable <IDataField> ExpandOverlays(IDataField field) => field is OverlayDataField odf ? odf.CurrentFieldList.Fields : Enumerable.Repeat(field, 1); }