maidr 1.0.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.
Files changed (206) hide show
  1. package/.Rbuildignore +1 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.json +6 -0
  4. package/.github/workflows/build.yml +20 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc.json +7 -0
  7. package/.vscode/extensions.json +25 -0
  8. package/.vscode/settings.json +30 -0
  9. package/.vscode/tasks.json +57 -0
  10. package/CHANGELOG.md +7 -0
  11. package/CITATION.cff +21 -0
  12. package/CONTRIBUTING.md +87 -0
  13. package/LICENSE.md +595 -0
  14. package/README.md +341 -0
  15. package/dist/maidr.js +8851 -0
  16. package/dist/maidr.min.js +1 -0
  17. package/dist/styles.css +244 -0
  18. package/dist/styles.min.css +1 -0
  19. package/docs/Audio.html +1398 -0
  20. package/docs/Constants.html +256 -0
  21. package/docs/Description.html +582 -0
  22. package/docs/Helper.html +364 -0
  23. package/docs/LogError.html +905 -0
  24. package/docs/Menu.html +665 -0
  25. package/docs/Position.html +174 -0
  26. package/docs/Resources.html +338 -0
  27. package/docs/Review.html +333 -0
  28. package/docs/Tracker.html +965 -0
  29. package/docs/audio.js.html +635 -0
  30. package/docs/constants.js.html +1242 -0
  31. package/docs/display.js.html +1184 -0
  32. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  33. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  34. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  35. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  36. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  37. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  38. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  39. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  40. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  41. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  42. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  43. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  44. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  45. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  46. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  47. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  48. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  49. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  50. package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
  51. package/docs/fonts/OpenSans-Semibold-webfont.svg +1830 -0
  52. package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
  53. package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
  54. package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
  55. package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +1830 -0
  56. package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  57. package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
  58. package/docs/index.html +66 -0
  59. package/docs/scripts/linenumber.js +25 -0
  60. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  61. package/docs/scripts/prettify/lang-css.js +2 -0
  62. package/docs/scripts/prettify/prettify.js +28 -0
  63. package/docs/styles/jsdoc-default.css +692 -0
  64. package/docs/styles/prettify-jsdoc.css +111 -0
  65. package/docs/styles/prettify-tomorrow.css +132 -0
  66. package/examples/dev_charts/barplot.html +1056 -0
  67. package/examples/dev_charts/boxplot.html +1856 -0
  68. package/examples/dev_charts/boxplot_flipped.svg +727 -0
  69. package/examples/dev_charts/heatmap.html +1217 -0
  70. package/examples/dev_charts/scatterplot/displ.js +18 -0
  71. package/examples/dev_charts/scatterplot/histogram_for_residual.svg +595 -0
  72. package/examples/dev_charts/scatterplot/hwy.js +15 -0
  73. package/examples/dev_charts/scatterplot/layers/point_layer.json +938 -0
  74. package/examples/dev_charts/scatterplot/layers/smooth_layer.json +322 -0
  75. package/examples/dev_charts/scatterplot/point_layer.js +938 -0
  76. package/examples/dev_charts/scatterplot/prediction_array.js +31 -0
  77. package/examples/dev_charts/scatterplot/prediction_array.json +31 -0
  78. package/examples/dev_charts/scatterplot/residual_array.js +29 -0
  79. package/examples/dev_charts/scatterplot/residual_array.json +29 -0
  80. package/examples/dev_charts/scatterplot/scatterplot.svg +1428 -0
  81. package/examples/dev_charts/scatterplot/scatterplot_data.html +2838 -0
  82. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_point_only.svg +1393 -0
  83. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_bestfit.svg +1424 -0
  84. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_loess_curve.svg +1402 -0
  85. package/examples/dev_charts/scatterplot/smooth_layer.js +322 -0
  86. package/examples/dev_charts/scatterplot.html +4560 -0
  87. package/examples/dodged_bar/dodged_bar.png +0 -0
  88. package/examples/dodged_bar/dodged_bar.svg +198 -0
  89. package/examples/dodged_bar/schema.json +41 -0
  90. package/examples/histogram/histogram_tutorial.svg +482 -0
  91. package/examples/histogram/histogram_tutorial_raw_data.json +362 -0
  92. package/examples/histogram/histogram_user_study.svg +578 -0
  93. package/examples/histogram/histogram_user_study_raw_data.json +362 -0
  94. package/examples/lineplot/lineplot_sample.svg +126 -0
  95. package/examples/lineplot/lineplot_sample_raw_data.json +1 -0
  96. package/examples/lineplot/point+lineplot_sample.svg +700 -0
  97. package/examples/other/audio_oscillator_boxplot.js +95 -0
  98. package/examples/other/barplot_labels.svg +314 -0
  99. package/examples/other/barplot_user_study.svg +313 -0
  100. package/examples/other/boxplot.html +927 -0
  101. package/examples/other/boxplot_data_frame.html +568 -0
  102. package/examples/other/boxplot_label.svg +751 -0
  103. package/examples/other/braille-display_boxplot.js +79 -0
  104. package/examples/other/control_boxplot.js +55 -0
  105. package/examples/other/draft.js +56 -0
  106. package/examples/other/getData.html +400 -0
  107. package/examples/other/getData.js +41 -0
  108. package/examples/other/ggplot_to_svg.R +371 -0
  109. package/examples/other/heatmap.svg +582 -0
  110. package/examples/other/heatmap_label.svg +608 -0
  111. package/examples/other/multiple_barplot.html +2250 -0
  112. package/examples/other/new_scatterplot_user_study_point_layer.json +122 -0
  113. package/examples/other/py_binder_output.html +1167 -0
  114. package/examples/other/scatterplot_label.svg +1429 -0
  115. package/examples/other/seaborn_plot.py +9 -0
  116. package/examples/other/svglite_bar.svg +136 -0
  117. package/examples/other/tutorial_boxplot.svg +727 -0
  118. package/examples/other/tutorial_boxplot_data.json +72 -0
  119. package/examples/other/user_study_boxplot.svg +676 -0
  120. package/examples/stacked_bar/schema.json +41 -0
  121. package/examples/stacked_bar/stack_bar.png +0 -0
  122. package/examples/stacked_bar/stacked_bar.svg +180 -0
  123. package/examples/stacked_normalized_bar/stacked_normalized_bar.png +0 -0
  124. package/examples/stacked_normalized_bar/stacked_normalized_bar.svg +189 -0
  125. package/examples/static/barplot.svg +263 -0
  126. package/examples/static/barplot_diamonds_gridSVG.svg +254 -0
  127. package/examples/static/boxplot.svg +424 -0
  128. package/examples/static/heatmap.svg +373 -0
  129. package/examples/static/heatmap_penguins_table.html +486 -0
  130. package/examples/static/scatterplot.svg +530 -0
  131. package/examples/svglite/task_heatmap.html +802 -0
  132. package/examples/svglite/task_heatmap.svg +111 -0
  133. package/examples/svglite/tutorial_bar.svg +136 -0
  134. package/examples/svglite/tutorial_bar_plot.html +504 -0
  135. package/examples/svglite/tutorial_boxplot.html +1850 -0
  136. package/examples/svglite/tutorial_boxplot.svg +727 -0
  137. package/examples/svglite/tutorial_scatterplot.html +3135 -0
  138. package/examples/svglite/tutorial_scatterplot.svg +311 -0
  139. package/gulpfile.js +49 -0
  140. package/index.html +40 -0
  141. package/jsconfig.json +10 -0
  142. package/jsdoc.json +19 -0
  143. package/package.json +47 -0
  144. package/src/css/styles.css +241 -0
  145. package/src/js/__tests__/audio.test.js +49 -0
  146. package/src/js/__tests__/constants.test.js +622 -0
  147. package/src/js/audio.js +575 -0
  148. package/src/js/barplot.js +254 -0
  149. package/src/js/boxplot.js +682 -0
  150. package/src/js/constants.js +1182 -0
  151. package/src/js/controls.js +3182 -0
  152. package/src/js/display.js +1124 -0
  153. package/src/js/heatmap.js +411 -0
  154. package/src/js/histogram.js +134 -0
  155. package/src/js/init.js +427 -0
  156. package/src/js/lineplot.js +219 -0
  157. package/src/js/scatterplot.js +619 -0
  158. package/src/js/segmented.js +268 -0
  159. package/user_study_pilot/binder_test.html +526 -0
  160. package/user_study_pilot/data/barplot_user_study.svg +492 -0
  161. package/user_study_pilot/data/barplot_user_study_raw_data.json +22 -0
  162. package/user_study_pilot/data/boxplot_tutorial.json +72 -0
  163. package/user_study_pilot/data/boxplot_tutorial_horizontal.svg +727 -0
  164. package/user_study_pilot/data/boxplot_user_study.json +52 -0
  165. package/user_study_pilot/data/boxplot_user_study_vertical.svg +675 -0
  166. package/user_study_pilot/data/boxplot_user_study_vertical_horizontal.svg +676 -0
  167. package/user_study_pilot/data/heatmap_user_study.svg +719 -0
  168. package/user_study_pilot/data/heatmap_user_study_raw_data.json +127 -0
  169. package/user_study_pilot/data/new_barplot_user_study.svg +269 -0
  170. package/user_study_pilot/data/new_heatmap_user_study.svg +367 -0
  171. package/user_study_pilot/data/new_scatterplot_user_study.svg +603 -0
  172. package/user_study_pilot/data/new_scatterplot_user_study_point_layer.json +122 -0
  173. package/user_study_pilot/data/scatterplot_user_study (1).svg +321 -0
  174. package/user_study_pilot/data/scatterplot_user_study.svg +603 -0
  175. package/user_study_pilot/data/scatterplot_user_study_point_layer.json +122 -0
  176. package/user_study_pilot/data/scatterplot_user_study_smooth_layer.json +322 -0
  177. package/user_study_pilot/intro.html +215 -0
  178. package/user_study_pilot/jaws_settings/Chrome.JDF +10 -0
  179. package/user_study_pilot/jaws_settings/Firefox.JDF +10 -0
  180. package/user_study_pilot/jaws_settings/backup_utf8/Chrome.JDF +10 -0
  181. package/user_study_pilot/jaws_settings/backup_utf8/Firefox.JDF +10 -0
  182. package/user_study_pilot/jaws_settings/backup_utf8/msedge.JDF +10 -0
  183. package/user_study_pilot/jaws_settings/msedge.JDF +10 -0
  184. package/user_study_pilot/nvda_settings/chrome.dic +10 -0
  185. package/user_study_pilot/nvda_settings/default.dic +10 -0
  186. package/user_study_pilot/nvda_settings/firefox.dic +10 -0
  187. package/user_study_pilot/nvda_settings/msedge.dic +10 -0
  188. package/user_study_pilot/scatterplot.html +4560 -0
  189. package/user_study_pilot/seaborn_test.html +1059 -0
  190. package/user_study_pilot/svglite_test.html +534 -0
  191. package/user_study_pilot/task1_bar_plot.html +1111 -0
  192. package/user_study_pilot/task2_heatmap.html +1661 -0
  193. package/user_study_pilot/task3_boxplot_horizontal.html +1690 -0
  194. package/user_study_pilot/task3_boxplot_vertical.html +1689 -0
  195. package/user_study_pilot/task4_scatterplot.html +2091 -0
  196. package/user_study_pilot/tutorial1_bar_plot.html +1159 -0
  197. package/user_study_pilot/tutorial2_heatmap.html +1276 -0
  198. package/user_study_pilot/tutorial3_boxplot_horizontal.html +1861 -0
  199. package/user_study_pilot/tutorial3_boxplot_vertical.html +1807 -0
  200. package/user_study_pilot/tutorial4_scatterplot.html +5893 -0
  201. package/user_study_pilot/tutorial5_histogram.html +1553 -0
  202. package/user_study_pilot/tutorial6_lineplot.html +1011 -0
  203. package/user_study_pilot/tutorial7_stacked.html +763 -0
  204. package/user_study_pilot/tutorial8_stacked_normalized.html +796 -0
  205. package/user_study_pilot/tutorial9_dodged_bar.html +831 -0
  206. package/user_study_pilot/voiceover_settings/user_study_VoiceOver Archive.voprefs +573 -0
@@ -0,0 +1,575 @@
1
+ /**
2
+ * Audio class
3
+ * Sets up audio stuff (compressor, gain),
4
+ * sets up an oscillator that has good falloff (no clipping sounds) and can be instanced to be played anytime and can handle overlaps,
5
+ * sets up an actual playTone function that plays tones based on current chart position
6
+ *
7
+ * @class
8
+ */
9
+ class Audio {
10
+ constructor() {
11
+ this.AudioContext = window['AudioContext'] || window['webkitAudioContext'];
12
+ this.audioContext = new AudioContext();
13
+ this.compressor = this.compressorSetup(this.audioContext);
14
+ }
15
+
16
+ /**
17
+ * Sets up a dynamics compressor for better audio quality.
18
+ * @returns {DynamicsCompressorNode} The created compressor.
19
+ */
20
+ compressorSetup() {
21
+ let compressor = this.audioContext.createDynamicsCompressor(); // create compressor for better audio quality
22
+
23
+ compressor.threshold.value = -50;
24
+ compressor.knee.value = 40;
25
+ compressor.ratio.value = 12;
26
+ compressor.attack.value = 0;
27
+ compressor.release.value = 0.25;
28
+ let gainMaster = this.audioContext.createGain(); // create master gain
29
+ gainMaster.gain.value = constants.vol;
30
+ compressor.connect(gainMaster);
31
+ gainMaster.connect(this.audioContext.destination);
32
+
33
+ return compressor;
34
+ }
35
+
36
+ // an oscillator is created and destroyed after some falloff
37
+ /**
38
+ * Plays a tone based on the current chart type and position.
39
+ */
40
+ playTone() {
41
+ let currentDuration = constants.duration;
42
+ let volume = constants.vol;
43
+
44
+ let rawPanning = 0;
45
+ let rawFreq = 0;
46
+ let frequency = 0;
47
+ let panning = 0;
48
+
49
+ let waveType = 'sine';
50
+
51
+ // freq goes between min / max as rawFreq goes between min(0) / max
52
+ if (constants.chartType == 'bar') {
53
+ rawFreq = plot.plotData[position.x];
54
+ rawPanning = position.x;
55
+ frequency = this.SlideBetween(
56
+ rawFreq,
57
+ constants.minY,
58
+ constants.maxY,
59
+ constants.MIN_FREQUENCY,
60
+ constants.MAX_FREQUENCY
61
+ );
62
+ panning = this.SlideBetween(
63
+ rawPanning,
64
+ constants.minX,
65
+ constants.maxX,
66
+ -1,
67
+ 1
68
+ );
69
+ } else if (constants.chartType == 'box') {
70
+ let plotPos =
71
+ constants.plotOrientation == 'vert' ? position.x : position.y;
72
+ let sectionKey = plot.GetSectionKey(
73
+ constants.plotOrientation == 'vert' ? position.y : position.x
74
+ );
75
+ if (Array.isArray(plot.plotData[plotPos][sectionKey])) {
76
+ // outliers are stored in values with a seperate itterator
77
+ rawFreq = plot.plotData[plotPos][sectionKey][position.z];
78
+ } else {
79
+ // normal points
80
+ rawFreq = plot.plotData[plotPos][sectionKey];
81
+ }
82
+ if (plot.plotData[plotPos][sectionKey] != null) {
83
+ if (constants.plotOrientation == 'vert') {
84
+ frequency = this.SlideBetween(
85
+ rawFreq,
86
+ constants.minY,
87
+ constants.maxY,
88
+ constants.MIN_FREQUENCY,
89
+ constants.MAX_FREQUENCY
90
+ );
91
+ panning = this.SlideBetween(
92
+ rawFreq,
93
+ constants.minY,
94
+ constants.maxY,
95
+ -1,
96
+ 1
97
+ );
98
+ } else {
99
+ frequency = this.SlideBetween(
100
+ rawFreq,
101
+ constants.minX,
102
+ constants.maxX,
103
+ constants.MIN_FREQUENCY,
104
+ constants.MAX_FREQUENCY
105
+ );
106
+ panning = this.SlideBetween(
107
+ rawFreq,
108
+ constants.minX,
109
+ constants.maxX,
110
+ -1,
111
+ 1
112
+ );
113
+ }
114
+ } else {
115
+ frequency = constants.MIN_FREQUENCY;
116
+ panning = 0;
117
+ }
118
+ } else if (constants.chartType == 'heat') {
119
+ rawFreq = plot.values[position.y][position.x];
120
+ rawPanning = position.x;
121
+ frequency = this.SlideBetween(
122
+ rawFreq,
123
+ constants.minY,
124
+ constants.maxY,
125
+ constants.MIN_FREQUENCY,
126
+ constants.MAX_FREQUENCY
127
+ );
128
+ panning = this.SlideBetween(
129
+ rawPanning,
130
+ constants.minX,
131
+ constants.maxX,
132
+ -1,
133
+ 1
134
+ );
135
+ } else if (
136
+ constants.chartType == 'point' ||
137
+ constants.chartType == 'smooth'
138
+ ) {
139
+ // are we using global min / max, or just this layer?
140
+ constants.globalMinMax = true;
141
+ let chartMin = constants.minY;
142
+ let chartMax = constants.maxY;
143
+ if (constants.chartType == 'smooth') {
144
+ chartMin = plot.curveMinY;
145
+ chartMax = plot.curveMaxY;
146
+ }
147
+ if (constants.globalMinMax) {
148
+ chartMin = Math.min(constants.minY, plot.curveMinY);
149
+ chartMax = Math.max(constants.maxY, plot.curveMaxY);
150
+ }
151
+ if (constants.chartType == 'point') {
152
+ // point layer
153
+ // more than one point with same x-value
154
+ rawFreq = plot.y[position.x][position.z];
155
+ if (plot.max_count == 1) {
156
+ volume = constants.vol;
157
+ } else {
158
+ volume = this.SlideBetween(
159
+ plot.points_count[position.x][position.z],
160
+ 1,
161
+ plot.max_count,
162
+ constants.vol,
163
+ constants.MAX_VOL
164
+ );
165
+ }
166
+
167
+ rawPanning = position.x;
168
+ frequency = this.SlideBetween(
169
+ rawFreq,
170
+ chartMin,
171
+ chartMax,
172
+ constants.MIN_FREQUENCY,
173
+ constants.MAX_FREQUENCY
174
+ );
175
+ panning = this.SlideBetween(rawPanning, chartMin, chartMax, -1, 1);
176
+ } else if (constants.chartType == 'smooth') {
177
+ // best fit smooth layer
178
+
179
+ rawFreq = plot.curvePoints[positionL1.x];
180
+ rawPanning = positionL1.x;
181
+ frequency = this.SlideBetween(
182
+ rawFreq,
183
+ chartMin,
184
+ chartMax,
185
+ constants.MIN_FREQUENCY,
186
+ constants.MAX_FREQUENCY
187
+ );
188
+ panning = this.SlideBetween(rawPanning, chartMin, chartMax, -1, 1);
189
+ }
190
+ } else if (constants.chartType == 'hist') {
191
+ rawFreq = plot.plotData[position.x].y;
192
+ rawPanning = plot.plotData[position.x].x;
193
+ frequency = this.SlideBetween(
194
+ rawFreq,
195
+ constants.minY,
196
+ constants.maxY,
197
+ constants.MIN_FREQUENCY,
198
+ constants.MAX_FREQUENCY
199
+ );
200
+ panning = this.SlideBetween(
201
+ rawPanning,
202
+ constants.minX,
203
+ constants.maxX,
204
+ -1,
205
+ 1
206
+ );
207
+ } else if (constants.chartType == 'line') {
208
+ rawFreq = plot.pointValuesY[position.x];
209
+ rawPanning = position.x;
210
+ frequency = this.SlideBetween(
211
+ rawFreq,
212
+ constants.minY,
213
+ constants.maxY,
214
+ constants.MIN_FREQUENCY,
215
+ constants.MAX_FREQUENCY
216
+ );
217
+ panning = this.SlideBetween(
218
+ rawPanning,
219
+ constants.minX,
220
+ constants.maxX,
221
+ -1,
222
+ 1
223
+ );
224
+ } else if (
225
+ constants.chartType == 'stacked_bar' ||
226
+ constants.chartType == 'stacked_normalized_bar' ||
227
+ constants.chartType == 'dodged_bar'
228
+ ) {
229
+ rawFreq = plot.plotData[position.x][position.y];
230
+ if (rawFreq == 0) {
231
+ this.PlayNull();
232
+ return;
233
+ } else if (Array.isArray(rawFreq)) {
234
+ rawFreq = rawFreq[position.z];
235
+ }
236
+ rawPanning = position.x;
237
+ frequency = this.SlideBetween(
238
+ rawFreq,
239
+ constants.minY,
240
+ constants.maxY,
241
+ constants.MIN_FREQUENCY,
242
+ constants.MAX_FREQUENCY
243
+ );
244
+ panning = this.SlideBetween(
245
+ rawPanning,
246
+ constants.minX,
247
+ constants.maxX,
248
+ -1,
249
+ 1
250
+ );
251
+ let waveTypeArr = ['triangle', 'square', 'sawtooth', 'sine'];
252
+ waveType = waveTypeArr[position.y];
253
+ }
254
+
255
+ if (constants.debugLevel > 5) {
256
+ console.log('will play tone at freq', frequency);
257
+ if (constants.chartType == 'box') {
258
+ console.log(
259
+ 'based on',
260
+ constants.minY,
261
+ '<',
262
+ rawFreq,
263
+ '<',
264
+ constants.maxY,
265
+ ' | freq min',
266
+ constants.MIN_FREQUENCY,
267
+ 'max',
268
+ constants.MAX_FREQUENCY
269
+ );
270
+ } else {
271
+ console.log(
272
+ 'based on',
273
+ constants.minX,
274
+ '<',
275
+ rawFreq,
276
+ '<',
277
+ constants.maxX,
278
+ ' | freq min',
279
+ constants.MIN_FREQUENCY,
280
+ 'max',
281
+ constants.MAX_FREQUENCY
282
+ );
283
+ }
284
+ }
285
+
286
+ if (constants.chartType == 'box') {
287
+ // different types of sounds for different regions.
288
+ // outlier = short tone
289
+ // whisker = normal tone
290
+ // range = chord
291
+ let sectionKey = plot.GetSectionKey(
292
+ constants.plotOrientation == 'vert' ? position.y : position.x
293
+ );
294
+ if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') {
295
+ currentDuration = constants.outlierDuration;
296
+ } else if (
297
+ sectionKey == 'q1' ||
298
+ sectionKey == 'q2' ||
299
+ sectionKey == 'q3'
300
+ ) {
301
+ //currentDuration = constants.duration * 2;
302
+ } else {
303
+ //currentDuration = constants.duration * 2;
304
+ }
305
+ }
306
+
307
+ // create tones
308
+ this.playOscillator(frequency, currentDuration, panning, volume, waveType);
309
+ if (constants.chartType == 'box') {
310
+ let sectionKey = plot.GetSectionKey(
311
+ constants.plotOrientation == 'vert' ? position.y : position.x
312
+ );
313
+ if (sectionKey == 'q1' || sectionKey == 'q2' || sectionKey == 'q3') {
314
+ // also play an octive below at lower vol
315
+ let freq2 = frequency / 2;
316
+ this.playOscillator(
317
+ freq2,
318
+ currentDuration,
319
+ panning,
320
+ constants.vol / 4,
321
+ 'triangle'
322
+ );
323
+ }
324
+ } else if (constants.chartType == 'heat') {
325
+ // Added heatmap tone feature
326
+ if (rawFreq == 0) {
327
+ this.PlayNull();
328
+ }
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Plays an oscillator with the given frequency, duration, panning, volume, and wave type.
334
+ * @param {number} frequency - The frequency of the oscillator.
335
+ * @param {number} currentDuration - The duration of the oscillator in seconds.
336
+ * @param {number} panning - The panning value of the oscillator.
337
+ * @param {number} [currentVol=1] - The volume of the oscillator.
338
+ * @param {string} [wave='sine'] - The wave type of the oscillator.
339
+ */
340
+ playOscillator(
341
+ frequency,
342
+ currentDuration,
343
+ panning,
344
+ currentVol = 1,
345
+ wave = 'sine'
346
+ ) {
347
+ const t = this.audioContext.currentTime;
348
+ const oscillator = this.audioContext.createOscillator();
349
+ oscillator.type = wave;
350
+ oscillator.frequency.value = parseFloat(frequency);
351
+ oscillator.start();
352
+
353
+ // create gain for this event
354
+ const gainThis = this.audioContext.createGain();
355
+ gainThis.gain.setValueCurveAtTime(
356
+ [
357
+ 0.5 * currentVol,
358
+ 1 * currentVol,
359
+ 0.5 * currentVol,
360
+ 0.5 * currentVol,
361
+ 0.5 * currentVol,
362
+ 0.1 * currentVol,
363
+ 1e-4 * currentVol,
364
+ ],
365
+ t,
366
+ currentDuration
367
+ ); // this is what makes the tones fade out properly and not clip
368
+
369
+ let MAX_DISTANCE = 10000;
370
+ let posZ = 1;
371
+ const panner = new PannerNode(this.audioContext, {
372
+ panningModel: 'HRTF',
373
+ distanceModel: 'linear',
374
+ positionX: position.x,
375
+ positionY: position.y,
376
+ positionZ: posZ,
377
+ plotOrientationX: 0.0,
378
+ plotOrientationY: 0.0,
379
+ plotOrientationZ: -1.0,
380
+ refDistance: 1,
381
+ maxDistance: MAX_DISTANCE,
382
+ rolloffFactor: 10,
383
+ coneInnerAngle: 40,
384
+ coneOuterAngle: 50,
385
+ coneOuterGain: 0.4,
386
+ });
387
+
388
+ // create panning
389
+ const stereoPanner = this.audioContext.createStereoPanner();
390
+ stereoPanner.pan.value = panning;
391
+ oscillator.connect(gainThis);
392
+ gainThis.connect(stereoPanner);
393
+ stereoPanner.connect(panner);
394
+ panner.connect(this.compressor);
395
+
396
+ // create panner node
397
+
398
+ // play sound for duration
399
+ setTimeout(() => {
400
+ panner.disconnect();
401
+ gainThis.disconnect();
402
+ oscillator.stop();
403
+ oscillator.disconnect();
404
+ }, currentDuration * 1e3 * 2);
405
+ }
406
+
407
+ /**
408
+ * Plays a smooth sound with the given frequency array, duration, panning array, volume, and wave type.
409
+ * @param {number[]} freqArr - The array of frequencies to play.
410
+ * @param {number} currentDuration - The duration of the sound in seconds.
411
+ * @param {number[]} panningArr - The array of panning values.
412
+ * @param {number} currentVol - The volume of the sound.
413
+ * @param {string} wave - The type of wave to use for the oscillator.
414
+ */
415
+ playSmooth(
416
+ freqArr = [600, 500, 400, 300],
417
+ currentDuration = 2,
418
+ panningArr = [-1, 0, 1],
419
+ currentVol = 1,
420
+ wave = 'sine'
421
+ ) {
422
+ // todo: make smooth duration dependant on how much line there is to do. Like, at max it should be max duration, but if we only have like a tiny bit to play we should just play for a tiny bit
423
+
424
+ let gainArr = new Array(freqArr.length * 3).fill(0.5 * currentVol);
425
+ gainArr.push(1e-4 * currentVol);
426
+
427
+ const t = this.audioContext.currentTime;
428
+ const smoothOscillator = this.audioContext.createOscillator();
429
+ smoothOscillator.type = wave;
430
+ smoothOscillator.frequency.setValueCurveAtTime(freqArr, t, currentDuration);
431
+ smoothOscillator.start();
432
+ constants.isSmoothAutoplay = true;
433
+
434
+ // create gain for this event
435
+ this.smoothGain = this.audioContext.createGain();
436
+ this.smoothGain.gain.setValueCurveAtTime(gainArr, t, currentDuration); // this is what makes the tones fade out properly and not clip
437
+
438
+ let MAX_DISTANCE = 10000;
439
+ let posZ = 1;
440
+ const panner = new PannerNode(this.audioContext, {
441
+ panningModel: 'HRTF',
442
+ distanceModel: 'linear',
443
+ positionX: position.x,
444
+ positionY: position.y,
445
+ positionZ: posZ,
446
+ plotOrientationX: 0.0,
447
+ plotOrientationY: 0.0,
448
+ plotOrientationZ: -1.0,
449
+ refDistance: 1,
450
+ maxDistance: MAX_DISTANCE,
451
+ rolloffFactor: 10,
452
+ coneInnerAngle: 40,
453
+ coneOuterAngle: 50,
454
+ coneOuterGain: 0.4,
455
+ });
456
+
457
+ // create panning
458
+ const stereoPanner = this.audioContext.createStereoPanner();
459
+ stereoPanner.pan.setValueCurveAtTime(panningArr, t, currentDuration);
460
+ smoothOscillator.connect(this.smoothGain);
461
+ this.smoothGain.connect(stereoPanner);
462
+ stereoPanner.connect(panner);
463
+ panner.connect(this.compressor);
464
+
465
+ // play sound for duration
466
+ constants.smoothId = setTimeout(() => {
467
+ panner.disconnect();
468
+ this.smoothGain.disconnect();
469
+ smoothOscillator.stop();
470
+ smoothOscillator.disconnect();
471
+ constants.isSmoothAutoplay = false;
472
+ }, currentDuration * 1e3 * 2);
473
+ }
474
+
475
+ /**
476
+ * Plays a null frequency sound.
477
+ */
478
+ PlayNull() {
479
+ let frequency = constants.NULL_FREQUENCY;
480
+ let duration = constants.duration;
481
+ let panning = 0;
482
+ let vol = constants.vol;
483
+ let wave = 'triangle';
484
+
485
+ this.playOscillator(frequency, duration, panning, vol, wave);
486
+
487
+ setTimeout(
488
+ function (audioThis) {
489
+ audioThis.playOscillator(
490
+ (frequency * 23) / 24,
491
+ duration,
492
+ panning,
493
+ vol,
494
+ wave
495
+ );
496
+ },
497
+ Math.round((duration / 5) * 1000),
498
+ this
499
+ );
500
+ }
501
+
502
+ /**
503
+ * Plays a pleasant end chime.
504
+ * @function
505
+ * @memberof audio
506
+ * @returns {void}
507
+ */
508
+ playEnd() {
509
+ // play a pleasent end chime. We'll use terminal chime from VSCode
510
+ if (constants.canPlayEndChime) {
511
+ let chimeClone = constants.endChime.cloneNode(true); // we clone so that we can trigger a tone while one is already playing
512
+ /*
513
+ * the following (panning) only works if we're on a server
514
+ let panning = 0;
515
+ try {
516
+ if ( constants.chartType == 'bar' ) {
517
+ panning = this.SlideBetween(position.x, 0, plot.bars.length-1, -1, 1);
518
+ } else if ( constants.chartType == 'box' ) {
519
+ panning = this.SlideBetween(position.x, 0, plot.plotData[position.y].length-1, -1, 1);
520
+ } else if ( constants.chartType == 'heat' ) {
521
+ panning = this.SlideBetween(position.x, 0, plot.num_cols-1, -1, 1);
522
+ } else if ( constants.chartType == 'point' ) {
523
+ panning = this.SlideBetween(position.x, 0, plot.x.length-1, -1, 1);
524
+ }
525
+ } catch {
526
+ }
527
+
528
+ const track = this.audioContext.createMediaElementSource(chimeClone);
529
+ const stereoNode = new StereoPannerNode(this.audioContext, {pan:panning} );
530
+ track.connect(stereoNode).connect(this.audioContext.destination);
531
+ */
532
+ chimeClone.play();
533
+ chimeClone = null;
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Stops the smooth gain and cancels any scheduled values.
539
+ * @function
540
+ * @memberof Audio
541
+ * @instance
542
+ * @returns {void}
543
+ */
544
+ KillSmooth() {
545
+ if (constants.smoothId) {
546
+ this.smoothGain.gain.cancelScheduledValues(0);
547
+ this.smoothGain.gain.exponentialRampToValueAtTime(
548
+ 0.0001,
549
+ this.audioContext.currentTime + 0.03
550
+ );
551
+
552
+ clearTimeout(constants.smoothId);
553
+
554
+ constants.isSmoothAutoplay = false;
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Goes between min and max proportional to how val goes between a and b.
560
+ * @param {number} val - The value to slide between a and b.
561
+ * @param {number} a - The start value of the slide.
562
+ * @param {number} b - The end value of the slide.
563
+ * @param {number} min - The minimum value of the slide.
564
+ * @param {number} max - The maximum value of the slide.
565
+ * @returns {number} The new value between min and max.
566
+ */
567
+ SlideBetween(val, a, b, min, max) {
568
+ // helper function that goes between min and max proportional to how val goes between a and b
569
+ let newVal = ((val - a) / (b - a)) * (max - min) + min;
570
+ if (a == 0 && b == 0) {
571
+ newVal = 0;
572
+ }
573
+ return newVal;
574
+ }
575
+ }