@guava-ai/guava-sdk 0.36.0 → 0.38.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/README.md +44 -2
- package/dist/guava-webrtc.esm.min.js +2 -0
- package/dist/guava-webrtc.esm.min.js.map +7 -0
- package/dist/guava-webrtc.min.js +2 -0
- package/dist/guava-webrtc.min.js.map +7 -0
- package/dist/src/call.d.ts +5 -4
- package/dist/src/call.js +4 -4
- package/dist/src/call.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/sip.d.ts +15 -0
- package/dist/src/sip.js +52 -0
- package/dist/src/sip.js.map +1 -0
- package/dist/src/socket/call-info.d.ts +16 -9
- package/dist/src/socket/call-info.js +43 -4
- package/dist/src/socket/call-info.js.map +1 -1
- package/dist/src/socket/listen-inbound.d.ts +14 -4
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/webrtc/index.d.ts +42 -0
- package/dist/src/webrtc/index.js +299 -0
- package/dist/src/webrtc/index.js.map +1 -0
- package/dist/src/webrtc/tones.d.ts +31 -0
- package/dist/src/webrtc/tones.js +172 -0
- package/dist/src/webrtc/tones.js.map +1 -0
- package/package.json +19 -3
- package/src/call.ts +7 -6
- package/src/index.ts +2 -0
- package/src/sip.ts +62 -0
- package/src/socket/call-info.ts +50 -8
- package/src/version.ts +1 -1
- package/src/webrtc/index.ts +372 -0
- package/src/webrtc/tones.ts +200 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Seconds from the start of bar 0 to the given 0-indexed beat.
|
|
2
|
+
function beatTime(beat: number, bpm: number): number {
|
|
3
|
+
return beat * (60 / bpm);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ConnectingTone {
|
|
7
|
+
start(): void;
|
|
8
|
+
stop(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// North American ringback: 440 + 480 Hz, 2s on, 4s off.
|
|
12
|
+
export class RingbackTone implements ConnectingTone {
|
|
13
|
+
private context: AudioContext | null = null;
|
|
14
|
+
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
15
|
+
|
|
16
|
+
private readonly volume = 0.15;
|
|
17
|
+
private readonly onSeconds = 2;
|
|
18
|
+
private readonly periodSeconds = 6;
|
|
19
|
+
// Delay before the first ring, so near-instant connections stay silent.
|
|
20
|
+
private readonly startDelay = 0.3;
|
|
21
|
+
|
|
22
|
+
start(): void {
|
|
23
|
+
if (this.context) return;
|
|
24
|
+
|
|
25
|
+
const context = new AudioContext();
|
|
26
|
+
const gain = context.createGain();
|
|
27
|
+
gain.gain.value = 0;
|
|
28
|
+
gain.connect(context.destination);
|
|
29
|
+
|
|
30
|
+
for (const frequency of [440, 480]) {
|
|
31
|
+
const oscillator = context.createOscillator();
|
|
32
|
+
oscillator.frequency.value = frequency;
|
|
33
|
+
oscillator.connect(gain);
|
|
34
|
+
oscillator.start();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let nextRing = context.currentTime + this.startDelay;
|
|
38
|
+
const ring = () => {
|
|
39
|
+
gain.gain.setValueAtTime(this.volume, nextRing);
|
|
40
|
+
gain.gain.setValueAtTime(0, nextRing + this.onSeconds);
|
|
41
|
+
nextRing += this.periodSeconds;
|
|
42
|
+
};
|
|
43
|
+
ring();
|
|
44
|
+
this.intervalId = setInterval(ring, this.periodSeconds * 1000);
|
|
45
|
+
this.context = context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stop(): void {
|
|
49
|
+
if (!this.context) return;
|
|
50
|
+
|
|
51
|
+
if (this.intervalId) {
|
|
52
|
+
clearInterval(this.intervalId);
|
|
53
|
+
this.intervalId = null;
|
|
54
|
+
}
|
|
55
|
+
// Closing the context stops the oscillators.
|
|
56
|
+
void this.context.close();
|
|
57
|
+
this.context = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class JingleTone implements ConnectingTone {
|
|
62
|
+
private context: AudioContext | null = null;
|
|
63
|
+
private masterGain: GainNode | null = null;
|
|
64
|
+
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
65
|
+
private running = false;
|
|
66
|
+
private pulseCount = 0;
|
|
67
|
+
// Audio-clock time of the next bar to schedule.
|
|
68
|
+
private nextBarStart = 0;
|
|
69
|
+
|
|
70
|
+
// Configuration
|
|
71
|
+
private readonly volume = 0.25;
|
|
72
|
+
private readonly bpm = 120;
|
|
73
|
+
private readonly beatsPerBar = 4;
|
|
74
|
+
// Delay before the first note, so near-instant connections stay silent.
|
|
75
|
+
private readonly startDelay = 0.3;
|
|
76
|
+
|
|
77
|
+
start(): void {
|
|
78
|
+
if (this.running) return;
|
|
79
|
+
|
|
80
|
+
this.running = true;
|
|
81
|
+
this.pulseCount = 0;
|
|
82
|
+
this.context = new AudioContext();
|
|
83
|
+
this.masterGain = this.context.createGain();
|
|
84
|
+
this.masterGain.gain.value = this.volume;
|
|
85
|
+
this.masterGain.connect(this.context.destination);
|
|
86
|
+
this.nextBarStart = this.context.currentTime + this.startDelay;
|
|
87
|
+
|
|
88
|
+
// Stay one bar ahead so timer jitter doesn't shift the beat.
|
|
89
|
+
this.playPulse();
|
|
90
|
+
this.playPulse();
|
|
91
|
+
|
|
92
|
+
const barLength = beatTime(this.beatsPerBar, this.bpm);
|
|
93
|
+
this.intervalId = setInterval(() => {
|
|
94
|
+
this.playPulse();
|
|
95
|
+
}, barLength * 1000);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private noteToFrequency(note: string): number {
|
|
99
|
+
const match = note.match(/^([A-G])(#|b)?(-?\d+)$/);
|
|
100
|
+
|
|
101
|
+
if (!match) {
|
|
102
|
+
throw new Error(`Invalid note: ${note}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const [, name, accidental, octave] = match;
|
|
106
|
+
|
|
107
|
+
const semitones: Record<string, number> = {
|
|
108
|
+
C: 0,
|
|
109
|
+
D: 2,
|
|
110
|
+
E: 4,
|
|
111
|
+
F: 5,
|
|
112
|
+
G: 7,
|
|
113
|
+
A: 9,
|
|
114
|
+
B: 11,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const offset = accidental === "#" ? 1 : accidental === "b" ? -1 : 0;
|
|
118
|
+
const midi = (Number(octave) + 1) * 12 + semitones[name] + offset;
|
|
119
|
+
|
|
120
|
+
return 440 * Math.pow(2, (midi - 69) / 12);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
stop(): void {
|
|
124
|
+
if (!this.running) return;
|
|
125
|
+
|
|
126
|
+
this.running = false;
|
|
127
|
+
|
|
128
|
+
if (this.intervalId) {
|
|
129
|
+
clearInterval(this.intervalId);
|
|
130
|
+
this.intervalId = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const context = this.context;
|
|
134
|
+
const gain = this.masterGain;
|
|
135
|
+
|
|
136
|
+
if (!context || !gain) return;
|
|
137
|
+
|
|
138
|
+
// Smooth fade-out
|
|
139
|
+
const now = context.currentTime;
|
|
140
|
+
gain.gain.setValueAtTime(gain.gain.value, now);
|
|
141
|
+
gain.gain.linearRampToValueAtTime(0, now + 0.15);
|
|
142
|
+
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
void context.close();
|
|
145
|
+
}, 200);
|
|
146
|
+
|
|
147
|
+
this.context = null;
|
|
148
|
+
this.masterGain = null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private playPulse(): void {
|
|
152
|
+
if (!this.context || !this.masterGain) return;
|
|
153
|
+
|
|
154
|
+
const bar = this.nextBarStart;
|
|
155
|
+
const beat = (n: number) => bar + beatTime(n, this.bpm);
|
|
156
|
+
const beatLength = beatTime(1, this.bpm);
|
|
157
|
+
|
|
158
|
+
// Notes on beats 1-3, silence on beat 4.
|
|
159
|
+
if (this.pulseCount % 4 === 0 || this.pulseCount % 4 === 2) {
|
|
160
|
+
this.playNote("C5", beat(0), beatLength);
|
|
161
|
+
this.playNote("E5", beat(0.5), beatLength);
|
|
162
|
+
this.playNote("G4", beat(1), beatLength);
|
|
163
|
+
} else if (this.pulseCount % 4 === 3) {
|
|
164
|
+
this.playNote("E5", beat(0), beatLength);
|
|
165
|
+
this.playNote("G5", beat(0.5), beatLength);
|
|
166
|
+
this.playNote("C5", beat(1), beatLength);
|
|
167
|
+
} else {
|
|
168
|
+
this.playNote("E5", beat(0), beatLength);
|
|
169
|
+
this.playNote("G4", beat(0.5), beatLength);
|
|
170
|
+
this.playNote("C5", beat(1), beatLength);
|
|
171
|
+
}
|
|
172
|
+
this.pulseCount++;
|
|
173
|
+
this.nextBarStart = beat(this.beatsPerBar);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private playNote(note: string, startTime: number, duration: number): void {
|
|
177
|
+
if (!this.context || !this.masterGain) return;
|
|
178
|
+
|
|
179
|
+
const oscillator = this.context.createOscillator();
|
|
180
|
+
const envelope = this.context.createGain();
|
|
181
|
+
|
|
182
|
+
oscillator.type = "sine";
|
|
183
|
+
oscillator.frequency.value = this.noteToFrequency(note);
|
|
184
|
+
|
|
185
|
+
envelope.gain.setValueAtTime(0, startTime);
|
|
186
|
+
envelope.gain.linearRampToValueAtTime(0.7, startTime + 0.05);
|
|
187
|
+
envelope.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
|
|
188
|
+
|
|
189
|
+
oscillator.connect(envelope);
|
|
190
|
+
envelope.connect(this.masterGain);
|
|
191
|
+
|
|
192
|
+
oscillator.start(startTime);
|
|
193
|
+
oscillator.stop(startTime + duration + 0.01);
|
|
194
|
+
|
|
195
|
+
oscillator.onended = () => {
|
|
196
|
+
oscillator.disconnect();
|
|
197
|
+
envelope.disconnect();
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|