From 1fb697626ed17dc2d1975a7ed421598178b8e7a8 Mon Sep 17 00:00:00 2001
From: Persephone Bubblegum-Holiday <kawaiizenbo@outlook.com>
Date: Sat, 10 May 2025 14:02:28 -0700
Subject: [PATCH] player kinda works

---
 .../Firmware-8Valve/Firmware-8Valve.ino       |   2 +-
 .../Firmware-Servo-ChuckEHelenGuestStar.ino   |   2 +-
 .../Firmware-Servo-HelenMitzi.ino             |   8 +-
 PC2Player/PC2Player/MainForm.cs               | 327 ++++++++++++++++--
 PC2Player/PC2Player/PC2Player.csproj          |   3 +-
 5 files changed, 298 insertions(+), 44 deletions(-)

diff --git a/Arduino Firmware/Firmware-8Valve/Firmware-8Valve.ino b/Arduino Firmware/Firmware-8Valve/Firmware-8Valve.ino
index e86b46c..9c4fd69 100644
--- a/Arduino Firmware/Firmware-8Valve/Firmware-8Valve.ino	
+++ b/Arduino Firmware/Firmware-8Valve/Firmware-8Valve.ino	
@@ -14,7 +14,7 @@ void setup()
   pinMode(8, OUTPUT);
   pinMode(9, OUTPUT);
   Serial.begin(9600);
-  Serial.write("PC2,8,Universal");
+  Serial.write("PC2,8,Universal,END");
 }
 
 void loop()
diff --git a/Arduino Firmware/Servo/Firmware-Servo-ChuckEHelenGuestStar/Firmware-Servo-ChuckEHelenGuestStar.ino b/Arduino Firmware/Servo/Firmware-Servo-ChuckEHelenGuestStar/Firmware-Servo-ChuckEHelenGuestStar.ino
index f56fd0f..17c095e 100644
--- a/Arduino Firmware/Servo/Firmware-Servo-ChuckEHelenGuestStar/Firmware-Servo-ChuckEHelenGuestStar.ino	
+++ b/Arduino Firmware/Servo/Firmware-Servo-ChuckEHelenGuestStar/Firmware-Servo-ChuckEHelenGuestStar.ino	
@@ -20,7 +20,7 @@ void setup()
   servoChannel[5].attach(7);
   for (int i = 0; i < 8; i++) servoChannel[mapping[i]].write(offDegrees[i]);
   Serial.begin(9600);
-  Serial.write("PC2,8,Chuck E./Helen/Guest Star");
+  Serial.write("PC2,8,Chuck E./Helen/Guest Star,END");
 }
 
 void loop()
diff --git a/Arduino Firmware/Servo/Firmware-Servo-HelenMitzi/Firmware-Servo-HelenMitzi.ino b/Arduino Firmware/Servo/Firmware-Servo-HelenMitzi/Firmware-Servo-HelenMitzi.ino
index a7269e6..6dfb626 100644
--- a/Arduino Firmware/Servo/Firmware-Servo-HelenMitzi/Firmware-Servo-HelenMitzi.ino	
+++ b/Arduino Firmware/Servo/Firmware-Servo-HelenMitzi/Firmware-Servo-HelenMitzi.ino	
@@ -2,10 +2,10 @@
 
 #include <Servo.h>
 
-Servo servoChannel[11]; // mouth earL earR eyelidL eyelidR eyeL eyeR headR headL headUp armUpL armUpR elbowR elbowL armTwistL armTwistR bodyTwistR bodyTwistL bodyLean
-byte onDegrees[19] =     { 45,   0,   0,   0,      0,      0,   0,   45,   135,  135,   90,    90,    90,    90,    90,       90,       0,         0,         0        };
+Servo servoChannel[11]; // mouth earL earR eyelidL eyelidR eyeL eyeR headL headR headUp armUpL armUpR elbowR elbowL armTwistL armTwistR bodyTwistR bodyTwistL bodyLean
+byte onDegrees[19] =     { 45,   0,   0,   0,      0,      0,   0,   135,  45,   135,   90,    90,    90,    90,    90,       90,       0,         0,         0        };
 byte offDegrees[19] =    { 0,    0,   0,   0,      0,      0,   0,   90,   90,   90,    180,   0,     0,     180,   180,      0 ,       0,         0,         0        };
-byte mapping[19] =       { 0,    0,   0,   0,      0,      0,   0,   2,    2,    1,     6,     3,     4,     7,     8,        5,        0,         0,         0        };
+byte mapping[19] =       { 0,    0,   0,   0,      0,      0,   0,   2,    2,    1,     6,     3,     4,     7,     8,        5,        9,         9,         10       };
 
 int byte1 = 0;
 int byte2 = 0;
@@ -29,7 +29,7 @@ void setup()
   servoChannel[8].attach(10);
   for (int i = 0; i < 19; i++) servoChannel[mapping[i]].write(offDegrees[i]);
   Serial.begin(9600);
-  Serial.write("PC2,32,Mitzi/Helen");
+  Serial.write("PC2,32,Mitzi/Helen,END");
 }
 
 void loop() {
diff --git a/PC2Player/PC2Player/MainForm.cs b/PC2Player/PC2Player/MainForm.cs
index f3bc1a3..1a03aaf 100644
--- a/PC2Player/PC2Player/MainForm.cs
+++ b/PC2Player/PC2Player/MainForm.cs
@@ -1,4 +1,11 @@
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.Ports;
+using System.Timers;
+
 using Eto.Forms;
 using Eto.Drawing;
 
@@ -6,52 +13,298 @@ namespace PC2Player
 {
 	public partial class MainForm : Form
 	{
+		SerialPort Port = null;
+		string[] Signals;
+		System.Timers.Timer FrameTimer;
+		long Index = 0;
+		int FrameSkip = 6;
+		byte[] AudioData;
+
+		string ShowtapeName = "";
+		string ShowtapeStageType = "";
+		string ShowtapeCharacter = "";
+		string ShowtapeBits = "";
+		string ShowtapeFormattedLength = "";
+
+		string ControllerCharacter = "";
+		string ControllerBits = "";
+
+		string ControllerHeader = "";
+
+		bool ControllerConnected = false;
+		bool ShowtapeLoaded = false;
+
+		bool Playing = false;
+
+		DropDown SerialPortDropDown = new DropDown();
+
+		Label ShowtapeNameLabel = new Label { Text = "Show Name: Not Loaded" };
+		Label ShowtapeStageTypeLabel = new Label { Text = "Stage Type: Not Loaded" };
+		Label ShowtapeCharacterLabel = new Label { Text = "Character: Not Loaded" };
+		Label ShowtapeBitsLabel = new Label { Text = "Bits: Not Loaded" };
+		Label ShowtapeLengthLabel = new Label { Text = "Length: Not Loaded" };
+
+		Label ControllerCharacterLabel = new Label { Text = "Character: Disconnected" };
+		Label ControllerBitsLabel = new Label { Text = "Bits: Disconnected" };
+
+		Button RefreshPortsButton = new Button { Text = "Refresh Ports", Width = 150 };
+		Button LoadShowtapeButton = new Button { Text = "Load Showtape", Width = 150 };
+
+		Button PlayButton = new Button { Text = "Play" };
+		Button StopButton = new Button { Text = "Stop" };
+
 		public MainForm()
 		{
-			Title = "My Eto Form";
-			MinimumSize = new Size(200, 200);
+			Title = "PinkConnection2 Showtape Player";
+			Size = new Size(500, 450);
+			Maximizable = false;
+			Resizable = false;
 
-			Content = new StackLayout
+			foreach (string s in SerialPort.GetPortNames())
 			{
-				Padding = 10,
-				Items =
+				SerialPortDropDown.Items.Add(s);
+			}
+			SerialPortDropDown.SelectedValueChanged += (sender, e) => Event_SelectSerialPort();
+
+			FrameTimer = new System.Timers.Timer((1000d/60d)*FrameSkip);
+			FrameTimer.Elapsed += Event_FrameTimerTick;
+			FrameTimer.AutoReset = true;
+
+			PlayButton.Enabled = false;
+			StopButton.Enabled = false;
+
+			PlayButton.Command = new Command( (sender, e) => Event_PlayButton() );
+			StopButton.Command = new Command( (sender, e) => Event_StopButton() );
+			RefreshPortsButton.Command = new Command( (sender, e) => Event_RefreshPortsButton() );
+			LoadShowtapeButton.Command = new Command( (sender, e) => Event_LoadShowtapeButton() );
+
+			Content = BuildLayout();
+		}
+
+		DynamicLayout BuildLayout()
+		{
+			GroupBox showtapeInfoGroupBox = new GroupBox{ Text = "Showtape" };
+			DynamicLayout showtapeInfoGroupLayout = new DynamicLayout { DefaultSpacing = new Size(5, 5), DefaultPadding = new Padding(5, 5, 5, 5) };
+			showtapeInfoGroupLayout.AddRow(ShowtapeNameLabel);
+			showtapeInfoGroupLayout.AddRow(ShowtapeStageTypeLabel);
+			showtapeInfoGroupLayout.AddRow(ShowtapeCharacterLabel);
+			showtapeInfoGroupLayout.AddRow(ShowtapeBitsLabel);
+			showtapeInfoGroupLayout.AddRow(ShowtapeLengthLabel);
+			showtapeInfoGroupBox.Content = showtapeInfoGroupLayout;
+
+			GroupBox controllerInfoGroupBox = new GroupBox{ Text = "Controller" };
+			DynamicLayout controllerInfoGroupLayout = new DynamicLayout { DefaultSpacing = new Size(5, 5), DefaultPadding = new Padding(5, 5, 5, 5) };
+			controllerInfoGroupLayout.AddRow(ControllerCharacterLabel);
+			controllerInfoGroupLayout.AddRow(ControllerBitsLabel);
+			controllerInfoGroupBox.Content = controllerInfoGroupLayout;
+
+			DynamicLayout layout = new DynamicLayout { DefaultSpacing = new Size(10, 10), DefaultPadding = new Padding(5, 5, 5, 5) };
+
+			layout.BeginVertical();
+			layout.BeginHorizontal();
+			layout.Add(PlayButton, true);
+			layout.Add(StopButton, true);
+			layout.EndHorizontal();
+			layout.EndVertical();
+
+			layout.BeginVertical();
+
+			layout.BeginHorizontal();
+			layout.Add(new Label { Text = "Serial Port" });
+			layout.EndHorizontal();
+
+			layout.BeginHorizontal();
+			layout.Add(SerialPortDropDown, true);
+			layout.EndHorizontal();
+
+			layout.BeginHorizontal();
+			layout.Add(showtapeInfoGroupBox, true);
+			layout.EndHorizontal();
+
+			layout.BeginHorizontal();
+			layout.Add(controllerInfoGroupBox, true);
+			layout.EndHorizontal();
+
+			layout.EndVertical();
+
+			layout.BeginVertical();
+			layout.BeginHorizontal();
+			layout.Add(null, true);
+			layout.Add(RefreshPortsButton);
+			layout.Add(LoadShowtapeButton);
+			layout.EndHorizontal();
+			layout.EndVertical();
+
+			layout.BeginVertical();
+			layout.Add(null, true);
+			layout.EndVertical();
+
+			return layout;
+		}
+
+		void CheckReady()
+		{
+			if (ControllerConnected && ShowtapeLoaded)
+			{
+				PlayButton.Enabled = true;
+				StopButton.Enabled = true;
+			}
+			else
+			{
+				PlayButton.Enabled = false;
+				StopButton.Enabled = false;
+			}
+		}
+
+		void Event_SelectSerialPort()
+		{
+			if (Port != null) Port.Close();
+			Port = new SerialPort((string)SerialPortDropDown.SelectedKey, 9600, Parity.None, 8, StopBits.One);
+			Port.DataReceived += new SerialDataReceivedEventHandler(Event_PortRecievedData);
+			ControllerHeader = "";
+			ControllerCharacterLabel.Text = "Character: Disconnected";
+			ControllerBitsLabel.Text = "Bits: Disconnected";
+			ControllerConnected = false;
+			CheckReady();
+			Port.Open();
+		}
+
+		void Event_PortRecievedData(object sender, SerialDataReceivedEventArgs e)
+		{
+			ControllerHeader += Port.ReadExisting();
+			if (ControllerHeader.EndsWith(",END"))
+			{
+				ControllerBits = ControllerHeader.Split(',')[1];
+				ControllerCharacter = ControllerHeader.Split(',')[2];
+
+				ControllerCharacterLabel.Text = $"Character: {ControllerCharacter}";
+				ControllerBitsLabel.Text = $"Bits: {ControllerBits}";
+				ControllerConnected = true;
+				CheckReady();
+			}
+		}
+
+		void Event_RefreshPortsButton()
+		{
+			foreach (string s in SerialPort.GetPortNames())
+			{
+				SerialPortDropDown.Items.Add(s);
+			}
+		}
+
+		void Event_LoadShowtapeButton()
+		{
+			OpenFileDialog fileDialog = new OpenFileDialog();
+			fileDialog.Filters.Add(new FileFilter("UST Files (.ust)", new string[]{ ".ust" }));
+			fileDialog.Title = "Select Show File.";
+			fileDialog.ShowDialog(this);
+
+			string tempUSTData = File.ReadAllText(fileDialog.FileName);
+			if (!tempUSTData.StartsWith("UST,1,"))
+			{
+				MessageBox.Show("This is not a UST Version 1 showtape.");
+				return;
+			}
+
+			string[] headerData = tempUSTData.Split(';')[0].Split(',');
+			string[] stringyBits = tempUSTData.Split(';')[1].Split(',');
+			AudioData = Convert.FromBase64String(tempUSTData.Split(';')[2]);
+
+			ShowtapeName = headerData[2];
+			ShowtapeBits = headerData[3];
+			ShowtapeStageType = headerData[4];
+			ShowtapeCharacter = headerData[5];
+
+			if (headerData[3] == "256")
+			{
+				MessageBox.Show("Can't load a full stage showtape right now!");
+				return;
+			}
+
+			TimeSpan time = TimeSpan.FromSeconds(stringyBits.Length/60);
+			ShowtapeFormattedLength = time.ToString(@"hh\:mm\:ss");
+
+			ShowtapeNameLabel.Text = $"Show Name: {ShowtapeName}";
+			ShowtapeStageTypeLabel.Text = $"Stage Type: {ShowtapeStageType}";
+			ShowtapeCharacterLabel.Text = $"Character: {ShowtapeCharacter}";
+			ShowtapeBitsLabel.Text = $"Bits: {ShowtapeBits}";
+			ShowtapeLengthLabel.Text = $"Length: {ShowtapeFormattedLength}";
+
+			List<string> tmpOut = new List<string>();
+			foreach (string frame in stringyBits)
+			{
+				int b = int.Parse(frame, NumberStyles.HexNumber);
+				char[] bytesOut = { (char)64 };
+				if (ShowtapeBits == "8")
 				{
-					"Hello World!",
-					// add more controls here
+					bytesOut = new char[]{
+						(char)(64 | ((b & 8) | (b & 4) | (b & 2) | (b & 1))),
+						(char)(64 | (((b & 128) | (b & 64) | (b & 32) | (b & 16)) >> 4))
+					};
 				}
-			};
+				else if (ShowtapeBits == "32")
+				{
+					bytesOut = new char[]{
+						(char)(64 | ((b & 8) | (b & 4) | (b & 2) | (b & 1))),
+						(char)(64 | (((b & 128) | (b & 64) | (b & 32) | (b & 16)) >> 4)),
+						(char)(64 | (((b & 2048) | (b & 1024) | (b & 512) | (b & 256)) >> 8)),
+						(char)(64 | (((b & 32768) | (b & 16384) | (b & 8192) | (b & 4096)) >> 12)),
+						(char)(64 | (((b & 524288) | (b & 262144) | (b & 131072) | (b & 65536)) >> 16)),
+						(char)(64 | (((b & 8388608) | (b & 4194304) | (b & 2097152) | (b & 1048576)) >> 20)),
+						(char)(64 | (((b & 134217728) | (b & 67108864) | (b & 33554432) | (b & 16777216)) >> 24)),
+						(char)(64 | (((b & 2147483648) | (b & 1073741824) | (b & 536870912) | (b & 268435456)) >> 28))
+					};
+				}
+				tmpOut.Add(new string(bytesOut));
+			}
+			Signals = tmpOut.ToArray();
+			ShowtapeLoaded = true;
+			CheckReady();
+		}
 
-			// create a few commands that can be used for the menu and toolbar
-			var clickMe = new Command { MenuText = "Click Me!", ToolBarText = "Click Me!" };
-			clickMe.Executed += (sender, e) => MessageBox.Show(this, "I was clicked!");
-
-			var quitCommand = new Command { MenuText = "Quit", Shortcut = Application.Instance.CommonModifier | Keys.Q };
-			quitCommand.Executed += (sender, e) => Application.Instance.Quit();
-
-			var aboutCommand = new Command { MenuText = "About..." };
-			aboutCommand.Executed += (sender, e) => new AboutDialog().ShowDialog(this);
-
-			// create menu
-			Menu = new MenuBar
+		void Event_PlayButton()
+		{
+			if (Playing) return;
+			if (ShowtapeBits != ControllerBits)
 			{
-				Items =
-				{
-					// File submenu
-					new SubMenuItem { Text = "&File", Items = { clickMe } },
-					// new SubMenuItem { Text = "&Edit", Items = { /* commands/items */ } },
-					// new SubMenuItem { Text = "&View", Items = { /* commands/items */ } },
-				},
-				ApplicationItems =
-				{
-					// application (OS X) or file menu (others)
-					new ButtonMenuItem { Text = "&Preferences..." },
-				},
-				QuitItem = quitCommand,
-				AboutItem = aboutCommand
-			};
+				MessageBox.Show("This showtape does not have the correct amount of bits for your controller.");
+				return;
+			}
+			File.WriteAllBytes("tmp.wav", AudioData);
+			Process.Start("mpv", "tmp.wav");
+			FrameTimer.Start();
 
-			// create toolbar			
-			ToolBar = new ToolBar { Items = { clickMe } };
+			Playing = true;
+
+			PlayButton.Enabled = false;
+			RefreshPortsButton.Enabled = false;
+			LoadShowtapeButton.Enabled = false;
+			SerialPortDropDown.Enabled = false;
+		}
+
+		void Event_StopButton()
+		{
+			FrameTimer.Stop();
+			File.Delete("tmp.wav");
+			Playing = false;
+
+			PlayButton.Enabled = true;
+			RefreshPortsButton.Enabled = true;
+			LoadShowtapeButton.Enabled = true;
+			SerialPortDropDown.Enabled = true;
+			Index = 0;
+		}
+
+		void Event_FrameTimerTick(Object sender, ElapsedEventArgs e)
+		{
+			if (Index >= Signals.Length)
+			{
+				Event_StopButton();
+				return;
+			}
+
+			Port.Write(Signals[Index]);
+			Index += FrameSkip;
 		}
 	}
 }
diff --git a/PC2Player/PC2Player/PC2Player.csproj b/PC2Player/PC2Player/PC2Player.csproj
index f8e4f2a..2542e44 100644
--- a/PC2Player/PC2Player/PC2Player.csproj
+++ b/PC2Player/PC2Player/PC2Player.csproj
@@ -6,6 +6,7 @@
 
   <ItemGroup>
     <PackageReference Include="Eto.Forms" Version="2.9.0" />
+    <PackageReference Include="System.IO.Ports" Version="9.0.4" />
   </ItemGroup>
 
-</Project>
\ No newline at end of file
+</Project>