using System.Globalization; using System.IO.Ports; using System.Numerics; using System.Timers; using SoundFlow.Backends.MiniAudio; using SoundFlow.Components; using SoundFlow.Enums; using SoundFlow.Providers; namespace ConsolePlayer { public class Program { static SerialPort Port; static System.Timers.Timer FrameTimer; static System.Timers.Timer ResyncTimer; static SoundPlayer AudioPlayer; static int FramesPerTick = 6; static int ShowtapeIndex = 0; static int ControllerBits; static string ShowtapeName; static string ShowtapeFormattedLength; static bool DetectedController = false; static bool Playing = false; static bool TripFlag = false; static bool Paused = false; static bool SyncMsg = false; static string[] ShowtapeFrames; public static void Main(string[] args) { Console.WriteLine("PinkConnection3 Console Player"); if (args.Length < 2) { Console.WriteLine("Not enough arguments!"); Console.WriteLine("Press any key to exit."); Console.ReadKey(); return; } if (!File.Exists(args[0])) { Console.WriteLine("Specified showtape does not exist!"); Console.WriteLine("Press any key to exit."); Console.ReadKey(); return; } if (!File.Exists(args[1])) { Console.WriteLine("Specified mapping file does not exist!"); Console.WriteLine("Press any key to exit."); Console.ReadKey(); return; } if (args.Length == 3) OpenSerialPortSpecific(args[2]); else OpenSerialPort(); if (TripFlag) return; LoadShowtape(args[0], args[1]); if (TripFlag) return; FrameTimer = new System.Timers.Timer((1000d/60d)*FramesPerTick); FrameTimer.Elapsed += PlayFrame; FrameTimer.AutoReset = true; ResyncTimer = new System.Timers.Timer(15000); ResyncTimer.Elapsed += Resync; ResyncTimer.AutoReset = true; using MiniAudioEngine audioEngine = new MiniAudioEngine(48000, Capability.Playback); using StreamDataProvider dataProvider = new StreamDataProvider(File.OpenRead("pc3playertempaudio.tmp")); AudioPlayer = new SoundPlayer(dataProvider); Mixer.Master.AddComponent(AudioPlayer); Console.WriteLine($"Playing Showtape \"{ShowtapeName}\" ({ShowtapeFormattedLength})"); Console.WriteLine("Controls:\n[SPACE] to pause and unpause\n[TAB] to toggle sync messages"); Playing = true; AudioPlayer.Play(); FrameTimer.Start(); ResyncTimer.Start(); while (Playing) { ConsoleKeyInfo input = Console.ReadKey(); if (Playing) { if (input.Key == ConsoleKey.Spacebar) { if (Paused) { Paused = false; FrameTimer.Start(); ResyncTimer.Start(); AudioPlayer.Seek((float)(((float)ShowtapeIndex) / 60.0)); AudioPlayer.Play(); } else { Paused = true; FrameTimer.Stop(); ResyncTimer.Stop(); AudioPlayer.Pause(); } } else if (input.Key == ConsoleKey.Tab) SyncMsg = !SyncMsg; } else if (!Playing) break; } Mixer.Master.RemoveComponent(AudioPlayer); } static void OpenSerialPortSpecific(string specifiedPortName) { string successPortName = ""; Console.WriteLine("Serial port was manually specified"); try { Console.Write("Waiting for controller"); Port = new SerialPort(specifiedPortName, 9600, Parity.None, 8, StopBits.One); Port.Open(); for (int i = 0; i < 10; i++) { if (i % 5 == 0) Console.Write("."); Port.Write("! "); Thread.Sleep(100); string readAttempt = Port.ReadExisting(); if (readAttempt.Split(",")[0] == "PC3") { ControllerBits = int.Parse(readAttempt.Split(",")[1]); DetectedController = true; successPortName = specifiedPortName; break; } } if (!DetectedController) { Console.WriteLine("\nCould not detect a PinkConnection3 controller on the specified serial port."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } } catch (Exception e) { Console.WriteLine("\nFailed to connect to the specified serial port."); Console.WriteLine(e.Message); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } } static void OpenSerialPort() { string successPortName = ""; Console.Write("Searching for controller"); foreach (string portName in SerialPort.GetPortNames()) { if (DetectedController) break; try { Port = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One); Port.Open(); for (int i = 0; i < 10; i++) { if (i % 5 == 0) Console.Write("."); Port.Write("! "); Thread.Sleep(100); string readAttempt = Port.ReadExisting(); if (readAttempt.Split(",")[0] == "PC3") { ControllerBits = int.Parse(readAttempt.Split(",")[1]); DetectedController = true; successPortName = portName; break; } } } catch (Exception) { continue; } } if (!DetectedController) { Console.WriteLine("\nCould not detect a PinkConnection3 controller on any of your serial ports."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } Console.WriteLine("\nDetected PinkConnection3 Controller on " + successPortName); } static void LoadShowtape(string ustPath, string mappingPath) { Console.WriteLine("Loading showtape file..."); string tempMappingData = File.ReadAllText(mappingPath); if (!tempMappingData.StartsWith("PC3MAPPING;")) { Console.WriteLine("Specified mapping file is not a PinkConnection3 channel map."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } List targetBits = new List(); foreach (string s in tempMappingData.Split(";")[1].Split(",")) { targetBits.Add(int.Parse(s)); } if (targetBits.Count != ControllerBits) { Console.WriteLine("The mapped channel count is not equal to the connected controller's bit count."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } if (targetBits.Count % 4 != 0) { Console.WriteLine("The mapped channel count is not divisible by 4."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } string tempUSTData = File.ReadAllText(ustPath); if (!tempUSTData.StartsWith("UST,2,")) { Console.WriteLine("Specified showtape is not a UST version 2 showtape."); Console.WriteLine("Press any key to exit."); Console.ReadKey(); TripFlag = true; return; } string[] headerData = tempUSTData.Split(';')[0].Split(','); string[] stringyBits = tempUSTData.Split(';')[1].Split(','); File.WriteAllBytes("pc3playertempaudio.tmp", Convert.FromBase64String(tempUSTData.Split(';')[2])); tempUSTData = null; ShowtapeName = headerData[2]; TimeSpan time = TimeSpan.FromSeconds(stringyBits.Length/60); ShowtapeFormattedLength = time.ToString(@"hh\:mm\:ss"); List tempShowData = new List(); foreach (string stringyFrame in stringyBits) { BigInteger frame = BigInteger.Parse(stringyFrame, NumberStyles.HexNumber); int selectBit = 0; char[] frameStringOut = new char[64]; for (int i = 0; i < targetBits.Count / 4; i++) { byte quartet = 64; for (int j = 0; j < 4; j++) { if (targetBits[selectBit] == 0) continue; if ((frame & BigInteger.Pow(2, targetBits[selectBit]-1)) == BigInteger.Pow(2, targetBits[selectBit]-1)) quartet += (byte)Math.Pow(2, j); selectBit++; } frameStringOut[i] = (char)quartet; } tempShowData.Add(new string(frameStringOut).Trim()); } ShowtapeFrames = tempShowData.ToArray(); } static void PlayFrame(Object sender, ElapsedEventArgs e) { if (ShowtapeIndex >= ShowtapeFrames.Length) { FrameTimer.Stop(); Playing = false; Console.WriteLine("Complete! Press any key to exit."); if (File.Exists("pc3playertempaudio.tmp")) File.Delete("pc3playertempaudio.tmp"); return; } Port.Write(ShowtapeFrames[ShowtapeIndex]); ShowtapeIndex += FramesPerTick; } static void Resync(Object sender, ElapsedEventArgs e) { if (SyncMsg) Console.WriteLine($"Resynced by {(int)(AudioPlayer.Time * 60) - ShowtapeIndex} frames"); ShowtapeIndex = (int)(AudioPlayer.Time * 60); } } }