├── 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 |
23 |
24 | 25 | 31 |
32 |
33 | 34 | 39 |
40 |
41 | Start 42 | Mute 43 |
44 |
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 | --------------------------------------------------------------------------------