@aj-archipelago/cortex 1.3.5 → 1.3.7

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.
Files changed (94) hide show
  1. package/helper-apps/cortex-autogen/agents.py +31 -2
  2. package/helper-apps/cortex-realtime-voice-server/.env.sample +6 -0
  3. package/helper-apps/cortex-realtime-voice-server/README.md +22 -0
  4. package/helper-apps/cortex-realtime-voice-server/bun.lockb +0 -0
  5. package/helper-apps/cortex-realtime-voice-server/client/bun.lockb +0 -0
  6. package/helper-apps/cortex-realtime-voice-server/client/index.html +12 -0
  7. package/helper-apps/cortex-realtime-voice-server/client/package.json +65 -0
  8. package/helper-apps/cortex-realtime-voice-server/client/postcss.config.js +6 -0
  9. package/helper-apps/cortex-realtime-voice-server/client/public/favicon.ico +0 -0
  10. package/helper-apps/cortex-realtime-voice-server/client/public/index.html +43 -0
  11. package/helper-apps/cortex-realtime-voice-server/client/public/logo192.png +0 -0
  12. package/helper-apps/cortex-realtime-voice-server/client/public/logo512.png +0 -0
  13. package/helper-apps/cortex-realtime-voice-server/client/public/manifest.json +25 -0
  14. package/helper-apps/cortex-realtime-voice-server/client/public/robots.txt +3 -0
  15. package/helper-apps/cortex-realtime-voice-server/client/public/sounds/connect.mp3 +0 -0
  16. package/helper-apps/cortex-realtime-voice-server/client/public/sounds/disconnect.mp3 +0 -0
  17. package/helper-apps/cortex-realtime-voice-server/client/src/App.test.tsx +9 -0
  18. package/helper-apps/cortex-realtime-voice-server/client/src/App.tsx +126 -0
  19. package/helper-apps/cortex-realtime-voice-server/client/src/SettingsModal.tsx +207 -0
  20. package/helper-apps/cortex-realtime-voice-server/client/src/chat/Chat.tsx +553 -0
  21. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubble.tsx +22 -0
  22. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubbleLeft.tsx +22 -0
  23. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubbleRight.tsx +21 -0
  24. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatMessage.tsx +27 -0
  25. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatMessageInput.tsx +74 -0
  26. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatTile.tsx +211 -0
  27. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/SoundEffects.ts +56 -0
  28. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavPacker.ts +112 -0
  29. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavRecorder.ts +571 -0
  30. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavStreamPlayer.ts +290 -0
  31. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/analysis/AudioAnalysis.ts +186 -0
  32. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/analysis/constants.ts +59 -0
  33. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/worklets/AudioProcessor.ts +214 -0
  34. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/worklets/StreamProcessor.ts +183 -0
  35. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/AudioVisualizer.tsx +151 -0
  36. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/CopyButton.tsx +32 -0
  37. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ImageOverlay.tsx +166 -0
  38. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/MicrophoneVisualizer.tsx +95 -0
  39. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ScreenshotCapture.tsx +116 -0
  40. package/helper-apps/cortex-realtime-voice-server/client/src/chat/hooks/useWindowResize.ts +27 -0
  41. package/helper-apps/cortex-realtime-voice-server/client/src/chat/utils/audio.ts +33 -0
  42. package/helper-apps/cortex-realtime-voice-server/client/src/index.css +20 -0
  43. package/helper-apps/cortex-realtime-voice-server/client/src/index.tsx +19 -0
  44. package/helper-apps/cortex-realtime-voice-server/client/src/logo.svg +1 -0
  45. package/helper-apps/cortex-realtime-voice-server/client/src/react-app-env.d.ts +1 -0
  46. package/helper-apps/cortex-realtime-voice-server/client/src/reportWebVitals.ts +15 -0
  47. package/helper-apps/cortex-realtime-voice-server/client/src/setupTests.ts +5 -0
  48. package/helper-apps/cortex-realtime-voice-server/client/src/utils/logger.ts +45 -0
  49. package/helper-apps/cortex-realtime-voice-server/client/tailwind.config.js +14 -0
  50. package/helper-apps/cortex-realtime-voice-server/client/tsconfig.json +30 -0
  51. package/helper-apps/cortex-realtime-voice-server/client/vite.config.ts +22 -0
  52. package/helper-apps/cortex-realtime-voice-server/index.ts +19 -0
  53. package/helper-apps/cortex-realtime-voice-server/package.json +28 -0
  54. package/helper-apps/cortex-realtime-voice-server/src/ApiServer.ts +35 -0
  55. package/helper-apps/cortex-realtime-voice-server/src/SocketServer.ts +737 -0
  56. package/helper-apps/cortex-realtime-voice-server/src/Tools.ts +520 -0
  57. package/helper-apps/cortex-realtime-voice-server/src/cortex/expert.ts +29 -0
  58. package/helper-apps/cortex-realtime-voice-server/src/cortex/image.ts +29 -0
  59. package/helper-apps/cortex-realtime-voice-server/src/cortex/memory.ts +91 -0
  60. package/helper-apps/cortex-realtime-voice-server/src/cortex/reason.ts +29 -0
  61. package/helper-apps/cortex-realtime-voice-server/src/cortex/search.ts +30 -0
  62. package/helper-apps/cortex-realtime-voice-server/src/cortex/style.ts +31 -0
  63. package/helper-apps/cortex-realtime-voice-server/src/cortex/utils.ts +95 -0
  64. package/helper-apps/cortex-realtime-voice-server/src/cortex/vision.ts +34 -0
  65. package/helper-apps/cortex-realtime-voice-server/src/realtime/client.ts +499 -0
  66. package/helper-apps/cortex-realtime-voice-server/src/realtime/realtimeTypes.ts +279 -0
  67. package/helper-apps/cortex-realtime-voice-server/src/realtime/socket.ts +27 -0
  68. package/helper-apps/cortex-realtime-voice-server/src/realtime/transcription.ts +75 -0
  69. package/helper-apps/cortex-realtime-voice-server/src/realtime/utils.ts +33 -0
  70. package/helper-apps/cortex-realtime-voice-server/src/utils/logger.ts +45 -0
  71. package/helper-apps/cortex-realtime-voice-server/src/utils/prompt.ts +81 -0
  72. package/helper-apps/cortex-realtime-voice-server/tsconfig.json +28 -0
  73. package/package.json +1 -1
  74. package/pathways/basePathway.js +3 -1
  75. package/pathways/system/entity/memory/sys_memory_manager.js +3 -0
  76. package/pathways/system/entity/memory/sys_memory_update.js +44 -45
  77. package/pathways/system/entity/memory/sys_read_memory.js +86 -6
  78. package/pathways/system/entity/memory/sys_search_memory.js +66 -0
  79. package/pathways/system/entity/shared/sys_entity_constants.js +2 -2
  80. package/pathways/system/entity/sys_entity_continue.js +2 -1
  81. package/pathways/system/entity/sys_entity_start.js +10 -0
  82. package/pathways/system/entity/sys_generator_expert.js +0 -2
  83. package/pathways/system/entity/sys_generator_memory.js +31 -0
  84. package/pathways/system/entity/sys_generator_voice_sample.js +36 -0
  85. package/pathways/system/entity/sys_router_tool.js +13 -10
  86. package/pathways/system/sys_parse_numbered_object_list.js +1 -1
  87. package/server/pathwayResolver.js +41 -31
  88. package/server/plugins/azureVideoTranslatePlugin.js +28 -16
  89. package/server/plugins/claude3VertexPlugin.js +0 -9
  90. package/server/plugins/gemini15ChatPlugin.js +18 -5
  91. package/server/plugins/modelPlugin.js +27 -6
  92. package/server/plugins/openAiChatPlugin.js +10 -8
  93. package/server/plugins/openAiVisionPlugin.js +56 -0
  94. package/tests/memoryfunction.test.js +73 -1
@@ -0,0 +1,74 @@
1
+ import React, { useCallback, useRef, useState } from "react";
2
+
3
+ type ChatMessageInputProps = {
4
+ placeholder: string;
5
+ onSend?: (message: string) => void;
6
+ onStartStop?: () => void;
7
+ };
8
+
9
+ export const ChatMessageInput = ({
10
+ placeholder,
11
+ onSend,
12
+ onStartStop
13
+ }: ChatMessageInputProps) => {
14
+ const [message, setMessage] = useState("");
15
+ const [isRecording, setIsRecording] = useState(false);
16
+ const inputRef = useRef<HTMLInputElement>(null);
17
+
18
+ const handleSend = useCallback(() => {
19
+ if (!onSend) {
20
+ return;
21
+ }
22
+ if (message === "") {
23
+ return;
24
+ }
25
+
26
+ onSend(message);
27
+ setMessage("");
28
+ }, [onSend, message]);
29
+
30
+ const handleStartStop = useCallback(() => {
31
+ if (!onStartStop) {
32
+ return;
33
+ }
34
+ setIsRecording(!isRecording);
35
+ onStartStop();
36
+ }, [isRecording, onStartStop]);
37
+
38
+ return (
39
+ <div className="flex flex-col gap-2 px-2 py-2 border-t border-t-gray-800">
40
+ <button
41
+ className="bg-transparent p-1.5 rounded-lg border-2 border-gray-600"
42
+ onClick={handleStartStop}>
43
+ <p className="text-s uppercase text-gray-200">{isRecording ? 'Stop' : 'Start'}</p>
44
+ </button>
45
+ <div className="flex flex-row pt-3 items-center">
46
+ <input
47
+ ref={inputRef}
48
+ className={`grow text-gray-200 bg-transparent border-2 border-gray-600 p-1.5 rounded-lg`}
49
+ style={{
50
+ paddingLeft: message.length > 0 ? 12 : 24,
51
+ caretShape: "block",
52
+ }}
53
+ placeholder={placeholder}
54
+ value={message}
55
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
56
+ e.target?.value && setMessage(e.target.value);
57
+ }}
58
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
59
+ if (e.key === "Enter") {
60
+ handleSend();
61
+ }
62
+ }}
63
+ />
64
+ <button
65
+ disabled={message.length === 0 || !onSend || !isRecording}
66
+ onClick={handleSend}
67
+ className={'bg-transparent p-1.5 ms-2 rounded-lg border-2 border-gray-600'}
68
+ >
69
+ <p className="text-s uppercase text-gray-200">Send</p>
70
+ </button>
71
+ </div>
72
+ </div>
73
+ );
74
+ };
@@ -0,0 +1,211 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import ReactMarkdown, { Components } from 'react-markdown';
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4
+ import { nightOwl } from 'react-syntax-highlighter/dist/esm/styles/prism';
5
+ import remarkGfm from 'remark-gfm';
6
+ import remarkMath from 'remark-math';
7
+ import rehypeKatex from 'rehype-katex';
8
+ import rehypeRaw from 'rehype-raw';
9
+ import SendIcon from '@mui/icons-material/Send';
10
+ import 'katex/dist/katex.min.css';
11
+ import { CopyButton } from './components/CopyButton';
12
+
13
+ // Define the code component props interface
14
+ interface CodeComponentProps {
15
+ node?: any;
16
+ inline?: boolean;
17
+ className?: string;
18
+ children?: React.ReactNode;
19
+ }
20
+
21
+ // Add types for math components
22
+ interface MathComponentProps {
23
+ value: string;
24
+ }
25
+
26
+ // Extend Components type to include math components
27
+ interface MarkdownComponents extends Components {
28
+ math?: (props: MathComponentProps) => JSX.Element;
29
+ inlineMath?: (props: MathComponentProps) => JSX.Element;
30
+ }
31
+
32
+ export type ChatMessage = {
33
+ id: string;
34
+ isSelf: boolean;
35
+ name: string;
36
+ message: string;
37
+ timestamp: number;
38
+ };
39
+
40
+ export type ChatTileProps = {
41
+ messages: ChatMessage[];
42
+ onSend: (message: string) => void;
43
+ isConnected?: boolean;
44
+ };
45
+
46
+ const MessageContent = ({ message }: { message: string }) => {
47
+ if (message.match(/^https?:\/\/.*\.(jpg|jpeg|png|gif|webp)$/i)) {
48
+ return (
49
+ <img
50
+ src={message}
51
+ alt="Shared"
52
+ className="max-w-full rounded-lg max-h-64 object-contain"
53
+ />
54
+ );
55
+ }
56
+
57
+ const components: MarkdownComponents = {
58
+ code({ inline, className, children }: CodeComponentProps) {
59
+ const match = /language-(\w+)|^(\w+)/.exec(className || '');
60
+ const language = match ? match[1] || match[2] : '';
61
+
62
+ if (!inline && language) {
63
+ return (
64
+ <div className="rounded-lg overflow-hidden my-1.5 relative group">
65
+ <div className="flex justify-between items-center px-2 py-1 bg-gray-800/50">
66
+ <div className="text-xs text-gray-400 select-none">
67
+ {language}
68
+ </div>
69
+ <div className="opacity-0 group-hover:opacity-100 transition-opacity duration-200">
70
+ <CopyButton text={String(children)} />
71
+ </div>
72
+ </div>
73
+ <SyntaxHighlighter
74
+ style={nightOwl}
75
+ language={language}
76
+ customStyle={{
77
+ margin: 0,
78
+ background: 'transparent',
79
+ fontSize: '0.75rem',
80
+ }}
81
+ >
82
+ {String(children).replace(/\n$/, '')}
83
+ </SyntaxHighlighter>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <code className="bg-gray-800/50 px-1 py-0.5 rounded text-cyan-300 text-xs inline-block">
90
+ {String(children)}
91
+ </code>
92
+ );
93
+ },
94
+ // Style links
95
+ a: ({ node, children, ...props }) => (
96
+ <a
97
+ {...props}
98
+ className="text-cyan-400 hover:text-cyan-300 underline text-sm"
99
+ target="_blank"
100
+ rel="noopener noreferrer"
101
+ >
102
+ {children}
103
+ </a>
104
+ ),
105
+ // Style tables
106
+ table: ({ node, ...props }) => (
107
+ <div className="overflow-x-auto my-2">
108
+ <table {...props} className="border-collapse table-auto w-full text-sm" />
109
+ </div>
110
+ ),
111
+ th: ({ node, ...props }) => (
112
+ <th {...props} className="border border-gray-600 px-3 py-1.5 bg-gray-800 text-sm" />
113
+ ),
114
+ td: ({ node, ...props }) => (
115
+ <td {...props} className="border border-gray-600 px-3 py-1.5 text-sm" />
116
+ ),
117
+ // Add special styling for math blocks
118
+ math: ({ value }) => (
119
+ <div className="py-1.5 overflow-x-auto text-sm">
120
+ <span>{value}</span>
121
+ </div>
122
+ ),
123
+ inlineMath: ({ value }) => (
124
+ <span className="px-1 text-sm">{value}</span>
125
+ ),
126
+ };
127
+
128
+ return (
129
+ <ReactMarkdown
130
+ className="prose prose-invert prose-sm max-w-none text-sm [&>p]:text-sm [&>ul]:text-sm [&>ol]:text-sm"
131
+ remarkPlugins={[remarkGfm, remarkMath]}
132
+ rehypePlugins={[rehypeRaw, rehypeKatex]}
133
+ components={components}
134
+ >
135
+ {message}
136
+ </ReactMarkdown>
137
+ );
138
+ };
139
+
140
+ export function ChatTile({ messages, onSend, isConnected = false }: ChatTileProps) {
141
+ const [message, setMessage] = useState('');
142
+ const messagesEndRef = useRef<HTMLDivElement>(null);
143
+
144
+ // Sort messages by timestamp before rendering
145
+ const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
146
+
147
+ const handleSubmit = (e: React.FormEvent) => {
148
+ e.preventDefault();
149
+ if (message.trim()) {
150
+ onSend(message.trim());
151
+ setMessage('');
152
+ }
153
+ };
154
+
155
+ useEffect(() => {
156
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
157
+ }, [messages]);
158
+
159
+ return (
160
+ <div className="h-full flex flex-col">
161
+ {/* Messages container - scrollable */}
162
+ <div className="flex-1 h-0 overflow-y-auto">
163
+ <div className="p-4 space-y-4">
164
+ {sortedMessages.map((msg) => (
165
+ <div key={msg.id} className="flex flex-col">
166
+ <div className={`w-full rounded-lg p-2.5 relative group ${
167
+ msg.isSelf
168
+ ? 'bg-blue-500/30 border border-blue-500/20'
169
+ : 'bg-gray-700/30 border border-gray-600/20'
170
+ }`}>
171
+ <div className="flex justify-between items-start">
172
+ <div className="text-2xs text-gray-400 mb-1">{msg.name}</div>
173
+ <div className="opacity-0 group-hover:opacity-100 transition-opacity duration-200">
174
+ <CopyButton text={msg.message} />
175
+ </div>
176
+ </div>
177
+ <MessageContent message={msg.message} />
178
+ </div>
179
+ </div>
180
+ ))}
181
+ <div ref={messagesEndRef} aria-hidden="true" />
182
+ </div>
183
+ </div>
184
+
185
+ {/* Input area - fixed height */}
186
+ <div className="flex-none h-[68px] border-t border-gray-700/50 p-4 bg-gray-900/30 backdrop-blur-sm">
187
+ <form onSubmit={handleSubmit} className="flex space-x-2">
188
+ <input
189
+ type="text"
190
+ value={message}
191
+ onChange={(e) => setMessage(e.target.value)}
192
+ className={`flex-grow bg-gray-800/50 border border-gray-700/50 rounded-lg px-3 py-1.5
193
+ text-sm text-gray-100 focus:outline-none focus:ring-2 focus:ring-cyan-500/50
194
+ ${!isConnected && 'opacity-50 cursor-not-allowed'}`}
195
+ placeholder={isConnected ? "Type a message..." : "Connect to send messages..."}
196
+ disabled={!isConnected}
197
+ />
198
+ <button
199
+ type="submit"
200
+ disabled={!isConnected}
201
+ className={`p-1.5 rounded-lg bg-gradient-to-r from-blue-500 to-cyan-500
202
+ ${isConnected ? 'hover:from-blue-600 hover:to-cyan-600' : 'opacity-50 cursor-not-allowed'}
203
+ shadow-lg shadow-cyan-500/20`}
204
+ >
205
+ <SendIcon sx={{ fontSize: 16 }} />
206
+ </button>
207
+ </form>
208
+ </div>
209
+ </div>
210
+ );
211
+ }
@@ -0,0 +1,56 @@
1
+ export class SoundEffects {
2
+ private static audioContext: AudioContext | null = null;
3
+ private static connectBuffer: AudioBuffer | null = null;
4
+ private static disconnectBuffer: AudioBuffer | null = null;
5
+
6
+ private static async getAudioContext() {
7
+ if (!this.audioContext) {
8
+ this.audioContext = new AudioContext();
9
+ }
10
+ return this.audioContext;
11
+ }
12
+
13
+ private static async loadSound(url: string): Promise<AudioBuffer> {
14
+ const context = await this.getAudioContext();
15
+ const response = await fetch(url);
16
+ const arrayBuffer = await response.arrayBuffer();
17
+ return await context.decodeAudioData(arrayBuffer);
18
+ }
19
+
20
+ static async init() {
21
+ try {
22
+ this.connectBuffer = await this.loadSound('/sounds/connect.mp3');
23
+ this.disconnectBuffer = await this.loadSound('/sounds/disconnect.mp3');
24
+ } catch (error) {
25
+ console.error('Failed to load sound effects:', error);
26
+ }
27
+ }
28
+
29
+ static async playConnect() {
30
+ if (!this.connectBuffer) return;
31
+
32
+ try {
33
+ const context = await this.getAudioContext();
34
+ const source = context.createBufferSource();
35
+ source.buffer = this.connectBuffer;
36
+ source.connect(context.destination);
37
+ source.start(0);
38
+ } catch (error) {
39
+ console.error('Failed to play connect sound:', error);
40
+ }
41
+ }
42
+
43
+ static async playDisconnect() {
44
+ if (!this.disconnectBuffer) return;
45
+
46
+ try {
47
+ const context = await this.getAudioContext();
48
+ const source = context.createBufferSource();
49
+ source.buffer = this.disconnectBuffer;
50
+ source.connect(context.destination);
51
+ source.start(0);
52
+ } catch (error) {
53
+ console.error('Failed to play disconnect sound:', error);
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Raw wav audio file contents
3
+ */
4
+ export interface WavPackerAudioType {
5
+ blob: Blob;
6
+ url: string;
7
+ channelCount: number;
8
+ sampleRate: number;
9
+ duration: number;
10
+ }
11
+
12
+ /**
13
+ * Utility class for assembling PCM16 "audio/wav" data
14
+ * @class
15
+ */
16
+ export class WavPacker {
17
+ /**
18
+ * Converts Float32Array of amplitude data to ArrayBuffer in Int16Array format
19
+ * @param {Float32Array} float32Array
20
+ * @returns {ArrayBuffer}
21
+ */
22
+ static floatTo16BitPCM(float32Array: Float32Array): ArrayBuffer {
23
+ const buffer = new ArrayBuffer(float32Array.length * 2);
24
+ const view = new DataView(buffer);
25
+ let offset = 0;
26
+ for (let i = 0; i < float32Array.length; i++, offset += 2) {
27
+ let s = Math.max(-1, Math.min(1, float32Array[i] || 0));
28
+ view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
29
+ }
30
+ return buffer;
31
+ }
32
+
33
+ /**
34
+ * Concatenates two ArrayBuffers
35
+ * @param {ArrayBuffer} leftBuffer
36
+ * @param {ArrayBuffer} rightBuffer
37
+ * @returns {ArrayBuffer}
38
+ */
39
+ static mergeBuffers(leftBuffer: ArrayBuffer, rightBuffer: ArrayBuffer): ArrayBuffer {
40
+ const tmpArray = new Uint8Array(
41
+ leftBuffer.byteLength + rightBuffer.byteLength
42
+ );
43
+ tmpArray.set(new Uint8Array(leftBuffer), 0);
44
+ tmpArray.set(new Uint8Array(rightBuffer), leftBuffer.byteLength);
45
+ return tmpArray.buffer as ArrayBuffer;
46
+ }
47
+
48
+ /**
49
+ * Packs data into an Int16 format
50
+ * @private
51
+ * @param {number} size 0 = 1x Int16, 1 = 2x Int16
52
+ * @param {number} arg value to pack
53
+ * @returns
54
+ */
55
+ _packData(size: number, arg: number): Uint8Array {
56
+ return [
57
+ new Uint8Array([arg, arg >> 8]),
58
+ new Uint8Array([arg, arg >> 8, arg >> 16, arg >> 24]),
59
+ ][size] || new Uint8Array();
60
+ }
61
+
62
+ /**
63
+ * Packs audio into "audio/wav" Blob
64
+ * @param {number} sampleRate
65
+ * @param {{bitsPerSample: number, channels: Array<Float32Array>, data: Int16Array}} audio
66
+ * @returns {WavPackerAudioType}
67
+ */
68
+ pack(sampleRate: number, audio: { bitsPerSample: number; channels: Array<Float32Array>; data: Int16Array; }): WavPackerAudioType {
69
+ if (!audio?.bitsPerSample) {
70
+ throw new Error(`Missing "bitsPerSample"`);
71
+ } else if (!audio?.channels) {
72
+ throw new Error(`Missing "channels"`);
73
+ } else if (!audio?.data) {
74
+ throw new Error(`Missing "data"`);
75
+ }
76
+ const { bitsPerSample, channels, data } = audio;
77
+ const output: (string | Uint8Array | Int16Array)[] = [
78
+ // Header
79
+ 'RIFF',
80
+ this._packData(
81
+ 1,
82
+ 4 + (8 + 24) /* chunk 1 length */ + (8 + 8) /* chunk 2 length */
83
+ ), // Length
84
+ 'WAVE',
85
+ // chunk 1
86
+ 'fmt ', // Sub-chunk identifier
87
+ this._packData(1, 16), // Chunk length
88
+ this._packData(0, 1), // Audio format (1 is linear quantization)
89
+ this._packData(0, channels.length),
90
+ this._packData(1, sampleRate),
91
+ this._packData(1, (sampleRate * channels.length * bitsPerSample) / 8), // Byte rate
92
+ this._packData(0, (channels.length * bitsPerSample) / 8),
93
+ this._packData(0, bitsPerSample),
94
+ // chunk 2
95
+ 'data', // Sub-chunk identifier
96
+ this._packData(
97
+ 1,
98
+ ((channels[0] ? channels[0].length : 0) * channels.length * bitsPerSample) / 8
99
+ ), // Chunk length
100
+ data,
101
+ ];
102
+ const blob = new Blob(output, { type: 'audio/mpeg' });
103
+ const url = URL.createObjectURL(blob);
104
+ return {
105
+ blob,
106
+ url,
107
+ channelCount: channels.length,
108
+ sampleRate,
109
+ duration: data.byteLength / (channels.length * sampleRate * 2),
110
+ };
111
+ }
112
+ }