Compare commits

...
5 Commits
10 changed files with 2142 additions and 145 deletions

No files matched your search

File diff suppressed because it is too large. Load diff
+558
View File
@@ -0,0 +1,558 @@
import pickle
from pathlib import Path
import cv2
import mediapipe as mp
import numpy as np
from mediapipe.tasks.python import vision
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.svm import SVC
WRIST = 0
THUMB_CMC = 1
THUMB_MCP = 2
THUMB_IP = 3
THUMB_TIP = 4
INDEX_MCP = 5
INDEX_PIP = 6
INDEX_DIP = 7
INDEX_TIP = 8
MIDDLE_MCP = 9
MIDDLE_PIP = 10
MIDDLE_DIP = 11
MIDDLE_TIP = 12
RING_MCP = 13
RING_PIP = 14
RING_DIP = 15
RING_TIP = 16
PINKY_MCP = 17
PINKY_PIP = 18
PINKY_DIP = 19
PINKY_TIP = 20
HAND_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 4),
(0, 5), (5, 6), (6, 7), (7, 8),
(0, 9), (9, 10), (10, 11), (11, 12),
(0, 13), (13, 14), (14, 15), (15, 16),
(0, 17), (17, 18), (18, 19), (19, 20),
(5, 9), (9, 13), (13, 17),
]
DEFAULT_UNKNOWN_THRESHOLD = 0.7
DEFAULT_KNN_K = 3
DEFAULT_RF_TREES = 200
DEFAULT_NUM_HANDS = 2
def landmark_to_pixel(lm, width, height):
return int(lm.x * width), int(lm.y * height)
def vec2_from_landmarks(hand_landmarks):
return np.array([[lm.x, lm.y] for lm in hand_landmarks], dtype=np.float32)
def vec3_from_world_landmarks(world_landmarks):
return np.array([[lm.x, lm.y, lm.z] for lm in world_landmarks], dtype=np.float32)
def hand_scale_2d(points2d):
s = np.linalg.norm(points2d[MIDDLE_MCP] - points2d[WRIST])
return max(float(s), 1e-6)
def hand_scale_3d(points3d):
s = np.linalg.norm(points3d[MIDDLE_MCP] - points3d[WRIST])
return max(float(s), 1e-6)
def normalize_2d_features(hand_landmarks):
pts = vec2_from_landmarks(hand_landmarks)
origin = pts[WRIST].copy()
pts = pts - origin
scale = hand_scale_2d(pts + origin)
pts = pts / scale
return pts.flatten().astype(np.float32)
def normalize_3d_features(world_landmarks):
pts = vec3_from_world_landmarks(world_landmarks)
origin = pts[WRIST].copy()
pts = pts - origin
scale = hand_scale_3d(pts + origin)
pts = pts / scale
return pts.flatten().astype(np.float32)
def angle_between(v1, v2):
n1 = np.linalg.norm(v1)
n2 = np.linalg.norm(v2)
if n1 < 1e-6 or n2 < 1e-6:
return 0.0
c = np.dot(v1, v2) / (n1 * n2)
c = np.clip(c, -1.0, 1.0)
return float(np.arccos(c))
def joint_angle(points, a, b, c):
ba = points[a] - points[b]
bc = points[c] - points[b]
return angle_between(ba, bc)
def extract_angle_features(world_landmarks):
pts = vec3_from_world_landmarks(world_landmarks)
thumb_angle_1 = joint_angle(pts, THUMB_CMC, THUMB_MCP, THUMB_IP)
thumb_angle_2 = joint_angle(pts, THUMB_MCP, THUMB_IP, THUMB_TIP)
index_angle_1 = joint_angle(pts, INDEX_MCP, INDEX_PIP, INDEX_DIP)
index_angle_2 = joint_angle(pts, INDEX_PIP, INDEX_DIP, INDEX_TIP)
middle_angle_1 = joint_angle(pts, MIDDLE_MCP, MIDDLE_PIP, MIDDLE_DIP)
middle_angle_2 = joint_angle(pts, MIDDLE_PIP, MIDDLE_DIP, MIDDLE_TIP)
ring_angle_1 = joint_angle(pts, RING_MCP, RING_PIP, RING_DIP)
ring_angle_2 = joint_angle(pts, RING_PIP, RING_DIP, RING_TIP)
pinky_angle_1 = joint_angle(pts, PINKY_MCP, PINKY_PIP, PINKY_DIP)
pinky_angle_2 = joint_angle(pts, PINKY_PIP, PINKY_DIP, PINKY_TIP)
wrist_to_thumb = pts[THUMB_TIP] - pts[WRIST]
wrist_to_index = pts[INDEX_TIP] - pts[WRIST]
wrist_to_middle = pts[MIDDLE_TIP] - pts[WRIST]
wrist_to_ring = pts[RING_TIP] - pts[WRIST]
wrist_to_pinky = pts[PINKY_TIP] - pts[WRIST]
spread_thumb_index = angle_between(wrist_to_thumb, wrist_to_index)
spread_index_middle = angle_between(wrist_to_index, wrist_to_middle)
spread_middle_ring = angle_between(wrist_to_middle, wrist_to_ring)
spread_ring_pinky = angle_between(wrist_to_ring, wrist_to_pinky)
feats = np.array([
thumb_angle_1, thumb_angle_2,
index_angle_1, index_angle_2,
middle_angle_1, middle_angle_2,
ring_angle_1, ring_angle_2,
pinky_angle_1, pinky_angle_2,
spread_thumb_index,
spread_index_middle,
spread_middle_ring,
spread_ring_pinky,
], dtype=np.float32)
return feats
def extract_feature_family(hand_landmarks, world_landmarks, family_name):
family_name = family_name.upper()
if family_name == "2D":
return normalize_2d_features(hand_landmarks)
if family_name == "3D":
return normalize_3d_features(world_landmarks)
if family_name == "ANGLES":
return extract_angle_features(world_landmarks)
raise ValueError(f"Unknown feature family: {family_name}")
def load_pickle(path):
with open(path, "rb") as f:
return pickle.load(f)
def family_key_from_name(name):
name = name.upper()
if name == "2D":
return "features_2d"
if name == "3D":
return "features_3d"
if name == "ANGLES":
return "features_angles"
raise ValueError(f"Unknown family name: {name}")
def ranking_key_from_method(method):
method = method.lower()
if method == "f":
return "rank_f"
if method == "mi":
return "rank_mi"
if method == "fisher":
return "rank_fisher"
raise ValueError(f"Unknown ranking method: {method}")
def flatten_training_data(data, family_key):
X = []
y = []
for label in sorted(data.keys()):
for sample in data[label]:
X.append(np.asarray(sample[family_key], dtype=np.float32))
y.append(label)
X = np.vstack([x.reshape(1, -1) for x in X])
y = np.asarray(y)
return X, y
def build_classifier(classifier_type):
classifier_type = classifier_type.lower()
if classifier_type == "knn":
return KNeighborsClassifier(n_neighbors=DEFAULT_KNN_K)
if classifier_type == "svm":
return SVC(kernel="rbf", probability=True)
if classifier_type == "rf":
return RandomForestClassifier(
n_estimators=DEFAULT_RF_TREES,
random_state=42,
class_weight=None,
)
if classifier_type == "logreg":
return LogisticRegression(
max_iter=2000,
solver="lbfgs",
)
raise ValueError(f"Unknown classifier type: {classifier_type}")
def get_selected_feature_indices(analysis, family_key, ranking_method, num_features):
rank_key = ranking_key_from_method(ranking_method)
rank = analysis[family_key][rank_key]
return np.asarray(rank[:num_features], dtype=np.int32)
def train_model(data, analysis, feature_family, classifier_type, ranking_method, num_features):
family_key = family_key_from_name(feature_family)
X, y = flatten_training_data(data, family_key)
selected_idx = get_selected_feature_indices(analysis, family_key, ranking_method, num_features)
X_sel = X[:, selected_idx]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_sel)
label_encoder = LabelEncoder()
y_enc = label_encoder.fit_transform(y)
clf = build_classifier(classifier_type)
clf.fit(X_scaled, y_enc)
return {
"classifier": clf,
"scaler": scaler,
"label_encoder": label_encoder,
"selected_idx": selected_idx,
"family_key": family_key,
}
def predict_with_confidence(model_bundle, feature_vector, unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD):
selected_idx = model_bundle["selected_idx"]
clf = model_bundle["classifier"]
scaler = model_bundle["scaler"]
label_encoder = model_bundle["label_encoder"]
x = feature_vector[selected_idx].reshape(1, -1)
x = scaler.transform(x)
pred_id = clf.predict(x)[0]
pred_label = label_encoder.inverse_transform([pred_id])[0]
if hasattr(clf, "predict_proba"):
probs = clf.predict_proba(x)[0]
conf = float(np.max(probs))
else:
conf = 1.0
if conf < unknown_threshold:
return "UNKNOWN", conf
return pred_label, conf
def create_hand_landmarker(
model_path="hand_landmarker.task",
num_hands=DEFAULT_NUM_HANDS,
min_hand_detection_confidence=0.5,
min_hand_presence_confidence=0.5,
min_tracking_confidence=0.5,
):
BaseOptions = mp.tasks.BaseOptions
HandLandmarkerOptions = vision.HandLandmarkerOptions
VisionRunningMode = vision.RunningMode
options = HandLandmarkerOptions(
base_options=BaseOptions(model_asset_path=model_path),
running_mode=VisionRunningMode.IMAGE,
num_hands=num_hands,
min_hand_detection_confidence=min_hand_detection_confidence,
min_hand_presence_confidence=min_hand_presence_confidence,
min_tracking_confidence=min_tracking_confidence,
)
return vision.HandLandmarker.create_from_options(options)
def load_gesture_model_bundle(
data_pickle="gesture_samples.pkl",
analysis_pickle="gesture_feature_analysis.pkl",
feature_family="ANGLES",
classifier_type="knn",
ranking_method="fisher",
num_features=10,
unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD,
hand_model_path="hand_landmarker.task",
):
if not Path(data_pickle).exists():
raise FileNotFoundError(f"Missing {data_pickle}")
if not Path(analysis_pickle).exists():
raise FileNotFoundError(f"Missing {analysis_pickle}")
data = load_pickle(data_pickle)
analysis = load_pickle(analysis_pickle)
model_bundle = train_model(
data=data,
analysis=analysis,
feature_family=feature_family,
classifier_type=classifier_type,
ranking_method=ranking_method,
num_features=num_features,
)
hand_landmarker = create_hand_landmarker(model_path=hand_model_path)
return {
"model_bundle": model_bundle,
"hand_landmarker": hand_landmarker,
"feature_family": feature_family,
"classifier_type": classifier_type,
"ranking_method": ranking_method,
"num_features": num_features,
"unknown_threshold": unknown_threshold,
}
def _put_text_with_background(frame, text, origin, font_scale=0.6, text_color=(255, 255, 255), bg_color=(0, 0, 0)):
x, y = origin
font = cv2.FONT_HERSHEY_SIMPLEX
thickness = 2
(text_w, text_h), baseline = cv2.getTextSize(text, font, font_scale, thickness)
top_left = (x, max(0, y - text_h - baseline - 6))
bottom_right = (x + text_w + 6, max(0, y + 4))
cv2.rectangle(frame, top_left, bottom_right, bg_color, -1)
cv2.putText(
frame,
text,
(x + 3, y - 3),
font,
font_scale,
text_color,
thickness,
cv2.LINE_AA,
)
def _ray_to_frame_bounds(start_point, direction_vector, frame_w, frame_h):
x0, y0 = start_point
dx, dy = direction_vector
norm = float(np.hypot(dx, dy))
if norm < 1e-6:
return None
dx /= norm
dy /= norm
candidates = []
if abs(dx) > 1e-6:
for x_bound in (0, frame_w - 1):
t = (x_bound - x0) / dx
if t <= 0:
continue
y = y0 + t * dy
if 0 <= y <= frame_h - 1:
candidates.append((t, (int(x_bound), int(round(y)))))
if abs(dy) > 1e-6:
for y_bound in (0, frame_h - 1):
t = (y_bound - y0) / dy
if t <= 0:
continue
x = x0 + t * dx
if 0 <= x <= frame_w - 1:
candidates.append((t, (int(round(x)), int(y_bound))))
if not candidates:
return None
candidates.sort(key=lambda item: item[0])
return candidates[0][1]
def draw_pointing_ray(frame, hand_landmarks, crop_x, crop_y, crop_w, crop_h, color=(255, 255, 0), thickness=3):
index_tip_x, index_tip_y = landmark_to_pixel(hand_landmarks[INDEX_TIP], crop_w, crop_h)
index_mcp_x, index_mcp_y = landmark_to_pixel(hand_landmarks[INDEX_MCP], crop_w, crop_h)
start_point = (index_tip_x + crop_x, index_tip_y + crop_y)
direction = (
(index_tip_x - index_mcp_x),
(index_tip_y - index_mcp_y),
)
end_point = _ray_to_frame_bounds(start_point, direction, frame.shape[1], frame.shape[0])
if end_point is None:
return
cv2.line(frame, start_point, end_point, color, thickness)
def draw_hand_on_frame(frame, hand_landmarks, crop_x, crop_y, crop_w, crop_h, label_text=""):
points = []
for a, b in HAND_CONNECTIONS:
x1, y1 = landmark_to_pixel(hand_landmarks[a], crop_w, crop_h)
x2, y2 = landmark_to_pixel(hand_landmarks[b], crop_w, crop_h)
x1 += crop_x
y1 += crop_y
x2 += crop_x
y2 += crop_y
cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
for i, lm in enumerate(hand_landmarks):
x, y = landmark_to_pixel(lm, crop_w, crop_h)
x += crop_x
y += crop_y
points.append((x, y))
color = (0, 0, 255) if i in [THUMB_TIP, INDEX_TIP, MIDDLE_TIP, RING_TIP, PINKY_TIP] else (255, 255, 0)
radius = 5 if i in [THUMB_TIP, INDEX_TIP, MIDDLE_TIP, RING_TIP, PINKY_TIP] else 3
cv2.circle(frame, (x, y), radius, color, -1)
if label_text and points:
label_x = max(0, min(x for x, _ in points))
label_y = max(20, min(y for _, y in points) - 10)
_put_text_with_background(frame, label_text, (label_x, label_y), font_scale=0.55)
class GestureRecognizer:
def __init__(
self,
data_pickle="gesture_samples.pkl",
analysis_pickle="gesture_feature_analysis.pkl",
feature_family="ANGLES",
classifier_type="knn",
ranking_method="fisher",
num_features=10,
unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD,
hand_model_path="hand_landmarker.task",
):
bundle = load_gesture_model_bundle(
data_pickle=data_pickle,
analysis_pickle=analysis_pickle,
feature_family=feature_family,
classifier_type=classifier_type,
ranking_method=ranking_method,
num_features=num_features,
unknown_threshold=unknown_threshold,
hand_model_path=hand_model_path,
)
self.model_bundle = bundle["model_bundle"]
self.hand_landmarker = bundle["hand_landmarker"]
self.feature_family = bundle["feature_family"]
self.classifier_type = bundle["classifier_type"]
self.ranking_method = bundle["ranking_method"]
self.num_features = bundle["num_features"]
self.unknown_threshold = bundle["unknown_threshold"]
def detect_gestures(self, crop_bgr):
crop_rgb = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=crop_rgb)
result = self.hand_landmarker.detect(mp_image)
detections = []
if not result.hand_landmarks or not result.hand_world_landmarks:
return detections
hand_count = min(len(result.hand_landmarks), len(result.hand_world_landmarks))
for hand_index in range(hand_count):
hand_landmarks = result.hand_landmarks[hand_index]
world_landmarks = result.hand_world_landmarks[hand_index]
feature_vector = extract_feature_family(
hand_landmarks=hand_landmarks,
world_landmarks=world_landmarks,
family_name=self.feature_family,
)
pred_label, conf = predict_with_confidence(
self.model_bundle,
feature_vector,
unknown_threshold=self.unknown_threshold,
)
handedness_text = ""
if result.handedness and len(result.handedness) > hand_index and len(result.handedness[hand_index]) > 0:
handedness_text = result.handedness[hand_index][0].category_name
detections.append(
{
"hand_landmarks": hand_landmarks,
"label": pred_label,
"confidence": conf,
"handedness": handedness_text,
}
)
return detections
def draw_gesture(self, frame, detection, crop_x, crop_y, crop_w, crop_h):
label = detection["label"]
confidence = detection["confidence"]
handedness_text = detection["handedness"]
if handedness_text:
label_text = f"{handedness_text}: {label} {confidence:.2f}"
else:
label_text = f"{label} {confidence:.2f}"
draw_hand_on_frame(
frame,
detection["hand_landmarks"],
crop_x,
crop_y,
crop_w,
crop_h,
label_text=label_text,
)
if label.upper() == "A":
draw_pointing_ray(
frame,
detection["hand_landmarks"],
crop_x,
crop_y,
crop_w,
crop_h,
)
def close(self):
self.hand_landmarker.close()
+45 -16
View File
@@ -112,9 +112,11 @@ namespace Wolf
logger.Log($"Spieler {p.Name} wacht auf und sieht sich seine Rolle an.");
ttsClient.Read(logger.getLastLine(), readHistory);
ShowRole(p);
Task.Delay(5000).Wait();
logger.Log($"Spieler {p.Name} hat seine Rolle gesehen und geht wieder schlafen.");
ttsClient.Read(logger.getLastLine(), readHistory);
getConfirmationEveryoneAsleep();
}
Console.Clear();
@@ -360,46 +362,73 @@ namespace Wolf
{
try
{
int res = logicClient.GetPlayerId(targets, pois).WaitAsync(TimeSpan.FromSeconds(10)).Result;
int res = logicClient.GetPlayerId(targets, pois).WaitAsync(TimeSpan.FromSeconds(30)).Result;
if (res >= 0)
{
return res;
}
logger.Log("GetPlayerId returned invalid value, defaulting to 0");
return 0;
}
catch
catch (TimeoutException)
{
logger.Log("GetPlayerId timed out after 30 seconds");
return 0;
}
catch (Exception ex)
{
logger.Log($"GetPlayerId error: {ex.Message}");
return 0;
}
return 0;
}
public bool getConfirmation(List<Player> players)
{
try
{
bool res = logicClient.GetConfirmation(players).WaitAsync(TimeSpan.FromSeconds(10)).Result;
if (res)
{
return true;
}
bool res = logicClient.GetConfirmation(players).WaitAsync(TimeSpan.FromSeconds(30)).Result;
return res;
}
catch
catch (TimeoutException)
{
logger.Log("GetConfirmation timed out after 30 seconds, defaulting to false");
return false;
}
catch (Exception ex)
{
logger.Log($"GetConfirmation error: {ex.Message}");
return false;
}
return false;
}
public bool getConfirmationEveryoneAsleep()
{
try
int maxRetries = 50;
int attempt = 0;
while (attempt < maxRetries)
{
return logicClient.GetConfirmationEveryoneAsleep().WaitAsync(TimeSpan.FromSeconds(30)).Result;
}
catch
{
return false;
attempt++;
try
{
bool result = logicClient.GetConfirmationEveryoneAsleep().WaitAsync(TimeSpan.FromSeconds(60)).Result;
if (result)
{
return true;
}
logger.Log($"GetConfirmationEveryoneAsleep returned false on attempt {attempt}, retrying...");
}
catch (TimeoutException)
{
logger.Log($"GetConfirmationEveryoneAsleep timed out on attempt {attempt}/{maxRetries}, retrying...");
}
catch (Exception ex)
{
logger.Log($"GetConfirmationEveryoneAsleep error on attempt {attempt}/{maxRetries}: {ex.Message}, retrying...");
}
}
logger.Log($"GetConfirmationEveryoneAsleep failed after {maxRetries} attempts, defaulting to false");
return false;
}
}
+239 -125
View File
@@ -8,65 +8,103 @@ using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace Wolf
{
internal class OSCClient
{
UdpClient udpClientSend;
UdpClient udpClientReceive;
private UdpClient udpClientSend;
private UdpClient udpClientReceive;
private readonly SemaphoreSlim _requestSemaphore;
private string _lastSentAddress = "";
private object[] _lastSentArguments = Array.Empty<object>();
public OSCClient(string hostname, int port)
{
// 1. Lokalen Port binden (für Empfang)
udpClientSend = new UdpClient(hostname, port);
udpClientReceive = new UdpClient(new IPEndPoint(IPAddress.Any, port));
_requestSemaphore = new SemaphoreSlim(1, 1);
}
public async Task SendMessage(string endpoint)
{
var message = new OscMessage(new Address(endpoint));
await udpClientSend.SendMessageAsync(message);
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(new Address(endpoint));
_lastSentAddress = endpoint;
_lastSentArguments = Array.Empty<object>();
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task SpeakId(int textId)
{
var message = new OscMessage(
address: new Address("/speak"),
arguments: new object[]
{
textId
});
await udpClientSend.SendMessageAsync(message);
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(
address: new Address("/speak"),
arguments: new object[]
{
textId
});
_lastSentAddress = "/speak";
_lastSentArguments = new object[] { textId };
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task Read(string text, bool readStory = true)
{
string endpoint = readStory ? "/read_story" : "/read";
var message = new OscMessage(
address: new Address(endpoint),
arguments: new object[]
{
text, 2
});
await udpClientSend.SendMessageAsync(message);
await _requestSemaphore.WaitAsync();
try
{
string endpoint = readStory ? "/read_story" : "/read";
var message = new OscMessage(
address: new Address(endpoint),
arguments: new object[]
{
text, 2
});
_lastSentAddress = endpoint;
_lastSentArguments = new object[] { text, 2 };
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task SendMessageWithArguments(string endpoint, params object[] param)
{
var message = new OscMessage(
address: new Address(endpoint),
arguments: param
);
await udpClientSend.SendMessageAsync(message);
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(
address: new Address(endpoint),
arguments: param
);
_lastSentAddress = endpoint;
_lastSentArguments = param;
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
@@ -100,20 +138,101 @@ namespace Wolf
}
}
/// <summary>
/// Generische Methode zur Ausführung von Request-Response-Kommunikation mit Retry-Logik.
/// </summary>
private async Task<T> ExecuteRequestWithRetry<T>(
string expectedAddress,
OscMessage requestMessage,
Func<OscMessage, T> responseParser,
int maxRetries = 50,
int timeoutSeconds = 20)
{
await _requestSemaphore.WaitAsync();
try
{
_lastSentAddress = requestMessage.Address.Value;
_lastSentArguments = requestMessage.Arguments.ToArray();
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
// Anfrage senden
await udpClientSend.SendMessageAsync(requestMessage);
// Auf Response warten mit Timeout
var receiveTask = udpClientReceive.ReceiveMessageAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
var completed = await Task.WhenAny(receiveTask, timeoutTask);
if (completed == receiveTask)
{
var response = await receiveTask;
// Prüfen, ob es die erwartete Antwort ist
if (response.Address.Value == expectedAddress)
{
try
{
return responseParser(response);
}
catch (Exception parseEx)
{
// Argument-Parsing-Fehler, ignorieren und nächste Anfrage senden
System.Diagnostics.Debug.WriteLine($"Parse error on attempt {attempt}: {parseEx.Message}");
continue;
}
}
// Falsche Antwort → ignorieren und weiter versuchen
}
// Timeout → erneut versuchen
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Request attempt {attempt} failed: {ex.Message}");
}
}
// Alle Versuche fehlgeschlagen
throw new TimeoutException($"Request to {expectedAddress} failed after {maxRetries} attempts");
}
finally
{
_requestSemaphore.Release();
}
}
public async Task<List<int>> GetAllIds()
{
SendMessage("/getAllIds");
var response = await udpClientReceive.ReceiveMessageAsync();
if (response.Address.Value == "/getAllIds")
var message = new OscMessage(new Address("/getAllIds"));
try
{
List<int> playerIds = new List<int>();
foreach (var argument in response.Arguments)
{
playerIds.Add((int)argument);
}
return playerIds;
var result = await ExecuteRequestWithRetry(
expectedAddress: "/getAllIds",
requestMessage: message,
responseParser: response =>
{
var playerIds = new List<int>();
foreach (var argument in response.Arguments)
{
playerIds.Add((int)argument);
}
return playerIds;
});
return result;
}
catch (TimeoutException)
{
return new List<int>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetAllIds error: {ex.Message}");
return new List<int>();
}
return new List<int>();
}
// < 0 = no confirmation; 0 = confirm; > 0 = player id
@@ -139,7 +258,6 @@ namespace Wolf
public async Task<int> GetPlayerId(List<Player> targets, List<Player> pois)
{
//return (int) targets.FirstOrDefault().id;
// Arrays extrahieren
var arr2 = targets.Select(p => p.id).ToArray();
var arr1 = pois.Select(p => p.id).ToArray();
@@ -156,121 +274,117 @@ namespace Wolf
foreach (var id in arr2)
args.Add(id);
// Nachricht senden
// Nachricht vorbereiten
var message = new OscMessage(
new Address("/getPlayerID"),
args.ToArray()
);
int maxRetries = 50;
int timeoutSeconds = 20;
// Anfrage senden
await udpClientSend.SendMessageAsync(message);
for (int attempt = 1; attempt <= maxRetries; attempt++)
try
{
// Timeout für Receive
var receiveTask = udpClientReceive.ReceiveMessageAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
var completed = await Task.WhenAny(receiveTask, timeoutTask);
if (completed == receiveTask)
{
var response = await receiveTask;
// Prüfen, ob es die erwartete Antwort ist
if (response.Address.Value == "/getPlayerID")
var result = await ExecuteRequestWithRetry(
expectedAddress: "/getPlayerID",
requestMessage: message,
responseParser: response =>
{
return (int)response.Arguments.ElementAt(0);
}
// Falsche Antwort → ignorieren und weiter versuchen
continue;
}
await udpClientSend.SendMessageAsync(message);
if (response.Arguments.Count() > 0)
{
return (int)response.Arguments.ElementAt(0);
}
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 20);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetPlayerId error: {ex.Message}");
return -1;
}
return -1;
}
public async Task<bool> GetConfirmation(List<Player> players)
{
int maxRetries = 50;
int timeoutSeconds = 20;
var args = new List<object>();
foreach (var p in players)
{
args.Add(p.id);
}
// Anfrage senden
var message = new OscMessage(
new Address("/confirmation"),
args.ToArray()
new Address("/confirmation"),
args.ToArray()
);
await udpClientSend.SendMessageAsync(message);
//await udpClientReceive.ReceiveMessageAsync();
for (int attempt = 1; attempt <= maxRetries; attempt++)
try
{
// Timeout für Receive
var receiveTask = udpClientReceive.ReceiveMessageAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
var completed = await Task.WhenAny(receiveTask, timeoutTask);
if (completed == receiveTask)
{
var response = await receiveTask;
// Prüfen, ob es die erwartete Antwort ist
if (response.Address.Value == "/confirmation")
var result = await ExecuteRequestWithRetry(
expectedAddress: "/confirmation",
requestMessage: message,
responseParser: response =>
{
if (response.Arguments.ElementAt(0) is OscFalse)
if (response.Arguments.Count() > 0)
{
return false;
var firstArg = response.Arguments.ElementAt(0);
if (firstArg is OscFalse)
{
return false;
}
if (firstArg is OscTrue)
{
return true;
}
throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}");
}
if (response.Arguments.ElementAt(0) is OscTrue)
{
return true;
}
}
// Falsche Antwort → ignorieren und weiter versuchen
continue;
}
await udpClientSend.SendMessageAsync(message);
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 20);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetConfirmation error: {ex.Message}");
return false;
}
return false;
}
public async Task<bool> GetConfirmationEveryoneAsleep()
{
return true;
SendMessage("/everyoneAsleep");
var message = new OscMessage(new Address("/everyoneAsleep"));
var response = await udpClientReceive.ReceiveMessageAsync();
if (response.Address.Value == "/everyoneAsleep")
try
{
if (response.Arguments.ElementAt(0) is OscFalse)
{
return false;
}
if (response.Arguments.ElementAt(0) is OscTrue)
{
return true;
}
var result = await ExecuteRequestWithRetry(
expectedAddress: "/everyoneAsleep",
requestMessage: message,
responseParser: response =>
{
if (response.Arguments.Count() > 0)
{
var firstArg = response.Arguments.ElementAt(0);
if (firstArg is OscFalse)
{
return false;
}
if (firstArg is OscTrue)
{
return true;
}
throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}");
}
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 30);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetConfirmationEveryoneAsleep error: {ex.Message}");
return false;
}
return false;
}
}
}
+7 -4
View File
@@ -11,17 +11,20 @@ namespace Wolf
{
//OSCClient logicClient = new OSCClient("127.0.0.1", 9000);
OSCClient logicClient = new OSCClient("100.81.182.220", 9000);
OSCClient ttsClient = new OSCClient("100.81.196.62", 9001);
OSCClient logicClient = new OSCClient("100.83.197.1", 9000);
OSCClient ttsClient = new OSCClient("100.83.246.218", 9001);
//await logicClient.SendMessageWithArguments("/test", "HILFE!!! . . Rettet mich aus diesem Laptop!!! . . . Ich bin eingesperrt!!!!!");
//logicClient.ReceiveMessageWithoutArguments().Wait();
//List<int> ids = await logicClient.GetAllIds();
List<int> ids = await logicClient.GetAllIds();
ids.Add(71);
ids.Add(72);
ids.Add(73);
Game game = new Game(logicClient, ttsClient, new Logger());
List<int> ids = new List<int>{ 31, 27, 71, 72, 73, 74};
//List<int> ids = new List<int>{ 31, 27, 71, 72, 73, 74};
foreach (int id in ids)
{
View File
Whitespace-only changes.
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
06_osc_receiver.py
Very simple OSC receiver demo.
It shows that different OSC addresses are mapped
to different handler functions.
"""
from pathlib import Path
from pythonosc.dispatcher import Dispatcher
from pythonosc.osc_server import BlockingOSCUDPServer
from piper_module import Narrator, Language, Quality, LLM
IP = "100.83.246.218"
PORT = 9001
def on_read(address, *args):
print(f"on_read() address={address} args={args}")
narrator.read(str(args[0]))
def on_write_story(address, *args):
print(f"on_write_story() address={address} args={args}")
story = narrator.write_story(str(args[0]), str(args[1]))
print(story)
def on_read_story(address, *args):
print(f"on_read_story() address={address} args={args}")
story = narrator.write_story(str(args[0]), str(args[1]))
narrator.read(story)
def main() -> None:
dispatcher = Dispatcher()
dispatcher.map("/read", on_read)
dispatcher.map("/write_story", on_write_story)
dispatcher.map("/read_story", on_read_story)
server = BlockingOSCUDPServer((IP, PORT), dispatcher)
global narrator
llm = LLM("qwen2.5-7b-instruct-1m","http://100.83.153.234:1234/api/v1/chat" )
narrator = Narrator(Language.de_DE, "thorsten", Path("C:\Interaktion\WerWolf\piper-voices"), llm)
print(f"Listening for OSC on {IP}:{PORT}")
print("Try sending:")
print(" /read")
print(" /write_story")
print(" /read_story")
server.serve_forever()
if __name__ == "__main__":
main()
Binary file not shown.
+126
View File
@@ -0,0 +1,126 @@
import wave
from piper import PiperVoice
import pygame
import time
import os
import sys
import pyaudio
import requests
from pathlib import Path
from enum import Enum
class Language(Enum):
en_US = "en_US"
de_DE = "de_DE"
en_GB = "en_GB"
class Quality(Enum):
high = 3
medium = 2
low = 1
x_low = 0
class LLM:
def __init__(self, model: str, url: str):
self.url = url
self.model = model
class Narrator:
def __init__(self, language: Language, name: str, repo: Path, llm: LLM):
for quality in Quality:
filename = f"{language.name}-{name}-{quality.name}.onnx"
self.path = repo / language.name / name / quality.name / filename
print("Teste:", self.path)
if self.path.exists():
print("Gefunden:", self.path)
break
else:
raise FileNotFoundError("Keine passende Voice-Datei gefunden")
print("load voice")
self.voice = PiperVoice.load(self.path)
# for narrator texts -> connect to LLM
print(" create llm")
self.llm = llm
# Stream audio chunks and play them immediately
def read(self, text: str):
p = pyaudio.PyAudio()
stream = None
for audio_chunk in self.voice.synthesize(text):
# Stream erst öffnen, wenn erster Chunk da ist
if stream is None:
stream = p.open(
format=p.get_format_from_width(audio_chunk.sample_width),
channels=audio_chunk.sample_channels,
rate=audio_chunk.sample_rate,
output=True
)
# Audio sofort abspielen
stream.write(audio_chunk.audio_int16_bytes)
if stream:
stream.stop_stream()
stream.close()
p.terminate()
# play selected audiofile and delete it after playing
def read_audio(self, number: int):
#setup
filename = f"output{number}.wav"
pygame.mixer.init()
#play next audio
sound = pygame.mixer.Sound(filename)
channel = sound.play()
# wait until the sound has finished playing
while channel.get_busy():
time.sleep(0.1)
# delete now obsolete file
os.remove(filename)
pygame.mixer.quit()
# create wavefile from text
def create_audio(self, texts: list[str]):
for i, text in enumerate(texts):
filename = f"output{i}.wav"
with wave.open(f"output{i}.wav", "wb") as wav_file:
self.voice.synthesize_wav(text, wav_file)
# create a story from a base text
def write_story(self, text, count):
session = requests.Session()
session.trust_env = False
payload = {
"model" : self.llm.model,
"input": "Formuliere:" + text +" zu einem erzählerischen Sprachtext um. Nicht mehr als " + count + " Sätze. Handlungsaufforderungen sollen klar heraushörbar sein."
}
response = requests.post(self.llm.url, json=payload, timeout=30)
response.raise_for_status()
data = response.json()
story = data["output"][0]["content"]
return story
+4
View File
@@ -11,4 +11,8 @@
<PackageReference Include="CoreOSC" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Sprachausgabe\" />
</ItemGroup>
</Project>