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.
- package/.Rbuildignore +1 -0
- package/.eslintignore +3 -0
- package/.eslintrc.json +6 -0
- package/.github/workflows/build.yml +20 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +7 -0
- package/.vscode/extensions.json +25 -0
- package/.vscode/settings.json +30 -0
- package/.vscode/tasks.json +57 -0
- package/CHANGELOG.md +7 -0
- package/CITATION.cff +21 -0
- package/CONTRIBUTING.md +87 -0
- package/LICENSE.md +595 -0
- package/README.md +341 -0
- package/dist/maidr.js +8851 -0
- package/dist/maidr.min.js +1 -0
- package/dist/styles.css +244 -0
- package/dist/styles.min.css +1 -0
- package/docs/Audio.html +1398 -0
- package/docs/Constants.html +256 -0
- package/docs/Description.html +582 -0
- package/docs/Helper.html +364 -0
- package/docs/LogError.html +905 -0
- package/docs/Menu.html +665 -0
- package/docs/Position.html +174 -0
- package/docs/Resources.html +338 -0
- package/docs/Review.html +333 -0
- package/docs/Tracker.html +965 -0
- package/docs/audio.js.html +635 -0
- package/docs/constants.js.html +1242 -0
- package/docs/display.js.html +1184 -0
- package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docs/index.html +66 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/styles/jsdoc-default.css +692 -0
- package/docs/styles/prettify-jsdoc.css +111 -0
- package/docs/styles/prettify-tomorrow.css +132 -0
- package/examples/dev_charts/barplot.html +1056 -0
- package/examples/dev_charts/boxplot.html +1856 -0
- package/examples/dev_charts/boxplot_flipped.svg +727 -0
- package/examples/dev_charts/heatmap.html +1217 -0
- package/examples/dev_charts/scatterplot/displ.js +18 -0
- package/examples/dev_charts/scatterplot/histogram_for_residual.svg +595 -0
- package/examples/dev_charts/scatterplot/hwy.js +15 -0
- package/examples/dev_charts/scatterplot/layers/point_layer.json +938 -0
- package/examples/dev_charts/scatterplot/layers/smooth_layer.json +322 -0
- package/examples/dev_charts/scatterplot/point_layer.js +938 -0
- package/examples/dev_charts/scatterplot/prediction_array.js +31 -0
- package/examples/dev_charts/scatterplot/prediction_array.json +31 -0
- package/examples/dev_charts/scatterplot/residual_array.js +29 -0
- package/examples/dev_charts/scatterplot/residual_array.json +29 -0
- package/examples/dev_charts/scatterplot/scatterplot.svg +1428 -0
- package/examples/dev_charts/scatterplot/scatterplot_data.html +2838 -0
- package/examples/dev_charts/scatterplot/scatterplot_no_jitter_point_only.svg +1393 -0
- package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_bestfit.svg +1424 -0
- package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_loess_curve.svg +1402 -0
- package/examples/dev_charts/scatterplot/smooth_layer.js +322 -0
- package/examples/dev_charts/scatterplot.html +4560 -0
- package/examples/dodged_bar/dodged_bar.png +0 -0
- package/examples/dodged_bar/dodged_bar.svg +198 -0
- package/examples/dodged_bar/schema.json +41 -0
- package/examples/histogram/histogram_tutorial.svg +482 -0
- package/examples/histogram/histogram_tutorial_raw_data.json +362 -0
- package/examples/histogram/histogram_user_study.svg +578 -0
- package/examples/histogram/histogram_user_study_raw_data.json +362 -0
- package/examples/lineplot/lineplot_sample.svg +126 -0
- package/examples/lineplot/lineplot_sample_raw_data.json +1 -0
- package/examples/lineplot/point+lineplot_sample.svg +700 -0
- package/examples/other/audio_oscillator_boxplot.js +95 -0
- package/examples/other/barplot_labels.svg +314 -0
- package/examples/other/barplot_user_study.svg +313 -0
- package/examples/other/boxplot.html +927 -0
- package/examples/other/boxplot_data_frame.html +568 -0
- package/examples/other/boxplot_label.svg +751 -0
- package/examples/other/braille-display_boxplot.js +79 -0
- package/examples/other/control_boxplot.js +55 -0
- package/examples/other/draft.js +56 -0
- package/examples/other/getData.html +400 -0
- package/examples/other/getData.js +41 -0
- package/examples/other/ggplot_to_svg.R +371 -0
- package/examples/other/heatmap.svg +582 -0
- package/examples/other/heatmap_label.svg +608 -0
- package/examples/other/multiple_barplot.html +2250 -0
- package/examples/other/new_scatterplot_user_study_point_layer.json +122 -0
- package/examples/other/py_binder_output.html +1167 -0
- package/examples/other/scatterplot_label.svg +1429 -0
- package/examples/other/seaborn_plot.py +9 -0
- package/examples/other/svglite_bar.svg +136 -0
- package/examples/other/tutorial_boxplot.svg +727 -0
- package/examples/other/tutorial_boxplot_data.json +72 -0
- package/examples/other/user_study_boxplot.svg +676 -0
- package/examples/stacked_bar/schema.json +41 -0
- package/examples/stacked_bar/stack_bar.png +0 -0
- package/examples/stacked_bar/stacked_bar.svg +180 -0
- package/examples/stacked_normalized_bar/stacked_normalized_bar.png +0 -0
- package/examples/stacked_normalized_bar/stacked_normalized_bar.svg +189 -0
- package/examples/static/barplot.svg +263 -0
- package/examples/static/barplot_diamonds_gridSVG.svg +254 -0
- package/examples/static/boxplot.svg +424 -0
- package/examples/static/heatmap.svg +373 -0
- package/examples/static/heatmap_penguins_table.html +486 -0
- package/examples/static/scatterplot.svg +530 -0
- package/examples/svglite/task_heatmap.html +802 -0
- package/examples/svglite/task_heatmap.svg +111 -0
- package/examples/svglite/tutorial_bar.svg +136 -0
- package/examples/svglite/tutorial_bar_plot.html +504 -0
- package/examples/svglite/tutorial_boxplot.html +1850 -0
- package/examples/svglite/tutorial_boxplot.svg +727 -0
- package/examples/svglite/tutorial_scatterplot.html +3135 -0
- package/examples/svglite/tutorial_scatterplot.svg +311 -0
- package/gulpfile.js +49 -0
- package/index.html +40 -0
- package/jsconfig.json +10 -0
- package/jsdoc.json +19 -0
- package/package.json +47 -0
- package/src/css/styles.css +241 -0
- package/src/js/__tests__/audio.test.js +49 -0
- package/src/js/__tests__/constants.test.js +622 -0
- package/src/js/audio.js +575 -0
- package/src/js/barplot.js +254 -0
- package/src/js/boxplot.js +682 -0
- package/src/js/constants.js +1182 -0
- package/src/js/controls.js +3182 -0
- package/src/js/display.js +1124 -0
- package/src/js/heatmap.js +411 -0
- package/src/js/histogram.js +134 -0
- package/src/js/init.js +427 -0
- package/src/js/lineplot.js +219 -0
- package/src/js/scatterplot.js +619 -0
- package/src/js/segmented.js +268 -0
- package/user_study_pilot/binder_test.html +526 -0
- package/user_study_pilot/data/barplot_user_study.svg +492 -0
- package/user_study_pilot/data/barplot_user_study_raw_data.json +22 -0
- package/user_study_pilot/data/boxplot_tutorial.json +72 -0
- package/user_study_pilot/data/boxplot_tutorial_horizontal.svg +727 -0
- package/user_study_pilot/data/boxplot_user_study.json +52 -0
- package/user_study_pilot/data/boxplot_user_study_vertical.svg +675 -0
- package/user_study_pilot/data/boxplot_user_study_vertical_horizontal.svg +676 -0
- package/user_study_pilot/data/heatmap_user_study.svg +719 -0
- package/user_study_pilot/data/heatmap_user_study_raw_data.json +127 -0
- package/user_study_pilot/data/new_barplot_user_study.svg +269 -0
- package/user_study_pilot/data/new_heatmap_user_study.svg +367 -0
- package/user_study_pilot/data/new_scatterplot_user_study.svg +603 -0
- package/user_study_pilot/data/new_scatterplot_user_study_point_layer.json +122 -0
- package/user_study_pilot/data/scatterplot_user_study (1).svg +321 -0
- package/user_study_pilot/data/scatterplot_user_study.svg +603 -0
- package/user_study_pilot/data/scatterplot_user_study_point_layer.json +122 -0
- package/user_study_pilot/data/scatterplot_user_study_smooth_layer.json +322 -0
- package/user_study_pilot/intro.html +215 -0
- package/user_study_pilot/jaws_settings/Chrome.JDF +10 -0
- package/user_study_pilot/jaws_settings/Firefox.JDF +10 -0
- package/user_study_pilot/jaws_settings/backup_utf8/Chrome.JDF +10 -0
- package/user_study_pilot/jaws_settings/backup_utf8/Firefox.JDF +10 -0
- package/user_study_pilot/jaws_settings/backup_utf8/msedge.JDF +10 -0
- package/user_study_pilot/jaws_settings/msedge.JDF +10 -0
- package/user_study_pilot/nvda_settings/chrome.dic +10 -0
- package/user_study_pilot/nvda_settings/default.dic +10 -0
- package/user_study_pilot/nvda_settings/firefox.dic +10 -0
- package/user_study_pilot/nvda_settings/msedge.dic +10 -0
- package/user_study_pilot/scatterplot.html +4560 -0
- package/user_study_pilot/seaborn_test.html +1059 -0
- package/user_study_pilot/svglite_test.html +534 -0
- package/user_study_pilot/task1_bar_plot.html +1111 -0
- package/user_study_pilot/task2_heatmap.html +1661 -0
- package/user_study_pilot/task3_boxplot_horizontal.html +1690 -0
- package/user_study_pilot/task3_boxplot_vertical.html +1689 -0
- package/user_study_pilot/task4_scatterplot.html +2091 -0
- package/user_study_pilot/tutorial1_bar_plot.html +1159 -0
- package/user_study_pilot/tutorial2_heatmap.html +1276 -0
- package/user_study_pilot/tutorial3_boxplot_horizontal.html +1861 -0
- package/user_study_pilot/tutorial3_boxplot_vertical.html +1807 -0
- package/user_study_pilot/tutorial4_scatterplot.html +5893 -0
- package/user_study_pilot/tutorial5_histogram.html +1553 -0
- package/user_study_pilot/tutorial6_lineplot.html +1011 -0
- package/user_study_pilot/tutorial7_stacked.html +763 -0
- package/user_study_pilot/tutorial8_stacked_normalized.html +796 -0
- package/user_study_pilot/tutorial9_dodged_bar.html +831 -0
- package/user_study_pilot/voiceover_settings/user_study_VoiceOver Archive.voprefs +573 -0
package/src/js/audio.js
ADDED
|
@@ -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
|
+
}
|