├── favicon.ico
├── images
└── pattern.png
├── app-icons
├── icon-16.png
├── icon-60.png
└── icon-128.png
├── audio
├── concert-crowd.mp3
└── concert-crowd.ogg
├── .htaccess
├── README.md
├── CODE_OF_CONDUCT.md
├── styles
├── install-button.css
├── app.css
└── normalize.css
├── scripts
├── install.js
├── respond.js
└── app.js
├── manifest.webapp
├── index.html
└── LICENSE
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/favicon.ico
--------------------------------------------------------------------------------
/images/pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/images/pattern.png
--------------------------------------------------------------------------------
/app-icons/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/app-icons/icon-16.png
--------------------------------------------------------------------------------
/app-icons/icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/app-icons/icon-60.png
--------------------------------------------------------------------------------
/app-icons/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/app-icons/icon-128.png
--------------------------------------------------------------------------------
/audio/concert-crowd.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/audio/concert-crowd.mp3
--------------------------------------------------------------------------------
/audio/concert-crowd.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/voice-change-o-matic-float-data/HEAD/audio/concert-crowd.ogg
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | AddType application/x-web-app-manifest+json .webapp
2 |
3 | AddType video/ogg .ogv
4 | AddType video/mp4 .mp4
5 | AddType video/webm .webm
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # voice-change-o-matic
2 |
3 | > NOTE: The contents of this repository has been moved to [webaudio-examples/voice-change-o-matic](https://github.com/mdn/webaudio-examples/tree/main/voice-change-o-matic)
4 |
5 | A Web Audio API-powered voice changer and visualizer.
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Participation Guidelines
2 |
3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines.
4 | For more details, please read the
5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
6 |
7 | ## How to Report
8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
9 |
10 |
16 |
--------------------------------------------------------------------------------
/styles/install-button.css:
--------------------------------------------------------------------------------
1 | #install-btn {
2 | background: #0088cc; /* Old browsers */
3 | background: -moz-linear-gradient(top, #0088cc 0%, #0055cc 100%); /* FF3.6+ */
4 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#0088cc), color-stop(100%,#0055cc)); /* Chrome,Safari4+ */
5 | background: -webkit-linear-gradient(top, #0088cc 0%,#0055cc 100%); /* Chrome10+,Safari5.1+ */
6 | background: -o-linear-gradient(top, #0088cc 0%,#0055cc 100%); /* Opera 11.10+ */
7 | background: -ms-linear-gradient(top, #0088cc 0%,#0055cc 100%); /* IE10+ */
8 | background: linear-gradient(to bottom, #0088cc 0%,#0055cc 100%); /* W3C */
9 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0088cc', endColorstr='#0055cc',GradientType=0 ); /* IE6-9 */
10 |
11 | text-align: center;
12 | font-size: 200%;
13 | margin: 1em auto;
14 | display: block;
15 | padding: .5em;
16 | color: white;
17 | width: 10em;
18 | max-width: 80%;
19 | line-height: 1.2em;
20 | }
--------------------------------------------------------------------------------
/scripts/install.js:
--------------------------------------------------------------------------------
1 | // get a reference to the install button
2 | var button = document.getElementById('install-btn');
3 |
4 | if(navigator.mozApps) {
5 |
6 | var manifest_url = location.href + 'manifest.webapp';
7 |
8 | function install(ev) {
9 | ev.preventDefault();
10 | // define the manifest URL
11 | // install the app
12 | var installLocFind = navigator.mozApps.install(manifest_url);
13 | installLocFind.onsuccess = function(data) {
14 | // App is installed, do something
15 | };
16 | installLocFind.onerror = function() {
17 | // App wasn't installed, info is in
18 | // installapp.error.name
19 | alert(installLocFind.error.name);
20 | };
21 | };
22 |
23 | //call install() on click if the app isn't already installed. If it is, hide the button.
24 |
25 | var installCheck = navigator.mozApps.checkInstalled(manifest_url);
26 |
27 | installCheck.onsuccess = function() {
28 | if(installCheck.result) {
29 | button.style.display = "none";
30 | } else {
31 | button.addEventListener('click', install, false);
32 | };
33 | };
34 |
35 | } else {
36 | button.style.display = "none";
37 | }
38 |
--------------------------------------------------------------------------------
/manifest.webapp:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1",
3 | "name": "Voice-change-o-matic",
4 | "description": "A sample MDN app that uses getUserMedia to grab an audio stream, which is then passed to the Web Audio API to apply effects to the audio and create visualizations.",
5 | "launch_path": "/index.html",
6 | "icons": {
7 | "60": "app-icons/icon-60.png",
8 | "128": "app-icons/icon-128.png"
9 | },
10 | "developer": {
11 | "name": "Chris Mills",
12 | "url": "https://developer.mozila.org"
13 | },
14 | "locales": {
15 | "es": {
16 | "description": "A sample MDN app that uses getUserMedia to grab an audio stream, which is then passed to the Web Audio API to apply effects to the audio and create visualizations.",
17 | "developer": {
18 | "url": "https://developer.mozila.org"
19 | }
20 | },
21 | "it": {
22 | "description": "A sample MDN app that uses getUserMedia to grab an audio stream, which is then passed to the Web Audio API to apply effects to the audio and create visualizations.",
23 | "developer": {
24 | "url": "https://developer.mozila.org"
25 | }
26 | }
27 | },
28 | "default_locale": "en",
29 | "permissions": {
30 | "audio-capture": {
31 | "description": "Required to capture audio via getUserMedia"
32 | }
33 | },
34 | "orientation" : "portrait"
35 | }
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Voice-change-O-matic
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Voice-change-O-matic
18 |
19 |
20 |
21 |
22 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/scripts/respond.js:
--------------------------------------------------------------------------------
1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
3 | window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
4 |
5 | /*! Respond.js v1.3.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
6 | (function(a){"use strict";function x(){u(!0)}var b={};if(a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,!b.mediaQueriesSupported){var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var b=m.shift();v(b.href,function(c){p(c,b.href,b.media),h[b.href]=!0,a.setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(b){var h="clientWidth",k=d[h],m="CSS1Compat"===c.compatMode&&k||c.body[h]||k,n={},o=l[l.length-1],p=(new Date).getTime();if(b&&q&&i>p-q)return a.clearTimeout(r),r=a.setTimeout(u,i),void 0;q=p;for(var v in e)if(e.hasOwnProperty(v)){var w=e[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?t||s():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?t||s():1)),w.hasquery&&(z&&A||!(z||m>=x)||!(A||y>=m))||(n[w.media]||(n[w.media]=[]),n[w.media].push(f[w.rules]))}for(var C in g)g.hasOwnProperty(C)&&g[C]&&g[C].parentNode===j&&j.removeChild(g[C]);for(var D in n)if(n.hasOwnProperty(D)){var E=c.createElement("style"),F=n[D].join("\n");E.type="text/css",E.media=D,j.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(c.createTextNode(F)),g.push(E)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)}})(this);
7 |
--------------------------------------------------------------------------------
/styles/app.css:
--------------------------------------------------------------------------------
1 | /* || General layout rules for narrow screens */
2 |
3 | html {
4 | height: 100%;
5 | font-family: 'Righteous', cursive;
6 | font-size: 10px;
7 | background-color: black;
8 | }
9 |
10 | body {
11 | width: 100%;
12 | height: inherit;
13 | background-color: #999;
14 | background-image: url(../images/pattern.png);
15 | }
16 |
17 | h1, h2, label {
18 | font-size: 3rem;
19 | font-family: 'Nova Square', cursive;
20 | text-align: center;
21 | color: black;
22 | text-shadow: -1px -1px 1px #aaa,
23 | 0px 1px 1px rgba(255,255,255,0.5),
24 | 1px 1px 2px rgba(255,255,255,0.7),
25 | 0px 0px 2px rgba(255,255,255,0.4);
26 | margin: 0;
27 | }
28 |
29 | h1 {
30 | font-size: 3.5rem;
31 | padding-top: 1.2rem;
32 | }
33 |
34 | .wrapper {
35 | height: 100%;
36 | max-width: 800px;
37 | margin: 0 auto;
38 | }
39 |
40 | /* || main UI sections */
41 |
42 | header {
43 | height: 120px;
44 | }
45 |
46 | canvas {
47 | border-top: 1px solid black;
48 | border-bottom: 1px solid black;
49 | margin-bottom: -3px;
50 | box-shadow: 0 -2px 4px rgba(0,0,0,0.7),
51 | 0 3px 4px rgba(0,0,0,0.7);
52 | width: 100%;
53 | }
54 |
55 | .controls {
56 | background-color: rgba(0,0,0,0.1);
57 | height: calc(100% - 225px);
58 | }
59 |
60 | /* || select element styling */
61 |
62 | .controls div {
63 | width: 100%;
64 | padding-top: 1rem;
65 | }
66 |
67 | .controls label, .controls select {
68 | display: block;
69 | margin: 0 auto;
70 | }
71 |
72 | .controls label {
73 | width: 100%;
74 | text-align: center;
75 | line-height: 3rem;
76 | padding: 1rem 0;
77 | }
78 |
79 | .controls select {
80 | width: 80%;
81 | font-size: 2rem;
82 | }
83 |
84 | /* || button styling */
85 |
86 | button, form a {
87 | background-color: #0088cc;
88 | background-image: linear-gradient(to bottom, #0088cc 0%,#0055cc 100%);
89 | text-shadow: 1px 1px 1px black;
90 | text-align: center;
91 | color: white;
92 | border: none;
93 | width: 90%;
94 | margin: 1rem auto 0.5rem;
95 | max-width: 80%;
96 | font-size: 1.6rem;
97 | line-height: 3rem;
98 | padding: .5rem;
99 | display: block;
100 | }
101 |
102 | #actions {
103 | display: flex;
104 | width: 80%;
105 | margin: 3rem auto 0.5rem;
106 | justify-content: space-between;
107 | }
108 |
109 | #actions a {
110 | width: 45%;
111 | }
112 |
113 | #start-btn {
114 | background-color: #006d12;
115 | background-image: linear-gradient(to bottom, #006d12 0%, #00580f 100%);
116 | }
117 |
118 | button:hover, button:focus, form a:hover, form a:focus {
119 | box-shadow: inset 1px 1px 2px rgba(0,0,0,0.7);
120 | }
121 |
122 | button:active, form a:active {
123 | box-shadow: inset 2px 2px 3px rgba(0,0,0,0.7);
124 | }
125 |
126 | a#activated {
127 | background-color: #fff;
128 | background-image: linear-gradient(to bottom, #f00 0%,#a06 100%);
129 | }
130 |
131 | /* || Checkbox hack to control information box display */
132 |
133 | label[for="toggle"] {
134 | font-family: 'NotoColorEmoji';
135 | font-size: 3rem;
136 | position: absolute;
137 | top: 4px;
138 | right: 5px;
139 | z-index: 5;
140 | cursor: pointer;
141 | }
142 |
143 | input[type=checkbox] {
144 | position: absolute;
145 | top: -100px;
146 | }
147 |
148 | aside {
149 | position: fixed;
150 | top: 0;
151 | left: 0;
152 | padding-top: 1.5rem;
153 | text-shadow: 1px 1px 1px black;
154 | width: 100%;
155 | height: 100%;
156 | transform: translateX(100%);
157 | transition: 0.6s all;
158 | background-color: #999;
159 | background-image: linear-gradient(to top right, rgba(0,0,0,0), rgba(0,0,0,0.5));
160 | }
161 |
162 | aside p, aside li {
163 | font-size: 1.6rem;
164 | line-height: 1.3;
165 | padding: 0rem 2rem 1rem;
166 | color: white;
167 | }
168 |
169 | aside li {
170 | padding-left: 10px;
171 | }
172 |
173 |
174 | /* Toggled State of information box */
175 |
176 | input[type=checkbox]:checked ~ aside {
177 | transform: translateX(0);
178 | }
179 |
180 | /* || Link styles */
181 |
182 | a {
183 | color: #aaa;
184 | }
185 |
186 | a:hover, a:focus {
187 | text-decoration: none;
188 | }
189 |
190 | @media (min-width: 481px) {
191 | /*CSS for medium width screens*/
192 |
193 | /* || Basic layout changes for the main control buttons */
194 |
195 | }
196 |
197 | @media all and (min-width: 800px) {
198 | /*CSS for wide screens*/
199 |
200 | h1 {
201 | font-size: 5rem;
202 | padding-top: 2.5rem;
203 | }
204 |
205 | aside {
206 | top: 0;
207 | left: 100%;
208 | text-shadow: 1px 1px 1px black;
209 | width: 480px;
210 | transform: translateX(0);
211 | border-left: 2px solid black;
212 | }
213 |
214 | /* Toggled State of information box */
215 |
216 | input[type=checkbox]:checked ~ aside {
217 | transform: translateX(-480px);
218 | }
219 |
220 | }
221 |
222 | @media (min-width: 1100px) {
223 | /*CSS for really wide screens*/
224 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/scripts/app.js:
--------------------------------------------------------------------------------
1 | document.getElementById("start-btn").addEventListener("click", (e) => {
2 | start();
3 | });
4 |
5 | function start() {
6 | // fork getUserMedia for multiple browser versions, for those
7 | // that need prefixes
8 | navigator.getUserMedia =
9 | navigator.getUserMedia ||
10 | navigator.webkitGetUserMedia ||
11 | navigator.mozGetUserMedia ||
12 | navigator.msGetUserMedia;
13 |
14 | // set up forked web audio context, for multiple browsers
15 | // window. is needed otherwise Safari explodes
16 | // Move to click event handler, because the AudioContext is not allowed to start until a user gesture on the page.
17 |
18 | var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
19 | var voiceSelect = document.getElementById("voice");
20 | var source;
21 | var stream;
22 |
23 | // grab the mute button to use below
24 |
25 | var mute = document.querySelector(".mute");
26 |
27 | //set up the different audio nodes we will use for the app
28 |
29 | var analyser = audioCtx.createAnalyser();
30 | analyser.minDecibels = -90;
31 | analyser.maxDecibels = -10;
32 | analyser.smoothingTimeConstant = 0.85;
33 |
34 | var distortion = audioCtx.createWaveShaper();
35 | var gainNode = audioCtx.createGain();
36 | var biquadFilter = audioCtx.createBiquadFilter();
37 | var convolver = audioCtx.createConvolver();
38 |
39 | // distortion curve for the waveshaper, thanks to Kevin Ennis
40 | // http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion
41 |
42 | function makeDistortionCurve(amount) {
43 | var k = typeof amount === "number" ? amount : 50,
44 | n_samples = 44100,
45 | curve = new Float32Array(n_samples),
46 | deg = Math.PI / 180,
47 | i = 0,
48 | x;
49 | for (; i < n_samples; ++i) {
50 | x = (i * 2) / n_samples - 1;
51 | curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
52 | }
53 | return curve;
54 | }
55 |
56 | // grab audio track via XHR for convolver node
57 |
58 | var soundSource, concertHallBuffer;
59 |
60 | ajaxRequest = new XMLHttpRequest();
61 |
62 | ajaxRequest.open(
63 | "GET",
64 | "https://mdn.github.io/voice-change-o-matic/audio/concert-crowd.ogg",
65 | true
66 | );
67 |
68 | ajaxRequest.responseType = "arraybuffer";
69 |
70 | ajaxRequest.onload = function () {
71 | var audioData = ajaxRequest.response;
72 |
73 | audioCtx.decodeAudioData(
74 | audioData,
75 | function (buffer) {
76 | concertHallBuffer = buffer;
77 | soundSource = audioCtx.createBufferSource();
78 | soundSource.buffer = concertHallBuffer;
79 | },
80 | function (e) {
81 | "Error with decoding audio data" + e.err;
82 | }
83 | );
84 |
85 | //soundSource.connect(audioCtx.destination);
86 | //soundSource.loop = true;
87 | //soundSource.start();
88 | };
89 |
90 | ajaxRequest.send();
91 |
92 | // set up canvas context for visualizer
93 |
94 | var canvas = document.querySelector(".visualizer");
95 | var canvasCtx = canvas.getContext("2d");
96 |
97 | var intendedWidth = document.querySelector(".wrapper").clientWidth;
98 |
99 | canvas.setAttribute("width", intendedWidth);
100 |
101 | var visualSelect = document.getElementById("visual");
102 |
103 | var drawVisual;
104 |
105 | //main block for doing the audio recording
106 |
107 | if (navigator.getUserMedia) {
108 | console.log("getUserMedia supported.");
109 | navigator.getUserMedia(
110 | // constraints - only audio needed for this app
111 | {
112 | audio: true,
113 | },
114 |
115 | // Success callback
116 | function (stream) {
117 | source = audioCtx.createMediaStreamSource(stream);
118 | source.connect(analyser);
119 | analyser.connect(distortion);
120 | distortion.connect(biquadFilter);
121 | biquadFilter.connect(convolver);
122 | convolver.connect(gainNode);
123 | gainNode.connect(audioCtx.destination);
124 |
125 | visualize();
126 | voiceChange();
127 | },
128 |
129 | // Error callback
130 | function (err) {
131 | console.log("The following gUM error occured: " + err);
132 | }
133 | );
134 | } else {
135 | console.log("getUserMedia not supported on your browser!");
136 | }
137 |
138 | function visualize() {
139 | WIDTH = canvas.width;
140 | HEIGHT = canvas.height;
141 |
142 | var visualSetting = visualSelect.value;
143 | console.log(visualSetting);
144 |
145 | if (visualSetting == "sinewave") {
146 | analyser.fftSize = 1024;
147 | var bufferLength = analyser.fftSize;
148 | console.log(bufferLength);
149 | var dataArray = new Float32Array(bufferLength);
150 |
151 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
152 |
153 | function draw() {
154 | drawVisual = requestAnimationFrame(draw);
155 |
156 | analyser.getFloatTimeDomainData(dataArray);
157 |
158 | canvasCtx.fillStyle = "rgb(200, 200, 200)";
159 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
160 |
161 | canvasCtx.lineWidth = 2;
162 | canvasCtx.strokeStyle = "rgb(0, 0, 0)";
163 |
164 | canvasCtx.beginPath();
165 |
166 | var sliceWidth = (WIDTH * 1.0) / bufferLength;
167 | var x = 0;
168 |
169 | for (var i = 0; i < bufferLength; i++) {
170 | var v = dataArray[i] * 200.0;
171 | var y = HEIGHT / 2 + v;
172 |
173 | if (i === 0) {
174 | canvasCtx.moveTo(x, y);
175 | } else {
176 | canvasCtx.lineTo(x, y);
177 | }
178 |
179 | x += sliceWidth;
180 | }
181 |
182 | canvasCtx.lineTo(canvas.width, canvas.height / 2);
183 | canvasCtx.stroke();
184 | }
185 |
186 | draw();
187 | } else if (visualSetting == "frequencybars") {
188 | analyser.fftSize = 256;
189 | var bufferLength = analyser.frequencyBinCount;
190 | console.log(bufferLength);
191 | var dataArray = new Float32Array(bufferLength);
192 |
193 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
194 |
195 | function draw() {
196 | drawVisual = requestAnimationFrame(draw);
197 |
198 | analyser.getFloatFrequencyData(dataArray);
199 |
200 | canvasCtx.fillStyle = "rgb(0, 0, 0)";
201 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
202 |
203 | var barWidth = (WIDTH / bufferLength) * 2.5;
204 | var barHeight;
205 | var x = 0;
206 |
207 | for (var i = 0; i < bufferLength; i++) {
208 | barHeight = (dataArray[i] + 140) * 2;
209 |
210 | canvasCtx.fillStyle =
211 | "rgb(" + Math.floor(barHeight + 100) + ",50,50)";
212 | canvasCtx.fillRect(
213 | x,
214 | HEIGHT - barHeight / 2,
215 | barWidth,
216 | barHeight / 2
217 | );
218 |
219 | x += barWidth + 1;
220 | }
221 | }
222 |
223 | draw();
224 | } else if (visualSetting == "off") {
225 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
226 | canvasCtx.fillStyle = "red";
227 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
228 | }
229 | }
230 |
231 | function voiceChange() {
232 | distortion.curve = new Float32Array(analyser.fftSize);
233 | distortion.oversample = "4x";
234 | biquadFilter.gain.value = 0;
235 | convolver.buffer = undefined;
236 |
237 | var voiceSetting = voiceSelect.value;
238 | console.log(voiceSetting);
239 |
240 | if (voiceSetting == "distortion") {
241 | distortion.curve = makeDistortionCurve(400);
242 | } else if (voiceSetting == "convolver") {
243 | convolver.buffer = concertHallBuffer;
244 | } else if (voiceSetting == "biquad") {
245 | biquadFilter.type = "lowshelf";
246 | biquadFilter.frequency.value = 1000;
247 | biquadFilter.gain.value = 25;
248 | } else if (voiceSetting == "off") {
249 | console.log("Voice settings turned off");
250 | }
251 | }
252 |
253 | // event listeners to change visualize and voice settings
254 |
255 | visualSelect.onchange = function () {
256 | window.cancelAnimationFrame(drawVisual);
257 | visualize();
258 | };
259 |
260 | voiceSelect.onchange = function () {
261 | voiceChange();
262 | };
263 |
264 | mute.onclick = voiceMute;
265 |
266 | function voiceMute() {
267 | if (mute.id == "") {
268 | gainNode.gain.value = 0;
269 | mute.id = "activated";
270 | mute.innerHTML = "Unmute";
271 | } else {
272 | gainNode.gain.value = 1;
273 | mute.id = "";
274 | mute.innerHTML = "Mute";
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
2 |
3 | /* ==========================================================================
4 | HTML5 display definitions
5 | ========================================================================== */
6 |
7 | /**
8 | * Correct `block` display not defined in IE 8/9.
9 | */
10 |
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | hgroup,
19 | main,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 |
26 | /**
27 | * Correct `inline-block` display not defined in IE 8/9.
28 | */
29 |
30 | audio,
31 | canvas,
32 | video {
33 | display: inline-block;
34 | }
35 |
36 | /**
37 | * Prevent modern browsers from displaying `audio` without controls.
38 | * Remove excess height in iOS 5 devices.
39 | */
40 |
41 | audio:not([controls]) {
42 | display: none;
43 | height: 0;
44 | }
45 |
46 | /**
47 | * Address `[hidden]` styling not present in IE 8/9.
48 | * Hide the `template` element in IE, Safari, and Firefox < 22.
49 | */
50 |
51 | [hidden],
52 | template {
53 | display: none;
54 | }
55 |
56 | /* ==========================================================================
57 | Base
58 | ========================================================================== */
59 |
60 | /**
61 | * 1. Set default font family to sans-serif.
62 | * 2. Prevent iOS text size adjust after orientation change, without disabling
63 | * user zoom.
64 | */
65 |
66 | html {
67 | font-family: sans-serif; /* 1 */
68 | -ms-text-size-adjust: 100%; /* 2 */
69 | -webkit-text-size-adjust: 100%; /* 2 */
70 | }
71 |
72 | /**
73 | * Remove default margin.
74 | */
75 |
76 | body {
77 | margin: 0;
78 | }
79 |
80 | /* ==========================================================================
81 | Links
82 | ========================================================================== */
83 |
84 | /**
85 | * Remove the gray background color from active links in IE 10.
86 | */
87 |
88 | a {
89 | background: transparent;
90 | }
91 |
92 | /**
93 | * Address `outline` inconsistency between Chrome and other browsers.
94 | */
95 |
96 | a:focus {
97 | outline: thin dotted;
98 | }
99 |
100 | /**
101 | * Improve readability when focused and also mouse hovered in all browsers.
102 | */
103 |
104 | a:active,
105 | a:hover {
106 | outline: 0;
107 | }
108 |
109 | /* ==========================================================================
110 | Typography
111 | ========================================================================== */
112 |
113 | /**
114 | * Address variable `h1` font-size and margin within `section` and `article`
115 | * contexts in Firefox 4+, Safari 5, and Chrome.
116 | */
117 |
118 | h1 {
119 | font-size: 2em;
120 | margin: 0.67em 0;
121 | }
122 |
123 | /**
124 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
125 | */
126 |
127 | abbr[title] {
128 | border-bottom: 1px dotted;
129 | }
130 |
131 | /**
132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
133 | */
134 |
135 | b,
136 | strong {
137 | font-weight: bold;
138 | }
139 |
140 | /**
141 | * Address styling not present in Safari 5 and Chrome.
142 | */
143 |
144 | dfn {
145 | font-style: italic;
146 | }
147 |
148 | /**
149 | * Address differences between Firefox and other browsers.
150 | */
151 |
152 | hr {
153 | -moz-box-sizing: content-box;
154 | box-sizing: content-box;
155 | height: 0;
156 | }
157 |
158 | /**
159 | * Address styling not present in IE 8/9.
160 | */
161 |
162 | mark {
163 | background: #ff0;
164 | color: #000;
165 | }
166 |
167 | /**
168 | * Correct font family set oddly in Safari 5 and Chrome.
169 | */
170 |
171 | code,
172 | kbd,
173 | pre,
174 | samp {
175 | font-family: monospace, serif;
176 | font-size: 1em;
177 | }
178 |
179 | /**
180 | * Improve readability of pre-formatted text in all browsers.
181 | */
182 |
183 | pre {
184 | white-space: pre-wrap;
185 | }
186 |
187 | /**
188 | * Set consistent quote types.
189 | */
190 |
191 | q {
192 | quotes: "\201C" "\201D" "\2018" "\2019";
193 | }
194 |
195 | /**
196 | * Address inconsistent and variable font size in all browsers.
197 | */
198 |
199 | small {
200 | font-size: 80%;
201 | }
202 |
203 | /**
204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
205 | */
206 |
207 | sub,
208 | sup {
209 | font-size: 75%;
210 | line-height: 0;
211 | position: relative;
212 | vertical-align: baseline;
213 | }
214 |
215 | sup {
216 | top: -0.5em;
217 | }
218 |
219 | sub {
220 | bottom: -0.25em;
221 | }
222 |
223 | /* ==========================================================================
224 | Embedded content
225 | ========================================================================== */
226 |
227 | /**
228 | * Remove border when inside `a` element in IE 8/9.
229 | */
230 |
231 | img {
232 | border: 0;
233 | }
234 |
235 | /**
236 | * Correct overflow displayed oddly in IE 9.
237 | */
238 |
239 | svg:not(:root) {
240 | overflow: hidden;
241 | }
242 |
243 | /* ==========================================================================
244 | Figures
245 | ========================================================================== */
246 |
247 | /**
248 | * Address margin not present in IE 8/9 and Safari 5.
249 | */
250 |
251 | figure {
252 | margin: 0;
253 | }
254 |
255 | /* ==========================================================================
256 | Forms
257 | ========================================================================== */
258 |
259 | /**
260 | * Define consistent border, margin, and padding.
261 | */
262 |
263 | fieldset {
264 | border: 1px solid #c0c0c0;
265 | margin: 0 2px;
266 | padding: 0.35em 0.625em 0.75em;
267 | }
268 |
269 | /**
270 | * 1. Correct `color` not being inherited in IE 8/9.
271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
272 | */
273 |
274 | legend {
275 | border: 0; /* 1 */
276 | padding: 0; /* 2 */
277 | }
278 |
279 | /**
280 | * 1. Correct font family not being inherited in all browsers.
281 | * 2. Correct font size not being inherited in all browsers.
282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
283 | */
284 |
285 | button,
286 | input,
287 | select,
288 | textarea {
289 | font-family: inherit; /* 1 */
290 | font-size: 100%; /* 2 */
291 | margin: 0; /* 3 */
292 | }
293 |
294 | /**
295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
296 | * the UA stylesheet.
297 | */
298 |
299 | button,
300 | input {
301 | line-height: normal;
302 | }
303 |
304 | /**
305 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
306 | * All other form control elements do not inherit `text-transform` values.
307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
308 | * Correct `select` style inheritance in Firefox 4+ and Opera.
309 | */
310 |
311 | button,
312 | select {
313 | text-transform: none;
314 | }
315 |
316 | /**
317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
318 | * and `video` controls.
319 | * 2. Correct inability to style clickable `input` types in iOS.
320 | * 3. Improve usability and consistency of cursor style between image-type
321 | * `input` and others.
322 | */
323 |
324 | button,
325 | html input[type="button"], /* 1 */
326 | input[type="reset"],
327 | input[type="submit"] {
328 | -webkit-appearance: button; /* 2 */
329 | cursor: pointer; /* 3 */
330 | }
331 |
332 | /**
333 | * Re-set default cursor for disabled elements.
334 | */
335 |
336 | button[disabled],
337 | html input[disabled] {
338 | cursor: default;
339 | }
340 |
341 | /**
342 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
343 | * 2. Remove excess padding in IE 8/9/10.
344 | */
345 |
346 | input[type="checkbox"],
347 | input[type="radio"] {
348 | box-sizing: border-box; /* 1 */
349 | padding: 0; /* 2 */
350 | }
351 |
352 | /**
353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
355 | * (include `-moz` to future-proof).
356 | */
357 |
358 | input[type="search"] {
359 | -webkit-appearance: textfield; /* 1 */
360 | -moz-box-sizing: content-box;
361 | -webkit-box-sizing: content-box; /* 2 */
362 | box-sizing: content-box;
363 | }
364 |
365 | /**
366 | * Remove inner padding and search cancel button in Safari 5 and Chrome
367 | * on OS X.
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Remove inner padding and border in Firefox 4+.
377 | */
378 |
379 | button::-moz-focus-inner,
380 | input::-moz-focus-inner {
381 | border: 0;
382 | padding: 0;
383 | }
384 |
385 | /**
386 | * 1. Remove default vertical scrollbar in IE 8/9.
387 | * 2. Improve readability and alignment in all browsers.
388 | */
389 |
390 | textarea {
391 | overflow: auto; /* 1 */
392 | vertical-align: top; /* 2 */
393 | }
394 |
395 | /* ==========================================================================
396 | Tables
397 | ========================================================================== */
398 |
399 | /**
400 | * Remove most spacing between table cells.
401 | */
402 |
403 | table {
404 | border-collapse: collapse;
405 | border-spacing: 0;
406 | }
407 |
--------------------------------------------------------------------------------