tone 15.5.2 → 15.5.3

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.
@@ -3,7 +3,7 @@ import { Note } from "../type/Units.js";
3
3
  /**
4
4
  * Test if the arg is undefined
5
5
  */
6
- export function isUndef(arg: any): arg is undefined {
6
+ export function isUndef(arg: unknown): arg is undefined {
7
7
  return arg === undefined;
8
8
  }
9
9
 
@@ -17,45 +17,45 @@ export function isDefined<T>(arg: T | undefined): arg is T {
17
17
  /**
18
18
  * Test if the arg is a function
19
19
  */
20
- export function isFunction(arg: any): arg is (a: any) => any {
20
+ export function isFunction(arg: unknown): arg is (a: any) => any {
21
21
  return typeof arg === "function";
22
22
  }
23
23
 
24
24
  /**
25
25
  * Test if the argument is a number.
26
26
  */
27
- export function isNumber(arg: any): arg is number {
27
+ export function isNumber(arg: unknown): arg is number {
28
28
  return typeof arg === "number";
29
29
  }
30
30
 
31
31
  /**
32
32
  * Test if the given argument is an object literal (i.e. `{}`);
33
33
  */
34
- export function isObject(arg: any): arg is object {
34
+ export function isObject(arg: unknown): arg is object {
35
35
  return (
36
36
  Object.prototype.toString.call(arg) === "[object Object]" &&
37
- arg.constructor === Object
37
+ (arg as object).constructor === Object
38
38
  );
39
39
  }
40
40
 
41
41
  /**
42
42
  * Test if the argument is a boolean.
43
43
  */
44
- export function isBoolean(arg: any): arg is boolean {
44
+ export function isBoolean(arg: unknown): arg is boolean {
45
45
  return typeof arg === "boolean";
46
46
  }
47
47
 
48
48
  /**
49
49
  * Test if the argument is an Array
50
50
  */
51
- export function isArray(arg: any): arg is any[] {
51
+ export function isArray(arg: unknown): arg is any[] {
52
52
  return Array.isArray(arg);
53
53
  }
54
54
 
55
55
  /**
56
56
  * Test if the argument is a string.
57
57
  */
58
- export function isString(arg: any): arg is string {
58
+ export function isString(arg: unknown): arg is string {
59
59
  return typeof arg === "string";
60
60
  }
61
61
 
@@ -63,6 +63,6 @@ export function isString(arg: any): arg is string {
63
63
  * Test if the argument is in the form of a note in scientific pitch notation.
64
64
  * e.g. "C4"
65
65
  */
66
- export function isNote(arg: any): arg is Note {
66
+ export function isNote(arg: unknown): arg is Note {
67
67
  return isString(arg) && /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i.test(arg);
68
68
  }
@@ -1,7 +1,11 @@
1
- import { expect } from "chai";
1
+ import { expect, use } from "chai";
2
+ import sinon from "sinon";
3
+ import sinonChai from "sinon-chai";
4
+ use(sinonChai);
2
5
 
3
6
  import { BasicTests } from "../../test/helper/Basic.js";
4
- import { OfflineContext } from "../core/index.js";
7
+ import { Context } from "../core/context/Context.js";
8
+ import { OfflineContext } from "../core/context/OfflineContext.js";
5
9
  import { UserMedia } from "./UserMedia.js";
6
10
 
7
11
  describe("UserMedia", () => {
@@ -37,139 +41,154 @@ describe("UserMedia", () => {
37
41
  });
38
42
  });
39
43
 
40
- context("Opening and closing", function () {
41
- // long timeout to give testers time to allow the microphone
42
- this.timeout(100000);
43
-
44
- let HAS_USER_MEDIA_INPUTS = false;
45
-
46
- before(() => {
47
- return UserMedia.enumerateDevices().then((devices) => {
48
- HAS_USER_MEDIA_INPUTS = devices.length > 0;
49
- });
44
+ context("Opening and closing", () => {
45
+ beforeEach(() => {
46
+ const mockTrack = { stop: sinon.stub() };
47
+ const mockStream = {
48
+ active: true,
49
+ getAudioTracks: () => [mockTrack],
50
+ } as unknown as MediaStream;
51
+ const mockMediaStreamSource = {
52
+ connect: sinon.stub(),
53
+ disconnect: sinon.stub(),
54
+ numberOfOutputs: 1,
55
+ } as unknown as MediaStreamAudioSourceNode;
56
+ sinon.stub(UserMedia, "enumerateDevices").resolves([
57
+ {
58
+ deviceId: "default",
59
+ groupId: "default",
60
+ label: "Default Device",
61
+ },
62
+ {
63
+ deviceId: "other",
64
+ groupId: "default",
65
+ label: "Other Device",
66
+ },
67
+ ] as MediaDeviceInfo[]);
68
+ sinon
69
+ .stub(navigator.mediaDevices, "getUserMedia")
70
+ .resolves(mockStream);
71
+ sinon
72
+ .stub(Context.prototype, "createMediaStreamSource")
73
+ .returns(mockMediaStreamSource);
74
+ });
75
+
76
+ afterEach(() => {
77
+ sinon.restore();
78
+ });
79
+
80
+ it("open returns a promise", async () => {
81
+ const extIn = new UserMedia();
82
+ const promise = extIn.open();
83
+ expect(promise).to.be.instanceOf(Promise);
84
+ await promise;
85
+ extIn.dispose();
50
86
  });
51
87
 
52
- it("open returns a promise", () => {
53
- if (HAS_USER_MEDIA_INPUTS) {
54
- const extIn = new UserMedia();
55
- const promise = extIn.open();
56
- expect(promise).to.have.property("then");
57
- return promise.then(() => {
58
- extIn.dispose();
59
- });
60
- }
88
+ it("can open an input", async () => {
89
+ const extIn = new UserMedia();
90
+ await extIn.open();
91
+ extIn.dispose();
61
92
  });
62
93
 
63
- it("can open an input", () => {
64
- if (HAS_USER_MEDIA_INPUTS) {
65
- const extIn = new UserMedia();
66
- return extIn.open().then(() => {
67
- extIn.dispose();
68
- });
69
- }
94
+ it("can open an input by name", async () => {
95
+ const extIn = new UserMedia();
96
+ const devices = await UserMedia.enumerateDevices();
97
+ const name = devices[0].deviceId;
98
+
99
+ await extIn.open(name);
100
+
101
+ expect(extIn.deviceId).to.equal(name);
102
+ expect(navigator.mediaDevices.getUserMedia).to.have.been.calledWith(
103
+ sinon.match.hasNested("audio.deviceId", name)
104
+ );
105
+ extIn.dispose();
70
106
  });
71
107
 
72
- it("can open an input by name", () => {
73
- if (HAS_USER_MEDIA_INPUTS) {
74
- const extIn = new UserMedia();
75
- let name: string;
76
- return UserMedia.enumerateDevices()
77
- .then((devices) => {
78
- name = devices[0].deviceId;
79
- return extIn.open(name);
80
- })
81
- .then(() => {
82
- expect(extIn.deviceId).to.equal(name);
83
- extIn.dispose();
84
- });
85
- }
108
+ it("can open an input by index", async () => {
109
+ const extIn = new UserMedia();
110
+
111
+ await extIn.open(0);
112
+
113
+ expect(navigator.mediaDevices.getUserMedia).to.have.been.calledWith(
114
+ sinon.match.hasNested("audio.deviceId", "default")
115
+ );
116
+ extIn.dispose();
86
117
  });
87
118
 
88
- it("can open an input by index", () => {
89
- if (HAS_USER_MEDIA_INPUTS) {
90
- const extIn = new UserMedia();
91
- return extIn.open(0).then(() => {
92
- extIn.dispose();
93
- });
94
- }
119
+ it("can pass in additional constraints", async () => {
120
+ const extIn = new UserMedia();
121
+ await extIn.open({
122
+ preferCurrentTab: true,
123
+ });
124
+ expect(navigator.mediaDevices.getUserMedia).to.have.been.calledWith(
125
+ sinon.match.hasNested("preferCurrentTab", true)
126
+ );
127
+ extIn.dispose();
95
128
  });
96
129
 
97
- it("throws an error if it cant find the device name", () => {
98
- if (HAS_USER_MEDIA_INPUTS) {
99
- const extIn = new UserMedia();
100
- return extIn
101
- .open("doesn't exist")
102
- .then(() => {
103
- throw new Error("shouldn't call 'then'");
104
- })
105
- .catch(() => {
106
- extIn.dispose();
107
- });
130
+ it("throws an error if it cant find the device name", async () => {
131
+ const extIn = new UserMedia();
132
+ try {
133
+ await extIn.open("doesn't exist");
134
+ throw new Error("shouldn't reach here");
135
+ } catch {
136
+ extIn.dispose();
108
137
  }
109
138
  });
110
139
 
111
- it("is 'started' after media is open and 'stopped' otherwise", () => {
112
- if (HAS_USER_MEDIA_INPUTS) {
113
- const extIn = new UserMedia();
114
- expect(extIn.state).to.equal("stopped");
115
- return extIn.open().then(() => {
116
- expect(extIn.state).to.equal("started");
117
- extIn.dispose();
118
- });
119
- }
140
+ it("is 'started' after media is open and 'stopped' otherwise", async () => {
141
+ const extIn = new UserMedia();
142
+ expect(extIn.state).to.equal("stopped");
143
+
144
+ await extIn.open();
145
+
146
+ expect(extIn.state).to.equal("started");
147
+ extIn.dispose();
120
148
  });
121
149
 
122
- it("has a label, group and device id when open", () => {
123
- if (HAS_USER_MEDIA_INPUTS) {
124
- const extIn = new UserMedia();
125
- return extIn.open().then(() => {
126
- expect(extIn.deviceId).to.be.a("string");
127
- expect(extIn.groupId).to.be.a("string");
128
- expect(extIn.label).to.be.a("string");
129
- extIn.dispose();
130
- });
131
- }
150
+ it("has a label, group and device id when open", async () => {
151
+ const extIn = new UserMedia();
152
+ expect(extIn.deviceId).to.be.undefined;
153
+ expect(extIn.groupId).to.be.undefined;
154
+ expect(extIn.label).to.be.undefined;
155
+
156
+ await extIn.open();
157
+
158
+ expect(extIn.deviceId).to.be.a("string");
159
+ expect(extIn.groupId).to.be.a("string");
160
+ expect(extIn.label).to.be.a("string");
161
+ extIn.dispose();
132
162
  });
133
163
 
134
- it("can reopen an input", () => {
135
- if (HAS_USER_MEDIA_INPUTS) {
136
- const extIn = new UserMedia();
137
- return extIn
138
- .open()
139
- .then(() => {
140
- return extIn.open();
141
- })
142
- .then(() => {
143
- extIn.dispose();
144
- });
145
- }
164
+ it("can reopen an input", async () => {
165
+ const extIn = new UserMedia();
166
+ await extIn.open();
167
+ extIn.close();
168
+ await extIn.open();
169
+ extIn.dispose();
146
170
  });
147
171
 
148
- it("can close an input", () => {
149
- if (HAS_USER_MEDIA_INPUTS) {
150
- const extIn = new UserMedia();
151
- return extIn.open().then(() => {
152
- extIn.close();
153
- extIn.dispose();
154
- });
155
- }
172
+ it("can close an input", async () => {
173
+ const extIn = new UserMedia();
174
+ await extIn.open();
175
+ extIn.close();
176
+ extIn.dispose();
156
177
  });
157
178
 
158
- it("can enumerate devices", () => {
159
- return UserMedia.enumerateDevices().then((devices) => {
160
- expect(devices).to.be.instanceOf(Array);
161
- });
179
+ it("can enumerate devices", async () => {
180
+ const devices = await UserMedia.enumerateDevices();
181
+ expect(devices).to.be.instanceOf(Array);
162
182
  });
163
183
 
164
- it("doesn't work in OfflineContext", (done) => {
165
- if (HAS_USER_MEDIA_INPUTS) {
166
- const context = new OfflineContext(2, 2, 44100);
167
- const extIn = new UserMedia({ context });
168
- extIn.open().catch(() => {
169
- done();
170
- });
171
- } else {
172
- done();
184
+ it("doesn't work in OfflineContext", async () => {
185
+ const context = new OfflineContext(2, 2, 44100);
186
+ const extIn = new UserMedia({ context });
187
+ try {
188
+ await extIn.open();
189
+ throw new Error("shouldn't reach here");
190
+ } catch {
191
+ // expected to throw in OfflineContext
173
192
  }
174
193
  });
175
194
  });
@@ -8,9 +8,9 @@ import {
8
8
  } from "../core/context/ToneAudioNode.js";
9
9
  import { Decibels } from "../core/type/Units.js";
10
10
  import { assert } from "../core/util/Debug.js";
11
- import { optionsFromArguments } from "../core/util/Defaults.js";
11
+ import { deepMerge, optionsFromArguments } from "../core/util/Defaults.js";
12
12
  import { readOnly } from "../core/util/Interface.js";
13
- import { isDefined, isNumber } from "../core/util/TypeCheck.js";
13
+ import { isDefined, isNumber, isObject } from "../core/util/TypeCheck.js";
14
14
 
15
15
  export interface UserMediaOptions extends ToneAudioNodeOptions {
16
16
  volume: Decibels;
@@ -100,43 +100,54 @@ export class UserMedia extends ToneAudioNode<UserMediaOptions> {
100
100
  * Open the media stream. If a string is passed in, it is assumed
101
101
  * to be the label or id of the stream, if a number is passed in,
102
102
  * it is the input number of the stream.
103
- * @param labelOrId The label or id of the audio input media device.
104
- * With no argument, the default stream is opened.
103
+ * @param labelOrIdOrConstraints The label or id of the audio input media device, or a getUserMedia constraints object.
104
+ * With no argument, the default stream is opened.
105
105
  * @return The promise is resolved when the stream is open.
106
106
  */
107
- async open(labelOrId?: string | number): Promise<this> {
107
+ async open(
108
+ labelOrIdOrConstraints?: string | number | MediaStreamConstraints
109
+ ): Promise<this> {
108
110
  assert(UserMedia.supported, "UserMedia is not supported");
109
111
  // close the previous stream
110
112
  if (this.state === "started") {
111
113
  this.close();
112
114
  }
113
- const devices = await UserMedia.enumerateDevices();
114
- if (isNumber(labelOrId)) {
115
- this._device = devices[labelOrId];
116
- } else {
117
- this._device = devices.find((device) => {
118
- return (
119
- device.label === labelOrId || device.deviceId === labelOrId
120
- );
121
- });
122
- // didn't find a matching device
123
- if (!this._device && devices.length > 0) {
124
- this._device = devices[0];
125
- }
126
- assert(isDefined(this._device), `No matching device ${labelOrId}`);
127
- }
128
- // do getUserMedia
129
- const constraints = {
115
+ let constraints: MediaStreamConstraints = {
130
116
  audio: {
131
117
  echoCancellation: false,
132
118
  sampleRate: this.context.sampleRate,
133
119
  noiseSuppression: false,
134
- mozNoiseSuppression: false,
135
120
  },
136
121
  };
137
- if (this._device) {
138
- // @ts-ignore
139
- constraints.audio.deviceId = this._device.deviceId;
122
+ if (isObject(labelOrIdOrConstraints)) {
123
+ // if the user passed in a constraints object
124
+ constraints = deepMerge(constraints, labelOrIdOrConstraints);
125
+ } else {
126
+ // if the user passed in a label or id
127
+ const devices = await UserMedia.enumerateDevices();
128
+ if (isNumber(labelOrIdOrConstraints)) {
129
+ this._device = devices[labelOrIdOrConstraints];
130
+ } else {
131
+ this._device = devices.find((device) => {
132
+ return (
133
+ device.label === labelOrIdOrConstraints ||
134
+ device.deviceId === labelOrIdOrConstraints
135
+ );
136
+ });
137
+ // didn't find a matching device
138
+ if (!this._device && devices.length > 0) {
139
+ this._device = devices[0];
140
+ }
141
+ assert(
142
+ isDefined(this._device),
143
+ `No matching device ${labelOrIdOrConstraints}`
144
+ );
145
+ }
146
+ // if there is a device, set the deviceId
147
+ if (this._device) {
148
+ // @ts-ignore
149
+ constraints.audio.deviceId = this._device.deviceId;
150
+ }
140
151
  }
141
152
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
142
153
  // start a new source only if the previous one is closed
@@ -19,9 +19,8 @@ describe("Oscillator", () => {
19
19
  SourceTests(Oscillator);
20
20
  OscillatorTests(Oscillator);
21
21
 
22
- const sandbox = sinon.createSandbox();
23
22
  afterEach(() => {
24
- sandbox.restore();
23
+ sinon.restore();
25
24
  });
26
25
 
27
26
  it("matches a file", () => {
@@ -38,8 +37,8 @@ describe("Oscillator", () => {
38
37
 
39
38
  it("cleans up connections after stopping", async () => {
40
39
  const SignalPrototype = Signal.prototype;
41
- const connectSpy = sandbox.spy(SignalPrototype, "connect");
42
- const disconnectSpy = sandbox.spy(SignalPrototype, "disconnect");
40
+ const connectSpy = sinon.spy(SignalPrototype, "connect");
41
+ const disconnectSpy = sinon.spy(SignalPrototype, "disconnect");
43
42
  await new Promise<void>((done) => {
44
43
  const osc = new Oscillator({
45
44
  onstop: () => done(),
package/Tone/version.ts CHANGED
@@ -1 +1 @@
1
- export const version: string = "15.5.2";
1
+ export const version: string = "15.5.3";