@primitiv/client 0.18.0

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/LICENSE.txt ADDED
@@ -0,0 +1,285 @@
1
+ ================================================================================
2
+ PRIMITIV ENGINE - END USER LICENSE AGREEMENT (EULA)
3
+ ================================================================================
4
+ Version 1.1 - February 2026
5
+
6
+
7
+ 1. DEFINITIONS
8
+ --------------
9
+
10
+ 1.1 "Software" means the Primitiv Engine packages, including all compiled code,
11
+ documentation, and associated materials provided by Licensor.
12
+
13
+ 1.2 "Licensor" means Thomas Piquet (THP Software), the copyright holder of the
14
+ Software.
15
+
16
+ 1.3 "You" or "Licensee" means the individual or legal entity exercising rights
17
+ under this License.
18
+
19
+ 1.4 "End Product" means a software application, game, developer tool,
20
+ framework, or specialized library (such as an NPM package) that
21
+ incorporates the Software as an integrated component and provides
22
+ independent functionality or vertical specialization beyond the
23
+ Software itself.
24
+
25
+ 1.5 "Commercial Use" means use of the Software in any manner primarily intended
26
+ for commercial advantage or monetary compensation.
27
+
28
+
29
+ 2. ACCEPTANCE OF TERMS
30
+ ----------------------
31
+
32
+ By downloading, installing, copying, or using the Software, You acknowledge that
33
+ You have read, understood, and agree to be bound by all terms of this Agreement.
34
+ If You do not agree, You are not authorized to use the Software.
35
+
36
+
37
+ 3. GRANT OF LICENSE
38
+ -------------------
39
+
40
+ 3.1 Subject to compliance with this Agreement, Licensor grants You a worldwide,
41
+ royalty-free, non-exclusive, non-transferable license to:
42
+
43
+ (a) Use the Software to develop End Products;
44
+ (b) Distribute the Software solely as an integrated, functional component
45
+ of Your End Products;
46
+ (c) Use the Software for both personal and Commercial Use without additional
47
+ royalties or fees.
48
+
49
+ 3.2 This license does not grant You any rights to patents, trademarks, or other
50
+ intellectual property of Licensor except as expressly stated herein.
51
+
52
+
53
+ 4. RESTRICTIONS
54
+ ---------------
55
+
56
+ 4.1 You may NOT:
57
+
58
+ (a) Redistribute as Standalone: Distribute, sublicense, sell, rent, lease,
59
+ or lend the Software as a standalone product, library, or development
60
+ tool where the Software serves as the primary or sole feature.
61
+
62
+ (b) No Core Modifications: Alter, modify, adapt, translate, or create
63
+ derivative works of the distributed compiled files or the core
64
+ logic of the Software itself. You may NOT re-bundle, patch, or
65
+ re-distribute a modified version of the Primitiv Engine.
66
+
67
+ (c) Remove Notices: Remove, obscure, or alter any copyright notices,
68
+ trademarks, or proprietary rights notices contained in or on the
69
+ Software;
70
+
71
+ (d) Misrepresent Origin: Claim authorship of the Software or misrepresent
72
+ the origin of the Software in any way.
73
+
74
+ 4.2 Reverse Engineering: Subject to applicable mandatory law, You agree not to
75
+ reverse engineer, decompile, disassemble, or otherwise attempt to derive
76
+ the source code, underlying algorithms, or structure of the Software.
77
+ This restriction does not apply where such activity is expressly permitted
78
+ by applicable law notwithstanding this limitation (such as interoperability
79
+ rights under EU Directive 2009/24/EC).
80
+
81
+ 4.3 Permitted External Layers: You ARE permitted to develop and distribute
82
+ external software layers, "sur-moteurs" (wrapper engines), developer
83
+ tools, or specialized frameworks that interface with the Software,
84
+ provided that:
85
+ (i) Your product interacts with the Software's original, unmodified
86
+ compiled files (e.g., as a dependency or via an API);
87
+ (ii) Your product adds independent and distinct functionality or
88
+ specialization beyond the Software itself;
89
+ (iii) You do not modify the core Software files to achieve this.
90
+
91
+ 4.4 Any modifications to Your own code that interfaces with the Software are
92
+ permitted and remain Your property.
93
+
94
+
95
+ 5. OWNERSHIP AND INTELLECTUAL PROPERTY
96
+ --------------------------------------
97
+
98
+ 5.1 The Software is licensed, not sold. Licensor retains all right, title, and
99
+ interest in and to the Software, including all intellectual property
100
+ rights therein.
101
+
102
+ 5.2 This Agreement does not grant You any ownership rights in the Software.
103
+
104
+
105
+ 6. OWNERSHIP OF END PRODUCTS
106
+ ----------------------------
107
+
108
+ 6.1 You retain full and exclusive ownership of Your End Products, including all
109
+ original code, assets, designs, and creative content You create.
110
+
111
+ 6.2 Licensor claims no ownership rights over Your End Products.
112
+
113
+ 6.3 You may license Your End Products under any terms You choose, including
114
+ open source or proprietary licenses.
115
+
116
+
117
+ 7. ATTRIBUTION
118
+ --------------
119
+
120
+ 7.1 While not strictly mandatory, You are encouraged to include the following
121
+ notice in Your End Product's documentation or credits section (digital or
122
+ physical). Such attribution is greatly appreciated and helps support the
123
+ growth and visibility of the Primitiv Engine:
124
+
125
+ "Powered by Primitiv Engine - https://primitivengine.dev"
126
+
127
+ 7.2 Naming and Branding: To avoid confusion of origin, You agree not to use
128
+ "Primitiv" as the leading or primary part of Your tool, framework, or
129
+ library's name. Descriptive terms such as "for Primitiv", "built with
130
+ Primitiv", or "powered by Primitiv" are encouraged.
131
+
132
+ (Example: "RTS Toolkit for Primitiv" is acceptable, whereas
133
+ "Primitiv RTS" is reserved for official modules).
134
+
135
+
136
+ 8. WARRANTY DISCLAIMER
137
+ ----------------------
138
+
139
+ 8.1 THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY
140
+ KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
141
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE,
142
+ AND NON-INFRINGEMENT.
143
+
144
+ 8.2 LICENSOR MAKES NO WARRANTIES ABOUT:
145
+
146
+ (a) The accuracy, reliability, or completeness of the Software;
147
+ (b) That the Software will meet Your requirements or expectations;
148
+ (c) That the Software will operate uninterrupted, error-free, or secure;
149
+ (d) That defects will be corrected;
150
+ (e) That the Software is free from viruses or harmful components.
151
+
152
+ 8.3 THE ENTIRE RISK ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE REMAINS
153
+ WITH YOU.
154
+
155
+ 8.4 Some jurisdictions do not allow the exclusion of implied warranties. In such
156
+ jurisdictions, the above exclusions may not apply to You.
157
+
158
+
159
+ 9. LIMITATION OF LIABILITY
160
+ --------------------------
161
+
162
+ 9.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR SHALL NOT BE
163
+ LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES
164
+ FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS
165
+ INFORMATION, LOSS OF DATA, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE
166
+ USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF LICENSOR HAS BEEN ADVISED
167
+ OF THE POSSIBILITY OF SUCH DAMAGES.
168
+
169
+ 9.2 IN JURISDICTIONS THAT DO NOT ALLOW THE COMPLETE EXCLUSION OR LIMITATION OF
170
+ LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, LICENSOR'S LIABILITY IS
171
+ LIMITED TO THE GREATEST EXTENT PERMITTED BY LAW.
172
+
173
+ 9.3 NOTWITHSTANDING ANY OTHER PROVISION OF THIS AGREEMENT, AND TO THE EXTENT
174
+ PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL LICENSOR'S TOTAL CUMULATIVE
175
+ LIABILITY UNDER THIS AGREEMENT EXCEED FIFTY EUROS (50 EUR). Given that the
176
+ Software is provided at no charge, You acknowledge that this cap represents
177
+ a reasonable allocation of risk between the parties.
178
+
179
+ 9.4 THE LIMITATIONS IN THIS SECTION 9 SHALL APPLY EVEN IF ANY REMEDY FAILS OF
180
+ ITS ESSENTIAL PURPOSE.
181
+
182
+ 9.5 Some jurisdictions do not allow the exclusion or limitation of certain
183
+ warranties or liabilities. In such cases, Licensor's liability shall be
184
+ limited to the maximum extent permitted by law.
185
+
186
+
187
+ 10. TERMINATION
188
+ ---------------
189
+
190
+ 10.1 This License is effective until terminated.
191
+
192
+ 10.2 Your rights under this License will terminate automatically without notice
193
+ if You fail to comply with any term of this Agreement.
194
+
195
+ 10.3 Licensor may terminate this License immediately upon written notice if You
196
+ breach any material term and fail to cure such breach within thirty (30)
197
+ days of receiving notice.
198
+
199
+ 10.4 Upon termination for breach of contract, You must:
200
+
201
+ (a) Cease all use of the Software;
202
+ (b) Destroy or remove all copies of the Software in Your possession or
203
+ control.
204
+
205
+ 10.5 Safe Harbor for Ongoing Projects:
206
+ (a) In the event of general discontinuance of the Software by the
207
+ Licensor or cancellation of this License for reasons other than
208
+ Your breach of contract, any End Products that are already completed
209
+ or actively in development at the time may continue to include and
210
+ use the Software. However, You may not start development of NEW
211
+ End Products using the Software after the date of discontinuance.
212
+ (b) This Safe Harbor does NOT apply if Your License is terminated due
213
+ to Your breach of any material term of this Agreement (Section 10.4).
214
+ In such cases, all rights to use the Software cease immediately.
215
+
216
+ 10.6 Sections 5, 6, 8, 9, 11, and 12 shall survive termination.
217
+
218
+
219
+ 11. UPDATES AND SUPPORT
220
+ -----------------------
221
+
222
+ 11.1 Licensor is under no obligation to provide updates, upgrades, support,
223
+ maintenance, or bug fixes for the Software.
224
+
225
+ 11.2 Any updates provided shall be considered part of the Software and subject
226
+ to this Agreement unless accompanied by a separate license.
227
+
228
+
229
+ 12. GENERAL PROVISIONS
230
+ ----------------------
231
+
232
+ 12.1 Governing Law: This Agreement shall be governed by and construed in
233
+ accordance with the laws of France, without regard to its conflict of
234
+ law provisions.
235
+
236
+ 12.2 Jurisdiction: Any disputes arising from this Agreement shall be subject
237
+ to the exclusive jurisdiction of the courts located in France.
238
+
239
+ 12.3 Severability: If any provision of this Agreement is held to be invalid or
240
+ unenforceable, the remaining provisions shall continue in full force and
241
+ effect, and the invalid or unenforceable provision shall be deemed
242
+ modified to the minimum extent necessary to make it valid and enforceable.
243
+
244
+ 12.4 Entire Agreement: This Agreement constitutes the entire agreement between
245
+ You and Licensor concerning the Software and supersedes all prior
246
+ agreements and understandings, whether written or oral.
247
+
248
+ 12.5 Waiver: No waiver of any provision of this Agreement shall be deemed or
249
+ shall constitute a waiver of any other provision, nor shall any waiver
250
+ constitute a continuing waiver unless expressly provided in writing.
251
+
252
+ 12.6 Assignment: You may not assign or transfer this License or any rights
253
+ granted hereunder without prior written consent of Licensor. Any attempted
254
+ assignment in violation of this provision is void. Licensor may assign
255
+ this Agreement without restriction.
256
+
257
+ 12.7 Export Compliance: You agree to comply with all applicable export and
258
+ import laws and regulations in Your use of the Software.
259
+
260
+ 12.8 Amendments: Licensor reserves the right to modify this Agreement for
261
+ future versions of the Software. Continued use of updated versions
262
+ constitutes acceptance of the revised terms. Your use of any specific
263
+ version is governed by the Agreement in effect at the time of download.
264
+
265
+ 12.9 Force Majeure: Licensor shall not be liable for any failure or delay in
266
+ performance under this Agreement due to causes beyond its reasonable
267
+ control.
268
+
269
+
270
+ 13. CONTACT INFORMATION
271
+ -----------------------
272
+
273
+ For questions regarding this License, please contact:
274
+
275
+ THP Software
276
+ Author: Thomas Piquet
277
+ Website: https://primitivengine.dev
278
+
279
+
280
+ BY USING THE SOFTWARE, YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT,
281
+ UNDERSTAND IT, AND AGREE TO BE BOUND BY ITS TERMS AND CONDITIONS.
282
+
283
+ --------------------------------------------------------------------------------
284
+ (c) 2026 Thomas Piquet (THP Software). All rights reserved.
285
+ ================================================================================
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @primitiv/client
2
+
3
+ > **Status: Prototype** — API is unstable and may change between minor versions.
4
+
5
+ Browser-side runtime for the Primitiv engine. Provides rendering (Canvas 2D & WebGL), audio, input handling and networking for multiplayer games.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @primitiv/client
11
+ # or
12
+ pnpm add @primitiv/client
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { ClientRuntime } from '@primitiv/client';
19
+ ```
20
+
21
+ ## Subpath exports
22
+
23
+ ```ts
24
+ import { Engine, Display, User } from '@primitiv/client/core';
25
+ import { Vector2, ScalingMode } from '@primitiv/client/types';
26
+ import { NetworkClient } from '@primitiv/client/network';
27
+ import { AudioEngine, SoundBank } from '@primitiv/client/audio';
28
+ import { Inputs, KeyboardInputs } from '@primitiv/client/input';
29
+ import { Terminal2D, TerminalGL } from '@primitiv/client/render';
30
+ ```
31
+
32
+ ## License
33
+
34
+ See [LICENSE.txt](./LICENSE.txt).
package/dist/audio.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var y=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var P=(e,t)=>{for(var i in t)y(e,i,{get:t[i],enumerable:!0})},q=(e,t,i,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of L(t))!F.call(e,n)&&n!==i&&y(e,n,{get:()=>t[n],enumerable:!(s=E(t,n))||s.enumerable});return e};var H=e=>q(y({},"__esModule",{value:!0}),e);var ie={};P(ie,{AudioEngine:()=>te,ManagedSource:()=>D,RetroTones:()=>R,SoundBank:()=>M,SpatialProcessor:()=>G});module.exports=H(ie);var v={trace:0,debug:1,info:2,warn:3,error:4,silent:5},u=[],T="info",w=new Map,d=new Map,O=typeof performance<"u"&&typeof performance.now=="function"?()=>performance.now():()=>Date.now(),x=null;function A(e){let t=null,i=-1;for(let[s,n]of w)if(z(s,e)){let a=s.length;a>i&&(i=a,t=n)}return t??T}function z(e,t){if(e==="*")return!0;if(e.endsWith(":*")){let i=e.slice(0,-1);return t===e.slice(0,-2)||t.startsWith(i)}return e===t}function N(){for(let[e,t]of d)t.c(A(e))}var m=class p{namespace;r;i;s;constructor(t,i,s){this.namespace=t,this.i=i,this.s=s,this.r=v[A(t)]}static create(t){let i=d.get(t);return i||(i=new p(t),d.set(t,i)),i}child(t){return p.create(`${this.namespace}:${t}`)}forClient(t,i){let s=`${this.namespace}:client:${t}`,n=d.get(s);return n?(n.i=t,n.s=i):(n=new p(s,t,i),d.set(s,n)),n}static dispose(t){d.delete(t)}trace(t,i){this.n("trace",t,i)}debug(t,i){this.n("debug",t,i)}info(t,i){this.n("info",t,i)}warn(t,i){this.n("warn",t,i)}error(t,i){this.n("error",t,i)}isEnabled(t){return v[t]>=this.r}c(t){this.r=v[t]}n(t,i,s){if(v[t]<this.r||u.length===0)return;let n=typeof i=="function"?i():i,a={timestamp:O(),level:t,namespace:this.namespace,message:n};x!==null&&(a.tick=x()),s!==void 0&&(a.data=s),this.i!==void 0&&(a.clientId=this.i),this.s!==void 0&&(a.username=this.s);for(let r=0;r<u.length;r++)u[r](a)}static setLevel(t,i){i===void 0?T=t:w.set(t,i),N()}static clearLevelOverrides(){w.clear(),N()}static enable(t){p.setLevel(t,"trace")}static disable(t){p.setLevel(t,"silent")}static addHandler(t){return u.push(t),()=>{let i=u.indexOf(t);i!==-1&&u.splice(i,1)}}static clearHandlers(){u.length=0}static get handlerCount(){return u.length}static setTickProvider(t){x=t}static reset(){u.length=0,w.clear(),d.clear(),T="info",x=null}},f="\x1B[0m",U={trace:"\x1B[90m",debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m"},j="\x1B[35m",$="\x1B[2m",X="\x1B[93m",Y={trace:"color:#888",debug:"color:#0cc",info:"color:#0a0",warn:"color:#cc0",error:"color:#c00;font-weight:bold"},W="color:#c0c;font-weight:bold",S="color:#888",J="color:#c90;font-weight:bold",I={trace:"debug",debug:"debug",info:"info",warn:"warn",error:"error"},K=typeof globalThis<"u"&&typeof globalThis.process=="object"&&typeof globalThis.process?.versions=="object";function b(e,t){return String(e).padStart(t,"0")}function C(e){let t=Math.floor(e/1e3)%60,i=Math.floor(e/6e4)%60,s=Math.floor(e%1e3);return`${b(i,2)}:${b(t,2)}.${b(s,3)}`}function V(e){return e.toUpperCase().padEnd(5)}function B(e){if(e.clientId===void 0)return"";let t=e.username?`/${e.username}`:"";return` [client:${e.clientId}${t}]`}function Q(e){let t=`${$}${C(e.timestamp)}${f}`,i=e.tick!==void 0?` ${X}#${e.tick}${f}`:"",s=`${U[e.level]}${V(e.level)}${f}`,n=`${j}${e.namespace}${f}`,a=e.clientId!==void 0?`${$}${B(e)}${f}`:"",r=I[e.level],o=`${t}${i} ${s} ${n}${a}`;e.data!==void 0?console[r](o,e.message,e.data):console[r](o,e.message)}function Z(e){let t=I[e.level],i=B(e),s=e.tick!==void 0?` %c#${e.tick}`:"",n=e.tick!==void 0?[J]:[],a=`%c${C(e.timestamp)}${s} %c${V(e.level)} %c${e.namespace}%c${i} %c${e.message}`,r=[S,...n,Y[e.level],W,S,""];e.data!==void 0?console[t](a,...r,e.data):console[t](a,...r)}var _=K?Q:Z;m.handlerCount===0&&m.addHandler(_);var k=m.create("audio:soundbank"),M=class{sounds=new Map;nameToId=new Map;audioContext;constructor(e){this.audioContext=e}async loadFromData(e,t,i){try{let s=await this.audioContext.decodeAudioData(i.buffer);this.store(e,t,s)}catch(s){throw k.error(`Failed to decode sound "${t}" (${e})`,s),s}}async loadFromUrl(e,t,i){try{let s=await(await fetch(i)).arrayBuffer(),n=await this.audioContext.decodeAudioData(s);this.store(e,t,n)}catch(s){throw k.error(`Failed to load sound "${t}" from URL ${i}`,s),s}}store(e,t,i){this.sounds.set(e,i),this.nameToId.set(t,e)}get(e){if(typeof e=="number")return this.sounds.get(e);let t=this.nameToId.get(e);return t!==void 0?this.sounds.get(t):void 0}has(e){return this.get(e)!==void 0}clear(){this.sounds.clear(),this.nameToId.clear()}getAll(){let e=[],t=new Map;for(let[i,s]of this.nameToId)t.set(s,i);for(let[i,s]of this.sounds)e.push({soundId:i,name:t.get(i)??`Sound ${i}`,duration:s.duration});return e}},D=class{instanceId;soundId;name;source;gainNode;pannerNode;lowpassNode;highpassNode;reverbSend;audioContext;destination;t=!1;i=!1;e=1;n=1;position;onEnded;constructor(e,t,i,s,n,a={}){this.audioContext=e,this.instanceId=t,this.soundId=i,this.name=a.name??"",this.destination=n,this.e=a.volume??1,this.position=a.position,this.source=e.createBufferSource(),this.source.buffer=s,this.source.loop=a.loop??!1,this.source.playbackRate.value=a.pitch??1,this.gainNode=e.createGain();let r=a.fadeIn??0;r>0?(this.gainNode.gain.setValueAtTime(0,e.currentTime),this.gainNode.gain.linearRampToValueAtTime(this.e,e.currentTime+r)):this.gainNode.gain.value=this.e,a.position&&(this.pannerNode=e.createStereoPanner()),this.source.connect(this.gainNode),this.pannerNode?(this.gainNode.connect(this.pannerNode),this.pannerNode.connect(this.destination)):this.gainNode.connect(this.destination),a.reverbNode&&(this.reverbSend=e.createGain(),this.reverbSend.gain.value=a.reverb??0,this.gainNode.connect(this.reverbSend),this.reverbSend.connect(a.reverbNode)),a.lowpass&&this.setLowpass(a.lowpass),a.highpass&&this.setHighpass(a.highpass),this.source.onended=()=>{this.i||(this.t=!0,this.onEnded?.())}}start(e=0){this.source.start(0,e)}stop(e=0){this.t||(e>0?(this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+e),setTimeout(()=>this.internalStop(),e*1e3)):this.internalStop())}internalStop(){try{this.source.stop()}catch{}this.t=!0,this.onEnded?.()}setVolume(e,t=0){this.e=e,this.updateTotalVolume(t)}setSpatialVolume(e){this.n=e,this.updateTotalVolume()}updateTotalVolume(e=0){let t=this.e*this.n;e>0?this.gainNode.gain.linearRampToValueAtTime(t,this.audioContext.currentTime+e):this.gainNode.gain.setValueAtTime(t,this.audioContext.currentTime)}setPan(e){this.pannerNode&&this.pannerNode.pan.setValueAtTime(e,this.audioContext.currentTime)}setPitch(e){this.source.playbackRate.setValueAtTime(e,this.audioContext.currentTime)}setLowpass(e){e<=0?this.lowpassNode=void 0:(this.lowpassNode||(this.lowpassNode=this.audioContext.createBiquadFilter(),this.lowpassNode.type="lowpass"),this.lowpassNode.frequency.setValueAtTime(e,this.audioContext.currentTime)),this.updateChain()}setHighpass(e){e<=0?this.highpassNode=void 0:(this.highpassNode||(this.highpassNode=this.audioContext.createBiquadFilter(),this.highpassNode.type="highpass"),this.highpassNode.frequency.setValueAtTime(e,this.audioContext.currentTime)),this.updateChain()}updateChain(){this.source.disconnect(),this.lowpassNode&&this.lowpassNode.disconnect(),this.highpassNode&&this.highpassNode.disconnect();let e=this.source;this.lowpassNode&&(e.connect(this.lowpassNode),e=this.lowpassNode),this.highpassNode&&(e.connect(this.highpassNode),e=this.highpassNode),e.connect(this.gainNode),this.reverbSend&&this.gainNode.connect(this.reverbSend)}setReverb(e){this.reverbSend&&this.reverbSend.gain.setTargetAtTime(e,this.audioContext.currentTime,.1)}isPlaying(){return!this.t&&!this.i}},G=class{config={listenerX:0,listenerY:0,maxDistance:200,referenceDistance:20,rolloffFactor:1,panSpread:1};configure(e){e.maxDistance!==void 0&&(this.config.maxDistance=e.maxDistance),e.referenceDistance!==void 0&&(this.config.referenceDistance=e.referenceDistance),e.rolloffFactor!==void 0&&(this.config.rolloffFactor=e.rolloffFactor),e.panSpread!==void 0&&(this.config.panSpread=e.panSpread)}setListenerPosition(e,t){this.config.listenerX=e,this.config.listenerY=t}getConfig(){return this.config}calculate2D(e,t){let{listenerX:i,listenerY:s,maxDistance:n,referenceDistance:a,panSpread:r}=this.config,o=e-i,c=t-s,l=Math.sqrt(o*o+c*c),h;l<=a?h=1:l>=n?h=0:h=1-(l-a)/(n-a);let g=0;return n>0&&r>0&&(g=o/n*r,g=Math.max(-1,Math.min(1,g))),{pan:g,distanceVolume:h}}},R=class{static playStartSound(e,t){let i=e.currentTime,s=[261.63,329.63,392,523.25],n=.06,a=.07;s.forEach((r,o)=>{let c=i+o*a,l=e.createOscillator(),h=e.createGain();l.type="square",l.frequency.setValueAtTime(r,c),h.gain.setValueAtTime(0,c),h.gain.linearRampToValueAtTime(.2,c+.005),h.gain.setValueAtTime(.2,c+n-.01),h.gain.linearRampToValueAtTime(0,c+n),l.connect(h),h.connect(t),l.start(c),l.stop(c+n)})}static noteToFrequency(e){let t=/^([A-G])([#b]?)(\d)$/i,i=e.match(t);if(!i)return null;let[,s,n,a]=i,r=parseInt(a,10),o={C:0,D:2,E:4,F:5,G:7,A:9,B:11}[s.toUpperCase()];n==="#"?o+=1:n==="b"&&(o-=1);let c=o+(r+1)*12;return 440*Math.pow(2,(c-49)/12)}},ee=m.create("audio:engine"),te=class{ctx=null;masterGain=null;reverbBus=null;soundBank=null;spatial=new G;instances=new Map;nextInstanceId=1;onAudioEvent=null;initialize(){if(this.ctx)return!0;try{return this.ctx=new(window.AudioContext||window.webkitAudioContext),this.masterGain=this.ctx.createGain(),this.masterGain.connect(this.ctx.destination),this.ctx.state==="suspended"&&this.ctx.resume(),this.reverbBus=this.ctx.createConvolver(),this.reverbBus.buffer=this.createImpulseResponse(2,2),this.reverbBus.connect(this.masterGain),this.soundBank=new M(this.ctx),!0}catch(e){return ee.error("Failed to initialize",e),!1}}createImpulseResponse(e,t){if(!this.ctx)throw new Error("Context not initialized");let i=this.ctx.sampleRate,s=i*e,n=this.ctx.createBuffer(2,s,i);for(let a=0;a<2;a++){let r=n.getChannelData(a);for(let o=0;o<s;o++){let c=Math.pow(1-o/s,t);r[o]=(Math.random()*2-1)*c}}return n}getLoader(){return this.soundBank}setMasterVolume(e){this.masterGain&&(this.masterGain.gain.value=Math.max(0,Math.min(1,e)))}getMasterVolume(){return this.masterGain?.gain.value??1}play(e,t={}){if(!this.ctx||!this.masterGain||!this.soundBank)return null;let i=this.soundBank.get(e);if(!i)return null;let s=t.instanceId??this.nextInstanceId++,n=typeof e=="number"?e:-1,a=new D(this.ctx,s,n,i,this.masterGain,{...t,name:t.name??(typeof e=="string"?e:""),reverbNode:this.reverbBus||void 0});if(t.position){let{pan:r,distanceVolume:o}=this.spatial.calculate2D(t.position.x,t.position.y);a.setSpatialVolume(o),a.setPan(r)}return a.onEnded=()=>{this.onAudioEvent?.({type:"playback-ended",instanceId:s,soundId:n}),this.instances.delete(s)},this.instances.set(s,a),a.start(),this.onAudioEvent?.({type:"playback-started",instanceId:s,soundId:n}),{instanceId:s}}stop(e){if(typeof e=="number"){let t=this.instances.get(e);if(t)return t.stop(),1}else{let t=0;for(let i of this.instances.values())(e==="all"||i.name===e)&&(i.stop(),t++);return t}return 0}stopAll(){let e=this.instances.size;return this.instances.forEach(t=>t.stop()),this.instances.clear(),e}fadeOut(e,t){if(e==="all"){let i=this.instances.size;return this.instances.forEach(s=>s.stop(t)),i}if(typeof e=="number"){let i=this.instances.get(e);if(i)return i.stop(t),1}return 0}pause(e){return 0}resume(e){return 0}setListenerPosition(e,t){this.spatial.setListenerPosition(e,t);for(let i of this.instances.values())if(i.position){let{pan:s,distanceVolume:n}=this.spatial.calculate2D(i.position.x,i.position.y);i.setPan(s),i.setSpatialVolume(n)}}configureSpatial(e){this.spatial.configure(e)}setEffects(e,t){let i=this.instances.get(e);i&&(t.lowpass!==void 0&&i.setLowpass(t.lowpass),t.highpass!==void 0&&i.setHighpass(t.highpass),t.reverb!==void 0&&i.setReverb(t.reverb),t.pitch!==void 0&&i.setPitch(t.pitch),t.volume!==void 0&&i.setVolume(t.volume))}stopBySoundId(e){let t=0;for(let i of this.instances.values())i.soundId===e&&(i.stop(),t++);return t}fadeOutBySoundId(e,t){let i=0;for(let s of this.instances.values())s.soundId===e&&(s.stop(t),i++);return i}pauseBySoundId(e){return 0}resumeBySoundId(e){return 0}playTone(e,t,i={}){if(!this.ctx||!this.masterGain)return;let{type:s="sine",volume:n=.3,attack:a=.01,release:r=.1}=i,o=this.ctx.currentTime,c=this.ctx.createOscillator();c.type=s,c.frequency.setValueAtTime(e,o);let l=this.ctx.createGain();l.gain.setValueAtTime(0,o),l.gain.linearRampToValueAtTime(n,o+a),l.gain.setValueAtTime(n,o+t-r),l.gain.linearRampToValueAtTime(0,o+t),c.connect(l),l.connect(this.masterGain),c.start(o),c.stop(o+t)}playStartSound(){this.ctx&&this.masterGain&&R.playStartSound(this.ctx,this.masterGain)}};
@@ -0,0 +1 @@
1
+ export * from '@primitiv/audio';
package/dist/audio.mjs ADDED
@@ -0,0 +1 @@
1
+ var v={trace:0,debug:1,info:2,warn:3,error:4,silent:5},u=[],b="info",w=new Map,d=new Map,k=typeof performance<"u"&&typeof performance.now=="function"?()=>performance.now():()=>Date.now(),x=null;function S(e){let t=null,i=-1;for(let[s,a]of w)if(M(s,e)){let n=s.length;n>i&&(i=n,t=a)}return t??b}function M(e,t){if(e==="*")return!0;if(e.endsWith(":*")){let i=e.slice(0,-1);return t===e.slice(0,-2)||t.startsWith(i)}return e===t}function T(){for(let[e,t]of d)t.c(S(e))}var m=class p{namespace;r;i;s;constructor(t,i,s){this.namespace=t,this.i=i,this.s=s,this.r=v[S(t)]}static create(t){let i=d.get(t);return i||(i=new p(t),d.set(t,i)),i}child(t){return p.create(`${this.namespace}:${t}`)}forClient(t,i){let s=`${this.namespace}:client:${t}`,a=d.get(s);return a?(a.i=t,a.s=i):(a=new p(s,t,i),d.set(s,a)),a}static dispose(t){d.delete(t)}trace(t,i){this.n("trace",t,i)}debug(t,i){this.n("debug",t,i)}info(t,i){this.n("info",t,i)}warn(t,i){this.n("warn",t,i)}error(t,i){this.n("error",t,i)}isEnabled(t){return v[t]>=this.r}c(t){this.r=v[t]}n(t,i,s){if(v[t]<this.r||u.length===0)return;let a=typeof i=="function"?i():i,n={timestamp:k(),level:t,namespace:this.namespace,message:a};x!==null&&(n.tick=x()),s!==void 0&&(n.data=s),this.i!==void 0&&(n.clientId=this.i),this.s!==void 0&&(n.username=this.s);for(let r=0;r<u.length;r++)u[r](n)}static setLevel(t,i){i===void 0?b=t:w.set(t,i),T()}static clearLevelOverrides(){w.clear(),T()}static enable(t){p.setLevel(t,"trace")}static disable(t){p.setLevel(t,"silent")}static addHandler(t){return u.push(t),()=>{let i=u.indexOf(t);i!==-1&&u.splice(i,1)}}static clearHandlers(){u.length=0}static get handlerCount(){return u.length}static setTickProvider(t){x=t}static reset(){u.length=0,w.clear(),d.clear(),b="info",x=null}},f="\x1B[0m",D={trace:"\x1B[90m",debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m"},G="\x1B[35m",N="\x1B[2m",R="\x1B[93m",E={trace:"color:#888",debug:"color:#0cc",info:"color:#0a0",warn:"color:#cc0",error:"color:#c00;font-weight:bold"},L="color:#c0c;font-weight:bold",$="color:#888",F="color:#c90;font-weight:bold",A={trace:"debug",debug:"debug",info:"info",warn:"warn",error:"error"},P=typeof globalThis<"u"&&typeof globalThis.process=="object"&&typeof globalThis.process?.versions=="object";function y(e,t){return String(e).padStart(t,"0")}function I(e){let t=Math.floor(e/1e3)%60,i=Math.floor(e/6e4)%60,s=Math.floor(e%1e3);return`${y(i,2)}:${y(t,2)}.${y(s,3)}`}function C(e){return e.toUpperCase().padEnd(5)}function V(e){if(e.clientId===void 0)return"";let t=e.username?`/${e.username}`:"";return` [client:${e.clientId}${t}]`}function q(e){let t=`${N}${I(e.timestamp)}${f}`,i=e.tick!==void 0?` ${R}#${e.tick}${f}`:"",s=`${D[e.level]}${C(e.level)}${f}`,a=`${G}${e.namespace}${f}`,n=e.clientId!==void 0?`${N}${V(e)}${f}`:"",r=A[e.level],o=`${t}${i} ${s} ${a}${n}`;e.data!==void 0?console[r](o,e.message,e.data):console[r](o,e.message)}function H(e){let t=A[e.level],i=V(e),s=e.tick!==void 0?` %c#${e.tick}`:"",a=e.tick!==void 0?[F]:[],n=`%c${I(e.timestamp)}${s} %c${C(e.level)} %c${e.namespace}%c${i} %c${e.message}`,r=[$,...a,E[e.level],L,$,""];e.data!==void 0?console[t](n,...r,e.data):console[t](n,...r)}var O=P?q:H;m.handlerCount===0&&m.addHandler(O);var B=m.create("audio:soundbank"),z=class{sounds=new Map;nameToId=new Map;audioContext;constructor(e){this.audioContext=e}async loadFromData(e,t,i){try{let s=await this.audioContext.decodeAudioData(i.buffer);this.store(e,t,s)}catch(s){throw B.error(`Failed to decode sound "${t}" (${e})`,s),s}}async loadFromUrl(e,t,i){try{let s=await(await fetch(i)).arrayBuffer(),a=await this.audioContext.decodeAudioData(s);this.store(e,t,a)}catch(s){throw B.error(`Failed to load sound "${t}" from URL ${i}`,s),s}}store(e,t,i){this.sounds.set(e,i),this.nameToId.set(t,e)}get(e){if(typeof e=="number")return this.sounds.get(e);let t=this.nameToId.get(e);return t!==void 0?this.sounds.get(t):void 0}has(e){return this.get(e)!==void 0}clear(){this.sounds.clear(),this.nameToId.clear()}getAll(){let e=[],t=new Map;for(let[i,s]of this.nameToId)t.set(s,i);for(let[i,s]of this.sounds)e.push({soundId:i,name:t.get(i)??`Sound ${i}`,duration:s.duration});return e}},U=class{instanceId;soundId;name;source;gainNode;pannerNode;lowpassNode;highpassNode;reverbSend;audioContext;destination;t=!1;i=!1;e=1;n=1;position;onEnded;constructor(e,t,i,s,a,n={}){this.audioContext=e,this.instanceId=t,this.soundId=i,this.name=n.name??"",this.destination=a,this.e=n.volume??1,this.position=n.position,this.source=e.createBufferSource(),this.source.buffer=s,this.source.loop=n.loop??!1,this.source.playbackRate.value=n.pitch??1,this.gainNode=e.createGain();let r=n.fadeIn??0;r>0?(this.gainNode.gain.setValueAtTime(0,e.currentTime),this.gainNode.gain.linearRampToValueAtTime(this.e,e.currentTime+r)):this.gainNode.gain.value=this.e,n.position&&(this.pannerNode=e.createStereoPanner()),this.source.connect(this.gainNode),this.pannerNode?(this.gainNode.connect(this.pannerNode),this.pannerNode.connect(this.destination)):this.gainNode.connect(this.destination),n.reverbNode&&(this.reverbSend=e.createGain(),this.reverbSend.gain.value=n.reverb??0,this.gainNode.connect(this.reverbSend),this.reverbSend.connect(n.reverbNode)),n.lowpass&&this.setLowpass(n.lowpass),n.highpass&&this.setHighpass(n.highpass),this.source.onended=()=>{this.i||(this.t=!0,this.onEnded?.())}}start(e=0){this.source.start(0,e)}stop(e=0){this.t||(e>0?(this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+e),setTimeout(()=>this.internalStop(),e*1e3)):this.internalStop())}internalStop(){try{this.source.stop()}catch{}this.t=!0,this.onEnded?.()}setVolume(e,t=0){this.e=e,this.updateTotalVolume(t)}setSpatialVolume(e){this.n=e,this.updateTotalVolume()}updateTotalVolume(e=0){let t=this.e*this.n;e>0?this.gainNode.gain.linearRampToValueAtTime(t,this.audioContext.currentTime+e):this.gainNode.gain.setValueAtTime(t,this.audioContext.currentTime)}setPan(e){this.pannerNode&&this.pannerNode.pan.setValueAtTime(e,this.audioContext.currentTime)}setPitch(e){this.source.playbackRate.setValueAtTime(e,this.audioContext.currentTime)}setLowpass(e){e<=0?this.lowpassNode=void 0:(this.lowpassNode||(this.lowpassNode=this.audioContext.createBiquadFilter(),this.lowpassNode.type="lowpass"),this.lowpassNode.frequency.setValueAtTime(e,this.audioContext.currentTime)),this.updateChain()}setHighpass(e){e<=0?this.highpassNode=void 0:(this.highpassNode||(this.highpassNode=this.audioContext.createBiquadFilter(),this.highpassNode.type="highpass"),this.highpassNode.frequency.setValueAtTime(e,this.audioContext.currentTime)),this.updateChain()}updateChain(){this.source.disconnect(),this.lowpassNode&&this.lowpassNode.disconnect(),this.highpassNode&&this.highpassNode.disconnect();let e=this.source;this.lowpassNode&&(e.connect(this.lowpassNode),e=this.lowpassNode),this.highpassNode&&(e.connect(this.highpassNode),e=this.highpassNode),e.connect(this.gainNode),this.reverbSend&&this.gainNode.connect(this.reverbSend)}setReverb(e){this.reverbSend&&this.reverbSend.gain.setTargetAtTime(e,this.audioContext.currentTime,.1)}isPlaying(){return!this.t&&!this.i}},j=class{config={listenerX:0,listenerY:0,maxDistance:200,referenceDistance:20,rolloffFactor:1,panSpread:1};configure(e){e.maxDistance!==void 0&&(this.config.maxDistance=e.maxDistance),e.referenceDistance!==void 0&&(this.config.referenceDistance=e.referenceDistance),e.rolloffFactor!==void 0&&(this.config.rolloffFactor=e.rolloffFactor),e.panSpread!==void 0&&(this.config.panSpread=e.panSpread)}setListenerPosition(e,t){this.config.listenerX=e,this.config.listenerY=t}getConfig(){return this.config}calculate2D(e,t){let{listenerX:i,listenerY:s,maxDistance:a,referenceDistance:n,panSpread:r}=this.config,o=e-i,c=t-s,l=Math.sqrt(o*o+c*c),h;l<=n?h=1:l>=a?h=0:h=1-(l-n)/(a-n);let g=0;return a>0&&r>0&&(g=o/a*r,g=Math.max(-1,Math.min(1,g))),{pan:g,distanceVolume:h}}},X=class{static playStartSound(e,t){let i=e.currentTime,s=[261.63,329.63,392,523.25],a=.06,n=.07;s.forEach((r,o)=>{let c=i+o*n,l=e.createOscillator(),h=e.createGain();l.type="square",l.frequency.setValueAtTime(r,c),h.gain.setValueAtTime(0,c),h.gain.linearRampToValueAtTime(.2,c+.005),h.gain.setValueAtTime(.2,c+a-.01),h.gain.linearRampToValueAtTime(0,c+a),l.connect(h),h.connect(t),l.start(c),l.stop(c+a)})}static noteToFrequency(e){let t=/^([A-G])([#b]?)(\d)$/i,i=e.match(t);if(!i)return null;let[,s,a,n]=i,r=parseInt(n,10),o={C:0,D:2,E:4,F:5,G:7,A:9,B:11}[s.toUpperCase()];a==="#"?o+=1:a==="b"&&(o-=1);let c=o+(r+1)*12;return 440*Math.pow(2,(c-49)/12)}},Y=m.create("audio:engine"),Q=class{ctx=null;masterGain=null;reverbBus=null;soundBank=null;spatial=new j;instances=new Map;nextInstanceId=1;onAudioEvent=null;initialize(){if(this.ctx)return!0;try{return this.ctx=new(window.AudioContext||window.webkitAudioContext),this.masterGain=this.ctx.createGain(),this.masterGain.connect(this.ctx.destination),this.ctx.state==="suspended"&&this.ctx.resume(),this.reverbBus=this.ctx.createConvolver(),this.reverbBus.buffer=this.createImpulseResponse(2,2),this.reverbBus.connect(this.masterGain),this.soundBank=new z(this.ctx),!0}catch(e){return Y.error("Failed to initialize",e),!1}}createImpulseResponse(e,t){if(!this.ctx)throw new Error("Context not initialized");let i=this.ctx.sampleRate,s=i*e,a=this.ctx.createBuffer(2,s,i);for(let n=0;n<2;n++){let r=a.getChannelData(n);for(let o=0;o<s;o++){let c=Math.pow(1-o/s,t);r[o]=(Math.random()*2-1)*c}}return a}getLoader(){return this.soundBank}setMasterVolume(e){this.masterGain&&(this.masterGain.gain.value=Math.max(0,Math.min(1,e)))}getMasterVolume(){return this.masterGain?.gain.value??1}play(e,t={}){if(!this.ctx||!this.masterGain||!this.soundBank)return null;let i=this.soundBank.get(e);if(!i)return null;let s=t.instanceId??this.nextInstanceId++,a=typeof e=="number"?e:-1,n=new U(this.ctx,s,a,i,this.masterGain,{...t,name:t.name??(typeof e=="string"?e:""),reverbNode:this.reverbBus||void 0});if(t.position){let{pan:r,distanceVolume:o}=this.spatial.calculate2D(t.position.x,t.position.y);n.setSpatialVolume(o),n.setPan(r)}return n.onEnded=()=>{this.onAudioEvent?.({type:"playback-ended",instanceId:s,soundId:a}),this.instances.delete(s)},this.instances.set(s,n),n.start(),this.onAudioEvent?.({type:"playback-started",instanceId:s,soundId:a}),{instanceId:s}}stop(e){if(typeof e=="number"){let t=this.instances.get(e);if(t)return t.stop(),1}else{let t=0;for(let i of this.instances.values())(e==="all"||i.name===e)&&(i.stop(),t++);return t}return 0}stopAll(){let e=this.instances.size;return this.instances.forEach(t=>t.stop()),this.instances.clear(),e}fadeOut(e,t){if(e==="all"){let i=this.instances.size;return this.instances.forEach(s=>s.stop(t)),i}if(typeof e=="number"){let i=this.instances.get(e);if(i)return i.stop(t),1}return 0}pause(e){return 0}resume(e){return 0}setListenerPosition(e,t){this.spatial.setListenerPosition(e,t);for(let i of this.instances.values())if(i.position){let{pan:s,distanceVolume:a}=this.spatial.calculate2D(i.position.x,i.position.y);i.setPan(s),i.setSpatialVolume(a)}}configureSpatial(e){this.spatial.configure(e)}setEffects(e,t){let i=this.instances.get(e);i&&(t.lowpass!==void 0&&i.setLowpass(t.lowpass),t.highpass!==void 0&&i.setHighpass(t.highpass),t.reverb!==void 0&&i.setReverb(t.reverb),t.pitch!==void 0&&i.setPitch(t.pitch),t.volume!==void 0&&i.setVolume(t.volume))}stopBySoundId(e){let t=0;for(let i of this.instances.values())i.soundId===e&&(i.stop(),t++);return t}fadeOutBySoundId(e,t){let i=0;for(let s of this.instances.values())s.soundId===e&&(s.stop(t),i++);return i}pauseBySoundId(e){return 0}resumeBySoundId(e){return 0}playTone(e,t,i={}){if(!this.ctx||!this.masterGain)return;let{type:s="sine",volume:a=.3,attack:n=.01,release:r=.1}=i,o=this.ctx.currentTime,c=this.ctx.createOscillator();c.type=s,c.frequency.setValueAtTime(e,o);let l=this.ctx.createGain();l.gain.setValueAtTime(0,o),l.gain.linearRampToValueAtTime(a,o+n),l.gain.setValueAtTime(a,o+t-r),l.gain.linearRampToValueAtTime(0,o+t),c.connect(l),l.connect(this.masterGain),c.start(o),c.stop(o+t)}playStartSound(){this.ctx&&this.masterGain&&X.playStartSound(this.ctx,this.masterGain)}};export{Q as AudioEngine,U as ManagedSource,X as RetroTones,z as SoundBank,j as SpatialProcessor};