ai.muna.muna 0.0.45 → 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/MunaMenu.cs +7 -7
- package/Plugins/Android/Muna.aar +0 -0
- package/Plugins/macOS/Function.dylib.meta +26 -25
- package/README.md +1 -1
- package/Runtime/Beta/OpenAI/SpeechService.cs +6 -0
- package/Runtime/Beta/OpenAI/Types.cs +0 -1
- package/Runtime/Beta/Remote/RemotePredictionService.cs +43 -42
- package/Runtime/Beta/{Remote → Types}/Value.cs +2 -2
- package/Runtime/Beta/Types.meta +8 -0
- package/Runtime/Muna.cs +1 -1
- package/Unity/Converters/Color.cs +46 -0
- package/Unity/Converters/Color.cs.meta +2 -0
- package/Unity/Converters/Rect.cs +230 -0
- package/Unity/Converters/Rect.cs.meta +2 -0
- package/Unity/Converters/Vector2.cs +44 -0
- package/Unity/Converters/Vector2.cs.meta +2 -0
- package/Unity/Converters/Vector3.cs +45 -0
- package/Unity/Converters/Vector3.cs.meta +2 -0
- package/Unity/Converters/Vector4.cs +46 -0
- package/Unity/Converters/Vector4.cs.meta +2 -0
- package/Unity/Converters.meta +8 -0
- package/Unity/MunaUnity.cs +67 -18
- package/package.json +1 -1
- /package/Runtime/Beta/{Remote → Types}/Value.cs.meta +0 -0
package/Editor/MunaMenu.cs
CHANGED
|
@@ -12,25 +12,25 @@ namespace Muna.Editor {
|
|
|
12
12
|
|
|
13
13
|
private const int BasePriority = -50;
|
|
14
14
|
|
|
15
|
-
[MenuItem(@"Muna/Muna " + Muna.Version, false, BasePriority)]
|
|
15
|
+
[MenuItem(@"Tools/Muna/Muna " + Muna.Version, false, BasePriority)]
|
|
16
16
|
private static void Version() { }
|
|
17
17
|
|
|
18
|
-
[MenuItem(@"Muna/Muna " + Muna.Version, true, BasePriority)]
|
|
18
|
+
[MenuItem(@"Tools/Muna/Muna " + Muna.Version, true, BasePriority)]
|
|
19
19
|
private static bool EnableVersion() => false;
|
|
20
20
|
|
|
21
|
-
[MenuItem(@"Muna/Get Access Key", false, BasePriority + 1)]
|
|
21
|
+
[MenuItem(@"Tools/Muna/Get Access Key", false, BasePriority + 1)]
|
|
22
22
|
private static void GetAccessKey() => Help.BrowseURL(@"https://muna.ai/settings/developer");
|
|
23
23
|
|
|
24
|
-
[MenuItem(@"Muna/Explore Predictors", false, BasePriority + 2)]
|
|
24
|
+
[MenuItem(@"Tools/Muna/Explore Predictors", false, BasePriority + 2)]
|
|
25
25
|
private static void OpenExplore() => Help.BrowseURL(@"https://muna.ai/explore");
|
|
26
26
|
|
|
27
|
-
[MenuItem(@"Muna/View the Docs", false, BasePriority + 3)]
|
|
27
|
+
[MenuItem(@"Tools/Muna/View the Docs", false, BasePriority + 3)]
|
|
28
28
|
private static void OpenDocs() => Help.BrowseURL(@"https://docs.muna.ai");
|
|
29
29
|
|
|
30
|
-
[MenuItem(@"Muna/Report an Issue", false, BasePriority + 4)]
|
|
30
|
+
[MenuItem(@"Tools/Muna/Report an Issue", false, BasePriority + 4)]
|
|
31
31
|
private static void ReportIssue() => Help.BrowseURL(@"https://github.com/muna-ai/muna-unity");
|
|
32
32
|
|
|
33
|
-
[MenuItem(@"Muna/Clear Predictor Cache", false, BasePriority + 5)]
|
|
33
|
+
[MenuItem(@"Tools/Muna/Clear Predictor Cache", false, BasePriority + 5)]
|
|
34
34
|
private static void ClearPredictorCache() {
|
|
35
35
|
Directory.Delete(
|
|
36
36
|
global::Muna.API.PredictionCacheClient.PredictorCachePath,
|
package/Plugins/Android/Muna.aar
CHANGED
|
Binary file
|
|
@@ -2,7 +2,7 @@ fileFormatVersion: 2
|
|
|
2
2
|
guid: 62aec3fdf35a340f78ab3a076fc7ca3e
|
|
3
3
|
PluginImporter:
|
|
4
4
|
externalObjects: {}
|
|
5
|
-
serializedVersion:
|
|
5
|
+
serializedVersion: 2
|
|
6
6
|
iconMap: {}
|
|
7
7
|
executionOrder: {}
|
|
8
8
|
defineConstraints: []
|
|
@@ -11,52 +11,53 @@ PluginImporter:
|
|
|
11
11
|
isExplicitlyReferenced: 0
|
|
12
12
|
validateReferences: 1
|
|
13
13
|
platformData:
|
|
14
|
-
|
|
14
|
+
- first:
|
|
15
|
+
:
|
|
16
|
+
second:
|
|
15
17
|
enabled: 0
|
|
16
|
-
settings:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Any:
|
|
18
|
+
settings: {}
|
|
19
|
+
- first:
|
|
20
|
+
: Any
|
|
21
|
+
second:
|
|
21
22
|
enabled: 0
|
|
22
23
|
settings:
|
|
23
|
-
Exclude Android: 1
|
|
24
24
|
Exclude Editor: 0
|
|
25
25
|
Exclude Linux64: 1
|
|
26
26
|
Exclude OSXUniversal: 0
|
|
27
|
-
Exclude WebGL: 1
|
|
28
27
|
Exclude Win: 1
|
|
29
28
|
Exclude Win64: 1
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
- first:
|
|
30
|
+
Editor: Editor
|
|
31
|
+
second:
|
|
32
32
|
enabled: 1
|
|
33
33
|
settings:
|
|
34
34
|
CPU: ARM64
|
|
35
35
|
DefaultValueInitialized: true
|
|
36
36
|
OS: OSX
|
|
37
|
-
|
|
37
|
+
- first:
|
|
38
|
+
Standalone: Linux64
|
|
39
|
+
second:
|
|
38
40
|
enabled: 0
|
|
39
41
|
settings:
|
|
40
|
-
CPU:
|
|
41
|
-
|
|
42
|
+
CPU: x86_64
|
|
43
|
+
- first:
|
|
44
|
+
Standalone: OSXUniversal
|
|
45
|
+
second:
|
|
42
46
|
enabled: 1
|
|
43
47
|
settings:
|
|
44
48
|
CPU: ARM64
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
CPU: AnyCPU
|
|
49
|
-
Win64:
|
|
49
|
+
- first:
|
|
50
|
+
Standalone: Win
|
|
51
|
+
second:
|
|
50
52
|
enabled: 0
|
|
51
53
|
settings:
|
|
52
|
-
CPU:
|
|
53
|
-
|
|
54
|
+
CPU: x86
|
|
55
|
+
- first:
|
|
56
|
+
Standalone: Win64
|
|
57
|
+
second:
|
|
54
58
|
enabled: 0
|
|
55
59
|
settings:
|
|
56
|
-
|
|
57
|
-
CPU: AnyCPU
|
|
58
|
-
CompileFlags:
|
|
59
|
-
FrameworkDependencies:
|
|
60
|
+
CPU: x86_64
|
|
60
61
|
userData:
|
|
61
62
|
assetBundleName:
|
|
62
63
|
assetBundleVariant:
|
package/README.md
CHANGED
|
@@ -190,6 +190,12 @@ namespace Muna.Beta.OpenAI {
|
|
|
190
190
|
StreamFormat streamFormat,
|
|
191
191
|
object acceleration
|
|
192
192
|
) => {
|
|
193
|
+
// Check response format
|
|
194
|
+
if (responseFormat != ResponseFormat.PCM)
|
|
195
|
+
throw new ArgumentException($"Cannot create speech with response format {responseFormat} because only `ResponseFormat.PCM` is supported.");
|
|
196
|
+
// Check stream format
|
|
197
|
+
if (streamFormat != StreamFormat.Audio)
|
|
198
|
+
throw new ArgumentException($"Cannot create speech with stream format {streamFormat} because only `StreamFormat.Audio` is supported.");
|
|
193
199
|
// Build prediction input map
|
|
194
200
|
var inputMap = new Dictionary<string, object?> {
|
|
195
201
|
[inputParam.name] = input,
|
|
@@ -69,51 +69,52 @@ namespace Muna.Beta.Services {
|
|
|
69
69
|
|
|
70
70
|
internal RemotePredictionService(MunaClient client) => this.client = client;
|
|
71
71
|
|
|
72
|
-
private async Task<
|
|
73
|
-
null => new
|
|
74
|
-
float x => new
|
|
75
|
-
double x => new
|
|
76
|
-
sbyte x => new
|
|
77
|
-
short x => new
|
|
78
|
-
int x => new
|
|
79
|
-
long x => new
|
|
80
|
-
byte x => new
|
|
81
|
-
ushort x => new
|
|
82
|
-
uint x => new
|
|
83
|
-
ulong x => new
|
|
84
|
-
bool x => new
|
|
85
|
-
float[] x => new
|
|
86
|
-
double[] x => new
|
|
87
|
-
sbyte[] x => new
|
|
88
|
-
short[] x => new
|
|
89
|
-
int[] x => new
|
|
90
|
-
long[] x => new
|
|
91
|
-
byte[] x => new
|
|
92
|
-
ushort[] x => new
|
|
93
|
-
uint[] x => new
|
|
94
|
-
ulong[] x => new
|
|
95
|
-
bool[] x => new
|
|
96
|
-
Tensor<float> x => new
|
|
97
|
-
Tensor<double> x => new
|
|
98
|
-
Tensor<sbyte> x => new
|
|
99
|
-
Tensor<short> x => new
|
|
100
|
-
Tensor<int> x => new
|
|
101
|
-
Tensor<long> x => new
|
|
102
|
-
Tensor<byte> x => new
|
|
103
|
-
Tensor<ushort> x => new
|
|
104
|
-
Tensor<uint> x => new
|
|
105
|
-
Tensor<ulong> x => new
|
|
106
|
-
Tensor<bool> x => new
|
|
107
|
-
string x => new
|
|
108
|
-
IList x => new
|
|
109
|
-
IDictionary x => new
|
|
110
|
-
Image x => new
|
|
111
|
-
Stream x => new
|
|
72
|
+
private async Task<RemoteValue> ToValue(object? value) => value switch { // INCOMPLETE // Image
|
|
73
|
+
null => new() { type = Dtype.Null },
|
|
74
|
+
float x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float32, shape = new int[0] },
|
|
75
|
+
double x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float64, shape = new int[0] },
|
|
76
|
+
sbyte x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int8, shape = new int[0] },
|
|
77
|
+
short x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int16, shape = new int[0] },
|
|
78
|
+
int x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int32, shape = new int[0] },
|
|
79
|
+
long x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int64, shape = new int[0] },
|
|
80
|
+
byte x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint8, shape = new int[0] },
|
|
81
|
+
ushort x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint16, shape = new int[0] },
|
|
82
|
+
uint x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint32, shape = new int[0] },
|
|
83
|
+
ulong x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint64, shape = new int[0] },
|
|
84
|
+
bool x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Bool, shape = new int[0] },
|
|
85
|
+
float[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Float32, shape = new [] { x.Length } },
|
|
86
|
+
double[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Float64, shape = new [] { x.Length } },
|
|
87
|
+
sbyte[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int8, shape = new [] { x.Length } },
|
|
88
|
+
short[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int16, shape = new [] { x.Length } },
|
|
89
|
+
int[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int32, shape = new [] { x.Length } },
|
|
90
|
+
long[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int64, shape = new [] { x.Length } },
|
|
91
|
+
byte[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint8, shape = new [] { x.Length } },
|
|
92
|
+
ushort[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint16, shape = new [] { x.Length } },
|
|
93
|
+
uint[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint32, shape = new [] { x.Length } },
|
|
94
|
+
ulong[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint64, shape = new [] { x.Length } },
|
|
95
|
+
bool[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Bool, shape = new [] { x.Length } },
|
|
96
|
+
Tensor<float> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Float32, shape = x.shape },
|
|
97
|
+
Tensor<double> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Float64, shape = x.shape },
|
|
98
|
+
Tensor<sbyte> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int8, shape = x.shape },
|
|
99
|
+
Tensor<short> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int16, shape = x.shape },
|
|
100
|
+
Tensor<int> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int32, shape = x.shape },
|
|
101
|
+
Tensor<long> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int64, shape = x.shape },
|
|
102
|
+
Tensor<byte> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint8, shape = x.shape },
|
|
103
|
+
Tensor<ushort> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint16, shape = x.shape },
|
|
104
|
+
Tensor<uint> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint32, shape = x.shape },
|
|
105
|
+
Tensor<ulong> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint64, shape = x.shape },
|
|
106
|
+
Tensor<bool> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Bool, shape = x.shape },
|
|
107
|
+
string x => new() { data = await Upload(x.ToStream(), mime: @"text/plain"), type = Dtype.String },
|
|
108
|
+
IList x => new() { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.List },
|
|
109
|
+
IDictionary x => new() { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.Dict },
|
|
110
|
+
Image x => new() { data = "", type = Dtype.Image },
|
|
111
|
+
Stream x => new() { data = await Upload(x), type = Dtype.Binary },
|
|
112
112
|
Enum x => await ToValue(x.ToObject()),
|
|
113
|
+
RemoteValue x => x,
|
|
113
114
|
_ => throw new InvalidOperationException($"Failed to serialize value '{value}' of type `{value.GetType()}` because it is not supported"),
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
private async Task<object?> ToObject(
|
|
117
|
+
private async Task<object?> ToObject(RemoteValue value) { // INCOMPLETE // Image
|
|
117
118
|
if (value.type == Dtype.Null)
|
|
118
119
|
return null;
|
|
119
120
|
using var stream = await Download(value.data!);
|
|
@@ -159,7 +160,7 @@ namespace Muna.Beta.Services {
|
|
|
159
160
|
|
|
160
161
|
[Preserve, Serializable]
|
|
161
162
|
private class RemotePrediction : Prediction {
|
|
162
|
-
public new
|
|
163
|
+
public new RemoteValue[]? results;
|
|
163
164
|
}
|
|
164
165
|
#endregion
|
|
165
166
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
#nullable enable
|
|
7
7
|
|
|
8
|
-
namespace Muna.Beta
|
|
8
|
+
namespace Muna.Beta {
|
|
9
9
|
|
|
10
10
|
using System;
|
|
11
11
|
using System.IO;
|
|
@@ -18,7 +18,7 @@ namespace Muna.Beta.Services {
|
|
|
18
18
|
/// Remote prediction value.
|
|
19
19
|
/// </summary>
|
|
20
20
|
[Preserve, Serializable]
|
|
21
|
-
|
|
21
|
+
public sealed class RemoteValue {
|
|
22
22
|
|
|
23
23
|
/// <summary>
|
|
24
24
|
/// Value URL.
|
package/Runtime/Muna.cs
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Muna
|
|
3
|
+
* Copyright © 2025 NatML Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#nullable enable
|
|
7
|
+
|
|
8
|
+
namespace Muna.Converters {
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using UnityEngine;
|
|
12
|
+
using Newtonsoft.Json;
|
|
13
|
+
using Newtonsoft.Json.Linq;
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Convert an array field to a `Color`.
|
|
17
|
+
/// The array MUST contain three or four numbers.
|
|
18
|
+
/// </summary>
|
|
19
|
+
public sealed class ArrayToColorConverter : JsonConverter<Color> {
|
|
20
|
+
|
|
21
|
+
public override void WriteJson(
|
|
22
|
+
JsonWriter writer,
|
|
23
|
+
Color value,
|
|
24
|
+
JsonSerializer serializer
|
|
25
|
+
) {
|
|
26
|
+
var obj = new JArray { value.r, value.g, value.b, value.a };
|
|
27
|
+
obj.WriteTo(writer);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public override Color ReadJson(
|
|
31
|
+
JsonReader reader,
|
|
32
|
+
Type type,
|
|
33
|
+
Color existing,
|
|
34
|
+
bool hasExisting,
|
|
35
|
+
JsonSerializer s
|
|
36
|
+
) {
|
|
37
|
+
var arr = JArray.Load(reader);
|
|
38
|
+
return new Color(
|
|
39
|
+
r: (float)arr[0],
|
|
40
|
+
g: (float)arr[1],
|
|
41
|
+
b: (float)arr[2],
|
|
42
|
+
a: arr.Count > 3 ? (float)arr[3] : 1f
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Muna
|
|
3
|
+
* Copyright © 2025 NatML Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#nullable enable
|
|
7
|
+
|
|
8
|
+
namespace Muna.Converters {
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using System.Runtime.Serialization;
|
|
12
|
+
using UnityEngine;
|
|
13
|
+
using Newtonsoft.Json;
|
|
14
|
+
using Newtonsoft.Json.Converters;
|
|
15
|
+
using Newtonsoft.Json.Linq;
|
|
16
|
+
|
|
17
|
+
/// <summary>
|
|
18
|
+
/// Box format.
|
|
19
|
+
/// </summary>
|
|
20
|
+
[JsonConverter(typeof(StringEnumConverter))]
|
|
21
|
+
public enum BoxFormat : int {
|
|
22
|
+
/// <summary>
|
|
23
|
+
/// Boxes are represented via corners: x1, y1 being top left and x2, y2 being bottom right.
|
|
24
|
+
/// </summary>
|
|
25
|
+
[EnumMember(Value = @"xyxy")]
|
|
26
|
+
XYXY = 1,
|
|
27
|
+
/// <summary>
|
|
28
|
+
/// Boxes are represented via corner, width and height: x1, y2 being top left.
|
|
29
|
+
/// </summary>
|
|
30
|
+
[EnumMember(Value = @"xywh")]
|
|
31
|
+
XYWH = 2,
|
|
32
|
+
/// <summary>
|
|
33
|
+
/// Boxes are represented via center, width, and height.
|
|
34
|
+
/// </summary>
|
|
35
|
+
[EnumMember(Value = @"cxcywh")]
|
|
36
|
+
CxCyWH = 3,
|
|
37
|
+
/// <summary>
|
|
38
|
+
/// Boxes are represented via corners:
|
|
39
|
+
/// - x1, y1 being top left
|
|
40
|
+
/// - x2, y2 top right
|
|
41
|
+
/// - x3, y3 bottom right
|
|
42
|
+
/// - x4, y4 bottom left
|
|
43
|
+
/// </summary>
|
|
44
|
+
[EnumMember(Value = @"xyxyxyxy")]
|
|
45
|
+
XYXYXYXY = 4,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// <summary>
|
|
49
|
+
/// Convert an object field to a `Rect`.
|
|
50
|
+
/// </summary>
|
|
51
|
+
public sealed class ObjectToRectConverter : JsonConverter<Rect> {
|
|
52
|
+
|
|
53
|
+
private readonly BoxFormat format;
|
|
54
|
+
private readonly string[] fieldNames;
|
|
55
|
+
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Create a converter with the provided box format.
|
|
58
|
+
/// </summary>
|
|
59
|
+
/// <param name="format">Box format to parse.</param>
|
|
60
|
+
public ObjectToRectConverter(BoxFormat format): this(
|
|
61
|
+
format,
|
|
62
|
+
GetReferenceFieldNames(format)
|
|
63
|
+
) { }
|
|
64
|
+
|
|
65
|
+
/// <summary>
|
|
66
|
+
/// Create a converter with the provided box format and corresponding field names.
|
|
67
|
+
/// </summary>
|
|
68
|
+
/// <param name="format">Box format to parse.</param>
|
|
69
|
+
/// <param name="fieldNames">Field names that correspond to the chosen format.</param>
|
|
70
|
+
public ObjectToRectConverter(BoxFormat format, string[] fieldNames) {
|
|
71
|
+
this.format = format;
|
|
72
|
+
this.fieldNames = fieldNames;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public override void WriteJson(
|
|
76
|
+
JsonWriter writer,
|
|
77
|
+
Rect value,
|
|
78
|
+
JsonSerializer serializer
|
|
79
|
+
) {
|
|
80
|
+
var values = GetRectValues(value, format);
|
|
81
|
+
var obj = new JObject();
|
|
82
|
+
for (var i = 0; i < fieldNames.Length; ++i)
|
|
83
|
+
obj[fieldNames[i]] = values[i];
|
|
84
|
+
obj.WriteTo(writer);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public override Rect ReadJson(
|
|
88
|
+
JsonReader reader,
|
|
89
|
+
Type type,
|
|
90
|
+
Rect existing,
|
|
91
|
+
bool hasExisting,
|
|
92
|
+
JsonSerializer s
|
|
93
|
+
) {
|
|
94
|
+
var obj = JObject.Load(reader);
|
|
95
|
+
return format switch {
|
|
96
|
+
BoxFormat.XYXY => Rect.MinMaxRect(
|
|
97
|
+
xmin: GetFieldValue(obj, fieldNames[0]),
|
|
98
|
+
ymin: GetFieldValue(obj, fieldNames[1]),
|
|
99
|
+
xmax: GetFieldValue(obj, fieldNames[2]),
|
|
100
|
+
ymax: GetFieldValue(obj, fieldNames[3])
|
|
101
|
+
),
|
|
102
|
+
BoxFormat.XYWH => new Rect(
|
|
103
|
+
x: GetFieldValue(obj, fieldNames[0]),
|
|
104
|
+
y: GetFieldValue(obj, fieldNames[1]),
|
|
105
|
+
width: GetFieldValue(obj, fieldNames[2]),
|
|
106
|
+
height: GetFieldValue(obj, fieldNames[3])
|
|
107
|
+
),
|
|
108
|
+
BoxFormat.CxCyWH => GetCenterRect(obj),
|
|
109
|
+
BoxFormat.XYXYXYXY => Rect.MinMaxRect(
|
|
110
|
+
xmin: GetFieldValue(obj, fieldNames[0]), // left
|
|
111
|
+
ymin: GetFieldValue(obj, fieldNames[1]), // top
|
|
112
|
+
xmax: GetFieldValue(obj, fieldNames[4]), // right
|
|
113
|
+
ymax: GetFieldValue(obj, fieldNames[5]) // bottom
|
|
114
|
+
),
|
|
115
|
+
_ => throw new JsonSerializationException($"Failed to read `Rect` from JSON object because of unsupported format: {format}")
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private Rect GetCenterRect(JObject obj) {
|
|
120
|
+
var cx = GetFieldValue(obj, fieldNames[0]);
|
|
121
|
+
var cy = GetFieldValue(obj, fieldNames[1]);
|
|
122
|
+
var w = GetFieldValue(obj, fieldNames[2]);
|
|
123
|
+
var h = GetFieldValue(obj, fieldNames[3]);
|
|
124
|
+
var center = new Vector2(cx, cy);
|
|
125
|
+
var size = new Vector2(w, h);
|
|
126
|
+
return new Rect(center - 0.5f * size, size);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private float GetFieldValue(
|
|
130
|
+
JObject obj,
|
|
131
|
+
string name
|
|
132
|
+
) {
|
|
133
|
+
if (!obj.TryGetValue(name, StringComparison.InvariantCulture, out var value))
|
|
134
|
+
throw new JsonSerializationException($"Missing '{name}' field for {format} box.");
|
|
135
|
+
return (float)value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
internal static float[] GetRectValues(
|
|
139
|
+
in Rect rect,
|
|
140
|
+
BoxFormat format
|
|
141
|
+
) => format switch {
|
|
142
|
+
BoxFormat.XYXY => new[] { rect.xMin, rect.yMin, rect.xMax, rect.yMax },
|
|
143
|
+
BoxFormat.XYWH => new[] { rect.xMin, rect.yMin, rect.width, rect.height },
|
|
144
|
+
BoxFormat.CxCyWH => new[] { rect.center.x, rect.center.y, rect.width, rect.height },
|
|
145
|
+
BoxFormat.XYXYXYXY => new[] {
|
|
146
|
+
rect.xMin, rect.yMin, // top-left
|
|
147
|
+
rect.xMax, rect.yMin, // top-right
|
|
148
|
+
rect.xMax, rect.yMax, // bottom-right
|
|
149
|
+
rect.xMin, rect.yMax // bottom-left
|
|
150
|
+
},
|
|
151
|
+
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
private static string[] GetReferenceFieldNames(BoxFormat format) => format switch {
|
|
155
|
+
BoxFormat.XYXY => new[] { @"x_min", @"y_min", @"x_max", @"y_max" },
|
|
156
|
+
BoxFormat.XYWH => new[] { @"x", @"y", @"width", @"height" },
|
|
157
|
+
BoxFormat.CxCyWH => new[] { @"x_center", @"y_center", @"width", @"height" },
|
|
158
|
+
BoxFormat.XYXYXYXY => new[] { @"x1", @"y1", @"x2", @"y2", @"x3", @"y3", @"x4", @"y4" },
|
|
159
|
+
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// <summary>
|
|
164
|
+
/// Convert an array field to a `Rect`.
|
|
165
|
+
/// The array MUST contain numbers, and have the required count depending on the box format.
|
|
166
|
+
/// </summary>
|
|
167
|
+
public sealed class ArrayToRectConverter : JsonConverter<Rect> {
|
|
168
|
+
|
|
169
|
+
private readonly BoxFormat format;
|
|
170
|
+
|
|
171
|
+
/// <summary>
|
|
172
|
+
/// Create a converter for the provided box format.
|
|
173
|
+
/// </summary>
|
|
174
|
+
/// <param name="format">Box format to parse.</param>
|
|
175
|
+
public ArrayToRectConverter(BoxFormat format) => this.format = format;
|
|
176
|
+
|
|
177
|
+
public override void WriteJson(
|
|
178
|
+
JsonWriter writer,
|
|
179
|
+
Rect value,
|
|
180
|
+
JsonSerializer serializer
|
|
181
|
+
) {
|
|
182
|
+
var values = ObjectToRectConverter.GetRectValues(value, format);
|
|
183
|
+
var arr = new JArray(values);
|
|
184
|
+
arr.WriteTo(writer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public override Rect ReadJson(
|
|
188
|
+
JsonReader reader,
|
|
189
|
+
Type type,
|
|
190
|
+
Rect existing,
|
|
191
|
+
bool hasExisting,
|
|
192
|
+
JsonSerializer s
|
|
193
|
+
) {
|
|
194
|
+
var arr = JArray.Load(reader);
|
|
195
|
+
var expected = ExpectedCount(format);
|
|
196
|
+
if (arr.Count != expected)
|
|
197
|
+
throw new JsonSerializationException($"Expected {expected} numbers for {format} box but got {arr.Count}.");
|
|
198
|
+
var data = arr.ToObject<float[]>()!;
|
|
199
|
+
return format switch {
|
|
200
|
+
BoxFormat.XYXY => Rect.MinMaxRect(
|
|
201
|
+
xmin: data[0],
|
|
202
|
+
ymin: data[1],
|
|
203
|
+
xmax: data[2],
|
|
204
|
+
ymax: data[3]
|
|
205
|
+
),
|
|
206
|
+
BoxFormat.XYWH => new Rect(
|
|
207
|
+
x: data[0],
|
|
208
|
+
y: data[1],
|
|
209
|
+
width: data[2],
|
|
210
|
+
height: data[3]
|
|
211
|
+
),
|
|
212
|
+
BoxFormat.CxCyWH => new Rect(
|
|
213
|
+
x: data[0] - 0.5f * data[2],
|
|
214
|
+
y: data[1] * 0.5f - data[3],
|
|
215
|
+
width: data[2],
|
|
216
|
+
height: data[3]
|
|
217
|
+
),
|
|
218
|
+
_ => throw new JsonSerializationException($"Failed to read `Rect` from JSON array because of unsupported format: {format}")
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private static int ExpectedCount(BoxFormat format) => format switch {
|
|
223
|
+
BoxFormat.XYXY => 4,
|
|
224
|
+
BoxFormat.XYWH => 4,
|
|
225
|
+
BoxFormat.CxCyWH => 4,
|
|
226
|
+
BoxFormat.XYXYXYXY => 8,
|
|
227
|
+
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Muna
|
|
3
|
+
* Copyright © 2025 NatML Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#nullable enable
|
|
7
|
+
|
|
8
|
+
namespace Muna.Converters {
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using UnityEngine;
|
|
12
|
+
using Newtonsoft.Json;
|
|
13
|
+
using Newtonsoft.Json.Linq;
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Convert an array field to a `Vector2`.
|
|
17
|
+
/// The array MUST contain exactly two numbers.
|
|
18
|
+
/// </summary>
|
|
19
|
+
public sealed class ArrayToVector2Converter : JsonConverter<Vector2> {
|
|
20
|
+
|
|
21
|
+
public override void WriteJson(
|
|
22
|
+
JsonWriter writer,
|
|
23
|
+
Vector2 value,
|
|
24
|
+
JsonSerializer serializer
|
|
25
|
+
) {
|
|
26
|
+
var obj = new JArray { value.x, value.y };
|
|
27
|
+
obj.WriteTo(writer);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public override Vector2 ReadJson(
|
|
31
|
+
JsonReader reader,
|
|
32
|
+
Type type,
|
|
33
|
+
Vector2 existing,
|
|
34
|
+
bool hasExisting,
|
|
35
|
+
JsonSerializer s
|
|
36
|
+
) {
|
|
37
|
+
var arr = JArray.Load(reader);
|
|
38
|
+
return new Vector2(
|
|
39
|
+
x: (float)arr[0],
|
|
40
|
+
y: (float)arr[1]
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Muna
|
|
3
|
+
* Copyright © 2025 NatML Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#nullable enable
|
|
7
|
+
|
|
8
|
+
namespace Muna.Converters {
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using UnityEngine;
|
|
12
|
+
using Newtonsoft.Json;
|
|
13
|
+
using Newtonsoft.Json.Linq;
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Convert an array field to a `Vector3`.
|
|
17
|
+
/// The array MUST contain exactly three numbers.
|
|
18
|
+
/// </summary>
|
|
19
|
+
public sealed class ArrayToVector3Converter : JsonConverter<Vector3> {
|
|
20
|
+
|
|
21
|
+
public override void WriteJson(
|
|
22
|
+
JsonWriter writer,
|
|
23
|
+
Vector3 value,
|
|
24
|
+
JsonSerializer serializer
|
|
25
|
+
) {
|
|
26
|
+
var obj = new JArray { value.x, value.y, value.z };
|
|
27
|
+
obj.WriteTo(writer);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public override Vector3 ReadJson(
|
|
31
|
+
JsonReader reader,
|
|
32
|
+
Type type,
|
|
33
|
+
Vector3 existing,
|
|
34
|
+
bool hasExisting,
|
|
35
|
+
JsonSerializer s
|
|
36
|
+
) {
|
|
37
|
+
var arr = JArray.Load(reader);
|
|
38
|
+
return new Vector3(
|
|
39
|
+
x: (float)arr[0],
|
|
40
|
+
y: (float)arr[1],
|
|
41
|
+
z: (float)arr[2]
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Muna
|
|
3
|
+
* Copyright © 2025 NatML Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#nullable enable
|
|
7
|
+
|
|
8
|
+
namespace Muna.Converters {
|
|
9
|
+
|
|
10
|
+
using System;
|
|
11
|
+
using UnityEngine;
|
|
12
|
+
using Newtonsoft.Json;
|
|
13
|
+
using Newtonsoft.Json.Linq;
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Convert an array field to a `Vector4`.
|
|
17
|
+
/// The array MUST contain exactly four numbers.
|
|
18
|
+
/// </summary>
|
|
19
|
+
public sealed class ArrayToVector4Converter : JsonConverter<Vector4> {
|
|
20
|
+
|
|
21
|
+
public override void WriteJson(
|
|
22
|
+
JsonWriter writer,
|
|
23
|
+
Vector4 value,
|
|
24
|
+
JsonSerializer serializer
|
|
25
|
+
) {
|
|
26
|
+
var obj = new JArray { value.x, value.y, value.z, value.w };
|
|
27
|
+
obj.WriteTo(writer);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public override Vector4 ReadJson(
|
|
31
|
+
JsonReader reader,
|
|
32
|
+
Type type,
|
|
33
|
+
Vector4 existing,
|
|
34
|
+
bool hasExisting,
|
|
35
|
+
JsonSerializer s
|
|
36
|
+
) {
|
|
37
|
+
var arr = JArray.Load(reader);
|
|
38
|
+
return new Vector4(
|
|
39
|
+
x: (float)arr[0],
|
|
40
|
+
y: (float)arr[1],
|
|
41
|
+
z: (float)arr[2],
|
|
42
|
+
w: (float)arr[3]
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/Unity/MunaUnity.cs
CHANGED
|
@@ -20,9 +20,11 @@ namespace Muna {
|
|
|
20
20
|
|
|
21
21
|
using System;
|
|
22
22
|
using System.Collections.Generic;
|
|
23
|
+
using System.Text.RegularExpressions;
|
|
23
24
|
using UnityEngine;
|
|
24
25
|
using Unity.Collections.LowLevel.Unsafe;
|
|
25
26
|
using API;
|
|
27
|
+
using Beta.OpenAI;
|
|
26
28
|
using Internal;
|
|
27
29
|
|
|
28
30
|
/// <summary>
|
|
@@ -61,34 +63,34 @@ namespace Muna {
|
|
|
61
63
|
this Texture2D texture,
|
|
62
64
|
byte[]? pixelBuffer = null
|
|
63
65
|
) {
|
|
66
|
+
// Check texture
|
|
64
67
|
if (texture == null)
|
|
65
68
|
throw new ArgumentNullException(nameof(texture));
|
|
69
|
+
// Check that texture is readable
|
|
66
70
|
if (!texture.isReadable)
|
|
67
71
|
throw new InvalidOperationException(@"Texture cannot be converted to a Muna image because it is not readable");
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
[TextureFormat.Alpha8] = 1,
|
|
71
|
-
[TextureFormat.RGB24] = 3,
|
|
72
|
-
[TextureFormat.RGBA32] = 4,
|
|
73
|
-
};
|
|
74
|
-
if (!FormatChannelMap.TryGetValue(texture.format, out var channels))
|
|
75
|
-
throw new InvalidOperationException($"Texture cannot be converted to a Muna image because it has unsupported format: {texture.format}");
|
|
72
|
+
// Allocate buffer
|
|
73
|
+
var channels = TextureFormatToImageChannels.GetValueOrDefault(texture.format, 4);
|
|
76
74
|
var rowStride = texture.width * channels;
|
|
77
75
|
var bufferSize = rowStride * texture.height;
|
|
78
76
|
pixelBuffer ??= new byte[bufferSize];
|
|
79
77
|
if (pixelBuffer.Length < bufferSize)
|
|
80
78
|
throw new InvalidOperationException($"Texture cannot be converted to a Muna image because pixel buffer length was expected to be greater than or equal to {bufferSize} but got {pixelBuffer.Length}");
|
|
81
|
-
|
|
79
|
+
// Copy
|
|
80
|
+
var colorData = !TextureFormatToImageChannels.ContainsKey(texture.format) ? texture.GetPixels32() : null;
|
|
81
|
+
fixed (void* dst = pixelBuffer, colors = colorData) {
|
|
82
|
+
var src = colors == null ? texture.GetRawTextureData<byte>().GetUnsafePtr() : colors;
|
|
82
83
|
UnsafeUtility.MemCpyStride(
|
|
83
84
|
dst,
|
|
84
85
|
rowStride,
|
|
85
|
-
(byte*)
|
|
86
|
+
(byte*)src + (rowStride * (texture.height - 1)),
|
|
86
87
|
-rowStride,
|
|
87
88
|
rowStride,
|
|
88
89
|
texture.height
|
|
89
90
|
);
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
}
|
|
92
|
+
// Return
|
|
93
|
+
return new Image(pixelBuffer, texture.width, texture.height, channels);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/// <summary>
|
|
@@ -101,12 +103,7 @@ namespace Muna {
|
|
|
101
103
|
this Image image,
|
|
102
104
|
Texture2D? texture = null
|
|
103
105
|
) {
|
|
104
|
-
|
|
105
|
-
[1] = TextureFormat.Alpha8,
|
|
106
|
-
[3] = TextureFormat.RGB24,
|
|
107
|
-
[4] = TextureFormat.RGBA32
|
|
108
|
-
};
|
|
109
|
-
if (!ChannelFormatMap.TryGetValue(image.channels, out var format))
|
|
106
|
+
if (!ImageChannelsToTextureFormat.TryGetValue(image.channels, out var format))
|
|
110
107
|
throw new InvalidOperationException($"Image cannot be converted to a Texture2D because it has unsupported channel count: {image.channels}");
|
|
111
108
|
texture = texture != null ? texture : new Texture2D(image.width, image.height, format, false);
|
|
112
109
|
if (texture.width != image.width || texture.height != image.height || texture.format != format)
|
|
@@ -124,6 +121,58 @@ namespace Muna {
|
|
|
124
121
|
texture.Apply();
|
|
125
122
|
return texture;
|
|
126
123
|
}
|
|
124
|
+
|
|
125
|
+
/// <summary>
|
|
126
|
+
/// Convert a `BinaryData` containing linear PCM audio into an audio clip.
|
|
127
|
+
/// </summary>
|
|
128
|
+
/// <param name="data">Binary data containing linear PCM audio.</param>
|
|
129
|
+
/// <returns>Audio clip.</returns>
|
|
130
|
+
public static unsafe AudioClip ToAudioClip(this BinaryData data) {
|
|
131
|
+
// Check that this contains LPCM data
|
|
132
|
+
if (string.IsNullOrEmpty(data.MediaType) || !data.MediaType.StartsWith(@"audio/pcm"))
|
|
133
|
+
throw new ArgumentException($"Failed to create audio clip from binary data because media type was expected to be 'audio/pcm' but got: '{data.MediaType}'");
|
|
134
|
+
// Match sample rate and channel count
|
|
135
|
+
var rateMatch = Regex.Match(data.MediaType, @"rate=(\d+)");
|
|
136
|
+
var channelsMatch = Regex.Match(data.MediaType, @"channels=(\d+)");
|
|
137
|
+
if (!rateMatch.Success || !channelsMatch.Success)
|
|
138
|
+
throw new ArgumentException($"Failed to create audio clip from binary data because media type is invalid: '{data.MediaType}'");
|
|
139
|
+
// Parse
|
|
140
|
+
if (!int.TryParse(rateMatch.Groups[1].Value, out var sampleRate))
|
|
141
|
+
throw new ArgumentException($"Failed to create audio clip from binary data because sample rate is invalid: '{rateMatch.Value}'");
|
|
142
|
+
if (!int.TryParse(channelsMatch.Groups[1].Value, out var channelCount))
|
|
143
|
+
throw new ArgumentException($"Failed to create audio clip from binary data because channel count is invalid: '{channelsMatch.Value}'");
|
|
144
|
+
// Create clip
|
|
145
|
+
var sampleCount = data.Length / sizeof(float);
|
|
146
|
+
var frameCount = sampleCount / channelCount;
|
|
147
|
+
var clip = AudioClip.Create(
|
|
148
|
+
"audio",
|
|
149
|
+
lengthSamples: frameCount,
|
|
150
|
+
channels: channelCount,
|
|
151
|
+
frequency: sampleRate,
|
|
152
|
+
stream: false
|
|
153
|
+
);
|
|
154
|
+
// Copy data
|
|
155
|
+
var samples = new float[sampleCount];
|
|
156
|
+
Buffer.BlockCopy(data.ToArray(), 0, samples, 0, data.Length);
|
|
157
|
+
clip.SetData(samples, 0);
|
|
158
|
+
// Return
|
|
159
|
+
return clip;
|
|
160
|
+
}
|
|
161
|
+
#endregion
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
#region --Operations--
|
|
165
|
+
private static readonly Dictionary<TextureFormat, int> TextureFormatToImageChannels = new() {
|
|
166
|
+
[TextureFormat.R8] = 1,
|
|
167
|
+
[TextureFormat.Alpha8] = 1,
|
|
168
|
+
[TextureFormat.RGB24] = 3,
|
|
169
|
+
[TextureFormat.RGBA32] = 4,
|
|
170
|
+
};
|
|
171
|
+
private static readonly Dictionary<int, TextureFormat> ImageChannelsToTextureFormat = new() {
|
|
172
|
+
[1] = TextureFormat.Alpha8,
|
|
173
|
+
[3] = TextureFormat.RGB24,
|
|
174
|
+
[4] = TextureFormat.RGBA32,
|
|
175
|
+
};
|
|
127
176
|
#endregion
|
|
128
177
|
}
|
|
129
178
|
}
|
package/package.json
CHANGED
|
File without changes
|